From 355381b7259d24248b228b3241556c42db3e9bfa Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Thu, 19 Aug 2010 11:26:24 +0000 Subject: Rename CMCopyTable to SPCopyTable. --- Source/CMCopyTable.h | 154 ----------- Source/CMCopyTable.m | 632 ------------------------------------------- Source/SPCopyTable.h | 154 +++++++++++ Source/SPCopyTable.m | 632 +++++++++++++++++++++++++++++++++++++++++++ Source/SPCustomQuery.h | 4 +- Source/SPCustomQuery.m | 2 +- Source/SPTableContent.h | 4 +- Source/SPTableContent.m | 6 +- Source/SPTextView.h | 2 +- Source/SPTextViewAdditions.m | 4 +- 10 files changed, 797 insertions(+), 797 deletions(-) delete mode 100644 Source/CMCopyTable.h delete mode 100644 Source/CMCopyTable.m create mode 100644 Source/SPCopyTable.h create mode 100644 Source/SPCopyTable.m diff --git a/Source/CMCopyTable.h b/Source/CMCopyTable.h deleted file mode 100644 index c7a6280a..00000000 --- a/Source/CMCopyTable.h +++ /dev/null @@ -1,154 +0,0 @@ -// -// $Id$ -// -// CMCopyTable.h -// sequel-pro -// -// Created by Stuart Glenn on Wed Apr 21 2004. -// Changed by Lorenz Textor on Sat Nov 13 2004 -// Copyright (c) 2004 Stuart Glenn. 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 - -#import -#import "SPTableView.h" - -#define SP_MAX_CELL_WIDTH_MULTICOLUMN 200 -#define SP_MAX_CELL_WIDTH 400 - -@class SPDataStorage; - -/*! - @class copyTable - @abstract subclassed NSTableView to implement copy & drag-n-drop - @discussion Allows copying by creating a string with each table row as - a separate line and each cell then separate via tabs. The drag out - is in similar format. The values for each cell are obtained via the - objects description method -*/ -@interface CMCopyTable : SPTableView -{ - id tableInstance; // the table content view instance - id mySQLConnection; // current MySQL connection - NSArray* columnDefinitions; // array of NSDictionary containing info about columns - NSString* selectedTable; // the name of the current selected table - SPDataStorage* tableStorage; // the underlying storage array holding the table data - - NSUserDefaults *prefs; -} - -/*! - @method copy: - @abstract does the work of copying - @discussion gets selected (if any) row(s) as a string setting it - then into th default pasteboard as a string type and tabular text type. - @param sender who asked for this copy? -*/ -- (void)copy:(id)sender; - -/*! - @method validateMenuItem: - @abstract Dynamically enable Copy menu item for the table view - @discussion Will only enable the Copy item when something is selected in - this table view - @param anItem the menu item being validated - @result YES if there is at least one row selected & the menu item is - copy, NO otherwise -*/ -- (BOOL)validateMenuItem:(NSMenuItem*)anItem; - -/*! - @method draggingSourceOperationMaskForLocal: - @discussion Allows for dragging out of the table to other applications - @param isLocal who cares - @result Always calls for a copy type drag operation -*/ -- (NSUInteger)draggingSourceOperationMaskForLocal:(BOOL)isLocal; - -/*! - @method selectedRowsAsTabStringWithHeaders - @abstract getter of the selected rows of the table for copy - @discussion For the selected rows returns a single string with each row - separated by a newline and then for each column value separated by a - tab. Values are from the objects description method, so make sure it - returns something meaningful. - @result The above described string, or nil if nothing selected -*/ -- (NSString *)selectedRowsAsTabStringWithHeaders:(BOOL)withHeaders; - -/*! - @method draggedRowsAsTabString: - @abstract getter of the dragged rows of the table for drag - @discussion For the dragged rows returns a single string with each row - separated by a newline and then for each column value separated by a - tab. Values are from the objects description method, so make sure it - returns something meaningful. - @result The above described string, or nil if nothing selected -*/ -- (NSString *)draggedRowsAsTabString; - -/* - * Generate a string in form of INSERT INTO VALUES () of - * currently selected rows. Support blob data as well. - */ -- (NSString *)selectedRowsAsSqlInserts; - -/* - * Set all necessary data from the table content view. - */ -- (void)setTableInstance:(id)anInstance withTableData:(SPDataStorage *)theTableStorage withColumns:(NSArray *)columnDefs withTableName:(NSString *)aTableName withConnection:(id)aMySqlConnection; - -/* - * Update the table storage location if necessary. - */ -- (void)setTableData:(SPDataStorage *)theTableStorage; - -/*! - @method autodetectColumnWidths - @abstract Autodetect and return column widths based on contents - @discussion Support autocalculating column widths for the represented data. - This uses the underlying table storage, calculates string widths, - and eventually returns an array of table column widths. - Suitable for calling on background threads, but ensure that the - data storage range in use (currently rows 1-200) won't be altered - while this accesses it. - @result A dictionary - mapped by column identifier - of the column widths to use -*/ -- (NSDictionary *) autodetectColumnWidths; - -/*! - @method autodetectWidthForColumnDefinition:maxRows: - @abstract Autodetect and return column width based on contents - @discussion Support autocalculating column width for the represented data. - This uses the underlying table storage, and the supplied column definition, - iterating through the data and returning a reasonable column width to - display that data. - Suitable for calling on background threads, but ensure that the data - storage range in use won't be altered while being accessed. - @param A column definition for a represented column; the column to use is derived - @param The maximum number of rows to process when looking at string lengths - @result A reasonable column width to use when displaying data -*/ -/** - * Autodetect the column width for a specified column - derived from the supplied - * column definition, using the stored data and the specified font. - */ -- (NSUInteger)autodetectWidthForColumnDefinition:(NSDictionary *)columnDefinition maxRows:(NSUInteger)rowsToCheck; - -@end - -extern NSInteger MENU_EDIT_COPY; -extern NSInteger MENU_EDIT_COPY_WITH_COLUMN; -extern NSInteger MENU_EDIT_COPY_AS_SQL; diff --git a/Source/CMCopyTable.m b/Source/CMCopyTable.m deleted file mode 100644 index 909dfa3d..00000000 --- a/Source/CMCopyTable.m +++ /dev/null @@ -1,632 +0,0 @@ -// -// $Id$ -// -// CMCopyTable.m -// sequel-pro -// -// Created by Stuart Glenn on Wed Apr 21 2004. -// Changed by Lorenz Textor on Sat Nov 13 2004 -// Copyright (c) 2004 Stuart Glenn. 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 - -#import - -#import "CMCopyTable.h" -#import "SPArrayAdditions.h" -#import "SPStringAdditions.h" -#import "SPTableContent.h" -#import "SPTableTriggers.h" -#import "SPTableRelations.h" -#import "SPCustomQuery.h" -#import "SPNotLoaded.h" -#import "SPConstants.h" -#import "SPDataStorage.h" - -NSInteger MENU_EDIT_COPY = 2001; -NSInteger MENU_EDIT_COPY_WITH_COLUMN = 2002; -NSInteger MENU_EDIT_COPY_AS_SQL = 2003; - -@implementation CMCopyTable - -- (void)copy:(id)sender -{ - NSString *tmp = nil; - - if([sender tag] == MENU_EDIT_COPY_AS_SQL) { - tmp = [self selectedRowsAsSqlInserts]; - if ( nil != tmp ) - { - NSPasteboard *pb = [NSPasteboard generalPasteboard]; - - [pb declareTypes:[NSArray arrayWithObjects: NSStringPboardType, nil] - owner:nil]; - - [pb setString:tmp forType:NSStringPboardType]; - } - } else { - tmp = [self selectedRowsAsTabStringWithHeaders:([sender tag] == MENU_EDIT_COPY_WITH_COLUMN)]; - if ( nil != tmp ) - { - NSPasteboard *pb = [NSPasteboard generalPasteboard]; - - [pb declareTypes:[NSArray arrayWithObjects: NSTabularTextPboardType, - NSStringPboardType, nil] - owner:nil]; - - [pb setString:tmp forType:NSStringPboardType]; - [pb setString:tmp forType:NSTabularTextPboardType]; - } - } -} - -//allow for drag-n-drop out of the application as a copy -- (NSUInteger)draggingSourceOperationMaskForLocal:(BOOL)isLocal -{ - return NSDragOperationCopy; -} - -/** - * Only have the copy menu item enabled when row(s) are selected in - * supported tables. - */ -- (BOOL)validateMenuItem:(NSMenuItem*)anItem -{ - NSInteger menuItemTag = [anItem tag]; - - // Don't validate anything other than the copy commands - if (menuItemTag != MENU_EDIT_COPY && menuItemTag != MENU_EDIT_COPY_WITH_COLUMN && menuItemTag != MENU_EDIT_COPY_AS_SQL) { - return YES; - } - - // Don't enable menus for relations or triggers - no action to take yet - if ([[self delegate] isKindOfClass:[SPTableRelations class]] || [[self delegate] isKindOfClass:[SPTableTriggers class]]) { - return NO; - } - - // Enable the Copy [with column names] commands if a row is selected - if (menuItemTag == MENU_EDIT_COPY || menuItemTag == MENU_EDIT_COPY_WITH_COLUMN) { - return ([self numberOfSelectedRows] > 0); - } - - // Enable the Copy as SQL commands if rows are selected and column definitions are available - if (menuItemTag == MENU_EDIT_COPY_AS_SQL) { - return (columnDefinitions != nil && [self numberOfSelectedRows] > 0); - } - - return NO; -} - -//get selected rows a string of newline separated lines of tab separated fields -//the value in each field is from the objects description method -- (NSString *)selectedRowsAsTabStringWithHeaders:(BOOL)withHeaders -{ - if ([self numberOfSelectedRows] == 0) return nil; - - NSIndexSet *selectedRows = [self selectedRowIndexes]; - - NSArray *columns = [self tableColumns]; - NSUInteger numColumns = [columns count]; - NSMutableString *result = [NSMutableString stringWithCapacity:2000]; - - // Add the table headers if requested to do so - if (withHeaders) { - NSUInteger i; - for( i = 0; i < numColumns; i++ ){ - [result appendString:[NSString stringWithFormat:@"%@\t", [[NSArrayObjectAtIndex(columns, i) headerCell] stringValue]]]; - } - [result appendString:[NSString stringWithFormat:@"\n"]]; - } - - NSUInteger c; - id cellData = nil; - - // Create an array of table column mappings for fast iteration - NSUInteger *columnMappings = malloc(numColumns * sizeof(NSUInteger)); - for ( c = 0; c < numColumns; c++) { - columnMappings[c] = [[[columns objectAtIndex:c] identifier] unsignedIntValue]; - } - - // Loop through the rows, adding their descriptive contents - NSUInteger rowIndex = [selectedRows firstIndex]; - while ( rowIndex != NSNotFound ) - { - for ( c = 0; c < numColumns; c++) { - cellData = SPDataStorageObjectAtRowAndColumn(tableStorage, rowIndex, columnMappings[c]); - - // Copy the shown representation of the cell - custom NULL display strings, (not loaded), - // and the string representation of any blobs or binary texts. - if (cellData) { - if ([cellData isNSNull]) - [result appendString:[NSString stringWithFormat:@"%@\t", [prefs objectForKey:SPNullValue]]]; - else if ([cellData isSPNotLoaded]) - [result appendString:[NSString stringWithFormat:@"%@\t", NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields")]]; - else if ([cellData isKindOfClass:[NSData class]]) { - NSString *displayString = [[NSString alloc] initWithData:cellData encoding:[mySQLConnection encoding]]; - if (!displayString) displayString = [[NSString alloc] initWithData:cellData encoding:NSASCIIStringEncoding]; - if (displayString) { - [result appendString:displayString]; - [displayString release]; - [result appendString:@"\t"]; - } - } else - [result appendString:[NSString stringWithFormat:@"%@\t", [cellData description]]]; - } else { - [result appendString:@"\t"]; - } - } - - // Remove the trailing tab and add the linebreak - if ([result length]){ - [result deleteCharactersInRange:NSMakeRange([result length]-1, 1)]; - } - [result appendString:[NSString stringWithFormat:@"\n"]]; - - // Select the next row index - rowIndex = [selectedRows indexGreaterThanIndex:rowIndex]; - } - - // Remove the trailing line end - if ([result length]) { - [result deleteCharactersInRange:NSMakeRange([result length]-1, 1)]; - } - - free(columnMappings); - - return result; -} - -/* - * Return selected rows as SQL INSERT INTO `foo` VALUES (baz) string. - * If no selected table name is given `
` will be used instead. - */ -- (NSString *)selectedRowsAsSqlInserts -{ - - if ( [self numberOfSelectedRows] < 1 ) return nil; - - NSArray *columns = [self tableColumns]; - NSUInteger numColumns = [columns count]; - - NSIndexSet *selectedRows = [self selectedRowIndexes]; - NSMutableString *value = [NSMutableString stringWithCapacity:10]; - - id cellData = nil; - - NSUInteger rowCounter = 0; - NSUInteger penultimateRowIndex = [selectedRows count]; - NSUInteger c; - NSUInteger valueLength = 0; - - NSMutableString *result = [NSMutableString stringWithCapacity:2000]; - - // Create an array of table column names - NSMutableArray *tbHeader = [NSMutableArray arrayWithCapacity:numColumns]; - for (id enumObj in columns) { - [tbHeader addObject:[[enumObj headerCell] stringValue]]; - } - - // Create arrays of table column mappings and types for fast iteration - NSUInteger *columnMappings = malloc(numColumns * sizeof(NSUInteger)); - NSUInteger *columnTypes = malloc(numColumns * sizeof(NSUInteger)); - for ( c = 0; c < numColumns; c++) { - columnMappings[c] = [[[columns objectAtIndex:c] identifier] unsignedIntValue]; - - NSString *t = [[columnDefinitions objectAtIndex:columnMappings[c]] objectForKey:@"typegrouping"]; - - // Numeric data - if ([t isEqualToString:@"bit"] || [t isEqualToString:@"integer"] || [t isEqualToString:@"float"]) - columnTypes[c] = 0; - - // Blob data or long text data - else if ([t isEqualToString:@"blobdata"] || [t isEqualToString:@"textdata"]) - columnTypes[c] = 2; - - // Default to strings - else - columnTypes[c] = 1; - } - - // Begin the SQL string - [result appendString:[NSString stringWithFormat:@"INSERT INTO %@ (%@)\nVALUES\n", - [(selectedTable == nil)?@"
":selectedTable backtickQuotedString], [tbHeader componentsJoinedAndBacktickQuoted]]]; - - NSUInteger rowIndex = [selectedRows firstIndex]; - while ( rowIndex != NSNotFound ) - { - [value appendString:@"\t("]; - cellData = nil; - rowCounter++; - for ( c = 0; c < numColumns; c++ ) - { - cellData = SPDataStorageObjectAtRowAndColumn(tableStorage, rowIndex, columnMappings[c]); - - // If the data is not loaded, attempt to fetch the value - if ([cellData isSPNotLoaded] && [[self delegate] isKindOfClass:[SPTableContent class]]) { - - // Abort if no table name given, not table content, or if there are no indices on this table - if (!selectedTable || ![[self delegate] isKindOfClass:[SPTableContent class]] || ![[tableInstance argumentForRow:rowIndex] length]) { - NSBeep(); - free(columnMappings); - free(columnTypes); - return nil; - } - - // Use the argumentForRow to retrieve the missing information - // TODO - this could be preloaded for all selected rows rather than cell-by-cell - cellData = [mySQLConnection getFirstFieldFromQuery: - [NSString stringWithFormat:@"SELECT %@ FROM %@ WHERE %@", - [[tbHeader objectAtIndex:columnMappings[c]] backtickQuotedString], - [selectedTable backtickQuotedString], - [tableInstance argumentForRow:rowIndex]]]; - } - - // Check for NULL value - if ([cellData isNSNull]) { - [value appendString:@"NULL, "]; - continue; - - } else if (cellData) { - - // Check column type and insert the data accordingly - switch(columnTypes[c]) { - - // Convert numeric types to unquoted strings - case 0: - [value appendString:[NSString stringWithFormat:@"%@, ", [cellData description]]]; - break; - - // Quote string, text and blob types appropriately - case 1: - case 2: - if ([cellData isKindOfClass:[NSData class]]) { - [value appendString:[NSString stringWithFormat:@"X'%@', ", [mySQLConnection prepareBinaryData:cellData]]]; - } else { - [value appendString:[NSString stringWithFormat:@"'%@', ", [mySQLConnection prepareString:[cellData description]]]]; - } - break; - - // Unhandled cases - abort - default: - NSBeep(); - free(columnMappings); - free(columnTypes); - return nil; - } - - // If nil is encountered, abort - } else { - NSBeep(); - free(columnMappings); - free(columnTypes); - return nil; - } - } - - // Remove the trailing ', ' from the query - if ( [value length] > 2 ) - [value deleteCharactersInRange:NSMakeRange([value length]-2, 2)]; - - valueLength += [value length]; - - // Close this VALUES group and set up the next one if appropriate - if ( rowCounter != penultimateRowIndex ) { - - // Add a new INSERT starter command every ~250k of data. - if ( valueLength > 250000 ) { - [result appendString:value]; - [result appendString:[NSString stringWithFormat:@");\n\nINSERT INTO %@ (%@)\nVALUES\n", - [(selectedTable == nil)?@"
":selectedTable backtickQuotedString], [tbHeader componentsJoinedAndBacktickQuoted]]]; - [value setString:@""]; - valueLength = 0; - } else { - [value appendString:@"),\n"]; - } - - } else { - [value appendString:@"),\n"]; - [result appendString:value]; - } - - // Get the next selected row index - rowIndex = [selectedRows indexGreaterThanIndex:rowIndex]; - - } - - // Remove the trailing ",\n" from the query string - if ( [result length] > 3 ) - [result deleteCharactersInRange:NSMakeRange([result length]-2, 2)]; - - [result appendString:@";\n"]; - - free(columnMappings); - free(columnTypes); - - return result; -} - - -//get dragged rows a string of newline separated lines of tab separated fields -//the value in each field is from the objects description method -- (NSString *)draggedRowsAsTabString -{ - NSArray *columns = [self tableColumns]; - NSUInteger numColumns = [columns count]; - NSIndexSet *selectedRows = [self selectedRowIndexes]; - - NSMutableString *result = [NSMutableString stringWithCapacity:2000]; - NSUInteger c; - id cellData = nil; - - // Create an array of table column mappings for fast iteration - NSUInteger *columnMappings = malloc(numColumns * sizeof(NSUInteger)); - for ( c = 0; c < numColumns; c++) { - columnMappings[c] = [[[columns objectAtIndex:c] identifier] unsignedIntValue]; - } - - // Loop through the rows, adding their descriptive contents - NSUInteger rowIndex = [selectedRows firstIndex]; - while ( rowIndex != NSNotFound ) - { - for ( c = 0; c < numColumns; c++) { - cellData = SPDataStorageObjectAtRowAndColumn(tableStorage, rowIndex, columnMappings[c]); - - // Copy the shown representation of the cell - custom NULL display strings, (not loaded), - // and the string representation of any blobs or binary texts. - if (cellData) { - if ([cellData isNSNull]) - [result appendString:[NSString stringWithFormat:@"%@\t", [prefs objectForKey:SPNullValue]]]; - else if ([cellData isSPNotLoaded]) - [result appendString:[NSString stringWithFormat:@"%@\t", NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields")]]; - else if ([cellData isKindOfClass:[NSData class]]) { - NSString *displayString = [[NSString alloc] initWithData:cellData encoding:[mySQLConnection encoding]]; - if (!displayString) displayString = [[NSString alloc] initWithData:cellData encoding:NSASCIIStringEncoding]; - if (displayString) { - [result appendString:displayString]; - [displayString release]; - } - } else - [result appendString:[NSString stringWithFormat:@"%@\t", [cellData description]]]; - } else { - [result appendString:@"\t"]; - } - } - - if ([result length]) { - [result deleteCharactersInRange:NSMakeRange([result length]-1, 1)]; - } - - [result appendString:[NSString stringWithFormat:@"\n"]]; - - // Retrieve the next selected row index - rowIndex = [selectedRows indexGreaterThanIndex:rowIndex]; - } - - // Trim the trailing line ending - if ([result length]) { - [result deleteCharactersInRange:NSMakeRange([result length]-1, 1)]; - } - - free(columnMappings); - - return result; -} - -/** - * Init self with data coming from the table content view. Mainly used for copying data properly. - */ -- (void)setTableInstance:(id)anInstance withTableData:(SPDataStorage *)theTableStorage withColumns:(NSArray *)columnDefs withTableName:(NSString *)aTableName withConnection:(id)aMySqlConnection -{ - selectedTable = aTableName; - mySQLConnection = aMySqlConnection; - tableInstance = anInstance; - tableStorage = theTableStorage; - - if (columnDefinitions) [columnDefinitions release]; - columnDefinitions = [[NSArray alloc] initWithArray:columnDefs]; -} - -/* - * Update the table storage location if necessary. - */ -- (void)setTableData:(SPDataStorage *)theTableStorage -{ - tableStorage = theTableStorage; -} - -/** - * Autodetect column widths for a specified font. - */ -- (NSDictionary *) autodetectColumnWidths -{ - NSMutableDictionary *columnWidths = [NSMutableDictionary dictionaryWithCapacity:[columnDefinitions count]]; - NSUInteger columnWidth; - NSUInteger allColumnWidths = 0; - - for (NSDictionary *columnDefinition in columnDefinitions) { - if ([[NSThread currentThread] isCancelled]) return nil; - - columnWidth = [self autodetectWidthForColumnDefinition:columnDefinition maxRows:100]; - [columnWidths setObject:[NSNumber numberWithUnsignedInteger:columnWidth] forKey:[columnDefinition objectForKey:@"datacolumnindex"]]; - allColumnWidths += columnWidth; - } - - // Compare the column widths to the table width. If wider, narrow down wide columns as necessary - if (allColumnWidths > [self bounds].size.width) { - NSUInteger availableWidthToReduce = 0; - - // Look for columns that are wider than the multi-column max - for (NSString *columnIdentifier in columnWidths) { - columnWidth = [[columnWidths objectForKey:columnIdentifier] unsignedIntegerValue]; - if (columnWidth > SP_MAX_CELL_WIDTH_MULTICOLUMN) availableWidthToReduce += columnWidth - SP_MAX_CELL_WIDTH_MULTICOLUMN; - } - - // Determine how much width can be reduced - NSUInteger widthToReduce = allColumnWidths - [self bounds].size.width; - if (availableWidthToReduce < widthToReduce) widthToReduce = availableWidthToReduce; - - // Proportionally decrease the column sizes - if (widthToReduce) { - NSArray *columnIdentifiers = [columnWidths allKeys]; - for (NSString *columnIdentifier in columnIdentifiers) { - columnWidth = [[columnWidths objectForKey:columnIdentifier] unsignedIntegerValue]; - if (columnWidth > SP_MAX_CELL_WIDTH_MULTICOLUMN) { - columnWidth -= ceil((double)(columnWidth - SP_MAX_CELL_WIDTH_MULTICOLUMN) / availableWidthToReduce * widthToReduce); - [columnWidths setObject:[NSNumber numberWithUnsignedInteger:columnWidth] forKey:columnIdentifier]; - } - } - } - } - - return columnWidths; -} - -/** - * Autodetect the column width for a specified column - derived from the supplied - * column definition, using the stored data and the specified font. - */ -- (NSUInteger)autodetectWidthForColumnDefinition:(NSDictionary *)columnDefinition maxRows:(NSUInteger)rowsToCheck -{ - CGFloat columnBaseWidth; - id contentString; - NSUInteger cellWidth, maxCellWidth, i; - NSRange linebreakRange; - double rowStep; - NSFont *tableFont = [NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPGlobalResultTableFont]]; - NSUInteger columnIndex = [[columnDefinition objectForKey:@"datacolumnindex"] unsignedIntegerValue]; - NSDictionary *stringAttributes = [NSDictionary dictionaryWithObject:tableFont forKey:NSFontAttributeName]; - - // Check the number of rows available to check, sampling every n rows - if ([tableStorage count] < rowsToCheck) { - rowStep = 1; - } else { - rowStep = floor([tableStorage count] / rowsToCheck); - } - rowsToCheck = [tableStorage count]; - - // Set a default padding for this column - columnBaseWidth = 24; - - // Iterate through the data store rows, checking widths - maxCellWidth = 0; - for (i = 0; i < rowsToCheck; i += rowStep) { - - // Retrieve the cell's content - contentString = [tableStorage cellDataAtRow:i column:columnIndex]; - - // Replace NULLs with their placeholder string - if ([contentString isNSNull]) { - contentString = [prefs objectForKey:SPNullValue]; - - // Same for cells for which loading has been deferred - likely blobs - } else if ([contentString isSPNotLoaded]) { - contentString = NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields"); - - } else { - - // Otherwise, ensure the cell is represented as a short string - if ([contentString isKindOfClass:[NSData class]]) { - contentString = [contentString shortStringRepresentationUsingEncoding:[mySQLConnection encoding]]; - } else if ([contentString length] > 500) { - contentString = [contentString substringToIndex:500]; - } - - // If any linebreaks are present, use only the visible part of the string - linebreakRange = [contentString rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet]]; - if (linebreakRange.location != NSNotFound) { - contentString = [contentString substringToIndex:linebreakRange.location]; - } - } - - // Calculate the width, using it if it's higher than the current stored width - cellWidth = [contentString sizeWithAttributes:stringAttributes].width; - if (cellWidth > maxCellWidth) maxCellWidth = cellWidth; - if (maxCellWidth > SP_MAX_CELL_WIDTH) { - maxCellWidth = SP_MAX_CELL_WIDTH; - break; - } - } - - // If the column has a foreign key link, expand the width; and also for enums - if ([columnDefinition objectForKey:@"foreignkeyreference"]) { - maxCellWidth += 18; - } else if ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"enum"]) { - maxCellWidth += 8; - } - - // Add the padding - maxCellWidth += columnBaseWidth; - - // If the header width is wider than this expanded width, use it instead - cellWidth = [[columnDefinition objectForKey:@"name"] sizeWithAttributes:[NSDictionary dictionaryWithObject:[NSFont labelFontOfSize:[NSFont smallSystemFontSize]] forKey:NSFontAttributeName]].width; - if (cellWidth + 10 > maxCellWidth) maxCellWidth = cellWidth + 10; - - return maxCellWidth; -} - -- (void)keyDown:(NSEvent *)theEvent -{ - // RETURN or ENTER invoke editing mode for selected row - // by calling tableView:shouldEditTableColumn: to validate - if([[[[self delegate] class] description] isEqualToString:@"SPTableContent"]) { - - id tableContentView = [[self delegate] valueForKeyPath:@"tableContentView"]; - if([tableContentView numberOfSelectedRows] == 1 && ([theEvent keyCode] == 36 || [theEvent keyCode] == 76)) { - if([[self delegate] tableView:tableContentView shouldEditTableColumn:[[tableContentView tableColumns] objectAtIndex:0] row:[tableContentView selectedRow]]) { - [self editColumn:0 row:[self selectedRow] withEvent:nil select:YES]; - return; - } - } - } - if([[[[self delegate] class] description] isEqualToString:@"SPCustomQuery"]) { - id tableContentView = [[self delegate] valueForKeyPath:@"customQueryView"]; - if([tableContentView numberOfSelectedRows] == 1 && ([theEvent keyCode] == 36 || [theEvent keyCode] == 76)) { - - // TODO: this works until the user presses OK in the Field Editor Sheet!! - // in the future we should store the new row data temporarily and then - // after editing the last column update the db field by field (ask HansJB) - NSInteger colNum = [[tableContentView tableColumns] count]; - NSInteger i; - for(i=0; i +#import "SPTableView.h" + +#define SP_MAX_CELL_WIDTH_MULTICOLUMN 200 +#define SP_MAX_CELL_WIDTH 400 + +@class SPDataStorage; + +/*! + @class copyTable + @abstract subclassed NSTableView to implement copy & drag-n-drop + @discussion Allows copying by creating a string with each table row as + a separate line and each cell then separate via tabs. The drag out + is in similar format. The values for each cell are obtained via the + objects description method +*/ +@interface SPCopyTable : SPTableView +{ + id tableInstance; // the table content view instance + id mySQLConnection; // current MySQL connection + NSArray* columnDefinitions; // array of NSDictionary containing info about columns + NSString* selectedTable; // the name of the current selected table + SPDataStorage* tableStorage; // the underlying storage array holding the table data + + NSUserDefaults *prefs; +} + +/*! + @method copy: + @abstract does the work of copying + @discussion gets selected (if any) row(s) as a string setting it + then into th default pasteboard as a string type and tabular text type. + @param sender who asked for this copy? +*/ +- (void)copy:(id)sender; + +/*! + @method validateMenuItem: + @abstract Dynamically enable Copy menu item for the table view + @discussion Will only enable the Copy item when something is selected in + this table view + @param anItem the menu item being validated + @result YES if there is at least one row selected & the menu item is + copy, NO otherwise +*/ +- (BOOL)validateMenuItem:(NSMenuItem*)anItem; + +/*! + @method draggingSourceOperationMaskForLocal: + @discussion Allows for dragging out of the table to other applications + @param isLocal who cares + @result Always calls for a copy type drag operation +*/ +- (NSUInteger)draggingSourceOperationMaskForLocal:(BOOL)isLocal; + +/*! + @method selectedRowsAsTabStringWithHeaders + @abstract getter of the selected rows of the table for copy + @discussion For the selected rows returns a single string with each row + separated by a newline and then for each column value separated by a + tab. Values are from the objects description method, so make sure it + returns something meaningful. + @result The above described string, or nil if nothing selected +*/ +- (NSString *)selectedRowsAsTabStringWithHeaders:(BOOL)withHeaders; + +/*! + @method draggedRowsAsTabString: + @abstract getter of the dragged rows of the table for drag + @discussion For the dragged rows returns a single string with each row + separated by a newline and then for each column value separated by a + tab. Values are from the objects description method, so make sure it + returns something meaningful. + @result The above described string, or nil if nothing selected +*/ +- (NSString *)draggedRowsAsTabString; + +/* + * Generate a string in form of INSERT INTO
VALUES () of + * currently selected rows. Support blob data as well. + */ +- (NSString *)selectedRowsAsSqlInserts; + +/* + * Set all necessary data from the table content view. + */ +- (void)setTableInstance:(id)anInstance withTableData:(SPDataStorage *)theTableStorage withColumns:(NSArray *)columnDefs withTableName:(NSString *)aTableName withConnection:(id)aMySqlConnection; + +/* + * Update the table storage location if necessary. + */ +- (void)setTableData:(SPDataStorage *)theTableStorage; + +/*! + @method autodetectColumnWidths + @abstract Autodetect and return column widths based on contents + @discussion Support autocalculating column widths for the represented data. + This uses the underlying table storage, calculates string widths, + and eventually returns an array of table column widths. + Suitable for calling on background threads, but ensure that the + data storage range in use (currently rows 1-200) won't be altered + while this accesses it. + @result A dictionary - mapped by column identifier - of the column widths to use +*/ +- (NSDictionary *) autodetectColumnWidths; + +/*! + @method autodetectWidthForColumnDefinition:maxRows: + @abstract Autodetect and return column width based on contents + @discussion Support autocalculating column width for the represented data. + This uses the underlying table storage, and the supplied column definition, + iterating through the data and returning a reasonable column width to + display that data. + Suitable for calling on background threads, but ensure that the data + storage range in use won't be altered while being accessed. + @param A column definition for a represented column; the column to use is derived + @param The maximum number of rows to process when looking at string lengths + @result A reasonable column width to use when displaying data +*/ +/** + * Autodetect the column width for a specified column - derived from the supplied + * column definition, using the stored data and the specified font. + */ +- (NSUInteger)autodetectWidthForColumnDefinition:(NSDictionary *)columnDefinition maxRows:(NSUInteger)rowsToCheck; + +@end + +extern NSInteger MENU_EDIT_COPY; +extern NSInteger MENU_EDIT_COPY_WITH_COLUMN; +extern NSInteger MENU_EDIT_COPY_AS_SQL; diff --git a/Source/SPCopyTable.m b/Source/SPCopyTable.m new file mode 100644 index 00000000..6a65a934 --- /dev/null +++ b/Source/SPCopyTable.m @@ -0,0 +1,632 @@ +// +// $Id$ +// +// SPCopyTable.m +// sequel-pro +// +// Created by Stuart Glenn on Wed Apr 21 2004. +// Changed by Lorenz Textor on Sat Nov 13 2004 +// Copyright (c) 2004 Stuart Glenn. 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 + +#import + +#import "SPCopyTable.h" +#import "SPArrayAdditions.h" +#import "SPStringAdditions.h" +#import "SPTableContent.h" +#import "SPTableTriggers.h" +#import "SPTableRelations.h" +#import "SPCustomQuery.h" +#import "SPNotLoaded.h" +#import "SPConstants.h" +#import "SPDataStorage.h" + +NSInteger MENU_EDIT_COPY = 2001; +NSInteger MENU_EDIT_COPY_WITH_COLUMN = 2002; +NSInteger MENU_EDIT_COPY_AS_SQL = 2003; + +@implementation SPCopyTable + +- (void)copy:(id)sender +{ + NSString *tmp = nil; + + if([sender tag] == MENU_EDIT_COPY_AS_SQL) { + tmp = [self selectedRowsAsSqlInserts]; + if ( nil != tmp ) + { + NSPasteboard *pb = [NSPasteboard generalPasteboard]; + + [pb declareTypes:[NSArray arrayWithObjects: NSStringPboardType, nil] + owner:nil]; + + [pb setString:tmp forType:NSStringPboardType]; + } + } else { + tmp = [self selectedRowsAsTabStringWithHeaders:([sender tag] == MENU_EDIT_COPY_WITH_COLUMN)]; + if ( nil != tmp ) + { + NSPasteboard *pb = [NSPasteboard generalPasteboard]; + + [pb declareTypes:[NSArray arrayWithObjects: NSTabularTextPboardType, + NSStringPboardType, nil] + owner:nil]; + + [pb setString:tmp forType:NSStringPboardType]; + [pb setString:tmp forType:NSTabularTextPboardType]; + } + } +} + +//allow for drag-n-drop out of the application as a copy +- (NSUInteger)draggingSourceOperationMaskForLocal:(BOOL)isLocal +{ + return NSDragOperationCopy; +} + +/** + * Only have the copy menu item enabled when row(s) are selected in + * supported tables. + */ +- (BOOL)validateMenuItem:(NSMenuItem*)anItem +{ + NSInteger menuItemTag = [anItem tag]; + + // Don't validate anything other than the copy commands + if (menuItemTag != MENU_EDIT_COPY && menuItemTag != MENU_EDIT_COPY_WITH_COLUMN && menuItemTag != MENU_EDIT_COPY_AS_SQL) { + return YES; + } + + // Don't enable menus for relations or triggers - no action to take yet + if ([[self delegate] isKindOfClass:[SPTableRelations class]] || [[self delegate] isKindOfClass:[SPTableTriggers class]]) { + return NO; + } + + // Enable the Copy [with column names] commands if a row is selected + if (menuItemTag == MENU_EDIT_COPY || menuItemTag == MENU_EDIT_COPY_WITH_COLUMN) { + return ([self numberOfSelectedRows] > 0); + } + + // Enable the Copy as SQL commands if rows are selected and column definitions are available + if (menuItemTag == MENU_EDIT_COPY_AS_SQL) { + return (columnDefinitions != nil && [self numberOfSelectedRows] > 0); + } + + return NO; +} + +//get selected rows a string of newline separated lines of tab separated fields +//the value in each field is from the objects description method +- (NSString *)selectedRowsAsTabStringWithHeaders:(BOOL)withHeaders +{ + if ([self numberOfSelectedRows] == 0) return nil; + + NSIndexSet *selectedRows = [self selectedRowIndexes]; + + NSArray *columns = [self tableColumns]; + NSUInteger numColumns = [columns count]; + NSMutableString *result = [NSMutableString stringWithCapacity:2000]; + + // Add the table headers if requested to do so + if (withHeaders) { + NSUInteger i; + for( i = 0; i < numColumns; i++ ){ + [result appendString:[NSString stringWithFormat:@"%@\t", [[NSArrayObjectAtIndex(columns, i) headerCell] stringValue]]]; + } + [result appendString:[NSString stringWithFormat:@"\n"]]; + } + + NSUInteger c; + id cellData = nil; + + // Create an array of table column mappings for fast iteration + NSUInteger *columnMappings = malloc(numColumns * sizeof(NSUInteger)); + for ( c = 0; c < numColumns; c++) { + columnMappings[c] = [[[columns objectAtIndex:c] identifier] unsignedIntValue]; + } + + // Loop through the rows, adding their descriptive contents + NSUInteger rowIndex = [selectedRows firstIndex]; + while ( rowIndex != NSNotFound ) + { + for ( c = 0; c < numColumns; c++) { + cellData = SPDataStorageObjectAtRowAndColumn(tableStorage, rowIndex, columnMappings[c]); + + // Copy the shown representation of the cell - custom NULL display strings, (not loaded), + // and the string representation of any blobs or binary texts. + if (cellData) { + if ([cellData isNSNull]) + [result appendString:[NSString stringWithFormat:@"%@\t", [prefs objectForKey:SPNullValue]]]; + else if ([cellData isSPNotLoaded]) + [result appendString:[NSString stringWithFormat:@"%@\t", NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields")]]; + else if ([cellData isKindOfClass:[NSData class]]) { + NSString *displayString = [[NSString alloc] initWithData:cellData encoding:[mySQLConnection encoding]]; + if (!displayString) displayString = [[NSString alloc] initWithData:cellData encoding:NSASCIIStringEncoding]; + if (displayString) { + [result appendString:displayString]; + [displayString release]; + [result appendString:@"\t"]; + } + } else + [result appendString:[NSString stringWithFormat:@"%@\t", [cellData description]]]; + } else { + [result appendString:@"\t"]; + } + } + + // Remove the trailing tab and add the linebreak + if ([result length]){ + [result deleteCharactersInRange:NSMakeRange([result length]-1, 1)]; + } + [result appendString:[NSString stringWithFormat:@"\n"]]; + + // Select the next row index + rowIndex = [selectedRows indexGreaterThanIndex:rowIndex]; + } + + // Remove the trailing line end + if ([result length]) { + [result deleteCharactersInRange:NSMakeRange([result length]-1, 1)]; + } + + free(columnMappings); + + return result; +} + +/* + * Return selected rows as SQL INSERT INTO `foo` VALUES (baz) string. + * If no selected table name is given `
` will be used instead. + */ +- (NSString *)selectedRowsAsSqlInserts +{ + + if ( [self numberOfSelectedRows] < 1 ) return nil; + + NSArray *columns = [self tableColumns]; + NSUInteger numColumns = [columns count]; + + NSIndexSet *selectedRows = [self selectedRowIndexes]; + NSMutableString *value = [NSMutableString stringWithCapacity:10]; + + id cellData = nil; + + NSUInteger rowCounter = 0; + NSUInteger penultimateRowIndex = [selectedRows count]; + NSUInteger c; + NSUInteger valueLength = 0; + + NSMutableString *result = [NSMutableString stringWithCapacity:2000]; + + // Create an array of table column names + NSMutableArray *tbHeader = [NSMutableArray arrayWithCapacity:numColumns]; + for (id enumObj in columns) { + [tbHeader addObject:[[enumObj headerCell] stringValue]]; + } + + // Create arrays of table column mappings and types for fast iteration + NSUInteger *columnMappings = malloc(numColumns * sizeof(NSUInteger)); + NSUInteger *columnTypes = malloc(numColumns * sizeof(NSUInteger)); + for ( c = 0; c < numColumns; c++) { + columnMappings[c] = [[[columns objectAtIndex:c] identifier] unsignedIntValue]; + + NSString *t = [[columnDefinitions objectAtIndex:columnMappings[c]] objectForKey:@"typegrouping"]; + + // Numeric data + if ([t isEqualToString:@"bit"] || [t isEqualToString:@"integer"] || [t isEqualToString:@"float"]) + columnTypes[c] = 0; + + // Blob data or long text data + else if ([t isEqualToString:@"blobdata"] || [t isEqualToString:@"textdata"]) + columnTypes[c] = 2; + + // Default to strings + else + columnTypes[c] = 1; + } + + // Begin the SQL string + [result appendString:[NSString stringWithFormat:@"INSERT INTO %@ (%@)\nVALUES\n", + [(selectedTable == nil)?@"
":selectedTable backtickQuotedString], [tbHeader componentsJoinedAndBacktickQuoted]]]; + + NSUInteger rowIndex = [selectedRows firstIndex]; + while ( rowIndex != NSNotFound ) + { + [value appendString:@"\t("]; + cellData = nil; + rowCounter++; + for ( c = 0; c < numColumns; c++ ) + { + cellData = SPDataStorageObjectAtRowAndColumn(tableStorage, rowIndex, columnMappings[c]); + + // If the data is not loaded, attempt to fetch the value + if ([cellData isSPNotLoaded] && [[self delegate] isKindOfClass:[SPTableContent class]]) { + + // Abort if no table name given, not table content, or if there are no indices on this table + if (!selectedTable || ![[self delegate] isKindOfClass:[SPTableContent class]] || ![[tableInstance argumentForRow:rowIndex] length]) { + NSBeep(); + free(columnMappings); + free(columnTypes); + return nil; + } + + // Use the argumentForRow to retrieve the missing information + // TODO - this could be preloaded for all selected rows rather than cell-by-cell + cellData = [mySQLConnection getFirstFieldFromQuery: + [NSString stringWithFormat:@"SELECT %@ FROM %@ WHERE %@", + [[tbHeader objectAtIndex:columnMappings[c]] backtickQuotedString], + [selectedTable backtickQuotedString], + [tableInstance argumentForRow:rowIndex]]]; + } + + // Check for NULL value + if ([cellData isNSNull]) { + [value appendString:@"NULL, "]; + continue; + + } else if (cellData) { + + // Check column type and insert the data accordingly + switch(columnTypes[c]) { + + // Convert numeric types to unquoted strings + case 0: + [value appendString:[NSString stringWithFormat:@"%@, ", [cellData description]]]; + break; + + // Quote string, text and blob types appropriately + case 1: + case 2: + if ([cellData isKindOfClass:[NSData class]]) { + [value appendString:[NSString stringWithFormat:@"X'%@', ", [mySQLConnection prepareBinaryData:cellData]]]; + } else { + [value appendString:[NSString stringWithFormat:@"'%@', ", [mySQLConnection prepareString:[cellData description]]]]; + } + break; + + // Unhandled cases - abort + default: + NSBeep(); + free(columnMappings); + free(columnTypes); + return nil; + } + + // If nil is encountered, abort + } else { + NSBeep(); + free(columnMappings); + free(columnTypes); + return nil; + } + } + + // Remove the trailing ', ' from the query + if ( [value length] > 2 ) + [value deleteCharactersInRange:NSMakeRange([value length]-2, 2)]; + + valueLength += [value length]; + + // Close this VALUES group and set up the next one if appropriate + if ( rowCounter != penultimateRowIndex ) { + + // Add a new INSERT starter command every ~250k of data. + if ( valueLength > 250000 ) { + [result appendString:value]; + [result appendString:[NSString stringWithFormat:@");\n\nINSERT INTO %@ (%@)\nVALUES\n", + [(selectedTable == nil)?@"
":selectedTable backtickQuotedString], [tbHeader componentsJoinedAndBacktickQuoted]]]; + [value setString:@""]; + valueLength = 0; + } else { + [value appendString:@"),\n"]; + } + + } else { + [value appendString:@"),\n"]; + [result appendString:value]; + } + + // Get the next selected row index + rowIndex = [selectedRows indexGreaterThanIndex:rowIndex]; + + } + + // Remove the trailing ",\n" from the query string + if ( [result length] > 3 ) + [result deleteCharactersInRange:NSMakeRange([result length]-2, 2)]; + + [result appendString:@";\n"]; + + free(columnMappings); + free(columnTypes); + + return result; +} + + +//get dragged rows a string of newline separated lines of tab separated fields +//the value in each field is from the objects description method +- (NSString *)draggedRowsAsTabString +{ + NSArray *columns = [self tableColumns]; + NSUInteger numColumns = [columns count]; + NSIndexSet *selectedRows = [self selectedRowIndexes]; + + NSMutableString *result = [NSMutableString stringWithCapacity:2000]; + NSUInteger c; + id cellData = nil; + + // Create an array of table column mappings for fast iteration + NSUInteger *columnMappings = malloc(numColumns * sizeof(NSUInteger)); + for ( c = 0; c < numColumns; c++) { + columnMappings[c] = [[[columns objectAtIndex:c] identifier] unsignedIntValue]; + } + + // Loop through the rows, adding their descriptive contents + NSUInteger rowIndex = [selectedRows firstIndex]; + while ( rowIndex != NSNotFound ) + { + for ( c = 0; c < numColumns; c++) { + cellData = SPDataStorageObjectAtRowAndColumn(tableStorage, rowIndex, columnMappings[c]); + + // Copy the shown representation of the cell - custom NULL display strings, (not loaded), + // and the string representation of any blobs or binary texts. + if (cellData) { + if ([cellData isNSNull]) + [result appendString:[NSString stringWithFormat:@"%@\t", [prefs objectForKey:SPNullValue]]]; + else if ([cellData isSPNotLoaded]) + [result appendString:[NSString stringWithFormat:@"%@\t", NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields")]]; + else if ([cellData isKindOfClass:[NSData class]]) { + NSString *displayString = [[NSString alloc] initWithData:cellData encoding:[mySQLConnection encoding]]; + if (!displayString) displayString = [[NSString alloc] initWithData:cellData encoding:NSASCIIStringEncoding]; + if (displayString) { + [result appendString:displayString]; + [displayString release]; + } + } else + [result appendString:[NSString stringWithFormat:@"%@\t", [cellData description]]]; + } else { + [result appendString:@"\t"]; + } + } + + if ([result length]) { + [result deleteCharactersInRange:NSMakeRange([result length]-1, 1)]; + } + + [result appendString:[NSString stringWithFormat:@"\n"]]; + + // Retrieve the next selected row index + rowIndex = [selectedRows indexGreaterThanIndex:rowIndex]; + } + + // Trim the trailing line ending + if ([result length]) { + [result deleteCharactersInRange:NSMakeRange([result length]-1, 1)]; + } + + free(columnMappings); + + return result; +} + +/** + * Init self with data coming from the table content view. Mainly used for copying data properly. + */ +- (void)setTableInstance:(id)anInstance withTableData:(SPDataStorage *)theTableStorage withColumns:(NSArray *)columnDefs withTableName:(NSString *)aTableName withConnection:(id)aMySqlConnection +{ + selectedTable = aTableName; + mySQLConnection = aMySqlConnection; + tableInstance = anInstance; + tableStorage = theTableStorage; + + if (columnDefinitions) [columnDefinitions release]; + columnDefinitions = [[NSArray alloc] initWithArray:columnDefs]; +} + +/* + * Update the table storage location if necessary. + */ +- (void)setTableData:(SPDataStorage *)theTableStorage +{ + tableStorage = theTableStorage; +} + +/** + * Autodetect column widths for a specified font. + */ +- (NSDictionary *) autodetectColumnWidths +{ + NSMutableDictionary *columnWidths = [NSMutableDictionary dictionaryWithCapacity:[columnDefinitions count]]; + NSUInteger columnWidth; + NSUInteger allColumnWidths = 0; + + for (NSDictionary *columnDefinition in columnDefinitions) { + if ([[NSThread currentThread] isCancelled]) return nil; + + columnWidth = [self autodetectWidthForColumnDefinition:columnDefinition maxRows:100]; + [columnWidths setObject:[NSNumber numberWithUnsignedInteger:columnWidth] forKey:[columnDefinition objectForKey:@"datacolumnindex"]]; + allColumnWidths += columnWidth; + } + + // Compare the column widths to the table width. If wider, narrow down wide columns as necessary + if (allColumnWidths > [self bounds].size.width) { + NSUInteger availableWidthToReduce = 0; + + // Look for columns that are wider than the multi-column max + for (NSString *columnIdentifier in columnWidths) { + columnWidth = [[columnWidths objectForKey:columnIdentifier] unsignedIntegerValue]; + if (columnWidth > SP_MAX_CELL_WIDTH_MULTICOLUMN) availableWidthToReduce += columnWidth - SP_MAX_CELL_WIDTH_MULTICOLUMN; + } + + // Determine how much width can be reduced + NSUInteger widthToReduce = allColumnWidths - [self bounds].size.width; + if (availableWidthToReduce < widthToReduce) widthToReduce = availableWidthToReduce; + + // Proportionally decrease the column sizes + if (widthToReduce) { + NSArray *columnIdentifiers = [columnWidths allKeys]; + for (NSString *columnIdentifier in columnIdentifiers) { + columnWidth = [[columnWidths objectForKey:columnIdentifier] unsignedIntegerValue]; + if (columnWidth > SP_MAX_CELL_WIDTH_MULTICOLUMN) { + columnWidth -= ceil((double)(columnWidth - SP_MAX_CELL_WIDTH_MULTICOLUMN) / availableWidthToReduce * widthToReduce); + [columnWidths setObject:[NSNumber numberWithUnsignedInteger:columnWidth] forKey:columnIdentifier]; + } + } + } + } + + return columnWidths; +} + +/** + * Autodetect the column width for a specified column - derived from the supplied + * column definition, using the stored data and the specified font. + */ +- (NSUInteger)autodetectWidthForColumnDefinition:(NSDictionary *)columnDefinition maxRows:(NSUInteger)rowsToCheck +{ + CGFloat columnBaseWidth; + id contentString; + NSUInteger cellWidth, maxCellWidth, i; + NSRange linebreakRange; + double rowStep; + NSFont *tableFont = [NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPGlobalResultTableFont]]; + NSUInteger columnIndex = [[columnDefinition objectForKey:@"datacolumnindex"] unsignedIntegerValue]; + NSDictionary *stringAttributes = [NSDictionary dictionaryWithObject:tableFont forKey:NSFontAttributeName]; + + // Check the number of rows available to check, sampling every n rows + if ([tableStorage count] < rowsToCheck) { + rowStep = 1; + } else { + rowStep = floor([tableStorage count] / rowsToCheck); + } + rowsToCheck = [tableStorage count]; + + // Set a default padding for this column + columnBaseWidth = 24; + + // Iterate through the data store rows, checking widths + maxCellWidth = 0; + for (i = 0; i < rowsToCheck; i += rowStep) { + + // Retrieve the cell's content + contentString = [tableStorage cellDataAtRow:i column:columnIndex]; + + // Replace NULLs with their placeholder string + if ([contentString isNSNull]) { + contentString = [prefs objectForKey:SPNullValue]; + + // Same for cells for which loading has been deferred - likely blobs + } else if ([contentString isSPNotLoaded]) { + contentString = NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields"); + + } else { + + // Otherwise, ensure the cell is represented as a short string + if ([contentString isKindOfClass:[NSData class]]) { + contentString = [contentString shortStringRepresentationUsingEncoding:[mySQLConnection encoding]]; + } else if ([contentString length] > 500) { + contentString = [contentString substringToIndex:500]; + } + + // If any linebreaks are present, use only the visible part of the string + linebreakRange = [contentString rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet]]; + if (linebreakRange.location != NSNotFound) { + contentString = [contentString substringToIndex:linebreakRange.location]; + } + } + + // Calculate the width, using it if it's higher than the current stored width + cellWidth = [contentString sizeWithAttributes:stringAttributes].width; + if (cellWidth > maxCellWidth) maxCellWidth = cellWidth; + if (maxCellWidth > SP_MAX_CELL_WIDTH) { + maxCellWidth = SP_MAX_CELL_WIDTH; + break; + } + } + + // If the column has a foreign key link, expand the width; and also for enums + if ([columnDefinition objectForKey:@"foreignkeyreference"]) { + maxCellWidth += 18; + } else if ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"enum"]) { + maxCellWidth += 8; + } + + // Add the padding + maxCellWidth += columnBaseWidth; + + // If the header width is wider than this expanded width, use it instead + cellWidth = [[columnDefinition objectForKey:@"name"] sizeWithAttributes:[NSDictionary dictionaryWithObject:[NSFont labelFontOfSize:[NSFont smallSystemFontSize]] forKey:NSFontAttributeName]].width; + if (cellWidth + 10 > maxCellWidth) maxCellWidth = cellWidth + 10; + + return maxCellWidth; +} + +- (void)keyDown:(NSEvent *)theEvent +{ + // RETURN or ENTER invoke editing mode for selected row + // by calling tableView:shouldEditTableColumn: to validate + if([[[[self delegate] class] description] isEqualToString:@"SPTableContent"]) { + + id tableContentView = [[self delegate] valueForKeyPath:@"tableContentView"]; + if([tableContentView numberOfSelectedRows] == 1 && ([theEvent keyCode] == 36 || [theEvent keyCode] == 76)) { + if([[self delegate] tableView:tableContentView shouldEditTableColumn:[[tableContentView tableColumns] objectAtIndex:0] row:[tableContentView selectedRow]]) { + [self editColumn:0 row:[self selectedRow] withEvent:nil select:YES]; + return; + } + } + } + if([[[[self delegate] class] description] isEqualToString:@"SPCustomQuery"]) { + id tableContentView = [[self delegate] valueForKeyPath:@"customQueryView"]; + if([tableContentView numberOfSelectedRows] == 1 && ([theEvent keyCode] == 36 || [theEvent keyCode] == 76)) { + + // TODO: this works until the user presses OK in the Field Editor Sheet!! + // in the future we should store the new row data temporarily and then + // after editing the last column update the db field by field (ask HansJB) + NSInteger colNum = [[tableContentView tableColumns] count]; + NSInteger i; + for(i=0; i #import -#import "CMCopyTable.h" +#import "SPCopyTable.h" #import "SPTextView.h" #import "RegexKitLite.h" @@ -76,7 +76,7 @@ IBOutlet NSPopUpButton *encodingPopUp; IBOutlet SPTextView *textView; - IBOutlet CMCopyTable *customQueryView; + IBOutlet SPCopyTable *customQueryView; IBOutlet NSScrollView *customQueryScrollView; IBOutlet id errorText; IBOutlet id affectedRowsText; diff --git a/Source/SPCustomQuery.m b/Source/SPCustomQuery.m index c2a480c6..939b0b9f 100644 --- a/Source/SPCustomQuery.m +++ b/Source/SPCustomQuery.m @@ -1663,7 +1663,7 @@ /** * This function changes the text color of text/blob fields whose content is NULL. */ -- (void)tableView:(CMCopyTable *)aTableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn*)aTableColumn row:(NSInteger)rowIndex +- (void)tableView:(SPCopyTable *)aTableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn*)aTableColumn row:(NSInteger)rowIndex { if (aTableView == customQueryView) { diff --git a/Source/SPTableContent.h b/Source/SPTableContent.h index 15059b6d..52828965 100644 --- a/Source/SPTableContent.h +++ b/Source/SPTableContent.h @@ -28,7 +28,7 @@ #import #import -@class CMCopyTable, SPTextAndLinkCell, SPHistoryController, SPTableInfo, SPDataStorage; +@class SPCopyTable, SPTextAndLinkCell, SPHistoryController, SPTableInfo, SPDataStorage; @interface SPTableContent : NSObject { @@ -40,7 +40,7 @@ IBOutlet SPTableInfo *tableInfoInstance; IBOutlet SPHistoryController *spHistoryControllerInstance; - IBOutlet CMCopyTable *tableContentView; + IBOutlet SPCopyTable *tableContentView; IBOutlet NSPopUpButton *fieldField; IBOutlet id compareField; IBOutlet id argumentField; diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index 795c9970..3f078b88 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -31,7 +31,7 @@ #import "SPTableInfo.h" #import "SPTablesList.h" #import "SPImageView.h" -#import "CMCopyTable.h" +#import "SPCopyTable.h" #import "SPDataCellFormatter.h" #import "SPTableData.h" #import "SPQueryController.h" @@ -2898,7 +2898,7 @@ return tableRowsCount; } -- (id)tableView:(CMCopyTable *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex +- (id)tableView:(SPCopyTable *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { NSUInteger columnIndex = [[aTableColumn identifier] integerValue]; id theValue = nil; @@ -2934,7 +2934,7 @@ /** * This function changes the text color of text/blob fields which are null or not yet loaded to gray */ -- (void)tableView:(CMCopyTable *)aTableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn*)aTableColumn row:(NSInteger)rowIndex +- (void)tableView:(SPCopyTable *)aTableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn*)aTableColumn row:(NSInteger)rowIndex { if (![cell respondsToSelector:@selector(setTextColor:)]) return; diff --git a/Source/SPTextView.h b/Source/SPTextView.h index 867d38f6..9730151c 100644 --- a/Source/SPTextView.h +++ b/Source/SPTextView.h @@ -26,7 +26,7 @@ #import #import "NoodleLineNumberView.h" -#import "CMCopyTable.h" +#import "SPCopyTable.h" #define SP_TEXT_SIZE_TRIGGER_FOR_PARTLY_PARSING 10000 diff --git a/Source/SPTextViewAdditions.m b/Source/SPTextViewAdditions.m index 97c0e07c..b3f68000 100644 --- a/Source/SPTextViewAdditions.m +++ b/Source/SPTextViewAdditions.m @@ -435,8 +435,8 @@ - (void) magnifyWithEvent:(NSEvent *)anEvent { - //Avoid font resizing for NSTextViews in CMCopyTable or NSTableView - if([[[[self delegate] class] description] isEqualToString:@"CMCopyTable"] + //Avoid font resizing for NSTextViews in SPCopyTable or NSTableView + if([[[[self delegate] class] description] isEqualToString:@"SPCopyTable"] || [[[[self delegate] class] description] isEqualToString:@"NSTableView"]) return; if([anEvent deltaZ]>5.0) -- cgit v1.2.3