aboutsummaryrefslogtreecommitdiffstats
path: root/Source/TableContent.m
diff options
context:
space:
mode:
Diffstat (limited to 'Source/TableContent.m')
-rw-r--r--Source/TableContent.m2018
1 files changed, 2018 insertions, 0 deletions
diff --git a/Source/TableContent.m b/Source/TableContent.m
new file mode 100644
index 00000000..1aa939f2
--- /dev/null
+++ b/Source/TableContent.m
@@ -0,0 +1,2018 @@
+//
+// 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 "TablesList.h"
+#import "TableSource.h"
+#import "CMImageView.h"
+
+
+@implementation TableContent
+
+- (id)init
+{
+ if (![super init])
+ return nil;
+
+ fullResult = [[NSMutableArray alloc] init];
+ filteredResult = [[NSMutableArray alloc] init];
+ oldRow = [[NSMutableDictionary alloc] init];
+ selectedTable = nil;
+ sortField = nil;
+ areShowingAllRows = false;
+
+ return self;
+}
+
+- (void)awakeFromNib
+{
+}
+
+/*
+ Loads aTable, retrieving column information and updating the tableViewColumns before
+ reloading table data into the fullResults array and redrawing the table.
+ */
+- (void)loadTable:(NSString *)aTable
+{
+ int i;
+ NSNumber *colWidth;
+ NSArray *theColumns;
+ NSTableColumn *theCol;
+ NSString *query;
+ CMMCPResult *queryResult;
+ BOOL preserveCurrentView = [aTable isEqualToString:selectedTable];
+ NSString *preservedFilterField = nil, *preservedFilterComparison, *preservedFilterValue;
+
+ // Clear the selection, and abort the reload if the user is still editing a row
+ [tableContentView deselectAll:self];
+ if ( isEditingRow )
+ return;
+
+ // Store the newly selected table name
+ selectedTable = aTable;
+
+ // Reset table key store for use in argumentForRow:
+ if ( keys )
+ keys = nil;
+
+ // 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 ( [aTable isEqualToString:@""] || !aTable )
+ {
+
+ // Empty the table and stored data arrays
+ theColumns = [tableContentView tableColumns];
+ while ([theColumns count]) {
+ [tableContentView removeTableColumn:[theColumns objectAtIndex:0]];
+ }
+ [fullResult removeAllObjects];
+ [filteredResult removeAllObjects];
+ [tableContentView reloadData];
+ areShowingAllRows = YES;
+ [countText setStringValue:@""];
+
+ // 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 setEnabled:NO];
+ [argumentField setStringValue:@""];
+ [filterButton setEnabled:NO];
+
+ // Empty and disable the limit field
+ [limitRowsField setStringValue:@""];
+ [limitRowsText setStringValue:NSLocalizedString(@"No limit", @"text showing that the result isn't limited")];
+ [limitRowsField setEnabled:NO];
+ [limitRowsButton setEnabled:NO];
+ [limitRowsStepper setEnabled:NO];
+
+ // Disable table action buttons
+ [addButton setEnabled:NO];
+ [copyButton setEnabled:NO];
+ [removeButton setEnabled:NO];
+
+ return;
+ }
+
+ // Post a notification that a query will be performed
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:self];
+
+
+ // Make a fast query to get fieldNames and fieldTypes for this table. This is used to decide whether to preserve the
+ // current filter/sort settings, and also used when grabbing all the data as part of the fieldListForQuery method.
+ queryResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SELECT * FROM `%@` LIMIT 0", selectedTable]];
+ if ( queryResult == nil ) {
+ NSLog(@"Loading table columns for %@ failed", aTable);
+ return;
+ }
+ fieldNames = [[queryResult fetchFieldNames] retain];
+ fieldTypes = [[queryResult fetchTypesAsArray] retain];
+
+ // Retrieve the number of rows in the table and initially mark all as being visible.
+ numRows = [self getNumberOfRows];
+ areShowingAllRows = YES;
+
+ // Remove existing columns from the table
+ theColumns = [tableContentView tableColumns];
+ while ([theColumns count]) {
+ [tableContentView removeTableColumn:[theColumns objectAtIndex:0]];
+ }
+
+ // Add the new columns to the table
+ for ( i = 0 ; i < [fieldNames count] ; i++ ) {
+
+ // Set up the column
+ theCol = [[NSTableColumn alloc] initWithIdentifier:[fieldNames objectAtIndex:i]];
+ [theCol setEditable:YES];
+ if ( [theCol respondsToSelector:@selector(setResizingMask:)] ) {
+ // Mac OS X 10.4+
+ [theCol setResizingMask:NSTableColumnUserResizingMask];
+ } else {
+ // Mac OS X pre-10.4
+ [theCol setResizable:YES];
+ }
+ [[theCol headerCell] setStringValue:[fieldNames objectAtIndex:i]];
+
+ // Set up the data cell depending on the column type
+ NSComboBoxCell *dataCell;
+ if ( [[tableSourceInstance enumFields] objectForKey:[fieldNames objectAtIndex:i]] )
+ {
+ dataCell = [[[NSComboBoxCell alloc] initTextCell:@""] autorelease];
+ [dataCell setButtonBordered:NO];
+ [dataCell setBezeled:NO];
+ [dataCell setDrawsBackground:NO];
+ [dataCell setCompletes:YES];
+ [dataCell setControlSize:NSSmallControlSize];
+ [dataCell addItemWithObjectValue:@"NULL"];
+ [dataCell addItemsWithObjectValues:[[tableSourceInstance enumFields] objectForKey:[fieldNames objectAtIndex:i]]];
+ }
+ else
+ {
+ dataCell = [[[NSTextFieldCell alloc] initTextCell:@""] autorelease];
+ }
+ [dataCell setEditable:YES];
+
+ if ( [dataCell respondsToSelector:@selector(setLineBreakMode:)] ) {
+ // Mac OS X 10.4+
+ [dataCell setLineBreakMode:NSLineBreakByTruncatingTail];
+ }
+
+ // Set the data cell font according to the preferences
+ if ( [prefs boolForKey:@"useMonospacedFonts"] )
+ {
+ [dataCell setFont:[NSFont fontWithName:@"Monaco" size:10]];
+ }
+ else
+ {
+ [dataCell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
+ }
+
+ // Assign the data cell
+ [theCol setDataCell:dataCell];
+
+ // Set the width of this column to saved value if exists
+ colWidth = [[[[prefs objectForKey:@"tableColumnWidths"] objectForKey:[NSString stringWithFormat:@"%@@%@", [tableDocumentInstance database], [tableDocumentInstance host]]] objectForKey:[tablesListInstance table]] objectForKey:[fieldNames objectAtIndex:i]];
+ if ( colWidth )
+ {
+ [theCol setWidth:[colWidth floatValue]];
+ }
+
+ // 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 (preserveCurrentView && [fieldNames containsObject:sortField])
+ {
+ theCol = [tableContentView tableColumnWithIdentifier:sortField];
+ [tableContentView setHighlightedTableColumn:theCol];
+ if ( isDesc ) {
+ [tableContentView setIndicatorImage:[NSImage imageNamed:@"NSDescendingSortIndicator"] inTableColumn:theCol];
+ } else {
+ [tableContentView setIndicatorImage:[NSImage imageNamed:@"NSAscendingSortIndicator"] inTableColumn:theCol];
+ }
+ }
+
+ // Otherwise, clear sorting
+ else
+ {
+ sortField = nil;
+ isDesc = NO;
+ }
+
+ // Preserve the stored filter settings if appropriate
+ if (preserveCurrentView && [fieldField isEnabled])
+ {
+ preservedFilterField = [NSString stringWithString:[[fieldField selectedItem] title]];
+ preservedFilterComparison = [NSString stringWithString:[[compareField selectedItem] title]];
+ preservedFilterValue = [NSString stringWithString:[argumentField stringValue]];
+ }
+
+ // Enable and initialize filter fields (with tags for position of menu item and field position)
+ [fieldField setEnabled:YES];
+ [fieldField removeAllItems];
+ [fieldField addItemsWithTitles:fieldNames];
+ 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:YES];
+
+ // Restore preserved filter settings if appropriate and valid
+ if (preserveCurrentView && preservedFilterField != nil && [fieldField itemWithTitle:preservedFilterField])
+ {
+ [fieldField selectItemWithTitle:preservedFilterField];
+ [self setCompareTypes:self];
+ }
+ if (preserveCurrentView && preservedFilterField != nil
+ && [fieldField itemWithTitle:preservedFilterField]
+ && [compareField itemWithTitle:preservedFilterComparison])
+ {
+ [compareField selectItemWithTitle:preservedFilterComparison];
+ [argumentField setStringValue:preservedFilterValue];
+ areShowingAllRows = NO;
+ }
+
+ // Enable or disable the limit fields according to preference setting
+ if ( [prefs boolForKey:@"limitRows"] )
+ {
+
+ // Attempt to preserve the limit value if it's still valid
+ if (!preserveCurrentView || [limitRowsField intValue] < 1 || [limitRowsField intValue] >= numRows) {
+ [limitRowsField setStringValue:@"1"];
+ }
+ [limitRowsField setEnabled:YES];
+ [limitRowsButton setEnabled:YES];
+ [limitRowsStepper setEnabled:YES];
+ [limitRowsText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Limited to %d rows starting with row", @"text showing the number of rows the result is limited to"),
+ [prefs integerForKey:@"limitRowsValue"]]];
+ if ([prefs integerForKey:@"limitRowsValue"] < numRows)
+ areShowingAllRows = NO;
+ }
+ else
+ {
+ [limitRowsField setEnabled:NO];
+ [limitRowsButton setEnabled:NO];
+ [limitRowsStepper setEnabled:NO];
+ [limitRowsField setStringValue:@""];
+ [limitRowsText setStringValue:NSLocalizedString(@"No limit", @"text showing that the result isn't limited")];
+ }
+
+ // Enable the table buttons
+ [addButton setEnabled:YES];
+ [copyButton setEnabled:YES];
+ [removeButton setEnabled:YES];
+
+
+ // Perform the data query and store the result as an array containing a dictionary per result row
+ query = [NSString stringWithFormat:@"SELECT %@ FROM `%@`", [self fieldListForQuery], selectedTable];
+ if ( sortField )
+ {
+ query = [NSString stringWithFormat:@"%@ ORDER BY `%@`", query, sortField];
+ if ( isDesc )
+ query = [query stringByAppendingString:@" DESC"];
+ }
+ if ( [prefs boolForKey:@"limitRows"] )
+ {
+ if ( [limitRowsField intValue] <= 0 )
+ {
+ [limitRowsField setStringValue:@"1"];
+ }
+ query = [query stringByAppendingString:
+ [NSString stringWithFormat:@" LIMIT %d,%d",
+ [limitRowsField intValue]-1, [prefs integerForKey:@"limitRowsValue"]]];
+ }
+ queryResult = [mySQLConnection queryString:query];
+ if ( queryResult == nil ) {
+ NSLog(@"Loading table data for %@ failed, query string was: %@", aTable, query);
+ return;
+ }
+ [fullResult setArray:[self fetchResultAsArray:queryResult]];
+
+ // Apply any filtering and update the row count
+ if ( !areShowingAllRows ) {
+ [self filterTable:self];
+ [countText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%d rows of %d selected", @"text showing how many rows are in the filtered result"), [filteredResult count], numRows]];
+ } else {
+ [filteredResult setArray:fullResult];
+ [countText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%d rows in table", @"text showing how many rows are in the result"), numRows]];
+ }
+
+ // Reload the table data.
+ [tableContentView reloadData];
+
+ // Post the notification that the query is finished
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:self];
+}
+
+/*
+ Reloads the current table data, performing a new SQL query. Now attempts to preserve sort order, filters, and viewport.
+ */
+- (IBAction)reloadTable:(id)sender
+{
+ // Store the current viewport location
+ NSRect viewRect = [tableContentView visibleRect];
+
+ [self loadTable:selectedTable];
+
+ // Restore the viewport
+ [tableContentView scrollRectToVisible:viewRect];
+}
+
+- (IBAction)reloadTableValues:(id)sender
+/*
+ reload the table values without reconfiguring the tableView (with filter and limit if set)
+ */
+{
+ NSString *queryString;
+ CMMCPResult *queryResult;
+
+ //query started
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:self];
+
+ //enable or disable limit fields
+ if ( [prefs boolForKey:@"limitRows"] ) {
+ [limitRowsField setEnabled:YES];
+ [limitRowsButton setEnabled:YES];
+ [limitRowsStepper setEnabled:YES];
+ [limitRowsText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Limited to %d rows starting with row", @"text showing the number of rows the result is limited to"),
+ [prefs integerForKey:@"limitRowsValue"]]];
+ } else {
+ [limitRowsField setEnabled:NO];
+ [limitRowsButton setEnabled:NO];
+ [limitRowsStepper setEnabled:NO];
+ [limitRowsText setStringValue:NSLocalizedString(@"No limit", @"text showing that the result isn't limited")];
+ [limitRowsField setStringValue:@""];
+ }
+
+ // queryString = [@"SELECT * FROM " stringByAppendingString:selectedTable];
+ queryString = [NSString stringWithFormat:@"SELECT %@ FROM `%@`", [self fieldListForQuery], selectedTable];
+ if ( sortField ) {
+ queryString = [NSString stringWithFormat:@"%@ ORDER BY `%@`", queryString, sortField];
+ // queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@" ORDER BY `%@`", sortField]];
+ if ( isDesc )
+ queryString = [queryString stringByAppendingString:@" DESC"];
+ }
+ if ( [prefs boolForKey:@"limitRows"] ) {
+ if ( [limitRowsField intValue] <= 0 ) {
+ [limitRowsField setStringValue:@"1"];
+ }
+ queryString = [queryString stringByAppendingString:
+ [NSString stringWithFormat:@" LIMIT %d,%d",
+ [limitRowsField intValue]-1, [prefs integerForKey:@"limitRowsValue"]]];
+ [limitRowsField selectText:self];
+ }
+ queryResult = [mySQLConnection queryString:queryString];
+ // [fullResult setArray:[[self fetchResultAsArray:queryResult] retain]];
+ [fullResult setArray:[self fetchResultAsArray:queryResult]];
+ numRows = [self getNumberOfRows];
+ if ( !areShowingAllRows ) {
+ [self filterTable:self];
+ [countText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%d rows of %d selected", @"text showing how many rows are in the filtered result"), [filteredResult count], numRows]];
+ } else {
+ [filteredResult setArray:fullResult];
+ [countText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%d rows in table", @"text showing how many rows are in the result"), numRows]];
+ }
+ [tableContentView reloadData];
+
+ //query finished
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:self];
+}
+
+/**
+ * filter the table with arguments given by the user
+ */
+- (IBAction)filterTable:(id)sender
+{
+ CMMCPResult *theResult;
+ int tag = [[compareField selectedItem] tag];
+ NSString *compareOperator = @"";
+ NSMutableString *argument = [[NSMutableString alloc] initWithString:[argumentField stringValue]];
+ NSString *queryString;
+ int i;
+
+ // Update negative limits
+ if ( [limitRowsField intValue] <= 0 ) {
+ [limitRowsField setStringValue:@"1"];
+ }
+
+ // If the filter field is empty, the limit field is at 1, and the selected filter is not looking
+ // for NULLs or NOT NULLs, then don't allow filtering.
+ if (([argument length] == 0) && (![[[compareField selectedItem] title] hasSuffix:@"NULL"]) && (![prefs boolForKey:@"limitRows"] || [limitRowsField intValue] == 1)) {
+ [argument release];
+ [self showAll:sender];
+ return;
+ }
+
+ //query started
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:self];
+
+ BOOL doQuote = YES;
+ BOOL ignoreArgument = NO;
+
+ // Start building the query string
+ queryString = [NSString stringWithFormat:@"SELECT %@ FROM `%@`",
+ [self fieldListForQuery], selectedTable];
+
+ // Add filter if appropriate
+ if (([argument length] > 0) || [[[compareField selectedItem] title] hasSuffix:@"NULL"]) {
+ if ( ![compareType isEqualToString:@""] ) {
+ if ( [compareType isEqualToString:@"string"] ) {
+ //string comparision
+ switch ( tag ) {
+ case 0:
+ compareOperator = @"LIKE";
+ break;
+ case 1:
+ compareOperator = @"NOT LIKE";
+ break;
+ case 2:
+ compareOperator = @"LIKE";
+ [argument setString:[[@"%" stringByAppendingString:argument] stringByAppendingString:@"%"]];
+ break;
+ case 3:
+ compareOperator = @"NOT LIKE";
+ [argument setString:[[@"%" stringByAppendingString:argument] stringByAppendingString:@"%"]];
+ break;
+ case 4:
+ compareOperator = @"IN";
+ doQuote = NO;
+ [argument setString:[[@"(" stringByAppendingString:argument] stringByAppendingString:@")"]];
+ break;
+ case 5:
+ compareOperator = @"IS NULL";
+ doQuote = NO;
+ ignoreArgument = YES;
+ break;
+ case 6:
+ compareOperator = @"IS NOT NULL";
+ doQuote = NO;
+ ignoreArgument = YES;
+ break;
+ }
+ } else if ( [compareType isEqualToString:@"number"] ) {
+ //number comparision
+ switch ( tag ) {
+ case 0:
+ compareOperator = @"=";
+ break;
+ case 1:
+ compareOperator = @"!=";
+ break;
+ case 2:
+ compareOperator = @">";
+ break;
+ case 3:
+ compareOperator = @"<";
+ break;
+ case 4:
+ compareOperator = @">=";
+ break;
+ case 5:
+ compareOperator = @"<=";
+ break;
+ case 6:
+ compareOperator = @"IN";
+ doQuote = NO;
+ [argument setString:[[@"(" stringByAppendingString:argument] stringByAppendingString:@")"]];
+ break;
+ case 7:
+ compareOperator = @"IS NULL";
+ doQuote = NO;
+ ignoreArgument = YES;
+ break;
+ case 8:
+ compareOperator = @"IS NOT NULL";
+ doQuote = NO;
+ ignoreArgument = YES;
+ break;
+ }
+ } else if ( [compareType isEqualToString:@"date"] ) {
+ //date comparision
+ switch ( tag ) {
+ case 0:
+ compareOperator = @"=";
+ break;
+ case 1:
+ compareOperator = @"!=";
+ break;
+ case 2:
+ compareOperator = @"<";
+ break;
+ case 3:
+ compareOperator = @">";
+ break;
+ case 4:
+ compareOperator = @"<=";
+ break;
+ case 5:
+ compareOperator = @">=";
+ break;
+ case 6:
+ compareOperator = @"IS NULL";
+ doQuote = NO;
+ ignoreArgument = YES;
+ break;
+ case 7:
+ compareOperator = @"IS NOT NULL";
+ doQuote = NO;
+ ignoreArgument = YES;
+ break;
+ }
+ } else {
+ doQuote = NO;
+ ignoreArgument = YES;
+ NSLog(@"ERROR: unknown compare type %@", compareType);
+ }
+
+ if (doQuote) {
+ //escape special characters
+ for ( i = 0 ; i < [argument length] ; i++ ) {
+ if ( [argument characterAtIndex:i] == '\\' ) {
+ [argument insertString:@"\\" atIndex:i];
+ i++;
+ }
+ }
+ [argument setString:[mySQLConnection prepareString:argument]];
+ queryString = [NSString stringWithFormat:@"%@ WHERE `%@` %@ \"%@\"",
+ queryString, [fieldField titleOfSelectedItem], compareOperator, argument];
+ } else {
+ queryString = [NSString stringWithFormat:@"%@ WHERE `%@` %@ %@",
+ queryString, [fieldField titleOfSelectedItem],
+ compareOperator, (ignoreArgument) ? @"" : argument];
+ }
+ }
+ }
+
+ // Add sorting details if appropriate
+ if ( sortField ) {
+ queryString = [NSString stringWithFormat:@"%@ ORDER BY `%@`", queryString, sortField];
+ if ( isDesc )
+ queryString = [queryString stringByAppendingString:@" DESC"];
+ }
+
+ // LIMIT if appropriate
+ if ( [prefs boolForKey:@"limitRows"] ) {
+ queryString = [NSString stringWithFormat:@"%@ LIMIT %d,%d", queryString,
+ [limitRowsField intValue]-1, [prefs integerForKey:@"limitRowsValue"]];
+ }
+
+ theResult = [mySQLConnection queryString:queryString];
+ [filteredResult setArray:[self fetchResultAsArray:theResult]];
+
+ [countText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%d rows of %d selected", @"text showing how many rows are in the filtered result"), [filteredResult count], numRows]];
+
+ // Reset the table view
+ [tableContentView scrollPoint:NSMakePoint(0.0, 0.0)];
+ [tableContentView reloadData];
+ areShowingAllRows = NO;
+
+ //query finished
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:self];
+ [argument release];
+}
+
+/**
+ * reload tableView with all results shown (no new mysql-query, it uses simply the fullResult array)
+ */
+- (IBAction)showAll:(id)sender
+{
+ [filteredResult setArray:fullResult];
+ [tableContentView reloadData];
+ areShowingAllRows = YES;
+
+ [countText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%d rows in table", @"text showing how many rows are in the result"), numRows]];
+}
+
+/**
+ * Enables or disables the filter input field based on the selected filter type.
+ */
+- (IBAction)toggleFilterField:(id)sender
+{
+ // If the user is filtering for NULLs then disabled the filter field, otherwise enable it.
+ [argumentField setEnabled:(![[[compareField selectedItem] title] hasSuffix:@"NULL"])];
+}
+
+
+//edit methods
+- (IBAction)addRow:(id)sender
+/*
+ adds an empty row to the table-array and goes into edit mode
+ */
+{
+ NSMutableDictionary *newRow = [NSMutableDictionary dictionary];
+ int i;
+
+ if ( ![self selectionShouldChangeInTableView:nil] )
+ return;
+
+ for ( i = 0 ; i < [fieldNames count] ; i++ ) {
+ // [newRow setObject:[prefs stringForKey:@"nullValue"] forKey:[fieldNames objectAtIndex:i]];
+ [newRow setObject:[tableSourceInstance defaultValueForField:[fieldNames objectAtIndex:i]]
+ forKey:[fieldNames objectAtIndex:i]];
+ }
+ [filteredResult addObject:newRow];
+
+ isEditingRow = YES;
+ isEditingNewRow = YES;
+ [tableContentView reloadData];
+ [tableContentView selectRow:[tableContentView numberOfRows]-1 byExtendingSelection:NO];
+ if ( [multipleLineEditingButton state] == NSOffState )
+ [tableContentView editColumn:0 row:[tableContentView numberOfRows]-1 withEvent:nil select:YES];
+}
+
+- (IBAction)copyRow:(id)sender
+/*
+ copies a row of the table-array and goes into edit mode
+ */
+{
+ NSMutableDictionary *tempRow;
+ CMMCPResult *queryResult;
+ NSDictionary *row;
+ int i;
+
+ if ( ![self selectionShouldChangeInTableView:nil] )
+ return;
+ if ( [tableContentView numberOfSelectedRows] < 1 )
+ return;
+ if ( [tableContentView numberOfSelectedRows] > 1 ) {
+ NSBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil, NSLocalizedString(@"You can only copy single rows.", @"message of panel when trying to copy multiple rows"));
+ return;
+ }
+
+ //copy row
+ tempRow = [NSMutableDictionary dictionaryWithDictionary:[filteredResult objectAtIndex:[tableContentView selectedRow]]];
+ [filteredResult insertObject:tempRow atIndex:[tableContentView selectedRow]+1];
+ isEditingRow = YES;
+ isEditingNewRow = YES;
+ //set autoincrement fields to NULL
+ queryResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW COLUMNS FROM `%@`", selectedTable]];
+ if ([queryResult numOfRows]) [queryResult dataSeek:0];
+ for ( i = 0 ; i < [queryResult numOfRows] ; i++ ) {
+ row = [queryResult fetchRowAsDictionary];
+ if ( [[row objectForKey:@"Extra"] isEqualToString:@"auto_increment"] ) {
+ [tempRow setObject:[prefs stringForKey:@"nullValue"] forKey:[row objectForKey:@"Field"]];
+ }
+ }
+ //select row and go in edit mode
+ [tableContentView reloadData];
+ [tableContentView selectRow:[tableContentView selectedRow]+1 byExtendingSelection:NO];
+ if ( [multipleLineEditingButton state] == NSOffState )
+ [tableContentView editColumn:0 row:[tableContentView selectedRow] withEvent:nil select:YES];
+}
+
+- (IBAction)removeRow:(id)sender
+/*
+ asks user if he really wants to delete the selected rows
+ */
+{
+ if ( ![self selectionShouldChangeInTableView:nil] )
+ return;
+ if ( ![tableContentView numberOfSelectedRows] )
+ return;
+ /*
+ if ( ([tableContentView numberOfSelectedRows] == [self numberOfRowsInTableView:tableContentView]) &&
+ areShowingAllRows &&
+ (![prefs boolForKey:@"limitRows"] || ([tableContentView numberOfSelectedRows] < [prefs integerForKey:@"limitRowsValue"])) ) {
+ */
+ if ( ([tableContentView numberOfSelectedRows] == [tableContentView numberOfRows]) &&
+ (([prefs boolForKey:@"limitRows"] && [tableContentView numberOfSelectedRows] == [self fetchNumberOfRows]) ||
+ (![prefs boolForKey:@"limitRows"] && [tableContentView numberOfSelectedRows] == [self getNumberOfRows])) ) {
+ NSBeginAlertSheet(NSLocalizedString(@"Warning", @"warning"), NSLocalizedString(@"Delete", @"delete button"), NSLocalizedString(@"Cancel", @"cancel button"), nil, tableWindow, self, @selector(sheetDidEnd:returnCode:contextInfo:),
+ nil, @"removeallrows", NSLocalizedString(@"Do you really want to delete all rows?", @"message of panel asking for confirmation for deleting all rows"));
+ } else if ( [tableContentView numberOfSelectedRows] == 1 ) {
+ NSBeginAlertSheet(NSLocalizedString(@"Warning", @"warning"), NSLocalizedString(@"Delete", @"delete button"), NSLocalizedString(@"Cancel", @"cancel button"), nil, tableWindow, self, @selector(sheetDidEnd:returnCode:contextInfo:),
+ nil, @"removerow", NSLocalizedString(@"Do you really want to delete the selected row?", @"message of panel asking for confirmation for deleting the selected row"));
+ } else {
+ NSBeginAlertSheet(NSLocalizedString(@"Warning", @"warning"), NSLocalizedString(@"Delete", @"delete button"), NSLocalizedString(@"Cancel", @"cancel button"), nil, tableWindow, self, @selector(sheetDidEnd:returnCode:contextInfo:),
+ nil, @"removerow",
+ [NSString stringWithFormat:NSLocalizedString(@"Do you really want to delete the selected %d rows?", @"message of panel asking for confirmation for deleting the selected rows"), [tableContentView numberOfSelectedRows]]);
+ }
+}
+
+
+//editSheet methods
+- (IBAction)closeEditSheet:(id)sender
+{
+ [NSApp stopModalWithCode:[sender tag]];
+}
+
+- (IBAction)openEditSheet:(id)sender
+/*
+ loads a file into the editSheet
+ */
+{
+ NSOpenPanel *panel = [NSOpenPanel openPanel];
+
+ if ( [panel runModal] == NSOKButton ) {
+ NSString *fileName = [panel filename];
+
+ // free old data
+ if ( editData != nil ) {
+ [editData release];
+ }
+
+ // load new data/images
+ editData = [[NSData alloc] initWithContentsOfFile:fileName];
+ NSImage *image = [[[NSImage alloc] initByReferencingFile:fileName] autorelease];
+ NSString *contents = [[NSString stringWithContentsOfFile:fileName] autorelease];
+
+ // set the image preview, string contents and hex representation
+ [editImage setImage:image];
+ [editTextView setString:contents];
+ [hexTextView setString:[self dataToHex:editData]];
+ }
+}
+
+- (IBAction)saveEditSheet:(id)sender
+/*
+ saves a file containing the content of the editSheet
+ */
+{
+ NSSavePanel *panel = [NSSavePanel savePanel];
+
+ if ( [panel runModal] == NSOKButton ) {
+ NSString *fileName = [panel filename];
+ NSString *data;
+
+ if ( [editData isKindOfClass:[NSData class]] ) {
+ data = editData;
+ } else {
+ data = [editData description];
+ }
+ if ( [editData respondsToSelector:@selector(writeToFile:atomically:encoding:error:)] ) {
+ // mac os 10.4 or later
+ [editData writeToFile:fileName atomically:YES encoding:[CMMCPConnection encodingForMySQLEncoding:[(NSString *)[tableDocumentInstance encoding] UTF8String]] error:NULL];
+ } else {
+ // mac os pre 10.4
+ [editData writeToFile:fileName atomically:YES];
+ }
+ }
+}
+
+- (IBAction)dropImage:(id)sender
+/*
+ invoked when user drag&drops image on imageView
+ */
+{
+ // load new data/images
+ if (nil != editData)
+ {
+ [editData release];
+ }
+ editData = [[[NSData alloc] initWithContentsOfFile:[sender draggedFilePath]] retain];
+ NSString *contents = [NSString stringWithContentsOfFile:[sender draggedFilePath]];
+
+ // set the string contents and hex representation
+ [editTextView setString:contents];
+ [hexTextView setString:[self dataToHex:editData]];
+}
+
+- (void)textDidChange:(NSNotification *)notification
+/*
+ invoked when the user changes the string in the editSheet
+ */
+{
+ // clear the image and hex (since i doubt someone can "type" a gif)
+ [editImage setImage:nil];
+ [hexTextView setString:@""];
+
+ // free old data
+ if ( editData != nil ) {
+ [editData release];
+ }
+
+ // set edit data to text
+ editData = [[editTextView string] retain];
+}
+
+- (NSString *)dataToHex:(NSData *)data
+/*
+ returns the hex representation of the given data
+ */
+{
+ unsigned i;
+ unsigned totalLength = [data length];
+ int bytesPerLine = 16;
+ NSMutableString *retVal = [NSMutableString string];
+ unsigned char *nodisplay = "\t\n\r\f";
+
+ // get the length of the longest location
+ int longest = [(NSString *)[NSString stringWithFormat:@"%X", totalLength - ( totalLength % bytesPerLine )] length];
+
+ for ( i = 0; i < totalLength; i += bytesPerLine ) {
+ int j;
+ NSMutableString *hex = [[NSMutableString alloc] initWithCapacity:(3 * bytesPerLine - 1)];
+ NSMutableString *location = [[NSMutableString alloc] initWithCapacity:(longest + 2)];
+ NSMutableString *chars = [[NSMutableString alloc] init];
+ unsigned char *buffer;
+ int buffLength = bytesPerLine;
+
+ // add hex value of location
+ [location appendString:[NSString stringWithFormat:@"%X", i]];
+
+ // pad it
+ while( longest > [location length] ) {
+ [location insertString:@"0" atIndex:0];
+ }
+
+ // get the chars from the NSData obj
+ if ( i + buffLength >= totalLength ) {
+ buffLength = totalLength - i;
+ }
+ buffer = (unsigned char*) malloc( sizeof( unsigned char ) * buffLength );
+ NSRange range = { i, buffLength };
+ [data getBytes:buffer range:range];
+
+ // build the hex string
+ for ( j = 0; j < buffLength; j++ ) {
+ unsigned char byte = *(buffer + j);
+ if ( byte < 16 ) {
+ [hex appendString:@"0"];
+ }
+ [hex appendString:[NSString stringWithFormat:@"%X", byte]];
+ [hex appendString:@" "];
+
+ // if the char is undisplayable, replace it with "."
+ unsigned char current;
+ int count = 0;
+ while ( ( current = *(nodisplay + count++) ) > 0 ) {
+ if ( current == byte ) {
+ *(buffer + j) = '.';
+ break;
+ }
+ }
+ }
+
+ // add padding to missing hex values.
+ for ( j = 0; j < bytesPerLine - buffLength; j++ ) {
+ [hex appendString:@" "];
+ }
+
+ // remove extra ghost characters
+ [chars appendString:[NSString stringWithCString:buffer]];
+ if ( [chars length] > bytesPerLine ) {
+ [chars deleteCharactersInRange:NSMakeRange( bytesPerLine, [chars length] - bytesPerLine )];
+ }
+
+ // build line
+ [retVal appendString:location];
+ [retVal appendString:@" "];
+ [retVal appendString:hex];
+ [retVal appendString:@" "];
+ [retVal appendString:chars];
+ [retVal appendString:@"\n"];
+
+ // clean up
+ [hex release];
+ [chars release];
+ [location release];
+ free( buffer );
+ }
+
+ return retVal;
+}
+
+//getter methods
+- (NSArray *)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 *tableColumns;
+ NSEnumerator *enumerator;
+ id tableColumn;
+ NSMutableArray *currentResult = [NSMutableArray array];
+ NSMutableArray *tempRow = [NSMutableArray array];
+ int i;
+
+ //load table if not already done
+ if ( ![tablesListInstance contentLoaded] ) {
+ [self loadTable:(NSString *)[tablesListInstance table]];
+ }
+
+ 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
+- (void)setConnection:(CMMCPConnection *)theConnection
+/*
+ sets the connection (received from TableDocument) and makes things that have to be done only once
+ */
+{
+ mySQLConnection = theConnection;
+
+ [tableContentView setVerticalMotionCanBeginDrag:NO];
+
+ prefs = [[NSUserDefaults standardUserDefaults] retain];
+ if ( [prefs boolForKey:@"useMonospacedFonts"] ) {
+ [argumentField setFont:[NSFont fontWithName:@"Monaco" size:10]];
+ [limitRowsField setFont:[NSFont fontWithName:@"Monaco" size:[NSFont smallSystemFontSize]]];
+ [editTextView setFont:[NSFont fontWithName:@"Monaco" size:[NSFont smallSystemFontSize]]];
+ } else {
+ [editTextView setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
+ [limitRowsField setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
+ [argumentField setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
+ }
+ [hexTextView setFont:[NSFont fontWithName:@"Monaco" size:[NSFont smallSystemFontSize]]];
+ [limitRowsStepper setEnabled:NO];
+ if ( [prefs boolForKey:@"limitRows"] ) {
+ [limitRowsText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Limited to %d rows starting with row", @"text showing the number of rows the result is limited to"),
+ [prefs integerForKey:@"limitRowsValue"]]];
+ } else {
+ [limitRowsText setStringValue:NSLocalizedString(@"No limit", @"text showing that the result isn't limited")];
+ [limitRowsField setStringValue:@""];
+ }
+}
+
+/**
+ * Sets the compare types for the filter and the appropriate formatter for the textField
+ */
+- (IBAction)setCompareTypes:(id)sender
+{
+ NSArray *stringFields = [NSArray arrayWithObjects:@"varstring", @"string", @"tinyblob", @"blob", @"mediumblob", @"longblob", @"set", @"enum", nil];
+ NSArray *stringTypes = [NSArray arrayWithObjects:NSLocalizedString(@"is", @"popup menuitem for field IS value"), NSLocalizedString(@"is not", @"popup menuitem for field IS NOT value"), NSLocalizedString(@"contains", @"popup menuitem for field CONTAINS value"), NSLocalizedString(@"contains not", @"popup menuitem for field CONTAINS NOT value"), @"IN", nil];
+ NSArray *numberFields = [NSArray arrayWithObjects:@"tiny", @"short", @"long", @"int24", @"longlong", @"decimal", @"float", @"double", nil];
+ NSArray *numberTypes = [NSArray arrayWithObjects:@"=", @"≠", @">", @"<", @"≥", @"≤", @"IN", nil];
+ NSArray *dateFields = [NSArray arrayWithObjects:@"timestamp", @"date", @"time", @"datetime", @"year", nil];
+ NSArray *dateTypes = [NSArray arrayWithObjects:NSLocalizedString(@"is", @"popup menuitem for field IS value"), NSLocalizedString(@"is not", @"popup menuitem for field IS NOT value"), NSLocalizedString(@"older than", @"popup menuitem for field OLDER THAN value"), NSLocalizedString(@"younger than", @"popup menuitem for field YOUNGER THAN value"), NSLocalizedString(@"older than or equal to", @"popup menuitem for field OLDER THAN OR EQUAL TO value"), NSLocalizedString(@"younger than or equal to", @"popup menuitem for field YOUNGER THAN OR EQUAL TO value"), nil];
+ NSString *fieldType = [NSString stringWithString:[fieldTypes objectAtIndex:[[fieldField selectedItem] tag]]];
+
+ int i;
+
+ [compareField removeAllItems];
+
+ // Why do we get "string" for enum fields? (error in framework?)
+ if ( [stringFields containsObject:fieldType] ) {
+ [compareField addItemsWithTitles:stringTypes];
+ compareType = @"string";
+ // [argumentField setFormatter:nil];
+ } else if ( [numberFields containsObject:fieldType] ) {
+ [compareField addItemsWithTitles:numberTypes];
+ compareType = @"number";
+ // [argumentField setFormatter:numberFormatter];
+ } else if ( [dateFields containsObject:fieldType] ) {
+ [compareField addItemsWithTitles:dateTypes];
+ 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]];
+ }
+ */
+ } else {
+ NSLog(@"ERROR: unknown type for comparision: %@", fieldType);
+ }
+
+ // Add IS NULL and IS NOT NULL as they should always be available
+ [compareField addItemWithTitle:@"IS NULL"];
+ [compareField addItemWithTitle:@"IS NOT NULL"];
+
+ for ( i = 0 ; i < [compareField numberOfItems] ; i++ ) {
+ [[compareField itemAtIndex:i] setTag:i];
+ }
+
+ // Update the argumentField enabled state
+ [self toggleFilterField:self];
+
+ // set focus on argumentField
+ [argumentField selectText:self];
+}
+
+- (IBAction)stepLimitRows:(id)sender
+/*
+ steps the start row up or down (+/- limitRowsValue)
+ */
+{
+ if ( [limitRowsStepper intValue] > 0 ) {
+ [limitRowsField setIntValue:[limitRowsField intValue]+[prefs integerForKey:@"limitRowsValue"]];
+ } else {
+ if ( ([limitRowsField intValue]-[prefs integerForKey:@"limitRowsValue"]) < 1 ) {
+ [limitRowsField setIntValue:1];
+ } else {
+ [limitRowsField setIntValue:[limitRowsField intValue]-[prefs integerForKey:@"limitRowsValue"]];
+ }
+ }
+ [limitRowsStepper setIntValue:0];
+}
+
+- (NSArray *)fetchResultAsArray:(CMMCPResult *)theResult
+/*
+ fetches the result as an array with a dictionary for each row in it
+ */
+{
+ NSMutableArray *tempResult = [NSMutableArray array];
+ NSDictionary *tempRow;
+ NSMutableDictionary *modifiedRow = [NSMutableDictionary dictionary];
+ NSEnumerator *enumerator;
+ id key;
+ int i,j;
+
+ if ([theResult numOfRows]) [theResult dataSeek:0];
+ for ( i = 0 ; i < [theResult numOfRows] ; i++ ) {
+ tempRow = [theResult fetchRowAsDictionary];
+ enumerator = [tempRow keyEnumerator];
+ while ( key = [enumerator nextObject] ) {
+ if ( [[tempRow objectForKey:key] isMemberOfClass:[NSNull class]] ) {
+ [modifiedRow setObject:[prefs stringForKey:@"nullValue"] forKey:key];
+ /*
+ //NSData objects now decoded in tableView:objectValueForTableColumn:row
+ //object in result remains a NSData object
+ } else if ( [[tempRow objectForKey:key] isKindOfClass:[NSData class]] ) {
+ [modifiedRow setObject:[[NSString alloc] initWithData:[tempRow objectForKey:key] encoding:[mySQLConnection encoding]]
+ forKey:key];
+ */
+ } else {
+ [modifiedRow setObject:[tempRow objectForKey:key] forKey:key];
+ }
+ //add values for hidden blob and text fields
+ if ( [prefs boolForKey:@"dontShowBlob"] ) {
+ for ( j = 0 ; j < [fieldTypes count] ; j++ ) {
+ if ( [self isBlobOrText:[fieldTypes objectAtIndex:j]] ) {
+ [modifiedRow setObject:NSLocalizedString(@"- blob or text -", @"value shown for hidden blob and text fields") forKey:[fieldNames objectAtIndex:j]];
+ }
+ }
+ }
+ }
+ [tempResult addObject:[NSMutableDictionary dictionaryWithDictionary:modifiedRow]];
+ }
+ return tempResult;
+}
+
+- (BOOL)addRowToDB
+/*
+ tries to write row to mysql-db
+ returns YES if row written to db, otherwies NO
+ returns YES if no row is beeing edited and nothing has to be written to db
+ */
+{
+ int rowIndex = [tableContentView selectedRow];
+ NSMutableArray *fieldValues = [[NSMutableArray alloc] init];
+ NSMutableString *queryString;
+ NSString *query;
+ CMMCPResult *queryResult;
+ id rowObject;
+ NSMutableString *rowValue = [NSMutableString string];
+ NSString *currentTime = [[NSDate date] descriptionWithCalendarFormat:@"%H:%M:%S" timeZone:nil locale:nil];
+ int i;
+
+ if ( !isEditingRow || rowIndex == -1) {
+ [fieldValues release];
+ return YES;
+ }
+
+ //get field values
+ for ( i=0 ; i < [fieldNames count] ; i++) {
+ rowObject = [[filteredResult objectAtIndex:rowIndex] objectForKey:[fieldNames objectAtIndex:i]];
+ //convert the object to a string (here we can add special treatment for date-, number- and data-fields)
+ if ( [[rowObject description] isEqualToString:[prefs stringForKey:@"nullValue"]] ||
+ ([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 {
+ if ( [rowObject isKindOfClass:[NSCalendarDate class]] ) {
+ // [rowValue setString:[NSString stringWithFormat:@"\"%@\"", [mySQLConnection prepareString:[rowObject description]]]];
+ [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 {
+ // [rowValue setString:[NSString stringWithFormat:@"\"%@\"", [mySQLConnection prepareString:[rowObject description]]]];
+ if ( [[rowObject description] isEqualToString:@"CURRENT_TIMESTAMP"] ) {
+ [rowValue setString:@"CURRENT_TIMESTAMP"];
+ } else {
+ [rowValue setString:[NSString stringWithFormat:@"'%@'", [mySQLConnection prepareString:[rowObject description]]]];
+ }
+ }
+ }
+ //escape special characters -> now escaped by framework
+ /*
+ for ( j = 0 ; j < [rowValue length] ; j++ ) {
+ if ( [rowValue characterAtIndex:j] == '\\' ) {
+ [rowValue insertString:@"\\" atIndex:j];
+ j++;
+ } else if ( [rowValue characterAtIndex:j] == '"' ) {
+ [rowValue insertString:@"\\" atIndex:j];
+ j++;
+ }
+ }
+ */
+ [fieldValues addObject:[NSString stringWithString:rowValue]];
+ }
+
+ if ( isEditingNewRow ) {
+ //INSERT syntax
+ queryString = [NSString stringWithFormat:@"INSERT INTO `%@` (`%@`) VALUES (%@)",
+ selectedTable, [fieldNames componentsJoinedByString:@"`,`"], [fieldValues componentsJoinedByString:@","]];
+ } else {
+ //UPDATE syntax
+ queryString = [NSMutableString stringWithFormat:@"UPDATE `%@` SET ", selectedTable];
+ for ( i = 0 ; i < [fieldNames count] ; i++ ) {
+ if ( i > 0 ) {
+ [queryString appendString:@", "];
+ }
+ [queryString appendString:[NSString stringWithFormat:@"`%@`=%@",
+ [fieldNames objectAtIndex:i], [fieldValues objectAtIndex:i]]];
+ }
+ [fieldValues release];
+ [queryString appendString:[NSString stringWithFormat:@" WHERE %@", [self argumentForRow:-2]]];
+ }
+ [mySQLConnection queryString:queryString];
+
+ //NSLog( @"%@", queryString );
+
+ if ( ![mySQLConnection affectedRows] ) {
+ //no rows changed
+ if ( [prefs boolForKey:@"showError"] ) {
+ NSBeginAlertSheet(NSLocalizedString(@"Warning", @"warning"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, 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();
+ }
+ [filteredResult replaceObjectAtIndex:rowIndex withObject:[NSMutableDictionary dictionaryWithDictionary:oldRow]];
+ isEditingRow = NO;
+ isEditingNewRow = NO;
+ [tableDocumentInstance 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]];
+ return YES;
+ } else if ( [[mySQLConnection getLastErrorMessage] isEqualToString:@""] ) {
+ //added new row with success
+ isEditingRow = NO;
+ if ( isEditingNewRow ) {
+ if ( [prefs boolForKey:@"reloadAfterAdding"] ) {
+ [self reloadTableValues:self];
+ // if ( sortField )
+ [tableContentView deselectAll:self];
+ } else {
+ //set insertId for fields with auto_increment
+ queryResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW COLUMNS FROM `%@`", selectedTable]];
+ if ([queryResult numOfRows]) [queryResult dataSeek:0];
+ for ( i = 0 ; i < [queryResult numOfRows] ; i++ ) {
+ rowObject = [queryResult fetchRowAsDictionary];
+ if ( [[rowObject objectForKey:@"Extra"] isEqualToString:@"auto_increment"] ) {
+ [[filteredResult objectAtIndex:rowIndex] setObject:[NSNumber numberWithLong:[mySQLConnection insertId]]
+ forKey:[rowObject objectForKey:@"Field"]];
+ }
+ }
+ [fullResult addObject:[filteredResult objectAtIndex:rowIndex]];
+ }
+ isEditingNewRow = NO;
+ } else {
+ //updated row with success
+ if ( [prefs boolForKey:@"reloadAfterEditing"] ) {
+ [self reloadTableValues:self];
+ // if ( sortField )
+ [tableContentView deselectAll:self];
+ } else {
+ // query = [@"SELECT * FROM " stringByAppendingString:selectedTable];
+ query = [NSString stringWithFormat:@"SELECT %@ FROM `%@`", [self fieldListForQuery], selectedTable];
+ if ( sortField ) {
+ // query = [query stringByAppendingString:[NSString stringWithFormat:@" ORDER BY `%@`", sortField]];
+ query = [NSString stringWithFormat:@"%@ ORDER BY `%@`", query, sortField];
+ if ( isDesc )
+ query = [query stringByAppendingString:@" DESC"];
+ }
+ if ( [prefs boolForKey:@"limitRows"] ) {
+ if ( [limitRowsField intValue] <= 0 ) {
+ [limitRowsField setStringValue:@"1"];
+ }
+ query = [query stringByAppendingString:
+ [NSString stringWithFormat:@" LIMIT %d,%d",
+ [limitRowsField intValue]-1, [prefs integerForKey:@"limitRowsValue"]]];
+ }
+ queryResult = [mySQLConnection queryString:query];
+ // [fullResult setArray:[[self fetchResultAsArray:queryResult] retain]];
+ [fullResult setArray:[self fetchResultAsArray:queryResult]];
+ }
+ }
+ return YES;
+ } else {
+ //error in mysql-query
+ NSBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), NSLocalizedString(@"Cancel", @"cancel button"), nil, tableWindow, self, @selector(sheetDidEnd:returnCode:contextInfo:), nil, @"addrow",
+ [NSString stringWithFormat:NSLocalizedString(@"Couldn't write row.\nMySQL said: %@", @"message of panel when error while adding row to db"), [mySQLConnection getLastErrorMessage]]);
+ return NO;
+ }
+}
+
+- (NSString *)argumentForRow:(int)row
+/*
+ returns the WHERE argument to identify a row
+ if row is -2, it uses the oldRow
+ if there is one, it uses the primary key, otherwise uses all fields as argument and sets LIMIT to 1
+ */
+{
+ CMMCPResult *theResult;
+ NSDictionary *theRow;
+ id tempValue;
+ NSMutableString *value = [NSMutableString string];
+ NSMutableString *argument = [NSMutableString string];
+ int i,j;
+ NSEnumerator *enumerator;
+ id type;
+ BOOL blob = NO;
+ NSArray *numberFields = [NSArray arrayWithObjects:@"tiny", @"short", @"long", @"int24", @"longlong", @"decimal", @"float", @"double", nil];
+
+ if ( row == -1 )
+ return @"";
+
+ //get primary key if there is one
+ /*
+ theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW INDEX FROM `%@`", selectedTable]];
+ if ([theResult numOfRows]) [theResult dataSeek:0];
+ for ( i = 0 ; i < [theResult numOfRows] ; i++ ) {
+ theRow = [theResult fetchRowAsDictionary];
+ if ( [[theRow objectForKey:@"Key_name"] isEqualToString:@"PRIMARY"] ) {
+ [keys addObject:[theRow objectForKey:@"Column_name"]];
+ }
+ }
+ */
+ if ( !keys ) {
+ setLimit = NO;
+ keys = [[NSMutableArray alloc] init];
+ theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW COLUMNS FROM `%@`", selectedTable]];
+ 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 ( ![keys count] ) {
+ //if there is no primary key, take all fields as argument
+ //here we have a problem when dontShowBlob == YES (we don't have the right values to use in the WHERE statement)
+ [keys setArray:fieldNames];
+ setLimit = YES;
+ enumerator = [fieldTypes objectEnumerator];
+ while ( (type = [enumerator nextObject]) ) {
+ if ( [self isBlobOrText:type] ) {
+ blob = YES;
+ }
+ }
+ if ( [prefs boolForKey:@"dontShowBlob"] && blob ) {
+ NSBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, 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 @"";
+ }
+ }
+ for ( i = 0 ; i < [keys count] ; i++ ) {
+ if ( i )
+ [argument appendString:@" AND "];
+ if ( row >= 0 ) {
+ //use selected row
+ tempValue = [[filteredResult objectAtIndex:row] objectForKey:[keys objectAtIndex:i]];
+ } else {
+ //use oldRow
+ tempValue = [oldRow objectForKey:[keys objectAtIndex:i]];
+ }
+ if ( [tempValue isKindOfClass:[NSData class]] ) {
+ [value setString:[[NSString alloc] initWithData:tempValue encoding:[mySQLConnection encoding]]];
+ } else {
+ [value setString:[tempValue description]];
+ }
+
+ if ( [value isEqualToString:[prefs stringForKey:@"nullValue"]] ) {
+ [value setString:@"NULL"];
+ } else {
+ //escape special characters (in WHERE statement!)
+ for ( j = 0 ; j < [value length] ; j++ ) {
+ if ( [value characterAtIndex:j] == '\\' ) {
+ [value insertString:@"\\" atIndex:j];
+ j++;
+ }
+ }
+ [value setString:[mySQLConnection prepareString:value]];
+ for ( j = 0 ; j < [value length] ; j++ ) {
+ if ( [value characterAtIndex:j] == '%' ||
+ [value characterAtIndex:j] == '_' ) {
+ [value insertString:@"\\" atIndex:j];
+ j++;
+ }
+ }
+ // [value setString:[NSString stringWithFormat:@"\"%@\"", value]];
+ [value setString:[NSString stringWithFormat:@"'%@'", value]];
+ }
+ if ( [value isEqualToString:@"NULL"] ) {
+ [argument appendString:[NSString stringWithFormat:@"`%@` IS NULL", [keys objectAtIndex:i]]];
+ } else {
+ if ( [numberFields containsObject:[fieldTypes objectAtIndex:[fieldNames indexOfObject:[keys objectAtIndex:i]]]] ) {
+ [argument appendString:[NSString stringWithFormat:@"`%@` = %@", [keys objectAtIndex:i], value]];
+ } else {
+ [argument appendString:[NSString stringWithFormat:@"`%@` LIKE %@", [keys objectAtIndex:i], value]];
+ }
+ }
+ }
+ if ( setLimit )
+ [argument appendString:@" LIMIT 1"];
+ return argument;
+}
+
+- (BOOL)isBlobOrText:(NSString *)fieldType
+/*
+ returns YES if fieldType is some kind of blob or text. afaik the type of this fields is always blob, but better we test it...
+ it would be nice to know if it is blob or text, but mysql doesn't want to tell it...
+ */
+{
+ if ( [fieldType isEqualToString:@"tinyblob"] || [fieldType isEqualToString:@"blob"] ||
+ [fieldType isEqualToString:@"mediumblob"] || [fieldType isEqualToString:@"longblob"] ) {
+ return YES;
+ } else {
+ return NO;
+ }
+}
+
+- (NSString *)fieldListForQuery
+/*
+ returns * if dontShowBlob == NO
+ returns a comma-separated list of all fields which aren't of type blob or text if dontShowBlob == YES
+ */
+{
+ int i;
+ NSMutableArray *fields = [NSMutableArray array];
+
+ if ( [prefs boolForKey:@"dontShowBlob"] ) {
+ for ( i = 0 ; i < [fieldTypes count] ; i++ ) {
+ if ( ![self isBlobOrText:[fieldTypes objectAtIndex:i]] ) {
+ [fields addObject:[fieldNames objectAtIndex:i]];
+ }
+ }
+ if ( [fields count] == 0 ) {
+ return [NSString stringWithFormat:@"`%@`", [fieldNames objectAtIndex:0]];
+ } else {
+ return [NSString stringWithFormat:@"`%@`", [fields componentsJoinedByString:@"`,`"]];
+ }
+ } else {
+ return @"*";
+ }
+}
+
+- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(NSString *)contextInfo
+/*
+ if contextInfo == addrow: remain in edit-mode if user hits OK, otherwise cancel editing
+ if contextInfo == removerow: removes row if user hits OK
+ */
+{
+ NSEnumerator *enumerator = [tableContentView selectedRowEnumerator];
+ NSNumber *index;
+ NSMutableArray *tempArray = [NSMutableArray array];
+ NSMutableArray *tempResult = [NSMutableArray array];
+ NSString *queryString;
+ CMMCPResult *queryResult;
+ int i, errors;
+
+ [sheet orderOut:self];
+
+ if ( [contextInfo isEqualToString:@"addrow"] ) {
+ if ( returnCode == NSAlertDefaultReturn ) {
+ //problem: reenter edit mode doesn't function
+ [tableContentView editColumn:0 row:[tableContentView selectedRow] withEvent:nil select:YES];
+ } else {
+ if ( !isEditingNewRow ) {
+ [filteredResult replaceObjectAtIndex:[tableContentView selectedRow]
+ withObject:[NSMutableDictionary dictionaryWithDictionary:oldRow]];
+ isEditingRow = NO;
+ } else {
+ [filteredResult removeObjectAtIndex:[tableContentView selectedRow]];
+ isEditingRow = NO;
+ isEditingNewRow = NO;
+ }
+ }
+ [tableContentView reloadData];
+ } else if ( [contextInfo isEqualToString:@"removeallrows"] ) {
+ if ( returnCode == NSAlertDefaultReturn ) {
+ /*
+ if ( ([tableContentView numberOfSelectedRows] == [self numberOfRowsInTableView:tableContentView]) &&
+ areShowingAllRows &&
+ ([tableContentView numberOfSelectedRows] < [prefs integerForKey:@"limitRowsValue"]) ) {
+ */
+ [mySQLConnection queryString:[NSString stringWithFormat:@"DELETE FROM `%@`", selectedTable]];
+ if ( [[mySQLConnection getLastErrorMessage] isEqualToString:@""] ) {
+ [self reloadTable:self];
+ } else {
+ NSBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
+ [NSString stringWithFormat:NSLocalizedString(@"Couldn't remove rows.\nMySQL said: %@", @"message of panel when field cannot be removed"),
+ [mySQLConnection getLastErrorMessage]]);
+ }
+ }
+ } else if ( [contextInfo isEqualToString:@"removerow"] ) {
+ if ( returnCode == NSAlertDefaultReturn ) {
+ errors = 0;
+
+ while ( (index = [enumerator nextObject]) ) {
+ [mySQLConnection queryString:[NSString stringWithFormat:@"DELETE FROM `%@` WHERE %@",
+ selectedTable, [self argumentForRow:[index intValue]]]];
+ if ( ![mySQLConnection affectedRows] ) {
+ //no rows deleted
+ errors++;
+ } else if ( [[mySQLConnection getLastErrorMessage] isEqualToString:@""] ) {
+ //rows deleted with success
+ [tempArray addObject:index];
+ } else {
+ //error in mysql-query
+ errors++;
+ }
+ }
+
+ if ( errors ) {
+ NSBeginAlertSheet(NSLocalizedString(@"Warning", @"warning"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"%d rows have not been removed. 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"), errors]);
+ }
+
+ //do deleting (after enumerating)
+ if ( [prefs boolForKey:@"reloadAfterRemoving"] ) {
+ [self reloadTableValues:self];
+ } else {
+ for ( i = 0 ; i < [filteredResult count] ; i++ ) {
+ if ( ![tempArray containsObject:[NSNumber numberWithInt:i]] )
+ [tempResult addObject:[filteredResult objectAtIndex:i]];
+ }
+ [filteredResult setArray:tempResult];
+ numRows = [self getNumberOfRows];
+ if ( !areShowingAllRows ) {
+ // queryString = [@"SELECT * FROM " stringByAppendingString:selectedTable];
+ queryString = [NSString stringWithFormat:@"SELECT %@ FROM `%@`", [self fieldListForQuery], selectedTable];
+ if ( sortField ) {
+ // queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@" ORDER BY `%@`", sortField]];
+ queryString = [NSString stringWithFormat:@"%@ ORDER BY `%@`", queryString, sortField];
+ if ( isDesc )
+ queryString = [queryString stringByAppendingString:@" DESC"];
+ }
+ if ( [prefs boolForKey:@"limitRows"] ) {
+ if ( [limitRowsField intValue] <= 0 ) {
+ [limitRowsField setStringValue:@"1"];
+ }
+ queryString = [queryString stringByAppendingString:
+ [NSString stringWithFormat:@" LIMIT %d,%d",
+ [limitRowsField intValue]-1, [prefs integerForKey:@"limitRowsValue"]]];
+ }
+ queryResult = [mySQLConnection queryString:queryString];
+ // [fullResult setArray:[[self fetchResultAsArray:queryResult] retain]];
+ [fullResult setArray:[self fetchResultAsArray:queryResult]];
+ [tableContentView reloadData];
+ [countText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%d rows of %d selected", @"text showing how many rows are in the filtered result"),
+ [filteredResult count], numRows]];
+ } else {
+ [fullResult setArray:filteredResult];
+ [tableContentView reloadData];
+ [countText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%d rows in table", @"text showing how many rows are in the result"), numRows]];
+ }
+ }
+ [tableContentView deselectAll:self];
+ }
+ }
+}
+
+- (int)getNumberOfRows
+/*
+ returns the number of rows in the selected table
+ queries the number from mysql if enabled in prefs and result is limited, otherwise just return the fullResult count
+ */
+{
+ if ( [prefs boolForKey:@"limitRows"] && [prefs boolForKey:@"fetchRowCount"] ) {
+ numRows = [self fetchNumberOfRows];
+ } else {
+ numRows = [fullResult count];
+ }
+ return numRows;
+}
+
+- (int)fetchNumberOfRows
+/*
+ fetches the number of rows in the selected table using a "SELECT COUNT(*)" query and return it
+ */
+{
+ return [[[[mySQLConnection queryString:[NSString stringWithFormat:@"SELECT COUNT(*) FROM `%@`", selectedTable]] fetchRowAsArray] objectAtIndex:0] intValue];
+}
+
+
+//tableView datasource methods
+- (int)numberOfRowsInTableView:(NSTableView *)aTableView
+{
+ return [filteredResult count];
+}
+
+- (id)tableView:(NSTableView *)aTableView
+objectValueForTableColumn:(NSTableColumn *)aTableColumn
+ row:(int)rowIndex
+{
+ id theRow, theValue;
+
+ theRow = [filteredResult objectAtIndex:rowIndex];
+ theValue = [theRow objectForKey:[aTableColumn identifier]];
+
+ if ( [theValue isKindOfClass:[NSData class]] ) {
+ theValue = [[NSString alloc] initWithData:theValue encoding:[mySQLConnection encoding]];
+ //show only first 50 characters to speed up interface (but return everything when this method is used to return the current result)
+ // if ( ([theValue length] > 100) && aTableView ) {
+ }
+
+// if ( ([(NSString *)theValue length] > 100) && aTableView ) {
+// theValue = [NSString stringWithFormat:@"%@(...)", [theValue substringToIndex:100]];
+// }
+
+ return theValue;
+}
+
+- (void)tableView:(NSTableView *)aTableView
+ setObjectValue:(id)anObject
+ forTableColumn:(NSTableColumn *)aTableColumn
+ row:(int)rowIndex
+{
+ if ( !isEditingRow ) {
+ [oldRow setDictionary:[filteredResult objectAtIndex:rowIndex]];
+ isEditingRow = YES;
+ }
+ if ( anObject ) {
+ [[filteredResult objectAtIndex:rowIndex] setObject:anObject forKey:[aTableColumn identifier]];
+ } else {
+ [[filteredResult objectAtIndex:rowIndex] setObject:@"" forKey:[aTableColumn identifier]];
+ }
+}
+
+#pragma mark -
+#pragma mark tableView delegate methods
+
+- (void)tableView:(NSTableView*)tableView didClickTableColumn:(NSTableColumn *)tableColumn
+/*
+ sorts the tableView by the clicked column
+ if clicked twice, order is descending
+ */
+{
+ NSString *queryString;
+ CMMCPResult *queryResult;
+
+ if ( [selectedTable isEqualToString:@""] || !selectedTable )
+ return;
+ if ( ![self selectionShouldChangeInTableView:nil] )
+ return;
+
+ //query started
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:self];
+
+ //sets order descending if a header is clicked twice
+ if ( [[tableColumn identifier] isEqualTo:sortField] ) {
+ if ( isDesc ) {
+ isDesc = NO;
+ } else {
+ isDesc = YES;
+ }
+ } else {
+ isDesc = NO;
+ [tableContentView setIndicatorImage:nil inTableColumn:[tableContentView tableColumnWithIdentifier:sortField]];
+ }
+ sortField = [tableColumn identifier];
+
+ //make queryString and perform query
+ queryString = [NSString stringWithFormat:@"SELECT %@ FROM `%@` ORDER BY `%@`", [self fieldListForQuery],
+ selectedTable, sortField];
+ if ( isDesc )
+ queryString = [queryString stringByAppendingString:@" DESC"];
+ if ( [prefs boolForKey:@"limitRows"] ) {
+ if ( [limitRowsField intValue] <= 0 ) {
+ [limitRowsField setStringValue:@"1"];
+ }
+ queryString = [queryString stringByAppendingString:
+ [NSString stringWithFormat:@" LIMIT %d,%d",
+ [limitRowsField intValue]-1, [prefs integerForKey:@"limitRowsValue"]]];
+ }
+ queryResult = [mySQLConnection queryString:queryString];
+
+ // [fullResult setArray:[[self fetchResultAsArray:queryResult] retain]];
+ [fullResult setArray:[self fetchResultAsArray:queryResult]];
+
+ if ( ![[mySQLConnection getLastErrorMessage] isEqualToString:@""] ) {
+ NSBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
+ [NSString stringWithFormat:NSLocalizedString(@"Couldn't sort table. MySQL said: %@", @"message of panel when sorting of table failed"), [mySQLConnection getLastErrorMessage]]);
+ return;
+ }
+
+ //sets highlight and indicatorImage
+ [tableContentView setHighlightedTableColumn:tableColumn];
+ if ( isDesc ) {
+ [tableContentView setIndicatorImage:[NSImage imageNamed:@"NSDescendingSortIndicator"] inTableColumn:tableColumn];
+ } else {
+ [tableContentView setIndicatorImage:[NSImage imageNamed:@"NSAscendingSortIndicator"] inTableColumn:tableColumn];
+ }
+
+ //query finished
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:self];
+
+ //if filter is activated filters the result, otherwise shows fullResult
+ if ( !areShowingAllRows ) {
+ [self filterTable:self];
+ } else {
+ [filteredResult setArray:fullResult];
+ [tableContentView reloadData];
+ }
+}
+
+- (BOOL)selectionShouldChangeInTableView:(NSTableView *)aTableView
+{
+ /*
+ int row = [tableContentView editedRow];
+ int column = [tableContentView editedColumn];
+ NSTableColumn *tableColumn;
+ NSCell *cell;
+
+ if ( row != -1 ) {
+ tableColumn = [[tableContentView tableColumns] objectAtIndex:column];
+ cell = [tableColumn dataCellForRow:row];
+ [cell endEditing:[tableContentView currentEditor]];
+ }
+ */
+ //end editing (otherwise problems when user hits reload button)
+ [tableWindow endEditingFor:nil];
+
+ return [self addRowToDB];
+}
+
+- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
+{
+ // Check our notification object is our table content view
+ if ([aNotification object] != tableContentView)
+ return;
+
+ if ( [tableContentView numberOfSelectedRows] > 0 ) {
+ [countText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%d of %d rows selected", @"Text showing how many rows are selected"), [tableContentView numberOfSelectedRows], [tableContentView numberOfRows]]];
+ } else {
+ [countText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%d rows", @"Text showing how many rows are in the result"), [tableContentView numberOfRows]]];
+ }
+}
+
+- (void)tableViewSelectionIsChanging:(NSNotification *)aNotification
+{
+ // Check our notification object is our table content view
+ if ([aNotification object] != tableContentView)
+ return;
+
+ if ( [tableContentView numberOfSelectedRows] > 0 ) {
+ [countText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%d of %d rows selected", @"Text showing how many rows are selected"), [tableContentView numberOfSelectedRows], [tableContentView numberOfRows]]];
+ } else {
+ [countText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%d rows", @"Text showing how many rows are in the result"), [tableContentView numberOfRows]]];
+ }
+}
+
+
+- (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 = (NSString *)[tablesListInstance table];
+
+ // get tableColumnWidths object
+ if ( [prefs objectForKey:@"tableColumnWidths"] != nil ) {
+ tableColumnWidths = [NSMutableDictionary dictionaryWithDictionary:[prefs objectForKey:@"tableColumnWidths"]];
+ } else {
+ tableColumnWidths = [NSMutableDictionary dictionary];
+ }
+ // get database object
+ if ( [tableColumnWidths objectForKey:database] == nil ) {
+ [tableColumnWidths setObject:[NSMutableDictionary dictionary] forKey:database];
+ } else {
+ [tableColumnWidths setObject:[[tableColumnWidths objectForKey:database] mutableCopy] 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:[[[tableColumnWidths objectForKey:database] objectForKey:table] mutableCopy] forKey:table];
+ }
+ // save column size
+ [[[tableColumnWidths objectForKey:database] objectForKey:table] setObject:[NSNumber numberWithFloat:[[[aNotification userInfo] objectForKey:@"NSTableColumn"] width]] forKey:[[[aNotification userInfo] objectForKey:@"NSTableColumn"] identifier]];
+ [prefs setObject:tableColumnWidths forKey:@"tableColumnWidths"];
+}
+
+- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
+/*
+ opens sheet if multipleLineEditingButton is clicked or field is a hidden blob or text field
+ */
+{
+ NSEnumerator *enumerator;
+ id type;
+ BOOL blob = NO;
+ NSDictionary *tempRow;
+ NSMutableDictionary *modifiedRow = [NSMutableDictionary dictionary];
+ id key;
+ int code;
+ NSString *query;
+ CMMCPResult *tempResult;
+ id theValue;
+ BOOL columnIsBlob = NO;
+ // int i;
+ // NSArray *columns = [aTableView tableColumns];
+
+ if ( [prefs boolForKey:@"dontShowBlob"] && !isEditingRow ) {
+ //get all row values if dontShowBlob == YES and table contains blob or text field and isEditingRow = NO
+ enumerator = [fieldTypes objectEnumerator];
+ while ( (type = [enumerator nextObject]) ) {
+ if ( [self isBlobOrText:type] ) {
+ blob = YES;
+ }
+ }
+
+ if ( blob ) {
+ query = [NSString stringWithFormat:@"SELECT * FROM `%@` WHERE %@",
+ selectedTable, [self argumentForRow:[tableContentView selectedRow]]];
+ tempResult = [mySQLConnection queryString:query];
+ if ( ![tempResult numOfRows] ) {
+ NSBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, 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;
+ }
+ tempRow = [tempResult fetchRowAsDictionary];
+ enumerator = [tempRow keyEnumerator];
+ while ( key = [enumerator nextObject] ) {
+ if ( [[tempRow objectForKey:key] isMemberOfClass:[NSNull class]] ) {
+ [modifiedRow setObject:[prefs stringForKey:@"nullValue"] forKey:key];
+ } else {
+ [modifiedRow setObject:[tempRow objectForKey:key] forKey:key];
+ }
+ }
+ [filteredResult replaceObjectAtIndex:rowIndex
+ withObject:[NSMutableDictionary dictionaryWithDictionary:modifiedRow]];
+ [tableContentView reloadData];
+ }
+ }
+
+ /*
+ // find the column we're trying to edit
+ for ( i = 0; i < [columns count]; i++ ) {
+ if ( [columns objectAtIndex:i] == aTableColumn ) {
+ // this flag will let us determine if we should "force" multi-line edit.
+ columnIsBlob = [self isBlobOrText:[fieldTypes objectAtIndex:i]];
+ break;
+ }
+ }
+ */
+ //is the column a blob field -> if YES force sheet editing
+ if ( [self isBlobOrText:[fieldTypes objectAtIndex:[fieldNames indexOfObject:[aTableColumn identifier]]]] ) {
+ columnIsBlob = YES;
+ }
+
+ if ( [multipleLineEditingButton state] == NSOnState || columnIsBlob ) {
+ theValue = [[filteredResult objectAtIndex:rowIndex] objectForKey:[aTableColumn identifier]];
+ NSImage *image = nil;
+ editData = [theValue retain];
+
+ if ( [theValue isKindOfClass:[NSData class]] ) {
+ image = [[NSImage alloc] initWithData:theValue];
+ [hexTextView setString:[self dataToHex:theValue]];
+ /*
+ // update displayed font to monospace
+ NSFont *font = [NSFont fontWithName:@"Courier" size:12.0f];
+ NSRange hexRange = { 0, [[hexTextView string] length] - 1 };
+ [hexTextView setFont:font range:hexRange];
+ */
+ theValue = [[NSString alloc] initWithData:theValue encoding:[mySQLConnection encoding]];
+ } else {
+ [hexTextView setString:@""];
+ theValue = [theValue description];
+ }
+
+ [editImage setImage:image];
+ [editTextView setString:theValue];
+ [editTextView setSelectedRange:NSMakeRange(0,[[editTextView string] length])];
+ //different sheets for date (with up/down arrows), number and text
+ [NSApp beginSheet:editSheet
+ modalForWindow:tableWindow modalDelegate:self
+ didEndSelector:nil contextInfo:nil];
+ code = [NSApp runModalForWindow:editSheet];
+
+ [NSApp endSheet:editSheet];
+ [editSheet orderOut:nil];
+
+ if ( code ) {
+ if ( !isEditingRow ) {
+ [oldRow setDictionary:[filteredResult objectAtIndex:rowIndex]];
+ isEditingRow = YES;
+ }
+
+ [[filteredResult objectAtIndex:rowIndex] setObject:[editData copy]
+ forKey:[aTableColumn identifier]];
+
+ // clean up
+ [editImage setImage:nil];
+ [editTextView setString:@""];
+ [hexTextView setString:@""];
+ if ( editData ) {
+ [editData release];
+ }
+ }
+ return NO;
+ } else {
+ return YES;
+ }
+}
+
+- (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows
+ toPasteboard:(NSPasteboard*)pboard
+/*
+ enable drag from tableview
+ */
+{
+ if ( tableView == tableContentView )
+ {
+ NSString *tmp = [tableContentView draggedRowsAsTabString:rows];
+
+ if ( nil != tmp )
+ {
+ [pboard declareTypes:[NSArray arrayWithObjects: NSTabularTextPboardType,
+ NSStringPboardType, nil]
+ owner:nil];
+
+ [pboard setString:tmp forType:NSStringPboardType];
+ [pboard setString:tmp forType:NSTabularTextPboardType];
+ return YES;
+ }
+ }
+ return NO;
+}
+
+#pragma mark -
+
+- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command
+/*
+ traps enter and esc an make/cancel editing without entering next row
+ */
+{
+ int row, column, i;
+
+ row = [tableContentView editedRow];
+ column = [tableContentView editedColumn];
+
+ if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(insertNewline:)] ||
+ [textView methodForSelector:command] == [textView methodForSelector:@selector(insertTab:)] ) //trap enter and tab
+ {
+ //save current line
+ [[control window] makeFirstResponder:control];
+ if ( column == ( [tableContentView numberOfColumns] - 1 ) ) {
+ [self addRowToDB];
+ /*
+ if ( [self addRowToDB] &&
+ ( [textView methodForSelector:command] == [textView methodForSelector:@selector(insertTab:)] ) &&
+ !(sortField && ([prefs boolForKey:@"reloadAfterAdding"] || [prefs boolForKey:@"reloadAfterEditing"])) ) {
+ //get in edit-mode of next row if user hit tab (and result isn't sorted and reloaded)
+ if ( row < ([tableContentView numberOfRows] - 1) ) {
+ [tableContentView selectRow:row+1 byExtendingSelection:NO];
+ [tableContentView editColumn:0 row:row+1 withEvent:nil select:YES];
+ } else {
+ [tableContentView selectRow:0 byExtendingSelection:NO];
+ [tableContentView editColumn:0 row:0 withEvent:nil select:YES];
+ }
+ }
+ */
+ } else {
+ //check if next column is a blob column
+ i = 1;
+ while ( [self isBlobOrText:[fieldTypes objectAtIndex:[fieldNames indexOfObject:[[[tableContentView tableColumns] objectAtIndex:column+i] identifier]]]] ) {
+ i++;
+ if ( (column+i) >= [tableContentView numberOfColumns] ) {
+ //there is no other column after the blob column
+ [self addRowToDB];
+ return TRUE;
+ }
+ }
+ //edit the column after the blob column
+ [tableContentView editColumn:column+i row:row withEvent:nil select:YES];
+ }
+ return TRUE;
+ }
+ else if ( [[control window] methodForSelector:command] == [[control window] methodForSelector:@selector(_cancelKey:)] ||
+ [textView methodForSelector:command] == [textView methodForSelector:@selector(complete:)] ) //trap esc
+ {
+ //abort editing
+ [control abortEditing];
+ if ( isEditingRow && !isEditingNewRow ) {
+ isEditingRow = NO;
+ [filteredResult replaceObjectAtIndex:row withObject:[NSMutableDictionary dictionaryWithDictionary:oldRow]];
+ } else if ( isEditingNewRow ) {
+ isEditingRow = NO;
+ isEditingNewRow = NO;
+ [filteredResult removeObjectAtIndex:row];
+ [tableContentView reloadData];
+ }
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+
+//textView delegate methods
+- (BOOL)textView:(NSTextView *)aTextView doCommandBySelector:(SEL)aSelector
+/*
+ traps enter and return key and closes editSheet instead of inserting a linebreak when user hits return
+ */
+{
+ if ( aTextView == editTextView ) {
+ if ( [aTextView methodForSelector:aSelector] == [aTextView methodForSelector:@selector(insertNewline:)] &&
+ [[[NSApp currentEvent] characters] isEqualToString:@"\003"] )
+ {
+ [NSApp stopModalWithCode:1];
+ return YES;
+ } else {
+ return NO;
+ }
+ }
+ return NO;
+}
+
+
+//last but not least
+
+- (void)dealloc
+{
+ // NSLog(@"TableContent dealloc");
+
+ [editData release];
+ [fullResult release];
+ [filteredResult release];
+ [keys release];
+ [oldRow release];
+ [fieldNames release];
+ [fieldTypes release];
+ [compareType release];
+ [sortField release];
+ [prefs release];
+
+ [super dealloc];
+}
+
+@end