aboutsummaryrefslogtreecommitdiffstats
path: root/Source/TableContent.m
diff options
context:
space:
mode:
authorstuconnolly <stuart02@gmail.com>2010-05-28 16:07:13 +0000
committerstuconnolly <stuart02@gmail.com>2010-05-28 16:07:13 +0000
commita3cdae0d22d41758152fd864f49bb894c1bd464e (patch)
tree0ca7c4b8d1883339bf5e0685cd8332e245ab6afc /Source/TableContent.m
parent9eb3012a29eb9adb658159c984716971f0141446 (diff)
downloadsequelpro-a3cdae0d22d41758152fd864f49bb894c1bd464e.tar.gz
sequelpro-a3cdae0d22d41758152fd864f49bb894c1bd464e.tar.bz2
sequelpro-a3cdae0d22d41758152fd864f49bb894c1bd464e.zip
Rename TableContent to SPTableContent.
Diffstat (limited to 'Source/TableContent.m')
-rw-r--r--Source/TableContent.m3413
1 files changed, 0 insertions, 3413 deletions
diff --git a/Source/TableContent.m b/Source/TableContent.m
deleted file mode 100644
index ce24134b..00000000
--- a/Source/TableContent.m
+++ /dev/null
@@ -1,3413 +0,0 @@
-//
-// $Id$
-//
-// TableDocument.h
-// sequel-pro
-//
-// Created by lorenz textor (lorenz@textor.ch) on Wed May 01 2002.
-// Copyright (c) 2002-2003 Lorenz Textor. All rights reserved.
-//
-// Forked by Abhi Beckert (abhibeckert.com) 2008-04-04
-//
-// This program is free software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation; either version 2 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-//
-// More info at <http://code.google.com/p/sequel-pro/>
-
-#import "TableContent.h"
-#import "TableDocument.h"
-#import "SPTableStructure.h"
-#import "SPTableInfo.h"
-#import "SPTablesList.h"
-#import "SPImageView.h"
-#import "CMCopyTable.h"
-#import "SPDataCellFormatter.h"
-#import "SPTableData.h"
-#import "SPQueryController.h"
-#import "SPStringAdditions.h"
-#import "SPArrayAdditions.h"
-#import "SPTextViewAdditions.h"
-#import "SPDataAdditions.h"
-#import "SPTextAndLinkCell.h"
-#import "QLPreviewPanel.h"
-#import "SPFieldEditorController.h"
-#import "SPTooltip.h"
-#import "RegexKitLite.h"
-#import "SPContentFilterManager.h"
-#import "SPNotLoaded.h"
-#import "SPConstants.h"
-#import "SPDataStorage.h"
-#import "SPAlertSheets.h"
-#import "SPMainThreadTrampoline.h"
-#import "SPHistoryController.h"
-
-@implementation TableContent
-
-/**
- * Standard init method. Initialize various ivars.
- */
-- (id)init
-{
- if ((self == [super init])) {
- _mainNibLoaded = NO;
- isWorking = NO;
- pthread_mutex_init(&tableValuesLock, NULL);
- nibObjectsToRelease = [[NSMutableArray alloc] init];
-
- tableValues = [[SPDataStorage alloc] init];
- tableRowsCount = 0;
- previousTableRowsCount = 0;
- dataColumns = [[NSMutableArray alloc] init];
- oldRow = [[NSMutableArray alloc] init];
-
- selectedTable = nil;
- sortCol = nil;
- isDesc = NO;
- keys = nil;
-
- currentlyEditingRow = -1;
- contentPage = 1;
-
- sortColumnToRestore = nil;
- sortColumnToRestoreIsAsc = YES;
- pageToRestore = 1;
- selectionIndexToRestore = nil;
- selectionViewportToRestore = NSZeroRect;
- filterFieldToRestore = nil;
- filterComparisonToRestore = nil;
- filterValueToRestore = nil;
- firstBetweenValueToRestore = nil;
- secondBetweenValueToRestore = nil;
- tableRowsSelectable = YES;
- contentFilterManager = nil;
-
- isFiltered = NO;
- isLimited = NO;
- isInterruptedLoad = NO;
-
- prefs = [NSUserDefaults standardUserDefaults];
-
- usedQuery = [[NSString alloc] initWithString:@""];
-
- // Init default filters for Content Browser
- contentFilters = nil;
- contentFilters = [[NSMutableDictionary alloc] init];
- numberOfDefaultFilters = [[NSMutableDictionary alloc] init];
-
- NSError *readError = nil;
- NSString *convError = nil;
- NSPropertyListFormat format;
- NSData *defaultFilterData = [NSData dataWithContentsOfFile:[NSBundle pathForResource:@"ContentFilters.plist" ofType:nil inDirectory:[[NSBundle mainBundle] bundlePath]]
- options:NSMappedRead error:&readError];
-
- [contentFilters setDictionary:[NSPropertyListSerialization propertyListFromData:defaultFilterData
- mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&convError]];
- if(contentFilters == nil || readError != nil || convError != nil) {
- NSLog(@"Error while reading 'ContentFilters.plist':\n%@\n%@", [readError localizedDescription], convError);
- NSBeep();
- } else {
- [numberOfDefaultFilters setObject:[NSNumber numberWithInteger:[[contentFilters objectForKey:@"number"] count]] forKey:@"number"];
- [numberOfDefaultFilters setObject:[NSNumber numberWithInteger:[[contentFilters objectForKey:@"date"] count]] forKey:@"date"];
- [numberOfDefaultFilters setObject:[NSNumber numberWithInteger:[[contentFilters objectForKey:@"string"] count]] forKey:@"string"];
- }
-
-
- }
-
- return self;
-}
-
-/**
- * Initialise various interface controls
- */
-- (void)awakeFromNib
-{
- if (_mainNibLoaded) return;
- _mainNibLoaded = YES;
-
- // Set the table content view's vertical gridlines if required
- [tableContentView setGridStyleMask:([prefs boolForKey:SPDisplayTableViewVerticalGridlines]) ? NSTableViewSolidVerticalGridLineMask : NSTableViewGridNone];
-
- // Load the pagination view, keeping references to the top-level objects for later release
- NSArray *paginationViewTopLevelObjects = nil;
- NSNib *nibLoader = [[NSNib alloc] initWithNibNamed:@"ContentPaginationView" bundle:[NSBundle mainBundle]];
- if (![nibLoader instantiateNibWithOwner:self topLevelObjects:&paginationViewTopLevelObjects]) {
- NSLog(@"Content pagination nib could not be loaded; pagination will not function correctly.");
- } else {
- [nibObjectsToRelease addObjectsFromArray:paginationViewTopLevelObjects];
- }
- [nibLoader release];
-
- // Add the pagination view to the content area
- NSRect paginationViewFrame = [paginationView frame];
- NSRect paginationButtonFrame = [paginationButton frame];
- paginationViewHeight = paginationViewFrame.size.height;
- paginationViewFrame.origin.x = paginationButtonFrame.origin.x + paginationButtonFrame.size.width - paginationViewFrame.size.width;
- paginationViewFrame.origin.y = paginationButtonFrame.origin.y + paginationButtonFrame.size.height - 2;
- paginationViewFrame.size.height = 0;
- [paginationView setFrame:paginationViewFrame];
- [contentViewPane addSubview:paginationView];
-
- // Add observers for document task activity
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(startDocumentTaskForTab:)
- name:SPDocumentTaskStartNotification
- object:tableDocumentInstance];
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(endDocumentTaskForTab:)
- name:SPDocumentTaskEndNotification
- object:tableDocumentInstance];
-}
-
-#pragma mark -
-#pragma mark Table loading methods and information
-
-/*
- * Loads aTable, retrieving column information and updating the tableViewColumns before
- * reloading table data into the data array and redrawing the table.
- */
-- (void)loadTable:(NSString *)aTable
-{
-
- // Abort the reload if the user is still editing a row
- if ( isEditingRow )
- return;
-
- // If no table has been supplied, clear the table interface and return
- if (!aTable || [aTable isEqualToString:@""]) {
- [self performSelectorOnMainThread:@selector(setTableDetails:) withObject:nil waitUntilDone:YES];
- return;
- }
-
- // Attempt to retrieve the table encoding; if that fails (indicating an error occurred
- // while retrieving table data), or if the Rows variable is null, clear and return
- if (![tableDataInstance tableEncoding] || [[[tableDataInstance statusValues] objectForKey:@"Rows"] isNSNull]) {
- [self performSelectorOnMainThread:@selector(setTableDetails:) withObject:nil waitUntilDone:YES];
- return;
- }
-
- // Post a notification that a query will be performed
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance];
-
- // Set up the table details for the new table, and trigger an interface update
- NSDictionary *tableDetails = [NSDictionary dictionaryWithObjectsAndKeys:
- aTable, @"name",
- [tableDataInstance columns], @"columns",
- [tableDataInstance columnNames], @"columnNames",
- [tableDataInstance getConstraints], @"constraints",
- nil];
- [self performSelectorOnMainThread:@selector(setTableDetails:) withObject:tableDetails waitUntilDone:YES];
-
- // Trigger a data refresh
- [self loadTableValues];
-
- // Restore the view origin if appropriate
- if (!NSEqualRects(selectionViewportToRestore, NSZeroRect)) {
-
- // Scroll the viewport to the saved location
- selectionViewportToRestore.size = [tableContentView visibleRect].size;
- [tableContentView scrollRectToVisible:selectionViewportToRestore];
- }
-
- // Restore selection indexes if appropriate
- if (selectionIndexToRestore) {
- BOOL previousTableRowsSelectable = tableRowsSelectable;
- tableRowsSelectable = YES;
- [tableContentView selectRowIndexes:selectionIndexToRestore byExtendingSelection:NO];
- tableRowsSelectable = previousTableRowsSelectable;
- }
-
- // Update display if necessary
- [[tableContentView onMainThread] setNeedsDisplay:YES];
-
- // Init copyTable with necessary information for copying selected rows as SQL INSERT
- [tableContentView setTableInstance:self withTableData:tableValues withColumns:dataColumns withTableName:selectedTable withConnection:mySQLConnection];
- // Post the notification that the query is finished
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
-
- // Clear any details to restore now that they have been restored
- [self clearDetailsToRestore];
-}
-
-/**
- * Update stored table details and update the interface to match the supplied
- * table details.
- * Should be called on the main thread.
- */
-- (void) setTableDetails:(NSDictionary *)tableDetails
-{
- NSString *newTableName;
- NSInteger i;
- NSNumber *colWidth, *sortColumnNumberToRestore = nil;
- NSArray *columnNames;
- NSDictionary *columnDefinition;
- NSTableColumn *theCol;
- BOOL enableInteraction = ![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableContent] || ![tableDocumentInstance isWorking];
-
- if (!tableDetails) {
- newTableName = nil;
- } else {
- newTableName = [tableDetails objectForKey:@"name"];
- }
-
- // Ensure the pagination view hides itself if visible, after a tiny delay for smoothness
- [self performSelector:@selector(setPaginationViewVisibility:) withObject:nil afterDelay:0.1];
-
- // Reset table key store for use in argumentForRow:
- if (keys) [keys release], keys = nil;
-
- // Reset data column store
- [dataColumns removeAllObjects];
-
- // Check the supplied table name. If it matches the old one, a reload is being performed;
- // reload the data in-place to maintain table state if possible.
- if ([selectedTable isEqualToString:newTableName]) {
- previousTableRowsCount = tableRowsCount;
-
- // Otherwise store the newly selected table name and reset the data
- } else {
- if (selectedTable) [selectedTable release], selectedTable = nil;
- if (newTableName) selectedTable = [[NSString alloc] initWithString:newTableName];
- previousTableRowsCount = 0;
- contentPage = 1;
- [paginationPageField setStringValue:@"1"];
-
- // Clear the selection
- [tableContentView deselectAll:self];
-
- // Restore the table content view to the top left
- [tableContentView scrollRowToVisible:0];
- [tableContentView scrollColumnToVisible:0];
- }
-
- // If no table has been supplied, reset the view to a blank table and disabled elements.
- if (!newTableName) {
- // Remove existing columns from the table
- while ([[tableContentView tableColumns] count]) {
- [tableContentView removeTableColumn:NSArrayObjectAtIndex([tableContentView tableColumns], 0)];
- }
-
- // Empty the stored data arrays, including emptying the tableValues array
- // by ressignment for thread safety.
- previousTableRowsCount = 0;
- [self clearTableValues];
- [tableContentView reloadData];
- isFiltered = NO;
- isLimited = NO;
- [countText setStringValue:@""];
-
- // Reset sort column
- if (sortCol) [sortCol release]; sortCol = nil;
- isDesc = NO;
-
- // Empty and disable filter options
- [fieldField setEnabled:NO];
- [fieldField removeAllItems];
- [fieldField addItemWithTitle:NSLocalizedString(@"field", @"popup menuitem for field (showing only if disabled)")];
- [compareField setEnabled:NO];
- [compareField removeAllItems];
- [compareField addItemWithTitle:NSLocalizedString(@"is", @"popup menuitem for field IS value")];
- [argumentField setHidden:NO];
- [argumentField setEnabled:NO];
- [firstBetweenField setEnabled:NO];
- [secondBetweenField setEnabled:NO];
- [firstBetweenField setStringValue:@""];
- [secondBetweenField setStringValue:@""];
- [argumentField setStringValue:@""];
- [filterButton setEnabled:NO];
-
- // Hide BETWEEN operator controls
- [firstBetweenField setHidden:YES];
- [secondBetweenField setHidden:YES];
- [betweenTextField setHidden:YES];
-
- // Disable pagination
- [paginationPreviousButton setEnabled:NO];
- [paginationButton setEnabled:NO];
- [paginationButton setTitle:@""];
- [paginationNextButton setEnabled:NO];
-
- // Disable table action buttons
- [addButton setEnabled:NO];
- [copyButton setEnabled:NO];
- [removeButton setEnabled:NO];
-
- // Clear restoration settings
- [self clearDetailsToRestore];
-
- return;
- }
-
- // Otherwise, prepare to set up the new table - the table data instance already has table details set.
-
- // Remove existing columns from the table
- while ([[tableContentView tableColumns] count]) {
- [tableContentView removeTableColumn:NSArrayObjectAtIndex([tableContentView tableColumns], 0)];
- }
-
- // Retrieve the field names and types for this table from the data cache. This is used when requesting all data as part
- // of the fieldListForQuery method, and also to decide whether or not to preserve the current filter/sort settings.
- [dataColumns addObjectsFromArray:[tableDetails objectForKey:@"columns"]];
- columnNames = [tableDetails objectForKey:@"columnNames"];
-
- // Retrieve the constraints, and loop through them to add up to one foreign key to each column
- NSArray *constraints = [tableDetails objectForKey:@"constraints"];
-
- for (NSDictionary *constraint in constraints)
- {
- NSString *firstColumn = [[constraint objectForKey:@"columns"] objectAtIndex:0];
- NSString *firstRefColumn = [[[constraint objectForKey:@"ref_columns"] componentsSeparatedByString:@","] objectAtIndex:0];
- NSUInteger columnIndex = [columnNames indexOfObject:firstColumn];
-
- if (columnIndex != NSNotFound && ![[dataColumns objectAtIndex:columnIndex] objectForKey:@"foreignkeyreference"]) {
- NSDictionary *refDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
- [constraint objectForKey:@"ref_table"], @"table",
- firstRefColumn, @"column",
- nil];
- NSMutableDictionary *rowDictionary = [NSMutableDictionary dictionaryWithDictionary:[dataColumns objectAtIndex:columnIndex]];
- [rowDictionary setObject:refDictionary forKey:@"foreignkeyreference"];
- [dataColumns replaceObjectAtIndex:columnIndex withObject:rowDictionary];
- }
- }
-
- NSString *nullValue = [prefs objectForKey:SPNullValue];
- NSFont *tableFont = [NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPGlobalResultTableFont]];
- [tableContentView setRowHeight:2.0f+NSSizeToCGSize([[NSString stringWithString:@"{ǞṶḹÜ∑zgyf"] sizeWithAttributes:[NSDictionary dictionaryWithObject:tableFont forKey:NSFontAttributeName]]).height];
-
- // Add the new columns to the table
- for ( i = 0 ; i < [dataColumns count] ; i++ ) {
- columnDefinition = NSArrayObjectAtIndex(dataColumns, i);
-
- // Set up the column
- theCol = [[NSTableColumn alloc] initWithIdentifier:[columnDefinition objectForKey:@"datacolumnindex"]];
- [[theCol headerCell] setStringValue:[columnDefinition objectForKey:@"name"]];
- [theCol setEditable:YES];
-
- // Set up the data cell depending on the column type
- id dataCell;
- if ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"enum"]) {
- dataCell = [[[NSComboBoxCell alloc] initTextCell:@""] autorelease];
- [dataCell setButtonBordered:NO];
- [dataCell setBezeled:NO];
- [dataCell setDrawsBackground:NO];
- [dataCell setCompletes:YES];
- [dataCell setControlSize:NSSmallControlSize];
- // add prefs NULL value representation if NULL value is allowed for that field
- if([[columnDefinition objectForKey:@"null"] boolValue])
- [dataCell addItemWithObjectValue:nullValue];
- [dataCell addItemsWithObjectValues:[columnDefinition objectForKey:@"values"]];
-
- // Add a foreign key arrow if applicable
- } else if ([columnDefinition objectForKey:@"foreignkeyreference"]) {
- dataCell = [[[SPTextAndLinkCell alloc] initTextCell:@""] autorelease];
- [dataCell setTarget:self action:@selector(clickLinkArrow:)];
-
- // Otherwise instantiate a text-only cell
- } else {
- dataCell = [[[SPTextAndLinkCell alloc] initTextCell:@""] autorelease];
- }
- [dataCell setEditable:YES];
-
- // Set the line break mode and an NSFormatter subclass which truncates long strings for display
- [dataCell setLineBreakMode:NSLineBreakByTruncatingTail];
- [dataCell setFormatter:[[SPDataCellFormatter new] autorelease]];
-
- // Set field length limit if field is a varchar to match varchar length
- if ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"string"]) {
- [[dataCell formatter] setTextLimit:[[columnDefinition objectForKey:@"length"] integerValue]];
- }
-
- // Set the data cell font according to the preferences
- [dataCell setFont:tableFont];
-
- // Assign the data cell
- [theCol setDataCell:dataCell];
-
- // Set the width of this column to saved value if exists
- colWidth = [[[[prefs objectForKey:SPTableColumnWidths] objectForKey:[NSString stringWithFormat:@"%@@%@", [tableDocumentInstance database], [tableDocumentInstance host]]] objectForKey:[tablesListInstance tableName]] objectForKey:[columnDefinition objectForKey:@"name"]];
- if ( colWidth ) {
- [theCol setWidth:[colWidth doubleValue]];
- }
-
- // Set the column to be reselected for sorting if appropriate
- if (sortColumnToRestore && [sortColumnToRestore isEqualToString:[columnDefinition objectForKey:@"name"]])
- sortColumnNumberToRestore = [columnDefinition objectForKey:@"datacolumnindex"];
-
- // Add the column to the table
- [tableContentView addTableColumn:theCol];
- [theCol release];
- }
-
- // If the table has been reloaded and the previously selected sort column is still present, reselect it.
- if (sortColumnNumberToRestore) {
- theCol = [tableContentView tableColumnWithIdentifier:sortColumnNumberToRestore];
- if (sortCol) [sortCol release];
- sortCol = [sortColumnNumberToRestore copy];
- [tableContentView setHighlightedTableColumn:theCol];
- isDesc = !sortColumnToRestoreIsAsc;
- if ( isDesc ) {
- [tableContentView setIndicatorImage:[NSImage imageNamed:@"NSDescendingSortIndicator"] inTableColumn:theCol];
- } else {
- [tableContentView setIndicatorImage:[NSImage imageNamed:@"NSAscendingSortIndicator"] inTableColumn:theCol];
- }
-
- // Otherwise, clear sorting
- } else {
- if (sortCol) {
- [sortCol release];
- sortCol = nil;
- }
- isDesc = NO;
- }
-
- // Store the current first responder so filter field doesn't steal focus
- id currentFirstResponder = [[tableDocumentInstance parentWindow] firstResponder];
-
- // Enable and initialize filter fields (with tags for position of menu item and field position)
- [fieldField setEnabled:YES];
- [fieldField removeAllItems];
- [fieldField addItemsWithTitles:columnNames];
- for ( i = 0 ; i < [fieldField numberOfItems] ; i++ ) {
- [[fieldField itemAtIndex:i] setTag:i];
- }
- [compareField setEnabled:YES];
- [self setCompareTypes:self];
- [argumentField setEnabled:YES];
- [argumentField setStringValue:@""];
- [filterButton setEnabled:enableInteraction];
-
- // Restore preserved filter settings if appropriate and valid
- if (filterFieldToRestore) {
- [fieldField selectItemWithTitle:filterFieldToRestore];
- [self setCompareTypes:self];
-
- if ([fieldField itemWithTitle:filterFieldToRestore]
- && ((!filterComparisonToRestore && filterValueToRestore)
- || [compareField itemWithTitle:filterComparisonToRestore]))
- {
- if (filterComparisonToRestore) [compareField selectItemWithTitle:filterComparisonToRestore];
- if([filterComparisonToRestore isEqualToString:@"BETWEEN"]) {
- [argumentField setHidden:YES];
- if (firstBetweenValueToRestore) [firstBetweenField setStringValue:firstBetweenValueToRestore];
- if (secondBetweenValueToRestore) [secondBetweenField setStringValue:secondBetweenValueToRestore];
- } else {
- if (filterValueToRestore) [argumentField setStringValue:filterValueToRestore];
- }
- [self toggleFilterField:self];
-
- }
- }
-
- // Restore page number if limiting is set
- if ([prefs boolForKey:SPLimitResults]) contentPage = pageToRestore;
-
- // Restore first responder
- [[tableDocumentInstance parentWindow] makeFirstResponder:currentFirstResponder];
-
- // Set the state of the table buttons
- [addButton setEnabled:enableInteraction];
- [copyButton setEnabled:NO];
- [removeButton setEnabled:NO];
-
- // Reset the table store if required - basically if the table is being changed,
- // reassigning before emptying for thread safety.
- if (!previousTableRowsCount) {
- [self clearTableValues];
- }
-
-}
-
-/**
- * Remove all items from the current table value store. Do this by
- * reassigning the tableValues store and releasing the old location,
- * while setting thread safety flags.
- */
-- (void) clearTableValues
-{
- SPDataStorage *tableValuesTransition;
-
- tableValuesTransition = tableValues;
- pthread_mutex_lock(&tableValuesLock);
- tableRowsCount = 0;
- tableValues = [[SPDataStorage alloc] init];
- [tableContentView setTableData:tableValues];
- pthread_mutex_unlock(&tableValuesLock);
- [tableValuesTransition release];
-}
-
-/**
- * Reload the table data without reconfiguring the tableView,
- * using filters and limits as appropriate.
- * Will not refresh the table view itself.
- * Note that this does not empty the table array - see use of previousTableRowsCount.
- */
-- (void) loadTableValues
-{
- // If no table is selected, return
- if (!selectedTable) return;
-
- NSMutableString *queryString;
- NSString *queryStringBeforeLimit = nil;
- NSString *filterString;
- MCPStreamingResult *streamingResult;
- NSInteger rowsToLoad = [[tableDataInstance statusValueForKey:@"Rows"] integerValue];
-
- [countText setStringValue:NSLocalizedString(@"Loading table data...", @"Loading table data string")];
-
- // Notify any listeners that a query has started
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance];
-
- // Start construction of the query string
- queryString = [NSMutableString stringWithFormat:@"SELECT %@ FROM %@", [self fieldListForQuery], [selectedTable backtickQuotedString]];
-
- // Add a filter string if appropriate
- filterString = [self tableFilterString];
-
- if (filterString) {
- [queryString appendFormat:@" WHERE %@", filterString];
- isFiltered = YES;
- } else {
- isFiltered = NO;
- }
-
- // Add sorting details if appropriate
- if (sortCol) {
- [queryString appendFormat:@" ORDER BY %@", [[[dataColumns objectAtIndex:[sortCol integerValue]] objectForKey:@"name"] backtickQuotedString]];
- if (isDesc) [queryString appendString:@" DESC"];
- }
-
- // Check to see if a limit needs to be applied
- if ([prefs boolForKey:SPLimitResults]) {
-
- // Ensure the page supplied is within the appropriate limits
- if (contentPage <= 0)
- contentPage = 1;
- else if (contentPage > 1 && (contentPage - 1) * [prefs integerForKey:SPLimitResultsValue] >= maxNumRows)
- contentPage = ceil((CGFloat)maxNumRows / [prefs floatForKey:SPLimitResultsValue]);
-
- // If the result set is from a late page, take a copy of the string to allow resetting limit
- // if no results are found
- if (contentPage > 1) {
- queryStringBeforeLimit = [NSString stringWithString:queryString];
- }
-
- // Append the limit settings
- [queryString appendFormat:@" LIMIT %ld,%ld", (long)((contentPage-1)*[prefs integerForKey:SPLimitResultsValue]), (long)[prefs integerForKey:SPLimitResultsValue]];
-
- // Update the approximate count of the rows to load
- rowsToLoad = rowsToLoad - (contentPage-1)*[prefs integerForKey:SPLimitResultsValue];
- if (rowsToLoad > [prefs integerForKey:SPLimitResultsValue]) rowsToLoad = [prefs integerForKey:SPLimitResultsValue];
- }
-
- // If within a task, allow this query to be cancelled
- [tableDocumentInstance enableTaskCancellationWithTitle:NSLocalizedString(@"Stop", @"stop button") callbackObject:nil callbackFunction:NULL];
-
- // Perform and process the query
- [tableContentView performSelectorOnMainThread:@selector(noteNumberOfRowsChanged) withObject:nil waitUntilDone:YES];
- [self setUsedQuery:queryString];
- streamingResult = [[mySQLConnection streamingQueryString:queryString] retain];
-
- // Ensure the number of columns are unchanged; if the column count has changed, abort the load
- // and queue a full table reload.
- BOOL fullTableReloadRequired = NO;
- if (streamingResult && [dataColumns count] != [streamingResult numOfFields]) {
- [tableDocumentInstance disableTaskCancellation];
- [mySQLConnection cancelCurrentQuery];
- [streamingResult cancelResultLoad];
- fullTableReloadRequired = YES;
- }
-
- // Process the result into the data store
- if (!fullTableReloadRequired && streamingResult) {
- [self processResultIntoDataStorage:streamingResult approximateRowCount:rowsToLoad];
- }
- if (streamingResult) [streamingResult release];
-
- // If the result is empty, and a late page is selected, reset the page
- if (!fullTableReloadRequired && [prefs boolForKey:SPLimitResults] && queryStringBeforeLimit && !tableRowsCount && ![mySQLConnection queryCancelled]) {
- contentPage = 1;
- queryString = [NSMutableString stringWithFormat:@"%@ LIMIT 0,%ld", queryStringBeforeLimit, (long)[prefs integerForKey:SPLimitResultsValue]];
- [self setUsedQuery:queryString];
- streamingResult = [[mySQLConnection streamingQueryString:queryString] retain];
- if (streamingResult) {
- [self processResultIntoDataStorage:streamingResult approximateRowCount:[prefs integerForKey:SPLimitResultsValue]];
- [streamingResult release];
- }
- }
-
- if ([mySQLConnection queryCancelled] || [mySQLConnection queryErrored])
- isInterruptedLoad = YES;
- else
- isInterruptedLoad = NO;
-
- // End cancellation ability
- [tableDocumentInstance disableTaskCancellation];
-
- if ([prefs boolForKey:SPLimitResults]
- && (contentPage > 1
- || tableRowsCount == [prefs integerForKey:SPLimitResultsValue]))
- {
- isLimited = YES;
- } else {
- isLimited = NO;
- }
-
- // Update the rows count as necessary
- [self updateNumberOfRows];
-
- // Set the filter text
- [self updateCountText];
-
- // Update pagination
- [self updatePaginationState];
-
- // Notify listenters that the query has finished
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
-
- // Trigger a full reload if required
- if (fullTableReloadRequired) [self reloadTable:self];
-}
-
-/*
- * Processes a supplied streaming result set, loading it into the data array.
- */
-- (void)processResultIntoDataStorage:(MCPStreamingResult *)theResult approximateRowCount:(NSUInteger)targetRowCount
-{
- NSArray *tempRow;
- NSUInteger i;
- NSUInteger dataColumnsCount = [dataColumns count];
- BOOL *columnBlobStatuses = malloc(dataColumnsCount * sizeof(BOOL));
-
- // Set the column count on the data store
- [tableValues setColumnCount:dataColumnsCount];
-
- CGFloat relativeTargetRowCount = 100.0/targetRowCount;
- NSUInteger nextTableDisplayBoundary = 50;
- BOOL tableViewRedrawn = NO;
-
- NSUInteger rowsProcessed = 0;
-
- NSAutoreleasePool *dataLoadingPool;
- NSProgressIndicator *dataLoadingIndicator = [tableDocumentInstance valueForKey:@"queryProgressBar"];
- BOOL prefsLoadBlobsAsNeeded = [prefs boolForKey:SPLoadBlobsAsNeeded];
-
- // Build up an array of which columns are blobs for faster iteration
- for ( i = 0; i < dataColumnsCount ; i++ ) {
- columnBlobStatuses[i] = [tableDataInstance columnIsBlobOrText:[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"name"]];
- }
-
- // Set up an autorelease pool for row processing
- dataLoadingPool = [[NSAutoreleasePool alloc] init];
-
- // Loop through the result rows as they become available
- while (tempRow = [theResult fetchNextRowAsArray]) {
- pthread_mutex_lock(&tableValuesLock);
-
- if (rowsProcessed < previousTableRowsCount) {
- SPDataStorageReplaceRow(tableValues, rowsProcessed, tempRow);
- } else {
- SPDataStorageAddRow(tableValues, tempRow);
- }
-
- // Alter the values for hidden blob and text fields if appropriate
- if ( prefsLoadBlobsAsNeeded ) {
- for ( i = 0 ; i < dataColumnsCount ; i++ ) {
- if (columnBlobStatuses[i]) {
- SPDataStorageReplaceObjectAtRowAndColumn(tableValues, rowsProcessed, i, [SPNotLoaded notLoaded]);
- }
- }
- }
- rowsProcessed++;
-
- pthread_mutex_unlock(&tableValuesLock);
-
- // Update the task interface as necessary
- if (!isFiltered) {
- if (rowsProcessed < targetRowCount) {
- [tableDocumentInstance setTaskPercentage:(rowsProcessed*relativeTargetRowCount)];
- } else if (rowsProcessed == targetRowCount) {
- [tableDocumentInstance setTaskPercentage:100.0];
- [[tableDocumentInstance onMainThread] setTaskProgressToIndeterminateAfterDelay:YES];
- }
- }
-
- // Update the table view with new results every now and then
- if (rowsProcessed > nextTableDisplayBoundary) {
- if (rowsProcessed > tableRowsCount) tableRowsCount = rowsProcessed;
- [[tableContentView onMainThread] noteNumberOfRowsChanged];
- if (!tableViewRedrawn) {
- [[tableContentView onMainThread] setNeedsDisplay:YES];
- tableViewRedrawn = YES;
- }
- nextTableDisplayBoundary *= 2;
- }
-
- // Drain and reset the autorelease pool every ~1024 rows
- if (!(rowsProcessed % 1024)) {
- [dataLoadingPool drain];
- dataLoadingPool = [[NSAutoreleasePool alloc] init];
- }
- }
- tableRowsCount = rowsProcessed;
-
- // If the reloaded table is shorter than the previous table, remove the extra values from the storage
- if (tableRowsCount < [tableValues count]) {
- pthread_mutex_lock(&tableValuesLock);
- [tableValues removeRowsInRange:NSMakeRange(tableRowsCount, [tableValues count] - tableRowsCount)];
- pthread_mutex_unlock(&tableValuesLock);
- }
-
- // Ensure the table is aware of changes
- if ([NSThread isMainThread]) {
- [tableContentView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES];
- } else {
- [tableContentView performSelectorOnMainThread:@selector(noteNumberOfRowsChanged) withObject:nil waitUntilDone:YES];
- [tableContentView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO];
- }
-
- // Clean up the autorelease pool and reset the progress indicator
- [dataLoadingPool drain];
- [dataLoadingIndicator setIndeterminate:YES];
-
- free(columnBlobStatuses);
-}
-
-/**
- * Returns the query string for the current filter settings,
- * ready to be dropped into a WHERE clause, or nil if no filtering
- * is active.
- */
-- (NSString *)tableFilterString
-{
-
- // If the clause has the placeholder $BINARY that placeholder will be replaced
- // by BINARY if the user pressed ⇧ while invoking 'Filter' otherwise it will
- // replaced by @"".
- BOOL caseSensitive = (([[[NSApp onMainThread] currentEvent] modifierFlags]
- & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask)) > 0);
-
- NSString *filterString;
-
- if(contentFilters == nil) {
- NSLog(@"Fatal error while retrieving content filters. No filters found.");
- NSBeep();
- return nil;
- }
-
- // Current selected filter type
- if(![contentFilters objectForKey:compareType]) {
- NSLog(@"Error while retrieving filters. Filter type “%@” unknown.", compareType);
- NSBeep();
- return nil;
- }
- NSDictionary *filter = [[contentFilters objectForKey:compareType] objectAtIndex:[[compareField selectedItem] tag]];
-
- if(![filter objectForKey:@"Clause"] || ![filter objectForKey:@"NumberOfArguments"]) {
- NSLog(@"Error while retrieving filter clause. No “Clause” or/and “NumberOfArguments” key found.");
- NSBeep();
- return nil;
- }
-
- NSUInteger numberOfArguments = [[filter objectForKey:@"NumberOfArguments"] integerValue];
-
- BOOL suppressLeadingTablePlaceholder = NO;
- if([filter objectForKey:@"SuppressLeadingFieldPlaceholder"])
- suppressLeadingTablePlaceholder = YES;
-
- // argument if Filter requires only one argument
- NSMutableString *argument = [[NSMutableString alloc] initWithString:[argumentField stringValue]];
-
- // If the filter field is empty and the selected filter does not require
- // only one argument, then no filtering is required - return nil.
- if (![argument length] && numberOfArguments == 1) {
- [argument release];
- return nil;
- }
-
- // arguments if Filter requires two arguments
- NSMutableString *firstBetweenArgument = [[NSMutableString alloc] initWithString:[firstBetweenField stringValue]];
- NSMutableString *secondBetweenArgument = [[NSMutableString alloc] initWithString:[secondBetweenField stringValue]];
-
- // If filter requires two arguments and either of the argument fields are empty
- // return nil.
- if (numberOfArguments == 2) {
- if (([firstBetweenArgument length] == 0) || ([secondBetweenArgument length] == 0)) {
- [argument release];
- [firstBetweenArgument release];
- [secondBetweenArgument release];
- return nil;
- }
- }
-
- // Retrieve actual WHERE clause
- NSMutableString *clause = [[NSMutableString alloc] init];
- [clause setString:[filter objectForKey:@"Clause"]];
-
- [clause replaceOccurrencesOfRegex:@"(?<!\\\\)\\$BINARY" withString:(caseSensitive) ? @"BINARY" : @""];
- [clause flushCachedRegexData];
- [clause replaceOccurrencesOfRegex:@"(?<!\\\\)\\$CURRENT_FIELD" withString:([fieldField titleOfSelectedItem]) ? [[fieldField titleOfSelectedItem] backtickQuotedString] : @""];
- [clause flushCachedRegexData];
-
- // Escape % sign
- [clause replaceOccurrencesOfRegex:@"%" withString:@"%%"];
- [clause flushCachedRegexData];
-
- // Replace placeholder ${} by %@
- NSRange matchedRange;
- NSString *re = @"(?<!\\\\)\\$\\{.*?\\}";
- if([clause isMatchedByRegex:re]) {
- while([clause isMatchedByRegex:re]) {
- matchedRange = [clause rangeOfRegex:re];
- [clause replaceCharactersInRange:matchedRange withString:@"%@"];
- [clause flushCachedRegexData];
- }
- }
-
- // Check number of placeholders and given 'NumberOfArguments'
- if([clause replaceOccurrencesOfString:@"%@" withString:@"%@" options:NSLiteralSearch range:NSMakeRange(0, [clause length])] != numberOfArguments) {
- NSLog(@"Error while setting filter string. “NumberOfArguments” differs from the number of arguments specified in “Clause”.");
- NSBeep();
- [argument release];
- [firstBetweenArgument release];
- [secondBetweenArgument release];
- [clause release];
- return nil;
- }
-
- // Construct the filter string according the required number of arguments
-
- if(suppressLeadingTablePlaceholder) {
- if (numberOfArguments == 2) {
- filterString = [NSString stringWithFormat:clause,
- [self escapeFilterArgument:firstBetweenArgument againstClause:clause],
- [self escapeFilterArgument:secondBetweenArgument againstClause:clause]];
- } else if (numberOfArguments == 1) {
- filterString = [NSString stringWithFormat:clause, [self escapeFilterArgument:argument againstClause:clause]];
- } else {
- filterString = [NSString stringWithString:clause];
- if(numberOfArguments > 2) {
- NSLog(@"Filter with more than 2 arguments is not yet supported.");
- NSBeep();
- }
- }
- } else {
- if (numberOfArguments == 2) {
- filterString = [NSString stringWithFormat:@"%@ %@",
- [[fieldField titleOfSelectedItem] backtickQuotedString],
- [NSString stringWithFormat:clause,
- [self escapeFilterArgument:firstBetweenArgument againstClause:clause],
- [self escapeFilterArgument:secondBetweenArgument againstClause:clause]]];
- } else if (numberOfArguments == 1) {
- filterString = [NSString stringWithFormat:@"%@ %@",
- [[fieldField titleOfSelectedItem] backtickQuotedString],
- [NSString stringWithFormat:clause, [self escapeFilterArgument:argument againstClause:clause]]];
- } else {
- filterString = [NSString stringWithFormat:@"%@ %@",
- [[fieldField titleOfSelectedItem] backtickQuotedString], clause];
- if(numberOfArguments > 2) {
- NSLog(@"Filter with more than 2 arguments is not yet supported.");
- NSBeep();
- }
- }
- }
-
- [argument release];
- [firstBetweenArgument release];
- [secondBetweenArgument release];
- [clause release];
-
- // Return the filter string
- return filterString;
-}
-
-- (NSString *)escapeFilterArgument:(NSString *)argument againstClause:(NSString *)clause
-{
-
- NSMutableString *arg = [[NSMutableString alloc] init];
- [arg setString:argument];
-
- [arg replaceOccurrencesOfRegex:@"(\\\\)(?![nrt_%])" withString:@"\\\\\\\\\\\\\\\\"];
- [arg flushCachedRegexData];
- [arg replaceOccurrencesOfRegex:@"(\\\\)(?=[nrt])" withString:@"\\\\\\"];
- [arg flushCachedRegexData];
-
- // Get quote sign for escaping - this should work for 99% of all cases
- NSString *quoteSign = [clause stringByMatching:@"([\"'])[^\\1]*?%@[^\\1]*?\\1" capture:1L];
- // Esape argument
- if(quoteSign != nil && [quoteSign length] == 1) {
- [arg replaceOccurrencesOfRegex:[NSString stringWithFormat:@"(%@)", quoteSign] withString:@"\\\\$1"];
- [arg flushCachedRegexData];
- }
- // if([clause isMatchedByRegex:@"(?i)\\blike\\b.*?%(?!@)"]) {
- // [arg replaceOccurrencesOfRegex:@"([_%])" withString:@"\\\\$1"];
- // [arg flushCachedRegexData];
- // }
- return [arg autorelease];
-}
-
-/*
- * Update the table count/selection text
- */
-- (void)updateCountText
-{
- NSString *rowString;
- NSMutableString *countString = [NSMutableString string];
- NSNumberFormatter *numberFormatter = [[[NSNumberFormatter alloc] init] autorelease];
- [numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
-
- // Set up a couple of common strings
- NSString *tableCountString = [numberFormatter stringFromNumber:[NSNumber numberWithUnsignedInteger:tableRowsCount]];
- NSString *maxRowsString = [numberFormatter stringFromNumber:[NSNumber numberWithUnsignedInteger:maxNumRows]];
-
- // If the result is partial due to an error or query cancellation, show a very basic count
- if (isInterruptedLoad) {
- if (tableRowsCount == 1)
- [countString appendFormat:NSLocalizedString(@"%@ row in partial load", @"text showing a single row a partially loaded result"), tableCountString];
- else
- [countString appendFormat:NSLocalizedString(@"%@ rows in partial load", @"text showing how many rows are in a partially loaded result"), tableCountString];
-
- // If no filter or limit is active, show just the count of rows in the table
- } else if (!isFiltered && !isLimited) {
- if (tableRowsCount == 1)
- [countString appendFormat:NSLocalizedString(@"%@ row in table", @"text showing a single row in the result"), tableCountString];
- else
- [countString appendFormat:NSLocalizedString(@"%@ rows in table", @"text showing how many rows are in the result"), tableCountString];
-
- // If a limit is active, display a string suggesting a limit is active
- } else if (!isFiltered && isLimited) {
- NSUInteger limitStart = (contentPage-1)*[prefs integerForKey:SPLimitResultsValue] + 1;
- [countString appendFormat:NSLocalizedString(@"Rows %@ - %@ of %@%@ from table", @"text showing how many rows are in the limited result"), [numberFormatter stringFromNumber:[NSNumber numberWithUnsignedInteger:limitStart]], [numberFormatter stringFromNumber:[NSNumber numberWithUnsignedInteger:(limitStart+tableRowsCount-1)]], maxNumRowsIsEstimate?@"~":@"", maxRowsString];
-
- // If just a filter is active, show a count and an indication a filter is active
- } else if (isFiltered && !isLimited) {
- if (tableRowsCount == 1)
- [countString appendFormat:NSLocalizedString(@"%@ row of %@%@ matches filter", @"text showing how a single rows matched filter"), tableCountString, maxNumRowsIsEstimate?@"~":@"", maxRowsString];
- else
- [countString appendFormat:NSLocalizedString(@"%@ rows of %@%@ match filter", @"text showing how many rows matched filter"), tableCountString, maxNumRowsIsEstimate?@"~":@"", maxRowsString];
-
- // If both a filter and limit is active, display full string
- } else {
- NSUInteger limitStart = (contentPage-1)*[prefs integerForKey:SPLimitResultsValue] + 1;
- [countString appendFormat:NSLocalizedString(@"Rows %@ - %@ from filtered matches", @"text showing how many rows are in the limited filter match"), [numberFormatter stringFromNumber:[NSNumber numberWithUnsignedInteger:limitStart]], [numberFormatter stringFromNumber:[NSNumber numberWithUnsignedInteger:(limitStart+tableRowsCount-1)]]];
- }
-
- // If rows are selected, append selection count
- if ([tableContentView numberOfSelectedRows] > 0) {
- [countString appendString:@"; "];
- if ([tableContentView numberOfSelectedRows] == 1)
- rowString = [NSString stringWithString:NSLocalizedString(@"row", @"singular word for row")];
- else
- rowString = [NSString stringWithString:NSLocalizedString(@"rows", @"plural word for rows")];
- [countString appendFormat:NSLocalizedString(@"%@ %@ selected", @"text showing how many rows are selected"), [numberFormatter stringFromNumber:[NSNumber numberWithInteger:[tableContentView numberOfSelectedRows]]], rowString];
- }
-
- [[countText onMainThread] setStringValue:countString];
-}
-
-#pragma mark -
-#pragma mark Table interface actions
-
-/*
- * Reloads the current table data, performing a new SQL query. Now attempts to preserve sort
- * order, filters, and viewport. Performs the action in a new thread if a task is not already
- * running.
- */
-- (IBAction)reloadTable:(id)sender
-{
- [tableDocumentInstance startTaskWithDescription:NSLocalizedString(@"Reloading data...", @"Reloading data task description")];
-
- if ([NSThread isMainThread]) {
- [NSThread detachNewThreadSelector:@selector(reloadTableTask) toTarget:self withObject:nil];
- } else {
- [self reloadTableTask];
- }
-}
-
-- (void)reloadTableTask
-{
- NSAutoreleasePool *reloadPool = [[NSAutoreleasePool alloc] init];
-
- // Check whether a save of the current row is required.
- if (![[self onMainThread] saveRowOnDeselect]) return;
-
- // Save view details to restore safely if possible (except viewport, which will be
- // preserved automatically, and can then be scrolled as the table loads)
- [self storeCurrentDetailsForRestoration];
- [self setViewportToRestore:NSZeroRect];
-
- // Clear the table data column cache
- [tableDataInstance resetColumnData];
-
- // Load the table's data
- [self loadTable:[tablesListInstance tableName]];
-
- [tableDocumentInstance endTask];
-
- [reloadPool drain];
-}
-
-/*
- * Filter the table with arguments given by the user.
- * Performs the action in a new thread if necessary.
- */
-- (IBAction)filterTable:(id)sender
-{
- NSString *taskString;
-
- if ([tableDocumentInstance isWorking]) return;
- [self setPaginationViewVisibility:FALSE];
-
- // Select the correct pagination value
- if (![prefs boolForKey:SPLimitResults] || [paginationPageField integerValue] <= 0)
- contentPage = 1;
- else if (([paginationPageField integerValue] - 1) * [prefs integerForKey:SPLimitResultsValue] >= maxNumRows)
- contentPage = ceil((CGFloat)maxNumRows / [prefs floatForKey:SPLimitResultsValue]);
- else
- contentPage = [paginationPageField integerValue];
-
- if ([self tableFilterString]) {
- taskString = NSLocalizedString(@"Filtering table...", @"Filtering table task description");
- } else if (contentPage == 1) {
- taskString = [NSString stringWithFormat:NSLocalizedString(@"Loading %@...", @"Loading table task string"), selectedTable];
- } else {
- taskString = [NSString stringWithFormat:NSLocalizedString(@"Loading page %lu...", @"Loading table page task string"), (unsigned long)contentPage];
- }
-
- [tableDocumentInstance startTaskWithDescription:taskString];
-
- if ([NSThread isMainThread]) {
- [NSThread detachNewThreadSelector:@selector(filterTableTask) toTarget:self withObject:nil];
- } else {
- [self filterTableTask];
- }
-}
-- (void)filterTableTask
-{
- NSAutoreleasePool *filterPool = [[NSAutoreleasePool alloc] init];
-
- // Check whether a save of the current row is required.
- if (![[self onMainThread] saveRowOnDeselect]) return;
-
- // Update history
- [spHistoryControllerInstance updateHistoryEntries];
-
- // Reset and reload data using the new filter settings
- previousTableRowsCount = 0;
- [self clearTableValues];
- [self loadTableValues];
- [[tableContentView onMainThread] scrollPoint:NSMakePoint(0.0, 0.0)];
-
- [tableDocumentInstance endTask];
- [filterPool drain];
-}
-
-/**
- * Enables or disables the filter input field based on the selected filter type.
- */
-- (IBAction)toggleFilterField:(id)sender
-{
-
- // Check if user called "Edit Filter…"
- if([[compareField selectedItem] tag] == [[contentFilters objectForKey:compareType] count]) {
- [self openContentFilterManager];
- return;
- }
-
- // Remember last selection for "Edit filter…"
- lastSelectedContentFilterIndex = [[compareField selectedItem] tag];
-
- NSDictionary *filter = [[contentFilters objectForKey:compareType] objectAtIndex:lastSelectedContentFilterIndex];
- NSUInteger numOfArgs = [[filter objectForKey:@"NumberOfArguments"] integerValue];
- if (numOfArgs == 2) {
- [argumentField setHidden:YES];
-
- if([filter objectForKey:@"ConjunctionLabels"] && [[filter objectForKey:@"ConjunctionLabels"] count] == 1)
- [betweenTextField setStringValue:[[filter objectForKey:@"ConjunctionLabels"] objectAtIndex:0]];
- else
- [betweenTextField setStringValue:@""];
-
- [betweenTextField setHidden:NO];
- [firstBetweenField setHidden:NO];
- [secondBetweenField setHidden:NO];
-
- [firstBetweenField setEnabled:YES];
- [secondBetweenField setEnabled:YES];
- [firstBetweenField selectText:self];
- }
- else if (numOfArgs == 1){
- [argumentField setHidden:NO];
- [argumentField setEnabled:YES];
- [argumentField selectText:self];
-
- [betweenTextField setHidden:YES];
- [firstBetweenField setHidden:YES];
- [secondBetweenField setHidden:YES];
- }
- else {
- [argumentField setHidden:NO];
- [argumentField setEnabled:NO];
-
- [betweenTextField setHidden:YES];
- [firstBetweenField setHidden:YES];
- [secondBetweenField setHidden:YES];
-
- // Start search if no argument is required
- if(numOfArgs == 0)
- [self filterTable:self];
- }
-
-}
-
-- (NSString *)usedQuery
-{
- return usedQuery;
-}
-
-- (void)setUsedQuery:(NSString *)query
-{
- if (usedQuery) [usedQuery release];
- usedQuery = [[NSString alloc] initWithString:query];
-}
-
-#pragma mark -
-#pragma mark Pagination
-
-/**
- * Move the pagination backwards or forwards one page
- */
-- (IBAction) navigatePaginationFromButton:(id)sender
-{
- if (sender == paginationPreviousButton) {
- if (contentPage <= 1) return;
- [paginationPageField setIntegerValue:(contentPage - 1)];
- [self filterTable:sender];
- } else if (sender == paginationNextButton) {
- if (contentPage * [prefs integerForKey:SPLimitResultsValue] >= maxNumRows) return;
- [paginationPageField setIntegerValue:(contentPage + 1)];
- [self filterTable:sender];
- }
-}
-
-/**
- * When the Pagination button is pressed, show or hide the pagination
- * layer depending on the current state.
- */
-- (IBAction) togglePagination:(id)sender
-{
- if ([sender state] == NSOnState) [self setPaginationViewVisibility:YES];
- else [self setPaginationViewVisibility:NO];
-}
-
-/**
- * Show or hide the pagination layer, also changing the first responder as appropriate.
- */
-- (void) setPaginationViewVisibility:(BOOL)makeVisible
-{
- NSRect paginationViewFrame = [paginationView frame];
-
- if (makeVisible) {
- if (paginationViewFrame.size.height == paginationViewHeight) return;
- paginationViewFrame.size.height = paginationViewHeight;
- [paginationButton setState:NSOnState];
- [paginationButton setImage:[NSImage imageNamed:@"button_action"]];
- [[tableDocumentInstance parentWindow] makeFirstResponder:paginationPageField];
- } else {
- if (paginationViewFrame.size.height == 0) return;
- paginationViewFrame.size.height = 0;
- [paginationButton setState:NSOffState];
- [paginationButton setImage:[NSImage imageNamed:@"button_pagination"]];
- if ([[tableDocumentInstance parentWindow] firstResponder] == paginationPageField
- || ([[[tableDocumentInstance parentWindow] firstResponder] respondsToSelector:@selector(superview)]
- && [(id)[[tableDocumentInstance parentWindow] firstResponder] superview]
- && [[(id)[[tableDocumentInstance parentWindow] firstResponder] superview] respondsToSelector:@selector(superview)]
- && [[(id)[[tableDocumentInstance parentWindow] firstResponder] superview] superview] == paginationPageField))
- {
- [[tableDocumentInstance parentWindow] makeFirstResponder:nil];
- }
- }
-
- [[paginationView animator] setFrame:paginationViewFrame];
-}
-
-/**
- * Update the state of the pagination buttons and text.
- */
-- (void) updatePaginationState
-{
- NSUInteger maxPage = ceil((CGFloat)maxNumRows / [prefs floatForKey:SPLimitResultsValue]);
- if (isFiltered && !isLimited) {
- maxPage = contentPage;
- }
- BOOL enabledMode = ![tableDocumentInstance isWorking];
-
- NSNumberFormatter *numberFormatter = [[[NSNumberFormatter alloc] init] autorelease];
- [numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
-
- // Set up the previous page button
- if ([prefs boolForKey:SPLimitResults] && contentPage > 1)
- [paginationPreviousButton setEnabled:enabledMode];
- else
- [paginationPreviousButton setEnabled:NO];
-
- // Set up the next page button
- if ([prefs boolForKey:SPLimitResults] && contentPage < maxPage)
- [paginationNextButton setEnabled:enabledMode];
- else
- [paginationNextButton setEnabled:NO];
-
- // As long as a table is selected (which it will be if this is called), enable pagination detail button
- [paginationButton setEnabled:enabledMode];
-
- // Set the values and maximums for the text field and associated pager
- [paginationPageField setStringValue:[numberFormatter stringFromNumber:[NSNumber numberWithUnsignedInteger:contentPage]]];
- [[paginationPageField formatter] setMaximum:[NSNumber numberWithUnsignedInteger:maxPage]];
- [paginationPageStepper setIntegerValue:contentPage];
- [paginationPageStepper setMaxValue:maxPage];
-}
-
-#pragma mark -
-#pragma mark Edit methods
-
-/*
- * Adds an empty row to the table-array and goes into edit mode
- */
-- (IBAction)addRow:(id)sender
-{
- NSMutableDictionary *column;
- NSMutableArray *newRow = [NSMutableArray array];
- NSUInteger i;
-
- // Check whether a save of the current row is required.
- if ( ![self saveRowOnDeselect] ) return;
-
- for ( i = 0 ; i < [dataColumns count] ; i++ ) {
- column = NSArrayObjectAtIndex(dataColumns, i);
- if ([column objectForKey:@"default"] == nil || [column objectForKey:@"default"] == [NSNull null]) {
- [newRow addObject:[NSNull null]];
- } else {
- [newRow addObject:[column objectForKey:@"default"]];
- }
- }
- [tableValues addRowWithContents:newRow];
- tableRowsCount++;
-
- [tableContentView reloadData];
- [tableContentView selectRowIndexes:[NSIndexSet indexSetWithIndex:[tableContentView numberOfRows]-1] byExtendingSelection:NO];
- [tableContentView scrollRowToVisible:[tableContentView selectedRow]];
- isEditingRow = YES;
- isEditingNewRow = YES;
- currentlyEditingRow = [tableContentView selectedRow];
- if ( [multipleLineEditingButton state] == NSOffState )
- [tableContentView editColumn:0 row:[tableContentView numberOfRows]-1 withEvent:nil select:YES];
-}
-
-/**
- * Copies a row of the table-array and goes into edit mode
- */
-- (IBAction)copyRow:(id)sender
-{
- NSMutableArray *tempRow;
- MCPResult *queryResult;
- NSDictionary *row;
- NSArray *dbDataRow = nil;
- NSUInteger i;
-
- // Check whether a save of the current row is required.
- if ( ![self saveRowOnDeselect] ) return;
-
- if ( [tableContentView numberOfSelectedRows] < 1 )
- return;
- if ( [tableContentView numberOfSelectedRows] > 1 ) {
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, NSLocalizedString(@"You can only copy single rows.", @"message of panel when trying to copy multiple rows"));
- return;
- }
-
- //copy row
- tempRow = [tableValues rowContentsAtIndex:[tableContentView selectedRow]];
-
- //if we don't show blobs, read data for this duplicate column from db
- if ([prefs boolForKey:SPLoadBlobsAsNeeded]) {
- // Abort if there are no indices on this table - argumentForRow will display an error.
- if (![[self argumentForRow:[tableContentView selectedRow]] length]){
- return;
- }
- //if we have indexes, use argumentForRow
- queryResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SELECT * FROM %@ WHERE %@", [selectedTable backtickQuotedString], [self argumentForRow:[tableContentView selectedRow]]]];
- dbDataRow = [queryResult fetchRowAsArray];
- }
-
- //set autoincrement fields to NULL
- queryResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW COLUMNS FROM %@", [selectedTable backtickQuotedString]]];
- [queryResult setReturnDataAsStrings:YES];
- if ([queryResult numOfRows]) [queryResult dataSeek:0];
- for ( i = 0 ; i < [queryResult numOfRows] ; i++ ) {
- row = [queryResult fetchRowAsDictionary];
- if ( [[row objectForKey:@"Extra"] isEqualToString:@"auto_increment"] ) {
- [tempRow replaceObjectAtIndex:i withObject:[NSNull null]];
- } else if ( [tableDataInstance columnIsBlobOrText:[row objectForKey:@"Field"]] && [prefs boolForKey:SPLoadBlobsAsNeeded] && dbDataRow) {
- [tempRow replaceObjectAtIndex:i withObject:[dbDataRow objectAtIndex:i]];
- }
- }
-
- //insert the copied row
- [tableValues insertRowContents:tempRow atIndex:[tableContentView selectedRow]+1];
- tableRowsCount++;
-
- //select row and go in edit mode
- [tableContentView reloadData];
- [tableContentView selectRowIndexes:[NSIndexSet indexSetWithIndex:[tableContentView selectedRow]+1] byExtendingSelection:NO];
- isEditingRow = YES;
- isEditingNewRow = YES;
- currentlyEditingRow = [tableContentView selectedRow];
- if ( [multipleLineEditingButton state] == NSOffState )
- [tableContentView editColumn:0 row:[tableContentView selectedRow] withEvent:nil select:YES];
-}
-
-/**
- * Asks the user if they really want to delete the selected rows
- */
-- (IBAction)removeRow:(id)sender
-{
- // Check whether a save of the current row is required.
- // if (![self saveRowOnDeselect])
- // return;
-
- // cancel editing (maybe this is not the ideal method -- see xcode docs for that method)
- [[tableDocumentInstance parentWindow] endEditingFor:nil];
-
-
- if (![tableContentView numberOfSelectedRows])
- return;
-
- NSAlert *alert = [NSAlert alertWithMessageText:@""
- defaultButton:NSLocalizedString(@"Delete", @"delete button")
- alternateButton:NSLocalizedString(@"Cancel", @"cancel button")
- otherButton:nil
- informativeTextWithFormat:@""];
-
- [alert setAlertStyle:NSCriticalAlertStyle];
-
- NSArray *buttons = [alert buttons];
-
- // Change the alert's cancel button to have the key equivalent of return
- [[buttons objectAtIndex:0] setKeyEquivalent:@"d"];
- [[buttons objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask];
- [[buttons objectAtIndex:1] setKeyEquivalent:@"\r"];
-
- [alert setShowsSuppressionButton:NO];
- [[alert suppressionButton] setState:NSOffState];
-
- NSString *contextInfo = @"removerow";
-
- if (([tableContentView numberOfSelectedRows] == [tableContentView numberOfRows]) && !isFiltered && !isLimited && !isInterruptedLoad && !isEditingNewRow) {
-
- contextInfo = @"removeallrows";
-
- // If table has PRIMARY KEY ask for resetting the auto increment after deletion if given
- if(![[tableDataInstance statusValueForKey:@"Auto_increment"] isKindOfClass:[NSNull class]]) {
- [alert setShowsSuppressionButton:YES];
- [[alert suppressionButton] setState:NSOnState];
- [[[alert suppressionButton] cell] setControlSize:NSSmallControlSize];
- [[[alert suppressionButton] cell] setFont:[NSFont systemFontOfSize:11]];
- [[alert suppressionButton] setTitle:NSLocalizedString(@"Reset AUTO_INCREMENT after deletion?", @"reset auto_increment after deletion of all rows message")];
- }
-
- [alert setMessageText:NSLocalizedString(@"Delete all rows?", @"delete all rows message")];
- [alert setInformativeText:NSLocalizedString(@"Are you sure you want to delete all the rows from this table? This action cannot be undone.", @"delete all rows informative message")];
- }
- else if ([tableContentView numberOfSelectedRows] == 1) {
- [alert setMessageText:NSLocalizedString(@"Delete selected row?", @"delete selected row message")];
- [alert setInformativeText:NSLocalizedString(@"Are you sure you want to delete the selected row from this table? This action cannot be undone.", @"delete selected row informative message")];
- }
- else {
- [alert setMessageText:NSLocalizedString(@"Delete rows?", @"delete rows message")];
- [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"Are you sure you want to delete the selected %ld rows from this table? This action cannot be undone.", @"delete rows informative message"), (long)[tableContentView numberOfSelectedRows]]];
- }
-
- [alert beginSheetModalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:@selector(removeRowSheetDidEnd:returnCode:contextInfo:) contextInfo:contextInfo];
-}
-
-/**
- * Perform the requested row deletion action.
- */
-- (void)removeRowSheetDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
-{
-
- NSMutableIndexSet *selectedRows = [NSMutableIndexSet indexSet];
- NSString *wherePart;
- NSInteger i, errors;
- BOOL consoleUpdateStatus;
- BOOL reloadAfterRemovingRow = [prefs boolForKey:SPReloadAfterRemovingRow];
-
- // Order out current sheet to suppress overlapping of sheets
- [[alert window] orderOut:nil];
-
- if ( [contextInfo isEqualToString:@"removeallrows"] ) {
- if ( returnCode == NSAlertDefaultReturn ) {
- //check if the user is currently editing a row
- if (isEditingRow) {
- //cancel the edit
- isEditingRow = NO;
- // in case the delete fails, make sure we at least stay in a somewhat consistent state
- [tableValues replaceRowAtIndex:currentlyEditingRow withRowContents:oldRow];
- currentlyEditingRow = -1;
- }
-
- [mySQLConnection queryString:[NSString stringWithFormat:@"DELETE FROM %@", [selectedTable backtickQuotedString]]];
- if ( ![mySQLConnection queryErrored] ) {
-
- // Reset auto increment if suppression button was ticked
- if([[alert suppressionButton] state] == NSOnState)
- [tableSourceInstance setAutoIncrementTo:@"1"];
-
- [self reloadTable:self];
-
- } else {
- [self performSelector:@selector(showErrorSheetWith:)
- withObject:[NSArray arrayWithObjects:NSLocalizedString(@"Error", @"error"),
- [NSString stringWithFormat:NSLocalizedString(@"Couldn't delete rows.\n\nMySQL said: %@", @"message when deleteing all rows failed"),
- [mySQLConnection getLastErrorMessage]],
- nil]
- afterDelay:0.3];
- }
- }
- } else if ( [contextInfo isEqualToString:@"removerow"] ) {
- if ( returnCode == NSAlertDefaultReturn ) {
- [selectedRows addIndexes:[tableContentView selectedRowIndexes]];
-
- //check if the user is currently editing a row
- if (isEditingRow) {
- //make sure that only one row is selected. This should never happen
- if ([selectedRows count]!=1) {
- NSLog(@"Expected only one selected row, but found %d",[selectedRows count]);
- }
- // this code is pretty much taken from the escape key handler
- if ( isEditingNewRow ) {
- // since the user is currently editing a new row, we don't actually have to delete any rows from the database
- // we just have to remove the row from the view (and the store)
- isEditingRow = NO;
- isEditingNewRow = NO;
- tableRowsCount--;
- [tableValues removeRowAtIndex:currentlyEditingRow];
- currentlyEditingRow = -1;
- [self updateCountText];
- [tableContentView reloadData];
-
- //deselect the row
- [tableContentView selectRowIndexes:[NSIndexSet indexSet] byExtendingSelection:NO];
-
- // we also don't have to reload the table, since no query went to the database
- return;
- } else {
- //cancel the edit
- isEditingRow = NO;
- // in case the delete fails, make sure we at least stay in a somewhat consistent state
- [tableValues replaceRowAtIndex:currentlyEditingRow withRowContents:oldRow];
- currentlyEditingRow = -1;
- }
-
- }
- [tableContentView selectRowIndexes:[NSIndexSet indexSet] byExtendingSelection:NO];
-
- errors = 0;
-
- // Disable updating of the Console Log window for large number of queries
- // to speed the deletion
- consoleUpdateStatus = [[SPQueryController sharedQueryController] allowConsoleUpdate];
- if([selectedRows count] > 10)
- [[SPQueryController sharedQueryController] setAllowConsoleUpdate:NO];
-
- NSUInteger index = [selectedRows firstIndex];
-
- NSArray *primaryKeyFieldNames = [tableDataInstance primaryKeyColumnNames];
-
- // If no PRIMARY KEY is found and numberOfSelectedRows > 3 then
- // check for uniqueness of rows via combining all column values;
- // if unique then use the all columns as 'primary keys'
- if([selectedRows count] >3 && primaryKeyFieldNames == nil) {
- primaryKeyFieldNames = [tableDataInstance columnNames];
-
- NSInteger numberOfRows = 0;
- // Get the number of rows in the table
- MCPResult *r;
- r = [mySQLConnection queryString:[NSString stringWithFormat:@"SELECT COUNT(1) FROM %@", [selectedTable backtickQuotedString]]];
- if (![mySQLConnection queryErrored]) {
- NSArray *a = [r fetchRowAsArray];
- if([a count])
- numberOfRows = [[a objectAtIndex:0] integerValue];
- }
- // Check for uniqueness via LIMIT numberOfRows-1,numberOfRows for speed
- if(numberOfRows > 0) {
- [mySQLConnection queryString:[NSString stringWithFormat:@"SELECT * FROM %@ GROUP BY %@ LIMIT %ld,%ld", [selectedTable backtickQuotedString], [primaryKeyFieldNames componentsJoinedAndBacktickQuoted], (long)(numberOfRows-1), (long)numberOfRows]];
- if([mySQLConnection affectedRows] == 0)
- primaryKeyFieldNames = nil;
- } else {
- primaryKeyFieldNames = nil;
- }
- }
-
- if(primaryKeyFieldNames == nil) {
- // delete row by row
- while (index != NSNotFound) {
-
- wherePart = [NSString stringWithString:[self argumentForRow:index]];
-
- //argumentForRow might return empty query, in which case we shouldn't execute the partial query
- if([wherePart length]) {
- [mySQLConnection queryString:[NSString stringWithFormat:@"DELETE FROM %@ WHERE %@", [selectedTable backtickQuotedString], wherePart]];
-
- // Check for errors
- if ( ![mySQLConnection affectedRows] || [mySQLConnection queryErrored]) {
- // If error delete that index from selectedRows for reloading table if
- // "ReloadAfterRemovingRow" is disbaled
- if(!reloadAfterRemovingRow)
- [selectedRows removeIndex:index];
- errors++;
- }
- } else {
- if(!reloadAfterRemovingRow)
- [selectedRows removeIndex:index];
- errors++;
- }
- index = [selectedRows indexGreaterThanIndex:index];
- }
- } else if ([primaryKeyFieldNames count] == 1) {
- // if table has only one PRIMARY KEY
- // delete the fast way by using the PRIMARY KEY in an IN clause
- NSMutableString *deleteQuery = [NSMutableString string];
- NSInteger affectedRows = 0;
-
- [deleteQuery setString:[NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ IN (", [selectedTable backtickQuotedString], [NSArrayObjectAtIndex(primaryKeyFieldNames,0) backtickQuotedString]]];
-
- while (index != NSNotFound) {
-
- id keyValue = [tableValues cellDataAtRow:index column:[[[tableDataInstance columnWithName:NSArrayObjectAtIndex(primaryKeyFieldNames,0)] objectForKey:@"datacolumnindex"] integerValue]];
-
- if([keyValue isKindOfClass:[NSData class]])
- [deleteQuery appendString:[NSString stringWithFormat:@"X'%@'", [mySQLConnection prepareBinaryData:keyValue]]];
- else
- [deleteQuery appendString:[NSString stringWithFormat:@"'%@'", [keyValue description]]];
-
- // Split deletion query into 256k chunks
- if([deleteQuery length] > 256000) {
- [deleteQuery appendString:@")"];
- [mySQLConnection queryString:deleteQuery];
- // Remember affected rows for error checking
- affectedRows += [mySQLConnection affectedRows];
- // Reinit a new deletion query
- [deleteQuery setString:[NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ IN (", [selectedTable backtickQuotedString], [NSArrayObjectAtIndex(primaryKeyFieldNames,0) backtickQuotedString]]];
- } else {
- [deleteQuery appendString:@","];
- }
-
- index = [selectedRows indexGreaterThanIndex:index];
- }
-
- // Check if deleteQuery's maximal length was reached for the last index
- // if yes omit the empty query
- if(![deleteQuery hasSuffix:@"("]) {
- // Replace final , by ) and delete the remaining rows
- [deleteQuery setString:[NSString stringWithFormat:@"%@)", [deleteQuery substringToIndex:([deleteQuery length]-1)]]];
- [mySQLConnection queryString:deleteQuery];
- // Remember affected rows for error checking
- affectedRows += [mySQLConnection affectedRows];
- }
-
- errors = (affectedRows > 0) ? [selectedRows count] - affectedRows : [selectedRows count];
- } else {
- // if table has more than one PRIMARY KEY
- // delete the row by using all PRIMARY KEYs in an OR clause
- NSMutableString *deleteQuery = [NSMutableString string];
- NSInteger affectedRows = 0;
-
- [deleteQuery setString:[NSString stringWithFormat:@"DELETE FROM %@ WHERE ", [selectedTable backtickQuotedString]]];
-
- while (index != NSNotFound) {
-
- // Build the AND clause of PRIMARY KEYS
- [deleteQuery appendString:@"("];
- for(NSString *primaryKeyFieldName in primaryKeyFieldNames) {
-
- id keyValue = [tableValues cellDataAtRow:index column:[[[tableDataInstance columnWithName:primaryKeyFieldName] objectForKey:@"datacolumnindex"] integerValue]];
-
- [deleteQuery appendString:[primaryKeyFieldName backtickQuotedString]];
- if ([keyValue isKindOfClass:[NSData class]]) {
- [deleteQuery appendString:@"=X'"];
- [deleteQuery appendString:[mySQLConnection prepareBinaryData:keyValue]];
- } else {
- [deleteQuery appendString:@"='"];
- [deleteQuery appendString:[mySQLConnection prepareString:[keyValue description]]];
- }
- [deleteQuery appendString:@"' AND "];
- }
-
- // Remove the trailing AND and add the closing bracket
- [deleteQuery deleteCharactersInRange:NSMakeRange([deleteQuery length]-5, 5)];
- [deleteQuery appendString:@")"];
-
- // Split deletion query into 64k chunks
- if([deleteQuery length] > 64000) {
- [mySQLConnection queryString:deleteQuery];
- // Remember affected rows for error checking
- affectedRows += [mySQLConnection affectedRows];
- // Reinit a new deletion query
- [deleteQuery setString:[NSString stringWithFormat:@"DELETE FROM %@ WHERE ", [selectedTable backtickQuotedString]]];
- } else {
- [deleteQuery appendString:@" OR "];
- }
-
- index = [selectedRows indexGreaterThanIndex:index];
- }
-
- // Check if deleteQuery's maximal length was reached for the last index
- // if yes omit the empty query
- if(![deleteQuery hasSuffix:@"WHERE "]) {
- // Remove final ' OR ' and delete the remaining rows
- [deleteQuery setString:[deleteQuery substringToIndex:([deleteQuery length]-4)]];
- [mySQLConnection queryString:deleteQuery];
- // Remember affected rows for error checking
- affectedRows += [mySQLConnection affectedRows];
- }
-
- errors = (affectedRows > 0) ? [selectedRows count] - affectedRows : [selectedRows count];
- }
-
- // Restore Console Log window's updating bahaviour
- [[SPQueryController sharedQueryController] setAllowConsoleUpdate:consoleUpdateStatus];
-
- if (errors) {
- NSArray *message;
- //TODO: The following three messages are NOT localisable!
- if (errors < 0) {
- message = [NSArray arrayWithObjects:NSLocalizedString(@"Warning", @"warning"),
- [NSString stringWithFormat:NSLocalizedString(@"%ld row%@ more %@ deleted! Please check the Console and inform the Sequel Pro team!", @"message of panel when more rows were deleted"), (long)(errors*-1), ((errors*-1)>1)?@"s":@"", (errors>1)?@"were":@"was"],
- nil];
- }
- else {
- if (primaryKeyFieldNames == nil)
- message = [NSArray arrayWithObjects:NSLocalizedString(@"Warning", @"warning"),
- [NSString stringWithFormat:NSLocalizedString(@"%ld row%@ ha%@ not been deleted. Reload the table to be sure that the rows exist and use a primary key for your table.", @"message of panel when not all selected fields have been deleted"), (long)errors, (errors>1)?@"s":@"", (errors>1)?@"ve":@"s"],
- nil];
- else
- message = [NSArray arrayWithObjects:NSLocalizedString(@"Warning", @"warning"),
- [NSString stringWithFormat:NSLocalizedString(@"%ld row%@ ha%@ not been deleted. Reload the table to be sure that the rows exist and check the Console for possible errors inside the primary key%@ for your table.", @"message of panel when not all selected fields have been deleted by using primary keys"), (long)errors, (errors>1)?@"s":@"", (errors>1)?@"ve":@"s", (errors>1)?@"s":@""],
- nil];
- }
-
- [self performSelector:@selector(showErrorSheetWith:)
- withObject:message
- afterDelay:0.3];
- }
-
- // Refresh table content
- if ( errors || reloadAfterRemovingRow ) {
- previousTableRowsCount = tableRowsCount;
- [self loadTableValues];
- } else {
- for ( i = tableRowsCount - 1 ; i >= 0 ; i-- ) {
- if ([selectedRows containsIndex:i]) [tableValues removeRowAtIndex:i];
- }
- tableRowsCount = [tableValues count];
- [tableContentView reloadData];
- }
- [tableContentView deselectAll:self];
- } else {
- // The user clicked cancel in the "sure you wanna delete" message
- // restore editing or whatever
- }
-
- }
-}
-
-
-// Accessors
-
-/**
- * Returns the current result (as shown in table content view) as array, the first object containing the field
- * names as array, the following objects containing the rows as array.
- */
-- (NSArray *)currentDataResult
-{
- NSArray *tableColumns;
- NSEnumerator *enumerator;
- id tableColumn;
- NSMutableArray *currentResult = [NSMutableArray array];
- NSMutableArray *tempRow = [NSMutableArray array];
- NSUInteger i;
-
- //load table if not already done
- if ( ![tablesListInstance contentLoaded] ) {
- [self loadTable:[tablesListInstance tableName]];
- }
-
- tableColumns = [tableContentView tableColumns];
- enumerator = [tableColumns objectEnumerator];
-
- //set field names as first line
- while ( (tableColumn = [enumerator nextObject]) ) {
- [tempRow addObject:[[tableColumn headerCell] stringValue]];
- }
- [currentResult addObject:[NSArray arrayWithArray:tempRow]];
-
- //add rows
- for ( i = 0 ; i < [self numberOfRowsInTableView:nil] ; i++) {
- [tempRow removeAllObjects];
- enumerator = [tableColumns objectEnumerator];
- while ( (tableColumn = [enumerator nextObject]) ) {
- id o = SPDataStorageObjectAtRowAndColumn(tableValues, i, [[tableColumn identifier] integerValue]);
- if([o isNSNull])
- [tempRow addObject:@"NULL"];
- else if ([o isSPNotLoaded])
- [tempRow addObject:NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields")];
- else if([o isKindOfClass:[NSString class]])
- [tempRow addObject:[o description]];
- else {
- NSImage *image = [[NSImage alloc] initWithData:o];
- if(image) {
- NSInteger imageWidth = [image size].width;
- if (imageWidth > 100) imageWidth = 100;
- [tempRow addObject:[NSString stringWithFormat:
- @"<IMG WIDTH='%ld' SRC=\"data:image/auto;base64,%@\">",
- (long)imageWidth,
- [[image TIFFRepresentationUsingCompression:NSTIFFCompressionJPEG factor:0.01] base64EncodingWithLineLength:0]]];
- } else {
- [tempRow addObject:@"&lt;BLOB&gt;"];
- }
- if(image) [image release];
- }
- }
- [currentResult addObject:[NSArray arrayWithArray:tempRow]];
- }
- return currentResult;
-}
-
-/**
- * Returns the current result (as shown in table content view) as array, the first object containing the field
- * names as array, the following objects containing the rows as array.
- */
-- (NSArray *)currentResult
-{
- NSArray *tableColumns;
- NSEnumerator *enumerator;
- id tableColumn;
- NSMutableArray *currentResult = [NSMutableArray array];
- NSMutableArray *tempRow = [NSMutableArray array];
- NSUInteger i;
-
- //load table if not already done
- if ( ![tablesListInstance contentLoaded] ) {
- [self loadTable:[tablesListInstance tableName]];
- }
-
- tableColumns = [tableContentView tableColumns];
- enumerator = [tableColumns objectEnumerator];
-
- //set field names as first line
- while ( (tableColumn = [enumerator nextObject]) ) {
- [tempRow addObject:[[tableColumn headerCell] stringValue]];
- }
- [currentResult addObject:[NSArray arrayWithArray:tempRow]];
-
- //add rows
- for ( i = 0 ; i < [self numberOfRowsInTableView:nil] ; i++) {
- [tempRow removeAllObjects];
- enumerator = [tableColumns objectEnumerator];
- while ( (tableColumn = [enumerator nextObject]) ) {
- [tempRow addObject:[self tableView:nil objectValueForTableColumn:tableColumn row:i]];
- }
- [currentResult addObject:[NSArray arrayWithArray:tempRow]];
- }
- return currentResult;
-}
-
-// Additional methods
-
-/**
- * Sets the connection (received from TableDocument) and makes things that have to be done only once
- */
-- (void)setConnection:(MCPConnection *)theConnection
-{
- mySQLConnection = theConnection;
-
- [tableContentView setVerticalMotionCanBeginDrag:NO];
-}
-
-/**
- * Performs the requested action - switching to another table
- * with the appropriate filter settings - when a link arrow is
- * selected.
- */
-- (void)clickLinkArrow:(SPTextAndLinkCell *)theArrowCell
-{
- if ([tableDocumentInstance isWorking]) return;
-
- if ([theArrowCell getClickedColumn] == NSNotFound || [theArrowCell getClickedRow] == NSNotFound) return;
-
- // Check whether a save of the current row is required.
- if ( ![self saveRowOnDeselect] ) return;
-
- // If on the main thread, fire up a thread to perform the load while keeping the modification flag
- [tableDocumentInstance startTaskWithDescription:NSLocalizedString(@"Loading reference...", @"Loading referece task string")];
- if ([NSThread isMainThread]) {
- [NSThread detachNewThreadSelector:@selector(clickLinkArrowTask:) toTarget:self withObject:theArrowCell];
- } else {
- [self clickLinkArrowTask:theArrowCell];
- }
-}
-- (void)clickLinkArrowTask:(SPTextAndLinkCell *)theArrowCell
-{
- NSAutoreleasePool *linkPool = [[NSAutoreleasePool alloc] init];
- NSUInteger dataColumnIndex = [[[[tableContentView tableColumns] objectAtIndex:[theArrowCell getClickedColumn]] identifier] integerValue];
- BOOL tableFilterRequired = NO;
-
- // Ensure the clicked cell has foreign key details available
- NSDictionary *refDictionary = [[dataColumns objectAtIndex:dataColumnIndex] objectForKey:@"foreignkeyreference"];
- if (!refDictionary) {
- [linkPool release];
- return;
- }
-
- // Save existing scroll position and details and mark that state is being modified
- [spHistoryControllerInstance updateHistoryEntries];
- [spHistoryControllerInstance setModifyingState:YES];
-
- NSString *targetFilterValue = [tableValues cellDataAtRow:[theArrowCell getClickedRow] column:dataColumnIndex];
-
- // If the link is within the current table, apply filter settings manually
- if ([[refDictionary objectForKey:@"table"] isEqualToString:selectedTable]) {
- [fieldField selectItemWithTitle:[refDictionary objectForKey:@"column"]];
- [self setCompareTypes:self];
- if ([targetFilterValue isNSNull]) {
- [compareField selectItemWithTitle:@"IS NULL"];
- } else {
- [argumentField setStringValue:targetFilterValue];
- }
- tableFilterRequired = YES;
- } else {
-
- // Store the filter details to use when loading the target table
- NSDictionary *filterSettings = [NSDictionary dictionaryWithObjectsAndKeys:
- [refDictionary objectForKey:@"column"], @"filterField",
- targetFilterValue, @"filterValue",
- ([targetFilterValue isNSNull]?@"IS NULL":nil), @"filterComparison",
- nil];
- [self setFiltersToRestore:filterSettings];
-
- // Attempt to switch to the target table
- if (![tablesListInstance selectItemWithName:[refDictionary objectForKey:@"table"]]) {
- NSBeep();
- [self setFiltersToRestore:nil];
- }
- }
-
- // End state and ensure a new history entry
- [spHistoryControllerInstance setModifyingState:NO];
- [spHistoryControllerInstance updateHistoryEntries];
-
- // End the task
- [tableDocumentInstance endTask];
-
- // If the same table is the target, trigger a filter task on the main thread
- if (tableFilterRequired)
- [self performSelectorOnMainThread:@selector(filterTable:) withObject:self waitUntilDone:NO];
-
- // Empty the loading pool and exit the thread
- [linkPool drain];
-}
-
-/**
- * Sets the compare types for the filter and the appropriate formatter for the textField
- */
-- (IBAction)setCompareTypes:(id)sender
-{
-
- if(contentFilters == nil
- || ![contentFilters objectForKey:@"number"]
- || ![contentFilters objectForKey:@"string"]
- || ![contentFilters objectForKey:@"date"]) {
- NSLog(@"Error while setting filter types.");
- NSBeep();
- return;
- }
-
-
- [compareField removeAllItems];
-
- NSString *fieldTypeGrouping;
- if([[tableDataInstance columnWithName:[[fieldField selectedItem] title]] objectForKey:@"typegrouping"])
- fieldTypeGrouping = [NSString stringWithString:[[tableDataInstance columnWithName:[[fieldField selectedItem] title]] objectForKey:@"typegrouping"]];
- else
- return;
-
- if ( [fieldTypeGrouping isEqualToString:@"date"] ) {
- compareType = @"date";
-
- /*
- if ([fieldType isEqualToString:@"timestamp"]) {
- [argumentField setFormatter:[[NSDateFormatter alloc]
- initWithDateFormat:@"%Y-%m-%d %H:%M:%S" allowNaturalLanguage:YES]];
- }
- if ([fieldType isEqualToString:@"datetime"]) {
- [argumentField setFormatter:[[NSDateFormatter alloc] initWithDateFormat:@"%Y-%m-%d %H:%M:%S" allowNaturalLanguage:YES]];
- }
- if ([fieldType isEqualToString:@"date"]) {
- [argumentField setFormatter:[[NSDateFormatter alloc] initWithDateFormat:@"%Y-%m-%d" allowNaturalLanguage:YES]];
- }
- if ([fieldType isEqualToString:@"time"]) {
- [argumentField setFormatter:[[NSDateFormatter alloc] initWithDateFormat:@"%H:%M:%S" allowNaturalLanguage:YES]];
- }
- if ([fieldType isEqualToString:@"year"]) {
- [argumentField setFormatter:[[NSDateFormatter alloc] initWithDateFormat:@"%Y" allowNaturalLanguage:YES]];
- }
- */
-
- // TODO: A bug in the framework previously meant enum fields had to be treated as string fields for the purposes
- // of comparison - this can now be split out to support additional comparison fucntionality if desired.
- } else if ([fieldTypeGrouping isEqualToString:@"string"] || [fieldTypeGrouping isEqualToString:@"binary"]
- || [fieldTypeGrouping isEqualToString:@"textdata"] || [fieldTypeGrouping isEqualToString:@"blobdata"]
- || [fieldTypeGrouping isEqualToString:@"enum"]) {
-
- compareType = @"string";
- // [argumentField setFormatter:nil];
-
- } else if ([fieldTypeGrouping isEqualToString:@"bit"] || [fieldTypeGrouping isEqualToString:@"integer"]
- || [fieldTypeGrouping isEqualToString:@"float"]) {
- compareType = @"number";
- // [argumentField setFormatter:numberFormatter];
-
- } else {
- compareType = @"";
- NSBeep();
- NSLog(@"ERROR: unknown type for comparision: %@, in %@", [[tableDataInstance columnWithName:[[fieldField selectedItem] title]] objectForKey:@"type"], fieldTypeGrouping);
- }
-
- // Add IS NULL and IS NOT NULL as they should always be available
- // [compareField addItemWithTitle:@"IS NULL"];
- // [compareField addItemWithTitle:@"IS NOT NULL"];
-
- // Remove user-defined filters first
- if([numberOfDefaultFilters objectForKey:compareType]) {
- NSUInteger cycles = [[contentFilters objectForKey:compareType] count] - [[numberOfDefaultFilters objectForKey:compareType] integerValue];
- while(cycles > 0) {
- [[contentFilters objectForKey:compareType] removeLastObject];
- cycles--;
- }
- }
-
- // Load global user-defined content filters
- if([prefs objectForKey:SPContentFilters]
- && [contentFilters objectForKey:compareType]
- && [[prefs objectForKey:SPContentFilters] objectForKey:compareType])
- {
- [[contentFilters objectForKey:compareType] addObjectsFromArray:[[prefs objectForKey:SPContentFilters] objectForKey:compareType]];
- }
-
- // Load doc-based user-defined content filters
- if([[SPQueryController sharedQueryController] contentFilterForFileURL:[tableDocumentInstance fileURL]]) {
- id filters = [[SPQueryController sharedQueryController] contentFilterForFileURL:[tableDocumentInstance fileURL]];
- if([filters objectForKey:compareType])
- [[contentFilters objectForKey:compareType] addObjectsFromArray:[filters objectForKey:compareType]];
- }
-
- // Rebuild operator popup menu
- NSUInteger i = 0;
- NSMenu *menu = [compareField menu];
- if([contentFilters objectForKey:compareType])
- for(id filter in [contentFilters objectForKey:compareType]) {
- NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:([filter objectForKey:@"MenuLabel"])?[filter objectForKey:@"MenuLabel"]:@"not specified" action:NULL keyEquivalent:@""];
- // Create the tooltip
- if([filter objectForKey:@"Tooltip"])
- [item setToolTip:[filter objectForKey:@"Tooltip"]];
- else {
- NSMutableString *tip = [[NSMutableString alloc] init];
- [tip setString:[[filter objectForKey:@"Clause"] stringByReplacingOccurrencesOfRegex:@"(?<!\\\\)(\\$\\{.*?\\})" withString:@"[arg]"]];
- if([tip isMatchedByRegex:@"(?<!\\\\)\\$BINARY"]) {
- [tip replaceOccurrencesOfRegex:@"(?<!\\\\)\\$BINARY" withString:@""];
- [tip appendString:NSLocalizedString(@"\n\nPress ⇧ for binary search (case-sensitive).", @"\n\npress shift for binary search tooltip message")];
- }
- [tip flushCachedRegexData];
- [tip replaceOccurrencesOfRegex:@"(?<!\\\\)\\$CURRENT_FIELD" withString:[[fieldField titleOfSelectedItem] backtickQuotedString]];
- [tip flushCachedRegexData];
- [item setToolTip:tip];
- [tip release];
- }
- [item setTag:i];
- [menu addItem:item];
- [item release];
- i++;
- }
-
- [menu addItem:[NSMenuItem separatorItem]];
- NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit Filters…", @"edit filter") action:NULL keyEquivalent:@""];
- [item setToolTip:NSLocalizedString(@"Edit user-defined Filters…", @"edit user-defined filter")];
- [item setTag:i];
- [menu addItem:item];
- [item release];
-
- // Update the argumentField enabled state
- [self performSelectorOnMainThread:@selector(toggleFilterField:) withObject:self waitUntilDone:YES];
-
- // set focus on argumentField
- [argumentField performSelectorOnMainThread:@selector(selectText:) withObject:self waitUntilDone:YES];
-
-}
-
-- (void)openContentFilterManager
-{
- [compareField selectItemWithTag:lastSelectedContentFilterIndex];
-
- // init query favorites controller
- [prefs synchronize];
- if(contentFilterManager) [contentFilterManager release];
- contentFilterManager = [[SPContentFilterManager alloc] initWithDelegate:self forFilterType:compareType];
-
- // Open query favorite manager
- [NSApp beginSheet:[contentFilterManager window]
- modalForWindow:[tableDocumentInstance parentWindow]
- modalDelegate:contentFilterManager
- didEndSelector:nil
- contextInfo:nil];
-}
-
-/*
- * Tries to write a new row to the database.
- * Returns YES if row is written to database, otherwise NO; also returns YES if no row
- * is being edited and nothing has to be written to the database.
- */
-- (BOOL)addRowToDB
-{
- NSMutableString *queryString;
- id rowObject;
- NSMutableString *rowValue = [NSMutableString string];
- NSString *currentTime = [[NSDate date] descriptionWithCalendarFormat:@"%H:%M:%S" timeZone:nil locale:nil];
- BOOL prefsLoadBlobsAsNeeded = [prefs boolForKey:SPLoadBlobsAsNeeded];
- NSInteger i;
-
- if ( !isEditingRow || currentlyEditingRow == -1) {
- return YES;
- }
-
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance];
-
- // If editing, compare the new row to the old row and if they are identical finish editing without saving.
- if (!isEditingNewRow && [oldRow isEqualToArray:[tableValues rowContentsAtIndex:currentlyEditingRow]]) {
- isEditingRow = NO;
- currentlyEditingRow = -1;
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
- return YES;
- }
-
- NSMutableArray *fieldValues = [[NSMutableArray alloc] init];
-
- // Get the field values
- for ( i = 0 ; i < [dataColumns count] ; i++ ) {
- rowObject = [tableValues cellDataAtRow:currentlyEditingRow column:i];
-
- // Add not loaded placeholders directly for easy comparison when added
- if (prefsLoadBlobsAsNeeded && !isEditingNewRow && [rowObject isSPNotLoaded])
- {
- [fieldValues addObject:[SPNotLoaded notLoaded]];
- continue;
-
- // Catch CURRENT_TIMESTAMP automatic updates - if the row is new and the cell value matches
- // the default value, or if the cell hasn't changed, update the current timestamp.
- } else if ([[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"onupdatetimestamp"] integerValue]
- && ( (isEditingNewRow && [rowObject isEqualTo:[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"default"]])
- || (!isEditingNewRow && [rowObject isEqualTo:NSArrayObjectAtIndex(oldRow, i)])))
- {
- [rowValue setString:@"CURRENT_TIMESTAMP"];
-
- // Convert the object to a string (here we can add special treatment for date-, number- and data-fields)
- } else if ( [rowObject isNSNull]
- || ([rowObject isMemberOfClass:[NSString class]] && [[rowObject description] isEqualToString:@""]) ) {
-
- //NULL when user entered the nullValue string defined in the prefs or when a number field isn't set
- // problem: when a number isn't set, sequel-pro enters 0
- // -> second if argument isn't necessary!
- [rowValue setString:@"NULL"];
- } else {
-
- // I don't believe any of these class matches are ever met at present.
- if ( [rowObject isKindOfClass:[NSCalendarDate class]] ) {
- [rowValue setString:[NSString stringWithFormat:@"'%@'", [mySQLConnection prepareString:[rowObject description]]]];
- } else if ( [rowObject isKindOfClass:[NSNumber class]] ) {
- [rowValue setString:[rowObject stringValue]];
- } else if ( [rowObject isKindOfClass:[NSData class]] ) {
- [rowValue setString:[NSString stringWithFormat:@"X'%@'", [mySQLConnection prepareBinaryData:rowObject]]];
- } else {
- if ([[rowObject description] isEqualToString:@"CURRENT_TIMESTAMP"]) {
- [rowValue setString:@"CURRENT_TIMESTAMP"];
- } else if ([[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"typegrouping"] isEqualToString:@"bit"]) {
- [rowValue setString:((![[rowObject description] length] || [[rowObject description] isEqualToString:@"0"])?@"0":@"1")];
- } else if ([[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"typegrouping"] isEqualToString:@"date"]
- && [[rowObject description] isEqualToString:@"NOW()"]) {
- [rowValue setString:@"NOW()"];
- } else {
- [rowValue setString:[NSString stringWithFormat:@"'%@'", [mySQLConnection prepareString:[rowObject description]]]];
- }
- }
- }
- [fieldValues addObject:[NSString stringWithString:rowValue]];
- }
-
- // Use INSERT syntax when creating new rows - no need to do not loaded checking, as all values have been entered
- if ( isEditingNewRow ) {
- queryString = [NSString stringWithFormat:@"INSERT INTO %@ (%@) VALUES (%@)",
- [selectedTable backtickQuotedString], [[tableDataInstance columnNames] componentsJoinedAndBacktickQuoted], [fieldValues componentsJoinedByString:@","]];
-
- // Use UPDATE syntax otherwise
- } else {
- BOOL firstCellOutput = NO;
- queryString = [NSMutableString stringWithFormat:@"UPDATE %@ SET ", [selectedTable backtickQuotedString]];
- for ( i = 0 ; i < [dataColumns count] ; i++ ) {
-
- // If data column loading is deferred and the value is the not loaded string, skip this cell
- if (prefsLoadBlobsAsNeeded && [[fieldValues objectAtIndex:i] isSPNotLoaded]) continue;
-
- if (firstCellOutput) [queryString appendString:@", "];
- else firstCellOutput = YES;
-
- [queryString appendString:[NSString stringWithFormat:@"%@=%@",
- [[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"name"] backtickQuotedString], [fieldValues objectAtIndex:i]]];
- }
- [queryString appendString:[NSString stringWithFormat:@" WHERE %@", [self argumentForRow:-2]]];
- }
-
- // If UTF-8 via Latin1 view encoding is set convert the queryString into Latin1 and
- // set the MySQL connection to Latin1 before executing this query to allow editing.
- // After executing reset all.
- if([tableDocumentInstance connectionEncodingViaLatin1:mySQLConnection]) {
-
- NSStringEncoding currentEncoding = [mySQLConnection encoding];
- NSString *latin1String = [[NSString alloc] initWithCString:[queryString UTF8String] encoding:NSISOLatin1StringEncoding];
- [mySQLConnection setEncoding:NSISOLatin1StringEncoding];
- [mySQLConnection queryString:@"SET NAMES 'latin1'"];
- [mySQLConnection queryString:latin1String];
- [mySQLConnection setEncoding:currentEncoding];
- [mySQLConnection queryString:@"SET NAMES 'utf8'"];
- [mySQLConnection queryString:@"SET CHARACTER_SET_RESULTS=latin1"];
- [latin1String release];
-
- } else {
- [mySQLConnection queryString:queryString];
- }
-
- [fieldValues release];
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance];
-
- // If no rows have been changed, show error if appropriate.
- if ( ![mySQLConnection affectedRows] && ![mySQLConnection queryErrored] ) {
- if ( [prefs boolForKey:SPShowNoAffectedRowsError] ) {
- SPBeginAlertSheet(NSLocalizedString(@"Warning", @"warning"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil,
- NSLocalizedString(@"The row was not written to the MySQL database. You probably haven't changed anything.\nReload the table to be sure that the row exists and use a primary key for your table.\n(This error can be turned off in the preferences.)", @"message of panel when no rows have been affected after writing to the db"));
- } else {
- NSBeep();
- }
- [tableValues replaceRowAtIndex:currentlyEditingRow withRowContents:oldRow];
- isEditingRow = NO;
- isEditingNewRow = NO;
- currentlyEditingRow = -1;
- [[SPQueryController sharedQueryController] showErrorInConsole:[NSString stringWithFormat:NSLocalizedString(@"/* WARNING %@ No rows have been affected */\n", @"warning shown in the console when no rows have been affected after writing to the db"), currentTime] connection:[tableDocumentInstance name]];
- return YES;
-
- // On success...
- } else if ( ![mySQLConnection queryErrored] ) {
- isEditingRow = NO;
-
- // New row created successfully
- if ( isEditingNewRow ) {
- if ( [prefs boolForKey:SPReloadAfterAddingRow] ) {
- [[tableDocumentInstance parentWindow] endEditingFor:nil];
- previousTableRowsCount = tableRowsCount;
- [self loadTableValues];
- } else {
-
- // Set the insertId for fields with auto_increment
- for ( i = 0; i < [dataColumns count] ; i++ ) {
- if ([[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"autoincrement"] integerValue]) {
- [tableValues replaceObjectInRow:currentlyEditingRow column:i withObject:[[NSNumber numberWithLong:[mySQLConnection insertId]] description]];
- }
- }
- }
- isEditingNewRow = NO;
-
- // Existing row edited successfully
- } else {
-
- // Reload table if set to - otherwise no action required.
- if ( [prefs boolForKey:SPReloadAfterEditingRow] ) {
- [[tableDocumentInstance parentWindow] endEditingFor:nil];
- previousTableRowsCount = tableRowsCount;
- [self loadTableValues];
- }
- }
- currentlyEditingRow = -1;
-
- return YES;
-
- // Report errors which have occurred
- } else {
- SPBeginAlertSheet(NSLocalizedString(@"Couldn't write row", @"Couldn't write row error"), NSLocalizedString(@"Edit row", @"Edit row button"), NSLocalizedString(@"Discard changes", @"discard changes button"), nil, [tableDocumentInstance parentWindow], self, @selector(addRowErrorSheetDidEnd:returnCode:contextInfo:), nil,
- [NSString stringWithFormat:NSLocalizedString(@"MySQL said:\n\n%@", @"message of panel when error while adding row to db"), [mySQLConnection getLastErrorMessage]]);
- return NO;
- }
-}
-
-/*
- * Handle the user decision as a result of an addRow error.
- */
-- (void) addRowErrorSheetDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
-{
- // Order out current sheet to suppress overlapping of sheets
- [[alert window] orderOut:nil];
-
- // Edit row selected - reselect the row, and start editing.
- if ( returnCode == NSAlertDefaultReturn ) {
- [tableContentView selectRowIndexes:[NSIndexSet indexSetWithIndex:currentlyEditingRow] byExtendingSelection:NO];
- [tableContentView performSelector:@selector(keyDown:) withObject:[NSEvent keyEventWithType:NSKeyDown location:NSMakePoint(0,0) modifierFlags:0 timestamp:0 windowNumber:[[tableDocumentInstance parentWindow] windowNumber] context:[NSGraphicsContext currentContext] characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:0x24] afterDelay:0.0];
-
- // Discard changes selected
- } else {
- if ( !isEditingNewRow ) {
- [tableValues replaceRowAtIndex:currentlyEditingRow withRowContents:oldRow];
- isEditingRow = NO;
- } else {
- tableRowsCount--;
- [tableValues removeRowAtIndex:currentlyEditingRow];
- isEditingRow = NO;
- isEditingNewRow = NO;
- }
- currentlyEditingRow = -1;
- }
- [tableContentView reloadData];
-}
-
-/*
- * A method to be called whenever the table selection changes; checks whether the current
- * row is being edited, and if so attempts to save it. Returns YES if no save was necessary
- * or the save was successful, and NO if a save was necessary and failed - in which case further
- * editing is required. In that case this method will reselect the row in question for reediting.
- */
-- (BOOL)saveRowOnDeselect
-{
- // Save any edits which have been made but not saved to the table yet.
- [[tableDocumentInstance parentWindow] endEditingFor:nil];
-
- // If no rows are currently being edited, or a save is in progress, return success at once.
- if (!isEditingRow || isSavingRow) return YES;
- isSavingRow = YES;
-
- // Attempt to save the row, and return YES if the save succeeded.
- if ([self addRowToDB]) {
- isSavingRow = NO;
- return YES;
- }
-
- // Saving failed - return failure.
- isSavingRow = NO;
- return NO;
-}
-
-/*
- * Returns the WHERE argument to identify a row.
- * If "row" is -2, it uses the oldRow.
- * Uses the primary key if available, otherwise uses all fields as argument and sets LIMIT to 1
- */
-- (NSString *)argumentForRow:(NSInteger)row
-{
- MCPResult *theResult;
- NSDictionary *theRow;
- id tempValue;
- NSMutableString *value = [NSMutableString string];
- NSMutableString *argument = [NSMutableString string];
- // NSString *columnType;
- NSArray *columnNames;
- NSInteger i;
-
- if ( row == -1 )
- return @"";
-
- // Retrieve the field names for this table from the data cache. This is used when requesting all data as part
- // of the fieldListForQuery method, and also to decide whether or not to preserve the current filter/sort settings.
- columnNames = [tableDataInstance columnNames];
-
- // Get the primary key if there is one
- if ( !keys ) {
- setLimit = NO;
- keys = [[NSMutableArray alloc] init];
- theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW COLUMNS FROM %@", [selectedTable backtickQuotedString]]];
- [theResult setReturnDataAsStrings:YES];
- if ([theResult numOfRows]) [theResult dataSeek:0];
- for ( i = 0 ; i < [theResult numOfRows] ; i++ ) {
- theRow = [theResult fetchRowAsDictionary];
- if ( [[theRow objectForKey:@"Key"] isEqualToString:@"PRI"] ) {
- [keys addObject:[theRow objectForKey:@"Field"]];
- }
- }
- }
-
- // If there is no primary key, all the fields are used in the argument.
- if ( ![keys count] ) {
- [keys setArray:columnNames];
- setLimit = YES;
-
- // When the option to not show blob or text options is set, we have a problem - we don't have
- // the right values to use in the WHERE statement. Throw an error if this is the case.
- if ( [prefs boolForKey:SPLoadBlobsAsNeeded] && [self tableContainsBlobOrTextColumns] ) {
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil,
- NSLocalizedString(@"You can't hide blob and text fields when working with tables without index.", @"message of panel when trying to edit tables without index and with hidden blob/text fields"));
- [keys removeAllObjects];
- [tableContentView deselectAll:self];
- return @"";
- }
- }
-
- // Walk through the keys list constructing the argument list
- for ( i = 0 ; i < [keys count] ; i++ ) {
- if ( i )
- [argument appendString:@" AND "];
-
- // Use the selected row if appropriate
- if ( row >= 0 ) {
- tempValue = [tableValues cellDataAtRow:row column:[[[tableDataInstance columnWithName:NSArrayObjectAtIndex(keys, i)] objectForKey:@"datacolumnindex"] integerValue]];
-
- // Otherwise use the oldRow
- } else {
- tempValue = [oldRow objectAtIndex:[[[tableDataInstance columnWithName:NSArrayObjectAtIndex(keys, i)] objectForKey:@"datacolumnindex"] integerValue]];
- }
-
- if ( [tempValue isNSNull] ) {
- [argument appendString:[NSString stringWithFormat:@"%@ IS NULL", [NSArrayObjectAtIndex(keys, i) backtickQuotedString]]];
- } else if ( [tempValue isSPNotLoaded] ) {
- NSLog(@"Exceptional case: SPNotLoaded object found for method “argumentForRow:”!");
- return @"";
- } else {
-
- if ( [tempValue isKindOfClass:[NSData class]] )
- [value setString:[NSString stringWithFormat:@"X'%@'", [mySQLConnection prepareBinaryData:tempValue]]];
- else
- [value setString:[NSString stringWithFormat:@"'%@'", [mySQLConnection prepareString:tempValue]]];
-
- [argument appendString:[NSString stringWithFormat:@"%@ = %@", [NSArrayObjectAtIndex(keys, i) backtickQuotedString], value]];
- }
- }
-
- if (setLimit) [argument appendString:@" LIMIT 1"];
-
- return argument;
-}
-
-
-/*
- * Returns YES if the table contains any columns which are of any of the blob or text types,
- * NO otherwise.
- */
-- (BOOL)tableContainsBlobOrTextColumns
-{
- NSInteger i;
-
- for ( i = 0 ; i < [dataColumns count]; i++ ) {
- if ( [tableDataInstance columnIsBlobOrText:[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"name"]] ) {
- return YES;
- }
- }
-
- return NO;
-}
-
-/*
- * Returns a string controlling which fields to retrieve for a query. Returns * (all fields) if the preferences
- * option dontShowBlob isn't set; otherwise, returns a comma-separated list of all non-blob/text fields.
- */
-- (NSString *)fieldListForQuery
-{
- NSInteger i;
- NSMutableArray *fields = [NSMutableArray array];
-
- if (([prefs boolForKey:SPLoadBlobsAsNeeded]) && ([dataColumns count] > 0)) {
-
- NSArray *columnNames = [tableDataInstance columnNames];
-
- for (i = 0 ; i < [columnNames count]; i++)
- {
- if (![tableDataInstance columnIsBlobOrText:[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"name"]] ) {
- [fields addObject:[NSArrayObjectAtIndex(columnNames, i) backtickQuotedString]];
- }
- else {
- // For blob/text fields, select a null placeholder so the column count is still correct
- [fields addObject:@"NULL"];
- }
- }
-
- return [fields componentsJoinedByString:@","];
- }
- else {
- return @"*";
- }
-}
-
-/*
- * Close an open sheet.
- */
-- (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo
-{
- [sheet orderOut:self];
-}
-
-/**
- * Show Error sheet (can be called from inside of a endSheet selector)
- * via [self performSelector:@selector(showErrorSheetWithTitle:) withObject: afterDelay:]
- */
--(void)showErrorSheetWith:(id)error
-{
- // error := first object is the title , second the message, only one button OK
- SPBeginAlertSheet([error objectAtIndex:0], NSLocalizedString(@"OK", @"OK button"),
- nil, nil, [tableDocumentInstance parentWindow], self, nil, nil,
- [error objectAtIndex:1]);
-}
-
-#pragma mark -
-#pragma mark Retrieving and setting table state
-
-/**
- * Provide a getter for the table's sort column name
- */
-- (NSString *) sortColumnName
-{
- if (!sortCol || !dataColumns) return nil;
-
- return [[dataColumns objectAtIndex:[sortCol integerValue]] objectForKey:@"name"];
-}
-
-/**
- * Provide a getter for the table current sort order
- */
-- (BOOL) sortColumnIsAscending
-{
- return !isDesc;
-}
-
-/**
- * Provide a getter for the table's selected rows index set
- */
-- (NSIndexSet *) selectedRowIndexes
-{
- return [tableContentView selectedRowIndexes];
-}
-
-/**
- * Provide a getter for the page number
- */
-- (NSUInteger) pageNumber
-{
- return contentPage;
-}
-
-/**
- * Provide a getter for the table's current viewport
- */
-- (NSRect) viewport
-{
- return [tableContentView visibleRect];
-}
-
-/**
- * Provide a getter for the current filter details
- */
-- (NSDictionary *) filterSettings
-{
- NSDictionary *theDictionary;
-
- if (![fieldField isEnabled]) return nil;
-
- theDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
- [self tableFilterString], @"menuLabel",
- [[fieldField selectedItem] title], @"filterField",
- [[compareField selectedItem] title], @"filterComparison",
- [NSNumber numberWithInteger:[[compareField selectedItem] tag]], @"filterComparisonTag",
- [argumentField stringValue], @"filterValue",
- [firstBetweenField stringValue], @"firstBetweenField",
- [secondBetweenField stringValue], @"secondBetweenField",
- nil];
-
- return theDictionary;
-}
-
-/**
- * Set the sort column and sort order to restore on next table load
- */
-- (void) setSortColumnNameToRestore:(NSString *)theSortColumnName isAscending:(BOOL)isAscending
-{
- if (sortColumnToRestore) [sortColumnToRestore release], sortColumnToRestore = nil;
-
- if (theSortColumnName) {
- sortColumnToRestore = [[NSString alloc] initWithString:theSortColumnName];
- sortColumnToRestoreIsAsc = isAscending;
- }
-}
-
-/**
- * Sets the value for the page number to use on next table load
- */
-- (void) setPageToRestore:(NSUInteger)thePage
-{
- pageToRestore = thePage;
-}
-
-/**
- * Set the selected row indexes to restore on next table load
- */
-- (void) setSelectedRowIndexesToRestore:(NSIndexSet *)theIndexSet
-{
- if (selectionIndexToRestore) [selectionIndexToRestore release], selectionIndexToRestore = nil;
-
- if (theIndexSet) selectionIndexToRestore = [[NSIndexSet alloc] initWithIndexSet:theIndexSet];
-}
-
-/**
- * Set the viewport to restore on next table load
- */
-- (void) setViewportToRestore:(NSRect)theViewport
-{
- selectionViewportToRestore = theViewport;
-}
-
-/**
- * Set the filter settings to restore (if possible) on next table load
- */
-- (void) setFiltersToRestore:(NSDictionary *)filterSettings
-{
- if (filterFieldToRestore) [filterFieldToRestore release], filterFieldToRestore = nil;
- if (filterComparisonToRestore) [filterComparisonToRestore release], filterComparisonToRestore = nil;
- if (filterValueToRestore) [filterValueToRestore release], filterValueToRestore = nil;
- if (firstBetweenValueToRestore) [firstBetweenValueToRestore release], firstBetweenValueToRestore = nil;
- if (secondBetweenValueToRestore) [secondBetweenValueToRestore release], secondBetweenValueToRestore = nil;
-
- if (filterSettings) {
- if ([filterSettings objectForKey:@"filterField"])
- filterFieldToRestore = [[NSString alloc] initWithString:[filterSettings objectForKey:@"filterField"]];
- if ([filterSettings objectForKey:@"filterComparison"]) {
- // Check if operator is BETWEEN, if so set up input fields
- if([[filterSettings objectForKey:@"filterComparison"] isEqualToString:@"BETWEEN"]) {
- [argumentField setHidden:YES];
- [betweenTextField setHidden:NO];
- [firstBetweenField setHidden:NO];
- [secondBetweenField setHidden:NO];
- [firstBetweenField setEnabled:YES];
- [secondBetweenField setEnabled:YES];
- }
-
- filterComparisonToRestore = [[NSString alloc] initWithString:[filterSettings objectForKey:@"filterComparison"]];
- }
- if([[filterSettings objectForKey:@"filterComparison"] isEqualToString:@"BETWEEN"]) {
- if ([filterSettings objectForKey:@"firstBetweenField"])
- firstBetweenValueToRestore = [[NSString alloc] initWithString:[filterSettings objectForKey:@"firstBetweenField"]];
- if ([filterSettings objectForKey:@"secondBetweenField"])
- secondBetweenValueToRestore = [[NSString alloc] initWithString:[filterSettings objectForKey:@"secondBetweenField"]];
- } else {
- if ([filterSettings objectForKey:@"filterValue"] && ![[filterSettings objectForKey:@"filterValue"] isNSNull])
- filterValueToRestore = [[NSString alloc] initWithString:[filterSettings objectForKey:@"filterValue"]];
- }
- }
-}
-
-/**
- * Convenience method for storing all current settings for restoration
- */
-- (void) storeCurrentDetailsForRestoration
-{
- [self setSortColumnNameToRestore:[self sortColumnName] isAscending:[self sortColumnIsAscending]];
- [self setPageToRestore:[self pageNumber]];
- [self setSelectedRowIndexesToRestore:[self selectedRowIndexes]];
- [self setViewportToRestore:[self viewport]];
- [self setFiltersToRestore:[self filterSettings]];
-}
-
-/**
- * Convenience method for clearing any settings to restore
- */
-- (void) clearDetailsToRestore
-{
- [self setSortColumnNameToRestore:nil isAscending:YES];
- [self setPageToRestore:1];
- [self setSelectedRowIndexesToRestore:nil];
- [self setViewportToRestore:NSZeroRect];
- [self setFiltersToRestore:nil];
-}
-
-#pragma mark -
-#pragma mark Table drawing and editing
-
-/**
- * Updates the number of rows in the selected table.
- * Attempts to use the fullResult count if available, also updating the
- * table data store; otherwise, uses the table data store if accurate or
- * falls back to a fetch if necessary and set in preferences.
- * The prefs option "fetch accurate row counts" is used as a last resort as
- * it can be very slow on large InnoDB tables which require a full table scan.
- */
-- (void)updateNumberOfRows
-{
- BOOL checkStatusCount = NO;
-
- // For unfiltered and non-limited tables, use the result count - and update the status count
- if (!isLimited && !isFiltered && !isInterruptedLoad) {
- maxNumRows = tableRowsCount;
- maxNumRowsIsEstimate = NO;
- [tableDataInstance setStatusValue:[NSString stringWithFormat:@"%ld", (long)maxNumRows] forKey:@"Rows"];
- [tableDataInstance setStatusValue:@"y" forKey:@"RowsCountAccurate"];
- [tableInfoInstance tableChanged:nil];
- [[tableDocumentInstance valueForKey:@"extendedTableInfoInstance"] performSelectorOnMainThread:@selector(loadTable:) withObject:selectedTable waitUntilDone:YES];
-
- // Otherwise, if the table status value is accurate, use it
- } else if ([[tableDataInstance statusValueForKey:@"RowsCountAccurate"] boolValue]) {
- maxNumRows = [[tableDataInstance statusValueForKey:@"Rows"] integerValue];
- maxNumRowsIsEstimate = NO;
- checkStatusCount = YES;
-
- // Choose whether to display an estimate, or to fetch the correct row count, based on prefs
- } else if ([[prefs objectForKey:SPTableRowCountQueryLevel] integerValue] == SPRowCountFetchAlways
- || ([[prefs objectForKey:SPTableRowCountQueryLevel] integerValue] == SPRowCountFetchIfCheap
- && [tableDataInstance statusValueForKey:@"Data_length"]
- && [[prefs objectForKey:SPTableRowCountCheapSizeBoundary] integerValue] > [[tableDataInstance statusValueForKey:@"Data_length"] integerValue]))
- {
- maxNumRows = [self fetchNumberOfRows];
- maxNumRowsIsEstimate = NO;
- [tableDataInstance setStatusValue:[NSString stringWithFormat:@"%ld", (long)maxNumRows] forKey:@"Rows"];
- [tableDataInstance setStatusValue:@"y" forKey:@"RowsCountAccurate"];
- [tableInfoInstance tableChanged:nil];
- [[tableDocumentInstance valueForKey:@"extendedTableInfoInstance"] performSelectorOnMainThread:@selector(loadTable:) withObject:selectedTable waitUntilDone:YES];
-
- // Use the estimate count
- } else {
- maxNumRows = [[tableDataInstance statusValueForKey:@"Rows"] integerValue];
- maxNumRowsIsEstimate = YES;
- checkStatusCount = YES;
- }
-
- // Check whether the estimated count requires updating, ie if the retrieved count exceeds it
- if (checkStatusCount) {
- NSInteger foundMaxRows;
- if ([prefs boolForKey:SPLimitResults]) {
- foundMaxRows = ((contentPage - 1) * [prefs integerForKey:SPLimitResultsValue]) + tableRowsCount;
- if (foundMaxRows > maxNumRows) {
- if (tableRowsCount == [prefs integerForKey:SPLimitResultsValue]) {
- maxNumRows = foundMaxRows + 1;
- maxNumRowsIsEstimate = YES;
- } else {
- maxNumRows = foundMaxRows;
- maxNumRowsIsEstimate = NO;
- }
- } else if (!isInterruptedLoad && !isFiltered && tableRowsCount < [prefs integerForKey:SPLimitResultsValue]) {
- maxNumRows = foundMaxRows;
- maxNumRowsIsEstimate = NO;
- }
- } else if (tableRowsCount > maxNumRows) {
- maxNumRows = tableRowsCount;
- maxNumRowsIsEstimate = YES;
- }
- [tableDataInstance setStatusValue:[NSString stringWithFormat:@"%ld", (long)maxNumRows] forKey:@"Rows"];
- [tableDataInstance setStatusValue:maxNumRowsIsEstimate?@"n":@"y" forKey:@"RowsCountAccurate"];
- [tableInfoInstance tableChanged:nil];
- }
-}
-
-/*
- * Fetches the number of rows in the selected table using a "SELECT COUNT(1)" query and return it
- */
-- (NSInteger)fetchNumberOfRows
-{
- return [[[[mySQLConnection queryString:[NSString stringWithFormat:@"SELECT COUNT(1) FROM %@", [selectedTable backtickQuotedString]]] fetchRowAsArray] objectAtIndex:0] integerValue];
-}
-
-#pragma mark -
-#pragma mark TableView delegate methods
-
-/**
- * Show the table cell content as tooltip
- * - for text displays line breaks and tabs as well
- * - if blob data can be interpret as image data display the image as transparent thumbnail
- * (up to now using base64 encoded HTML data)
- */
-- (NSString *)tableView:(NSTableView *)aTableView toolTipForCell:(SPTextAndLinkCell *)aCell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)row mouseLocation:(NSPoint)mouseLocation
-{
-
- if([[aCell stringValue] length] < 2 || [tableDocumentInstance isWorking]) return nil;
-
- NSImage *image;
-
- NSPoint pos = [NSEvent mouseLocation];
- pos.y -= 20;
-
- // Try to get the original data. If not possible return nil.
- // @try clause is used due to the multifarious cases of
- // possible exceptions (eg for reloading tables etc.)
- id theValue;
- @try{
- theValue = [tableValues cellDataAtRow:row column:[[aTableColumn identifier] integerValue]];
- }
- @catch(id ae) {
- return nil;
- }
-
- // Get the original data for trying to display the blob data as an image
- if ([theValue isKindOfClass:[NSData class]]) {
- image = [[[NSImage alloc] initWithData:theValue] autorelease];
- if(image) {
- [SPTooltip showWithObject:image atLocation:pos ofType:@"image"];
- return nil;
- }
- }
-
- // Show the cell string value as tooltip (including line breaks and tabs)
- // by using the cell's font
- [SPTooltip showWithObject:[aCell stringValue]
- atLocation:pos
- ofType:@"text"
- displayOptions:[NSDictionary dictionaryWithObjectsAndKeys:
- [[aCell font] familyName], @"fontname",
- [NSString stringWithFormat:@"%f",[[aCell font] pointSize]], @"fontsize",
- nil]];
-
- return nil;
-}
-
-- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
-{
- return tableRowsCount;
-}
-
-- (id)tableView:(CMCopyTable *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
-{
- NSUInteger columnIndex = [[aTableColumn identifier] integerValue];
- id theValue = nil;
-
- // While the table is being loaded, additional validation is required - data
- // locks must be used to avoid crashes, and indexes higher than the available
- // rows or columns may be requested. Return "..." to indicate loading in these
- // cases.
- if (isWorking) {
- pthread_mutex_lock(&tableValuesLock);
- if (rowIndex < tableRowsCount && columnIndex < [tableValues columnCount]) {
- theValue = [[SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex) copy] autorelease];
- }
- pthread_mutex_unlock(&tableValuesLock);
-
- if (!theValue) return @"...";
- } else {
- theValue = SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex);
- }
-
- if ([theValue isNSNull])
- return [prefs objectForKey:SPNullValue];
-
- if ([theValue isKindOfClass:[NSData class]])
- return [theValue shortStringRepresentationUsingEncoding:[mySQLConnection encoding]];
-
- if ([theValue isSPNotLoaded])
- return NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields");
-
- return theValue;
-}
-
-/**
- * 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
-{
- if (![cell respondsToSelector:@selector(setTextColor:)]) return;
-
- NSUInteger columnIndex = [[aTableColumn identifier] integerValue];
- id theValue = nil;
-
- // While the table is being loaded, additional validation is required - data
- // locks must be used to avoid crashes, and indexes higher than the available
- // rows or columns may be requested. Use gray to indicate loading in these cases.
- if (isWorking) {
- pthread_mutex_lock(&tableValuesLock);
- if (rowIndex < tableRowsCount && columnIndex < [tableValues columnCount]) {
- theValue = SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex);
- }
- pthread_mutex_unlock(&tableValuesLock);
-
- if (!theValue) {
- [cell setTextColor:[NSColor lightGrayColor]];
- return;
- }
- } else {
- theValue = SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex);
- }
-
- // If user wants to edit 'cell' set text color to black and return to avoid
- // writing in gray if value was NULL
- if ([aTableView editedColumn] != -1
- && [aTableView editedRow] == rowIndex
- && [[NSArrayObjectAtIndex([aTableView tableColumns], [aTableView editedColumn]) identifier] integerValue] == columnIndex) {
- [cell setTextColor:[NSColor blackColor]];
- return;
- }
-
- // For null cells and not loaded cells, display the contents in gray.
- if ([theValue isNSNull] || [theValue isSPNotLoaded]) {
- [cell setTextColor:[NSColor lightGrayColor]];
-
- // Otherwise, set the color to black - required as NSTableView reuses NSCells.
- } else {
- [cell setTextColor:[NSColor blackColor]];
- }
-}
-
-- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
-{
- // Catch editing events in the row and if the row isn't currently being edited,
- // start an edit. This allows edits including enum changes to save correctly.
- if ( !isEditingRow ) {
- [oldRow setArray:[tableValues rowContentsAtIndex:rowIndex]];
- isEditingRow = YES;
- currentlyEditingRow = rowIndex;
- }
-
- NSDictionary *column = NSArrayObjectAtIndex(dataColumns, [[aTableColumn identifier] integerValue]);
-
- if (anObject) {
-
- // Restore NULLs if necessary
- if ([anObject isEqualToString:[prefs objectForKey:SPNullValue]] && [[column objectForKey:@"null"] boolValue])
- anObject = [NSNull null];
-
- [tableValues replaceObjectInRow:rowIndex column:[[aTableColumn identifier] integerValue] withObject:anObject];
- } else {
- [tableValues replaceObjectInRow:rowIndex column:[[aTableColumn identifier] integerValue] withObject:@""];
- }
-}
-
-#pragma mark -
-#pragma mark TableView delegate methods
-
-/**
- * Sorts the tableView by the clicked column.
- * If clicked twice, order is altered to descending.
- * Performs the task in a new thread if necessary.
- */
-- (void)tableView:(NSTableView*)tableView didClickTableColumn:(NSTableColumn *)tableColumn
-{
-
- if ( [selectedTable isEqualToString:@""] || !selectedTable )
- return;
-
- // Prevent sorting while the table is still loading
- if ([tableDocumentInstance isWorking]) return;
-
- // Start the task
- [tableDocumentInstance startTaskWithDescription:NSLocalizedString(@"Sorting table...", @"Sorting table task description")];
- if ([NSThread isMainThread]) {
- [NSThread detachNewThreadSelector:@selector(sortTableTaskWithColumn:) toTarget:self withObject:tableColumn];
- } else {
- [self sortTableTaskWithColumn:tableColumn];
- }
-}
-- (void)sortTableTaskWithColumn:(NSTableColumn *)tableColumn
-{
- NSAutoreleasePool *sortPool = [[NSAutoreleasePool alloc] init];
-
- // Check whether a save of the current row is required.
- if (![[self onMainThread] saveRowOnDeselect]) {
- [sortPool drain];
- return;
- }
-
- // Sets order descending if a header is clicked twice
- if ([[tableColumn identifier] isEqualTo:sortCol]) {
- isDesc = !isDesc;
- } else {
- isDesc = NO;
- [[tableContentView onMainThread] setIndicatorImage:nil inTableColumn:[tableContentView tableColumnWithIdentifier:sortCol]];
- }
- if (sortCol) [sortCol release];
- sortCol = [[NSNumber alloc] initWithInteger:[[tableColumn identifier] integerValue]];
-
- // Set the highlight and indicatorImage
- [[tableContentView onMainThread] setHighlightedTableColumn:tableColumn];
- if (isDesc) {
- [[tableContentView onMainThread] setIndicatorImage:[NSImage imageNamed:@"NSDescendingSortIndicator"] inTableColumn:tableColumn];
- } else {
- [[tableContentView onMainThread] setIndicatorImage:[NSImage imageNamed:@"NSAscendingSortIndicator"] inTableColumn:tableColumn];
- }
-
- // Update data using the new sort order
- previousTableRowsCount = tableRowsCount;
- [self loadTableValues];
-
- if ([mySQLConnection queryErrored]) {
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil,
- [NSString stringWithFormat:NSLocalizedString(@"Couldn't sort table. MySQL said: %@", @"message of panel when sorting of table failed"), [mySQLConnection getLastErrorMessage]]);
- [tableDocumentInstance endTask];
- [sortPool drain];
- return;
- }
-
- [tableDocumentInstance endTask];
- [sortPool drain];
-}
-
-- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
-{
- // Check our notification object is our table content view
- if ([aNotification object] != tableContentView) return;
-
- // If we are editing a row, attempt to save that row - if saving failed, reselect the edit row.
- if (isEditingRow && [tableContentView selectedRow] != currentlyEditingRow && ![self saveRowOnDeselect]) return;
-
- if (![tableDocumentInstance isWorking]) {
-
- // Update the row selection count
- // and update the status of the delete/duplicate buttons
- if ([tableContentView numberOfSelectedRows] > 0) {
- [copyButton setEnabled:YES];
- [removeButton setEnabled:YES];
- }
- else {
- [copyButton setEnabled:NO];
- [removeButton setEnabled:NO];
- }
- }
-
- [self updateCountText];
-}
-
-- (void)tableViewColumnDidResize:(NSNotification *)aNotification
-/*
- saves the new column size in the preferences
- */
-{
- // sometimes the column has no identifier. I can't figure out what is causing it, so we just skip over this item
- if (![[[aNotification userInfo] objectForKey:@"NSTableColumn"] identifier])
- return;
-
- NSMutableDictionary *tableColumnWidths;
- NSString *database = [NSString stringWithFormat:@"%@@%@", [tableDocumentInstance database], [tableDocumentInstance host]];
- NSString *table = [tablesListInstance tableName];
-
- // get tableColumnWidths object
- if ( [prefs objectForKey:SPTableColumnWidths] != nil ) {
- tableColumnWidths = [NSMutableDictionary dictionaryWithDictionary:[prefs objectForKey:SPTableColumnWidths]];
- } else {
- tableColumnWidths = [NSMutableDictionary dictionary];
- }
- // get database object
- if ( [tableColumnWidths objectForKey:database] == nil ) {
- [tableColumnWidths setObject:[NSMutableDictionary dictionary] forKey:database];
- } else {
- [tableColumnWidths setObject:[NSMutableDictionary dictionaryWithDictionary:[tableColumnWidths objectForKey:database]] forKey:database];
-
- }
- // get table object
- if ( [[tableColumnWidths objectForKey:database] objectForKey:table] == nil ) {
- [[tableColumnWidths objectForKey:database] setObject:[NSMutableDictionary dictionary] forKey:table];
- } else {
- [[tableColumnWidths objectForKey:database] setObject:[NSMutableDictionary dictionaryWithDictionary:[[tableColumnWidths objectForKey:database] objectForKey:table]] forKey:table];
-
- }
- // save column size
- [[[tableColumnWidths objectForKey:database] objectForKey:table] setObject:[NSNumber numberWithDouble:[(NSTableColumn *)[[aNotification userInfo] objectForKey:@"NSTableColumn"] width]] forKey:[[[[aNotification userInfo] objectForKey:@"NSTableColumn"] headerCell] stringValue]];
- [prefs setObject:tableColumnWidths forKey:SPTableColumnWidths];
-}
-
-/**
- * Confirm whether to allow editing of a row. Returns YES by default, unless the multipleLineEditingButton is in
- * the ON state, or for blob or text fields - in those cases opens a sheet for editing instead and returns NO.
- */
-- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
-{
- if ([tableDocumentInstance isWorking]) return NO;
-
- // Ensure that row is editable since it could contain "(not loaded)" columns together with
- // issue that the table has no primary key
- NSString *wherePart = [NSString stringWithString:[self argumentForRow:[tableContentView selectedRow]]];
- if ([wherePart length] == 0) return NO;
-
- // If the selected cell hasn't been loaded, load it.
- if ([[tableValues cellDataAtRow:rowIndex column:[[aTableColumn identifier] integerValue]] isSPNotLoaded]) {
-
- // Only get the data for the selected column, not all of them
- NSString *query = [NSString stringWithFormat:@"SELECT %@ FROM %@ WHERE %@", [[[aTableColumn headerCell] stringValue] backtickQuotedString], [selectedTable backtickQuotedString], wherePart];
-
- MCPResult *tempResult = [mySQLConnection queryString:query];
- if (![tempResult numOfRows]) {
- SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil,
- NSLocalizedString(@"Couldn't load the row. Reload the table to be sure that the row exists and use a primary key for your table.", @"message of panel when loading of row failed"));
- return NO;
- }
-
- NSArray *tempRow = [tempResult fetchRowAsArray];
- [tableValues replaceObjectInRow:rowIndex column:[[tableContentView tableColumns] indexOfObject:aTableColumn] withObject:[tempRow objectAtIndex:0]];
- [tableContentView reloadData];
- }
-
- BOOL isBlob = [tableDataInstance columnIsBlobOrText:[[aTableColumn headerCell] stringValue]];
-
- // Open the sheet if the multipleLineEditingButton is enabled or the column was a blob or a text.
- if ([multipleLineEditingButton state] == NSOnState || isBlob) {
-
- SPFieldEditorController *fieldEditor = [[SPFieldEditorController alloc] init];
-
- [fieldEditor setTextMaxLength:[[[aTableColumn dataCellForRow:rowIndex] formatter] textLimit]];
-
- id cellValue = [tableValues cellDataAtRow:rowIndex column:[[aTableColumn identifier] integerValue]];
- if ([cellValue isNSNull]) cellValue = [NSString stringWithString:[prefs objectForKey:SPNullValue]];
-
- id editData = [[fieldEditor editWithObject:cellValue
- fieldName:[[aTableColumn headerCell] stringValue]
- usingEncoding:[mySQLConnection encoding]
- isObjectBlob:isBlob
- isEditable:YES
- withWindow:[tableDocumentInstance parentWindow]] retain];
-
- if (editData) {
- if (!isEditingRow) {
- [oldRow setArray:[tableValues rowContentsAtIndex:rowIndex]];
- isEditingRow = YES;
- currentlyEditingRow = rowIndex;
- }
-
- if ([editData isKindOfClass:[NSString class]]
- && [editData isEqualToString:[prefs objectForKey:SPNullValue]]
- && [[NSArrayObjectAtIndex(dataColumns, [[aTableColumn identifier] integerValue]) objectForKey:@"null"] boolValue])
- {
- [editData release];
- editData = [[NSNull null] retain];
- }
- [tableValues replaceObjectInRow:rowIndex column:[[aTableColumn identifier] integerValue] withObject:[[editData copy] autorelease]];
- }
-
- [fieldEditor release];
-
- if (editData) [editData release];
-
- return NO;
- }
-
- return YES;
-}
-
-/**
- * Enable drag from tableview
- */
-- (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rows toPasteboard:(NSPasteboard*)pboard
-{
- if (aTableView == tableContentView) {
- NSString *tmp;
-
- // By holding ⌘, ⇧, or/and ⌥ copies selected rows as SQL INSERTS
- // otherwise \t delimited lines
- if([[NSApp currentEvent] modifierFlags] & (NSCommandKeyMask|NSShiftKeyMask|NSAlternateKeyMask))
- tmp = [tableContentView selectedRowsAsSqlInserts];
- else
- tmp = [tableContentView draggedRowsAsTabString];
-
- if ( nil != tmp && [tmp length] )
- {
- [pboard declareTypes:[NSArray arrayWithObjects: NSTabularTextPboardType,
- NSStringPboardType, nil]
- owner:nil];
-
- [pboard setString:tmp forType:NSStringPboardType];
- [pboard setString:tmp forType:NSTabularTextPboardType];
- return YES;
- }
- }
-
- return NO;
-}
-
-/**
- * Disable row selection while the document is working.
- */
-- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex
-{
- return tableRowsSelectable;
-}
-
-#pragma mark -
-#pragma mark SplitView delegate methods
-
-- (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview
-{
- return NO;
-}
-
-- (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset
-{
- return (proposedMax - 180);
-}
-
-- (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset
-{
- return (proposedMin + 200);
-}
-
-#pragma mark -
-#pragma mark Task interaction
-
-/**
- * Disable all content interactive elements during an ongoing task.
- */
-- (void) startDocumentTaskForTab:(NSNotification *)aNotification
-{
- isWorking = YES;
-
- // Only proceed if this view is selected.
- if (![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableContent])
- return;
-
- [addButton setEnabled:NO];
- [removeButton setEnabled:NO];
- [copyButton setEnabled:NO];
- [reloadButton setEnabled:NO];
- [filterButton setEnabled:NO];
- tableRowsSelectable = NO;
- [paginationPreviousButton setEnabled:NO];
- [paginationNextButton setEnabled:NO];
- [paginationButton setEnabled:NO];
-}
-
-/**
- * Enable all content interactive elements after an ongoing task.
- */
-- (void) endDocumentTaskForTab:(NSNotification *)aNotification
-{
- isWorking = NO;
-
- // Only proceed if this view is selected.
- if (![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableContent])
- return;
-
- if ( ![[[tableDataInstance statusValues] objectForKey:@"Rows"] isNSNull] && selectedTable && [selectedTable length] && [tableDataInstance tableEncoding]) {
- [addButton setEnabled:YES];
- [self updatePaginationState];
- [reloadButton setEnabled:YES];
- }
- if ([tableContentView numberOfSelectedRows] > 0) {
- [removeButton setEnabled:YES];
- [copyButton setEnabled:YES];
- }
- [filterButton setEnabled:[fieldField isEnabled]];
- tableRowsSelectable = YES;
-}
-
-#pragma mark -
-#pragma mark Other methods
-
-/*
- * Trap the enter, escape, tab and arrow keys, overriding default behaviour and continuing/ending editing,
- * only within the current row.
- */
-- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command
-{
- NSString *fieldType;
- NSUInteger row, column, i;
-
- row = [tableContentView editedRow];
- column = [tableContentView editedColumn];
-
- // Trap tab key
- if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(insertTab:)] )
- {
- [[control window] makeFirstResponder:control];
-
- // Save the current line if it's the last field in the table
- if ( column == ( [tableContentView numberOfColumns] - 1 ) ) {
- [self addRowToDB];
- } else {
-
- // Check if next column is a blob column, and skip to the next non-blob column
- i = 1;
- while (
- (fieldType = [[tableDataInstance columnWithName:[[NSArrayObjectAtIndex([tableContentView tableColumns], column+i) headerCell] stringValue]] objectForKey:@"typegrouping"])
- && ([fieldType isEqualToString:@"textdata"] || [fieldType isEqualToString:@"blobdata"])
- ) {
- i++;
-
- // If there are no columns after the latest blob or text column, save the current line.
- if ( (column+i) >= [tableContentView numberOfColumns] ) {
- [self addRowToDB];
- return TRUE;
- }
- }
-
- // Edit the column after the blob column
- [tableContentView editColumn:column+i row:row withEvent:nil select:YES];
- }
- return TRUE;
- }
-
- // Trap enter key
- else if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(insertNewline:)] )
- {
- [[control window] makeFirstResponder:control];
- [self addRowToDB];
- return TRUE;
- }
-
- // Trap down arrow key
- else if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(moveDown:)] )
- {
- NSUInteger newRow = row+1;
- if (newRow>=tableRowsCount) return TRUE; //check if we're already at the end of the list
-
- [[control window] makeFirstResponder:control];
- [self addRowToDB];
-
- if (newRow>=tableRowsCount) return TRUE; //check again. addRowToDB could reload the table and change the number of rows
- if (column>=[tableValues columnCount]) return TRUE; //the column count could change too
-
- [tableContentView selectRowIndexes:[NSIndexSet indexSetWithIndex:newRow] byExtendingSelection:NO];
- [tableContentView editColumn:column row:newRow withEvent:nil select:YES];
- return TRUE;
- }
-
- // Trap up arrow key
- else if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(moveUp:)] )
- {
- if (row==0) return TRUE; //already at the beginning of the list
- NSUInteger newRow = row-1;
-
- [[control window] makeFirstResponder:control];
- [self addRowToDB];
-
- if (newRow>=tableRowsCount) return TRUE; // addRowToDB could reload the table and change the number of rows
- if (column>=[tableValues columnCount]) return TRUE; //the column count could change too
-
- [tableContentView selectRowIndexes:[NSIndexSet indexSetWithIndex:newRow] byExtendingSelection:NO];
- [tableContentView editColumn:column row:newRow withEvent:nil select:YES];
- return TRUE;
- }
-
- // Trap the escape key
- else if ( [[control window] methodForSelector:command] == [[control window] methodForSelector:@selector(_cancelKey:)] ||
- [textView methodForSelector:command] == [textView methodForSelector:@selector(complete:)] )
- {
-
- // Abort editing
- [control abortEditing];
- if ( isEditingRow && !isEditingNewRow ) {
- isEditingRow = NO;
- [tableValues replaceRowAtIndex:row withRowContents:oldRow];
- } else if ( isEditingNewRow ) {
- isEditingRow = NO;
- isEditingNewRow = NO;
- tableRowsCount--;
- [tableValues removeRowAtIndex:row];
- [self updateCountText];
- [tableContentView reloadData];
- }
- currentlyEditingRow = -1;
- return TRUE;
- }
- else
- {
- return FALSE;
- }
-}
-
-/**
- * This method is called as part of Key Value Observing which is used to watch for prefernce changes which effect the interface.
- */
-- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
-{
- // Display table veiew vertical gridlines preference changed
- if ([keyPath isEqualToString:SPDisplayTableViewVerticalGridlines]) {
- [tableContentView setGridStyleMask:([[change objectForKey:NSKeyValueChangeNewKey] boolValue]) ? NSTableViewSolidVerticalGridLineMask : NSTableViewGridNone];
- }
- // Table font preference changed
- else if ([keyPath isEqualToString:SPGlobalResultTableFont]) {
- NSFont *tableFont = [NSUnarchiver unarchiveObjectWithData:[change objectForKey:NSKeyValueChangeNewKey]];
- [tableContentView setRowHeight:2.0f+NSSizeToCGSize([[NSString stringWithString:@"{ǞṶḹÜ∑zgyf"] sizeWithAttributes:[NSDictionary dictionaryWithObject:tableFont forKey:NSFontAttributeName]]).height];
- [tableContentView setFont:tableFont];
- [tableContentView reloadData];
- }
-}
-
-/**
- * Menu validation
- */
-- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
-{
- // Remove row
- if ([menuItem action] == @selector(removeRow:)) {
- [menuItem setTitle:([tableContentView numberOfSelectedRows] > 1) ? @"Delete Rows" : @"Delete Row"];
-
- return ([tableContentView numberOfSelectedRows] > 0);
- }
-
- // Duplicate row
- if ([menuItem action] == @selector(copyRow:)) {
- return ([tableContentView numberOfSelectedRows] == 1);
- }
-
- return YES;
-}
-
-/**
- * Makes the content filter field have focus by making it the first responder.
- */
-- (void)makeContentFilterHaveFocus
-{
-
- NSDictionary *filter = [[contentFilters objectForKey:compareType] objectAtIndex:[[compareField selectedItem] tag]];
-
- if([filter objectForKey:@"NumberOfArguments"]) {
- NSUInteger numOfArgs = [[filter objectForKey:@"NumberOfArguments"] integerValue];
- switch(numOfArgs) {
- case 2:
- [[tableDocumentInstance parentWindow] makeFirstResponder:firstBetweenField];
- break;
- case 1:
- [[tableDocumentInstance parentWindow] makeFirstResponder:argumentField];
- break;
- default:
- [[tableDocumentInstance parentWindow] makeFirstResponder:compareField];
- }
- }
-}
-
-#pragma mark -
-
-// Last but not least
-- (void)dealloc
-{
- [[NSNotificationCenter defaultCenter] removeObserver:self];
-
- [tableValues release];
- pthread_mutex_destroy(&tableValuesLock);
- [dataColumns release];
- [oldRow release];
- if (selectedTable) [selectedTable release];
- if (contentFilters) [contentFilters release];
- if (numberOfDefaultFilters) [numberOfDefaultFilters release];
- if (keys) [keys release];
- if (sortCol) [sortCol release];
- [usedQuery release];
- if (sortColumnToRestore) [sortColumnToRestore release];
- if (selectionIndexToRestore) [selectionIndexToRestore release];
- if (filterFieldToRestore) filterFieldToRestore = nil;
- if (filterComparisonToRestore) filterComparisonToRestore = nil;
- if (filterValueToRestore) filterValueToRestore = nil;
- if (firstBetweenValueToRestore) firstBetweenValueToRestore = nil;
- if (secondBetweenValueToRestore) secondBetweenValueToRestore = nil;
-
- [super dealloc];
-}
-
-@end