diff options
author | rowanbeentje <rowan@beent.je> | 2009-07-18 16:02:04 +0000 |
---|---|---|
committer | rowanbeentje <rowan@beent.je> | 2009-07-18 16:02:04 +0000 |
commit | 11e10321f97577204b74f84b528029490e64ef47 (patch) | |
tree | 624f2897d1937f02996471b681a485d6358a8535 | |
parent | 4aa27ac121818e97bb70f28943d11590226a64ce (diff) | |
download | sequelpro-11e10321f97577204b74f84b528029490e64ef47.tar.gz sequelpro-11e10321f97577204b74f84b528029490e64ef47.tar.bz2 sequelpro-11e10321f97577204b74f84b528029490e64ef47.zip |
Add the ability to navigate between tables via foreign key relationships, addressing the first part of #209:
- For the first column linked by each foreign key, display a link arrow within the table cell
- When clicking on the link arrow, select the reference table and set the table filters to select the clicked value
- Also uses the table cell subclass to allow the entire cell to be editable, not just the contained text (addresses #250)
-rw-r--r-- | Resources/Images/link-arrow-clicked.png | bin | 0 -> 287 bytes | |||
-rw-r--r-- | Resources/Images/link-arrow-highlighted.png | bin | 0 -> 249 bytes | |||
-rw-r--r-- | Resources/Images/link-arrow.png | bin | 0 -> 287 bytes | |||
-rw-r--r-- | Source/CustomQuery.m | 3 | ||||
-rw-r--r-- | Source/SPTextAndLinkCell.h | 52 | ||||
-rw-r--r-- | Source/SPTextAndLinkCell.m | 202 | ||||
-rw-r--r-- | Source/TableContent.h | 6 | ||||
-rw-r--r-- | Source/TableContent.m | 169 | ||||
-rw-r--r-- | Source/TablesList.h | 1 | ||||
-rw-r--r-- | Source/TablesList.m | 24 | ||||
-rw-r--r-- | sequel-pro.xcodeproj/project.pbxproj | 18 |
11 files changed, 419 insertions, 56 deletions
diff --git a/Resources/Images/link-arrow-clicked.png b/Resources/Images/link-arrow-clicked.png Binary files differnew file mode 100644 index 00000000..541f9b04 --- /dev/null +++ b/Resources/Images/link-arrow-clicked.png diff --git a/Resources/Images/link-arrow-highlighted.png b/Resources/Images/link-arrow-highlighted.png Binary files differnew file mode 100644 index 00000000..5ce39b58 --- /dev/null +++ b/Resources/Images/link-arrow-highlighted.png diff --git a/Resources/Images/link-arrow.png b/Resources/Images/link-arrow.png Binary files differnew file mode 100644 index 00000000..3b5759c4 --- /dev/null +++ b/Resources/Images/link-arrow.png diff --git a/Source/CustomQuery.m b/Source/CustomQuery.m index dde53df3..2457f932 100644 --- a/Source/CustomQuery.m +++ b/Source/CustomQuery.m @@ -35,6 +35,7 @@ #import "TablesList.h" #import "RegexKitLite.h" #import "SPFieldEditorController.h" +#import "SPTextAndLinkCell.h" #define SP_MYSQL_DEV_SEARCH_URL @"http://search.mysql.com/search?q=%@&site=refman-%@" #define SP_HELP_SEARCH_IN_MYSQL 0 @@ -668,7 +669,7 @@ theCol = [[NSTableColumn alloc] initWithIdentifier:[NSArrayObjectAtIndex(cqColumnDefinition,i) objectForKey:@"datacolumnindex"]]; [theCol setResizingMask:NSTableColumnUserResizingMask]; [theCol setEditable:YES]; - NSTextFieldCell *dataCell = [[[NSTextFieldCell alloc] initTextCell:@""] autorelease]; + SPTextAndLinkCell *dataCell = [[[SPTextAndLinkCell alloc] initTextCell:@""] autorelease]; [dataCell setEditable:YES]; [dataCell setFormatter:[[SPDataCellFormatter new] autorelease]]; if ( [prefs boolForKey:@"UseMonospacedFonts"] ) { diff --git a/Source/SPTextAndLinkCell.h b/Source/SPTextAndLinkCell.h new file mode 100644 index 00000000..48d6bb18 --- /dev/null +++ b/Source/SPTextAndLinkCell.h @@ -0,0 +1,52 @@ +// +// $Id: SPTextAndLinkCell.h 866 2009-06-15 16:05:54Z bibiko $ +// +// SPTextAndLinkCell.h +// sequel-pro +// +// Created by Rowan Beentje on 16/07/2009. +// +// 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 <Cocoa/Cocoa.h> + +enum sptextandlinkcell_drawstates +{ + SP_LINKDRAWSTATE_NORMAL = 0, + SP_LINKDRAWSTATE_HIGHLIGHT = 1, + SP_LINKDRAWSTATE_BACKGROUNDHIGHLIGHT = 2 +}; + + +@interface SPTextAndLinkCell : NSTextFieldCell { + BOOL hasLink; + + NSButtonCell *linkButton; + id linkTarget; + SEL linkAction; + + NSRect linkRect; + int lastLinkColumn; + int lastLinkRow; + int drawState; +} + +- (void) setTarget:(id)theTarget action:(SEL)theAction; +- (int) getClickedColumn; +- (int) getClickedRow; + +@end diff --git a/Source/SPTextAndLinkCell.m b/Source/SPTextAndLinkCell.m new file mode 100644 index 00000000..2a57c8b3 --- /dev/null +++ b/Source/SPTextAndLinkCell.m @@ -0,0 +1,202 @@ +// +// $Id: SPTextAndLinkCell.m 866 2009-06-15 16:05:54Z bibiko $ +// +// SPTextAndLinkCell.m +// sequel-pro +// +// Created by Rowan Beentje on 16/07/2009. +// With thanks to Brian Dunagan ( http://www.bdunagan.com/ ) for original approach +// +// 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 "SPTextAndLinkCell.h" + + +@implementation SPTextAndLinkCell + +#pragma mark - +#pragma mark Setup and teardown + +/** + * Initialise + */ +- (id) initWithCoder:(NSCoder *)coder +{ + self = [super initWithCoder:coder]; + if (self) { + hasLink = NO; + linkButton = nil; + linkTarget = nil; + drawState = SP_LINKDRAWSTATE_NORMAL; + + lastLinkColumn = NSNotFound; + lastLinkRow = NSNotFound; + } + return self; +} + +/** + * Deallocate + */ +- (void) dealloc +{ + if (linkButton) [linkButton release]; + + [super dealloc]; +} + +/** + * Encodes using a given receiver. + */ +- (void) encodeWithCoder:(NSCoder *)coder +{ + [super encodeWithCoder:coder]; +} + +/** + * Returns a new instance which is a copy of the receiver + */ +- (id) copyWithZone:(NSZone *)zone { + SPTextAndLinkCell *copy = [super copyWithZone:zone]; + if (linkButton) copy->linkButton = [linkButton copyWithZone:zone]; + return copy; +} + + +#pragma mark - +#pragma mark Enabling link functionality + +/** + * Set the link target and action - this also enables the link + * arrow within the cell. + */ +- (void) setTarget:(id)theTarget action:(SEL)theAction +{ + linkTarget = theTarget; + linkAction = theAction; + + if (!hasLink) { + hasLink = YES; + + linkButton = [[NSButtonCell alloc] init]; + [linkButton setButtonType:NSMomentaryChangeButton]; + [linkButton setImagePosition:NSImageRight]; + [linkButton setTitle:@""]; + [linkButton setBordered:NO]; + [linkButton setShowsBorderOnlyWhileMouseInside:YES]; + [linkButton setImage:[NSImage imageNamed:@"link-arrow"]]; + } +} + +#pragma mark - +#pragma mark Drawing and interaction + +/** + * Redraw the table cell, altering super draw behavior to leave space + * for the link if necessary. + */ +- (void)drawInteriorWithFrame:(NSRect)aRect inView:(NSView *)controlView +{ + + // Fast case for no arrow + if (!hasLink) { + [super drawInteriorWithFrame:aRect inView:controlView]; + return; + } + + // Set up new rects + NSRect textRect = NSMakeRect(aRect.origin.x, aRect.origin.y, aRect.size.width - 18, aRect.size.height); + linkRect = NSMakeRect(aRect.origin.x + aRect.size.width - 15, aRect.origin.y - 1, 12, aRect.size.height); + + // Draw the text + [super drawInteriorWithFrame:textRect inView:controlView]; + + // Get the new link state + int newDrawState = ([self isHighlighted])? + ((([(NSTableView *)[self controlView] editedColumn] != -1 + || [[[self controlView] window] firstResponder] == [self controlView]) + && [[[self controlView] window] isKeyWindow])?SP_LINKDRAWSTATE_HIGHLIGHT:SP_LINKDRAWSTATE_BACKGROUNDHIGHLIGHT): + SP_LINKDRAWSTATE_NORMAL; + + // Update the link arrow style if the state has changed + if (drawState != newDrawState) { + drawState = newDrawState; + switch (drawState) { + case SP_LINKDRAWSTATE_NORMAL: + [linkButton setImage:[NSImage imageNamed:@"link-arrow"]]; + break; + case SP_LINKDRAWSTATE_HIGHLIGHT: + [linkButton setImage:[NSImage imageNamed:@"link-arrow-highlighted"]]; + break; + case SP_LINKDRAWSTATE_BACKGROUNDHIGHLIGHT: + [linkButton setImage:[NSImage imageNamed:@"link-arrow-clicked"]]; + break; + } + } + + [linkButton drawInteriorWithFrame:linkRect inView:controlView]; +} + +/** + * Allow hit tracking for link functionality + */ +- (NSUInteger) hitTestForEvent:(NSEvent *)event inRect:(NSRect)cellFrame ofView:(NSView *)controlView +{ + + // Fast case for no link - make entire cell editable click area + if (!hasLink) return NSCellHitContentArea | NSCellHitEditableTextArea; + + NSPoint p = [[[NSApp mainWindow] contentView] convertPoint:[event locationInWindow] toView:controlView]; + + // Hit the link if it falls within the link rectangle for this cell, set when drawing + if (p.x > linkRect.origin.x && p.x < (linkRect.origin.x + linkRect.size.width)) { + + // Capture the clicked row and cell + NSTableView *tableView = (NSTableView *)[self controlView]; + p = [[[NSApp mainWindow] contentView] convertPoint:[event locationInWindow] toView:tableView]; + lastLinkColumn = [tableView columnAtPoint:p]; + lastLinkRow = [tableView rowAtPoint:p]; + + [linkTarget performSelector:linkAction withObject:self]; + return NSCellHitContentArea; + + // Otherwise return an editable hit - this allows the entire cell to be clicked to edit the contents. + } else { + return NSCellHitContentArea | NSCellHitEditableTextArea; + } +} + +#pragma mark - +#pragma mark Information getters + +/** + * Retrieve the last column that recorded a click with the link cell + */ +- (int) getClickedColumn +{ + return lastLinkColumn; +} + +/** + * Retrieve the last row that recorded a click with the link cell + */ +- (int) getClickedRow +{ + return lastLinkRow; +} + +@end diff --git a/Source/TableContent.h b/Source/TableContent.h index e2bc5730..3053db1b 100644 --- a/Source/TableContent.h +++ b/Source/TableContent.h @@ -28,7 +28,7 @@ #import <Cocoa/Cocoa.h> #import <MCPKit_bundled/MCPKit_bundled.h> -@class CMMCPConnection, CMMCPResult, CMCopyTable; +@class CMMCPConnection, CMMCPResult, CMCopyTable, SPTextAndLinkCell; @interface TableContent : NSObject { @@ -55,8 +55,9 @@ CMMCPConnection *mySQLConnection; NSString *selectedTable, *usedQuery; - NSMutableArray *fullResult, *filteredResult, *keys, *oldRow; + NSMutableArray *fullResult, *filteredResult, *dataColumns, *keys, *oldRow; NSString *compareType, *lastField; + NSString *targetFilterColumn, *targetFilterValue; NSNumber *sortCol; BOOL isEditingRow, isEditingNewRow, isSavingRow, isDesc, setLimit; NSUserDefaults *prefs; @@ -87,6 +88,7 @@ //additional methods - (void)setConnection:(CMMCPConnection *)theConnection; +- (void)clickLinkArrow:(SPTextAndLinkCell *)theArrowCell; - (IBAction)setCompareTypes:(id)sender; - (IBAction)stepLimitRows:(id)sender; - (NSArray *)fetchResultAsArray:(CMMCPResult *)theResult; diff --git a/Source/TableContent.m b/Source/TableContent.m index e5dc4141..d308fd6a 100644 --- a/Source/TableContent.m +++ b/Source/TableContent.m @@ -39,6 +39,8 @@ #import "SPArrayAdditions.h" #import "SPTextViewAdditions.h" #import "SPDataAdditions.h" +#import "SPTextAndLinkCell.h" +#import "QLPreviewPanel.h" #import "SPFieldEditorController.h" @@ -53,6 +55,7 @@ fullResult = [[NSMutableArray alloc] init]; filteredResult = [[NSMutableArray alloc] init]; + dataColumns = [[NSMutableArray alloc] init]; oldRow = [[NSMutableArray alloc] init]; selectedTable = nil; @@ -60,6 +63,8 @@ lastField = nil; // editData = nil; keys = nil; + targetFilterColumn = nil; + targetFilterValue = nil; areShowingAllRows = false; currentlyEditingRow = -1; @@ -86,7 +91,7 @@ { int i; NSNumber *colWidth, *savedSortCol = nil; - NSArray *theColumns, *columnNames; + NSArray *columnNames; NSDictionary *columnDefinition; NSTableColumn *theCol; NSString *query; @@ -101,7 +106,7 @@ // Store the newly selected table name selectedTable = aTable; - + // Reset table key store for use in argumentForRow: if (keys) [keys release], keys = nil; @@ -110,12 +115,13 @@ [tableContentView scrollColumnToVisible:0]; // Remove existing columns from the table - theColumns = [tableContentView tableColumns]; - - while ([theColumns count]) { - [tableContentView removeTableColumn:NSArrayObjectAtIndex(theColumns, 0)]; + while ([[tableContentView tableColumns] count]) { + [tableContentView removeTableColumn:NSArrayObjectAtIndex([tableContentView tableColumns], 0)]; } + // Reset data column store + [dataColumns removeAllObjects]; + // If no table has been supplied, reset the view to a blank table and disabled elements. // [tableDataInstance tableEncoding] == nil indicates that an error occured while retrieving table data if ( [[[tableDataInstance statusValues] objectForKey:@"Rows"] isKindOfClass:[NSNull class]] || [aTable isEqualToString:@""] || !aTable || [tableDataInstance tableEncoding] == nil) @@ -158,8 +164,25 @@ // 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. - theColumns = [tableDataInstance columns]; + [dataColumns addObjectsFromArray:[tableDataInstance columns]]; columnNames = [tableDataInstance columnNames]; + + // Retrieve the constraints, and loop through them to add up to one foreign key to each column + NSArray *constraints = [tableDataInstance getConstraints]; + for (NSDictionary *constraint in constraints) { + NSString *firstColumn = [[[constraint objectForKey:@"columns"] componentsSeparatedByString:@","] objectAtIndex:0]; + NSString *firstRefColumn = [[[constraint objectForKey:@"ref_columns"] componentsSeparatedByString:@","] objectAtIndex:0]; + int 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]; + } + } // Retrieve the total number of rows of the current table // to adjustify "Limit From:" @@ -172,8 +195,8 @@ NSString *nullValue = [prefs objectForKey:@"NullValue"]; // Add the new columns to the table - for ( i = 0 ; i < [theColumns count] ; i++ ) { - columnDefinition = NSArrayObjectAtIndex(theColumns, i); + for ( i = 0 ; i < [dataColumns count] ; i++ ) { + columnDefinition = NSArrayObjectAtIndex(dataColumns, i); // Set up the column theCol = [[NSTableColumn alloc] initWithIdentifier:[columnDefinition objectForKey:@"datacolumnindex"]]; @@ -181,7 +204,7 @@ [theCol setEditable:YES]; // Set up the data cell depending on the column type - NSComboBoxCell *dataCell; + id dataCell; if ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"enum"]) { dataCell = [[[NSComboBoxCell alloc] initTextCell:@""] autorelease]; [dataCell setButtonBordered:NO]; @@ -193,14 +216,21 @@ 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 = [[[NSTextFieldCell alloc] initTextCell:@""] autorelease]; + 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]]; + //[dataCell setFormatter:[[SPDataCellFormatter new] autorelease]]; // Set field length limit if field is a varchar to match varchar length if ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"string"]) { @@ -254,7 +284,7 @@ } // Preserve the stored filter settings if appropriate - if (preserveCurrentView && [fieldField isEnabled]) { + if (!targetFilterColumn && preserveCurrentView && [fieldField isEnabled]) { preservedFilterField = [NSString stringWithString:[[fieldField selectedItem] title]]; preservedFilterComparison = [NSString stringWithString:[[compareField selectedItem] title]]; preservedFilterValue = [NSString stringWithString:[argumentField stringValue]]; @@ -273,18 +303,30 @@ [argumentField setStringValue:@""]; [filterButton setEnabled:YES]; - // Restore preserved filter settings if appropriate and valid - if (preserveCurrentView && preservedFilterField != nil && [fieldField itemWithTitle:preservedFilterField]) { - [fieldField selectItemWithTitle:preservedFilterField]; + // Select the specified target filter settings if set + if (targetFilterColumn) { + [fieldField selectItemWithTitle:targetFilterColumn]; [self setCompareTypes:self]; - } - - if (preserveCurrentView && preservedFilterField != nil - && [fieldField itemWithTitle:preservedFilterField] - && [compareField itemWithTitle:preservedFilterComparison]) { - [compareField selectItemWithTitle:preservedFilterComparison]; - [argumentField setStringValue:preservedFilterValue]; + if ([targetFilterValue isEqualToString:[prefs objectForKey:@"NullValue"]]) { + [compareField selectItemWithTitle:@"IS NULL"]; + } else { + [compareField selectItemAtIndex:0]; // "=", "IS", etc + [argumentField setStringValue:targetFilterValue]; + } areShowingAllRows = NO; + targetFilterColumn = nil; + targetFilterValue = nil; + + // Otherwise, restore preserved filter settings if appropriate and valid + } else if (preserveCurrentView && preservedFilterField != nil && [fieldField itemWithTitle:preservedFilterField]) { + [fieldField selectItemWithTitle:preservedFilterField]; + [self setCompareTypes:self]; + + if ([fieldField itemWithTitle:preservedFilterField] && [compareField itemWithTitle:preservedFilterComparison]) { + [compareField selectItemWithTitle:preservedFilterComparison]; + [argumentField setStringValue:preservedFilterValue]; + areShowingAllRows = NO; + } } // Enable or disable the limit fields according to preference setting @@ -317,7 +359,7 @@ // 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 backtickQuotedString]]; if ( sortCol ) { - query = [NSString stringWithFormat:@"%@ ORDER BY %@", query, [[[[tableDataInstance columns] objectAtIndex:[sortCol intValue]] objectForKey:@"name"] backtickQuotedString]]; + query = [NSString stringWithFormat:@"%@ ORDER BY %@", query, [[[dataColumns objectAtIndex:[sortCol intValue]] objectForKey:@"name"] backtickQuotedString]]; if ( isDesc ) query = [query stringByAppendingString:@" DESC"]; } @@ -362,7 +404,7 @@ [tableContentView reloadData]; // Init copyTable with necessary information for copying selected rows as SQL INSERT - [tableContentView setTableInstance:self withTableData:filteredResult withColumns:theColumns withTableName:selectedTable withConnection:mySQLConnection]; + [tableContentView setTableInstance:self withTableData:filteredResult withColumns:dataColumns withTableName:selectedTable withConnection:mySQLConnection]; // Post the notification that the query is finished [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:self]; @@ -418,7 +460,7 @@ // queryString = [@"SELECT * FROM " stringByAppendingString:selectedTable]; queryString = [NSString stringWithFormat:@"SELECT %@ FROM %@", [self fieldListForQuery], [selectedTable backtickQuotedString]]; if ( sortCol ) { - queryString = [NSString stringWithFormat:@"%@ ORDER BY %@", queryString, [[[[tableDataInstance columns] objectAtIndex:[sortCol intValue]] objectForKey:@"name"] backtickQuotedString]]; + queryString = [NSString stringWithFormat:@"%@ ORDER BY %@", queryString, [[[dataColumns objectAtIndex:[sortCol intValue]] objectForKey:@"name"] backtickQuotedString]]; // queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@" ORDER BY %@", [sortField backtickQuotedString]]]; if ( isDesc ) queryString = [queryString stringByAppendingString:@" DESC"]; @@ -637,7 +679,7 @@ // Add sorting details if appropriate if ( sortCol ) { - queryString = [NSString stringWithFormat:@"%@ ORDER BY %@", queryString, [[[[tableDataInstance columns] objectAtIndex:[sortCol intValue]] objectForKey:@"name"] backtickQuotedString]]; + queryString = [NSString stringWithFormat:@"%@ ORDER BY %@", queryString, [[[dataColumns objectAtIndex:[sortCol intValue]] objectForKey:@"name"] backtickQuotedString]]; if ( isDesc ) queryString = [queryString stringByAppendingString:@" DESC"]; } @@ -722,7 +764,6 @@ */ - (IBAction)addRow:(id)sender { - NSArray *columns; NSMutableDictionary *column; NSMutableArray *newRow = [NSMutableArray array]; int i; @@ -730,9 +771,8 @@ // Check whether a save of the current row is required. if ( ![self saveRowOnDeselect] ) return; - columns = [[NSArray alloc] initWithArray:[tableDataInstance columns]]; - for ( i = 0 ; i < [columns count] ; i++ ) { - column = NSArrayObjectAtIndex(columns, i); + for ( i = 0 ; i < [dataColumns count] ; i++ ) { + column = NSArrayObjectAtIndex(dataColumns, i); if ([column objectForKey:@"default"] == nil || [[column objectForKey:@"default"] isEqualToString:@"NULL"]) { [newRow addObject:[prefs stringForKey:@"NullValue"]]; } else { @@ -740,7 +780,6 @@ } } [filteredResult addObject:newRow]; - [columns release]; [tableContentView reloadData]; [tableContentView selectRow:[tableContentView numberOfRows]-1 byExtendingSelection:NO]; @@ -983,6 +1022,35 @@ } /** + * Performs the requested action - switching to another table + * with the appropriate filter settings - when a link arrow is + * selected. + */ +- (void)clickLinkArrow:(SPTextAndLinkCell *)theArrowCell +{ + if ([theArrowCell getClickedColumn] == NSNotFound || [theArrowCell getClickedRow] == NSNotFound) return; + int dataColumnIndex = [[[[tableContentView tableColumns] objectAtIndex:[theArrowCell getClickedColumn]] identifier] intValue]; + + // Ensure the clicked cell has foreign key details available + NSDictionary *refDictionary = [[dataColumns objectAtIndex:dataColumnIndex] objectForKey:@"foreignkeyreference"]; + if (!refDictionary) return; + + // Check whether a save of the current row is required. + if ( ![self saveRowOnDeselect] ) return; + + // Store the filter details to use when next loading the table + targetFilterColumn = [refDictionary objectForKey:@"column"]; + targetFilterValue = [[filteredResult objectAtIndex:[theArrowCell getClickedRow]] objectAtIndex:dataColumnIndex]; + + // Attempt to switch to the new table + if (![tablesListInstance selectTableOrViewWithName:[refDictionary objectForKey:@"table"]]) { + NSBeep(); + targetFilterColumn = nil; + targetFilterValue = nil; + } +} + +/** * Sets the compare types for the filter and the appropriate formatter for the textField */ - (IBAction)setCompareTypes:(id)sender @@ -1074,7 +1142,6 @@ */ - (NSArray *)fetchResultAsArray:(CMMCPResult *)theResult { - NSArray *columns; unsigned long numOfRows = [theResult numOfRows]; NSMutableArray *tempResult = [NSMutableArray arrayWithCapacity:numOfRows]; @@ -1086,12 +1153,11 @@ id prefsNullValue = [prefs objectForKey:@"NullValue"]; BOOL prefsLoadBlobsAsNeeded = [prefs boolForKey:@"LoadBlobsAsNeeded"]; - columns = [tableDataInstance columns]; - long columnsCount = [columns count]; + long columnsCount = [dataColumns count]; // Build up an array of which columns are blobs for faster iteration for ( i = 0; i < columnsCount ; i++ ) { - [columnBlobStatuses addObject:[NSNumber numberWithBool:[tableDataInstance columnIsBlobOrText:[NSArrayObjectAtIndex(columns, i) objectForKey:@"name"] ]]]; + [columnBlobStatuses addObject:[NSNumber numberWithBool:[tableDataInstance columnIsBlobOrText:[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"name"] ]]]; } if (numOfRows) [theResult dataSeek:0]; @@ -1130,7 +1196,7 @@ */ - (BOOL)addRowToDB { - NSArray *theColumns, *columnNames; + NSArray *columnNames; NSMutableString *queryString; NSString *query; CMMCPResult *queryResult; @@ -1155,7 +1221,6 @@ // 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. - theColumns = [tableDataInstance columns]; columnNames = [tableDataInstance columnNames]; NSMutableArray *fieldValues = [[NSMutableArray alloc] init]; @@ -1182,9 +1247,9 @@ } else { if ( [[rowObject description] isEqualToString:@"CURRENT_TIMESTAMP"] ) { [rowValue setString:@"CURRENT_TIMESTAMP"]; - } else if ([[NSArrayObjectAtIndex(theColumns, i) objectForKey:@"typegrouping"] isEqualToString:@"bit"]) { + } else if ([[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"typegrouping"] isEqualToString:@"bit"]) { [rowValue setString:((![[rowObject description] length] || [[rowObject description] isEqualToString:@"0"])?@"0":@"1")]; - } else if ([[NSArrayObjectAtIndex(theColumns, i) objectForKey:@"typegrouping"] isEqualToString:@"date"] + } else if ([[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"typegrouping"] isEqualToString:@"date"] && [[rowObject description] isEqualToString:@"NOW()"]) { [rowValue setString:@"NOW()"]; } else { @@ -1244,8 +1309,8 @@ } else { // Set the insertId for fields with auto_increment - for ( i = 0; i < [theColumns count] ; i++ ) { - if ([[NSArrayObjectAtIndex(theColumns, i) objectForKey:@"autoincrement"] intValue]) { + for ( i = 0; i < [dataColumns count] ; i++ ) { + if ([[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"autoincrement"] intValue]) { [[filteredResult objectAtIndex:currentlyEditingRow] replaceObjectAtIndex:i withObject:[[NSNumber numberWithLong:[mySQLConnection insertId]] description]]; } } @@ -1264,7 +1329,7 @@ } else { query = [NSString stringWithFormat:@"SELECT %@ FROM %@", [self fieldListForQuery], [selectedTable backtickQuotedString]]; if ( sortCol ) { - query = [NSString stringWithFormat:@"%@ ORDER BY %@", query, [[[[tableDataInstance columns] objectAtIndex:[sortCol intValue]] objectForKey:@"name"] backtickQuotedString]]; + query = [NSString stringWithFormat:@"%@ ORDER BY %@", query, [[[dataColumns objectAtIndex:[sortCol intValue]] objectForKey:@"name"] backtickQuotedString]]; if ( isDesc ) query = [query stringByAppendingString:@" DESC"]; } @@ -1437,10 +1502,9 @@ - (BOOL)tableContainsBlobOrTextColumns { int i; - NSArray *tableColumns = [tableDataInstance columns]; - for ( i = 0 ; i < [tableColumns count]; i++ ) { - if ( [tableDataInstance columnIsBlobOrText:[NSArrayObjectAtIndex(tableColumns, i) objectForKey:@"name"]] ) { + for ( i = 0 ; i < [dataColumns count]; i++ ) { + if ( [tableDataInstance columnIsBlobOrText:[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"name"]] ) { return YES; } } @@ -1456,12 +1520,11 @@ { int i; NSMutableArray *fields = [NSMutableArray array]; - NSArray *columns = [tableDataInstance columns]; NSArray *columnNames = [tableDataInstance columnNames]; if ( [prefs boolForKey:@"LoadBlobsAsNeeded"] ) { for ( i = 0 ; i < [columnNames count] ; i++ ) { - if (![tableDataInstance columnIsBlobOrText:[NSArrayObjectAtIndex(columns, i) objectForKey:@"name"]] ) { + if (![tableDataInstance columnIsBlobOrText:[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"name"]] ) { [fields addObject:[NSArrayObjectAtIndex(columnNames, i) backtickQuotedString]]; } else { @@ -1576,7 +1639,7 @@ queryString = [NSString stringWithFormat:@"SELECT %@ FROM %@", [self fieldListForQuery], [selectedTable backtickQuotedString]]; if ( sortCol ) { // queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@" ORDER BY %@", [sortField backtickQuotedString]]]; - queryString = [NSString stringWithFormat:@"%@ ORDER BY %@", queryString, [[[[tableDataInstance columns] objectAtIndex:[sortCol intValue]] objectForKey:@"name"] backtickQuotedString]]; + queryString = [NSString stringWithFormat:@"%@ ORDER BY %@", queryString, [[[dataColumns objectAtIndex:[sortCol intValue]] objectForKey:@"name"] backtickQuotedString]]; if ( isDesc ) queryString = [queryString stringByAppendingString:@" DESC"]; } @@ -1680,14 +1743,13 @@ objectValueForTableColumn:(NSTableColumn *)aTableColumn // that don't support this selector if ([cell respondsToSelector:@selector(setTextColor:)]) { - NSArray *columns = [tableDataInstance columns]; NSString *columnTypeGrouping; NSUInteger indexOfColumn; indexOfColumn = [[aTableColumn identifier] intValue]; // Test if the current column is a text or a blob field - columnTypeGrouping = [[columns objectAtIndex:indexOfColumn] objectForKey:@"typegrouping"]; + columnTypeGrouping = [[dataColumns objectAtIndex:indexOfColumn] objectForKey:@"typegrouping"]; if ([columnTypeGrouping isEqualToString:@"textdata"] || [columnTypeGrouping isEqualToString:@"blobdata"]) { // now check if the field has been loaded already or not @@ -1766,11 +1828,11 @@ objectValueForTableColumn:(NSTableColumn *)aTableColumn // Save the sort field name for use when refreshing the table if (lastField) [lastField release]; - lastField = [[NSString alloc] initWithString:[[[tableDataInstance columns] objectAtIndex:[[tableColumn identifier] intValue]] objectForKey:@"name"]]; + lastField = [[NSString alloc] initWithString:[[dataColumns objectAtIndex:[[tableColumn identifier] intValue]] objectForKey:@"name"]]; //make queryString and perform query queryString = [NSString stringWithFormat:@"SELECT %@ FROM %@ ORDER BY %@", [self fieldListForQuery], - [selectedTable backtickQuotedString], [[[[tableDataInstance columns] objectAtIndex:[sortCol intValue]] objectForKey:@"name"] backtickQuotedString]]; + [selectedTable backtickQuotedString], [[[dataColumns objectAtIndex:[sortCol intValue]] objectForKey:@"name"] backtickQuotedString]]; if ( isDesc ) queryString = [queryString stringByAppendingString:@" DESC"]; if ( [prefs boolForKey:@"LimitResults"] ) { @@ -2096,6 +2158,7 @@ objectValueForTableColumn:(NSTableColumn *)aTableColumn { [fullResult release]; [filteredResult release]; + [dataColumns release]; [oldRow release]; // if (editData) [editData release]; if (keys) [keys release]; diff --git a/Source/TablesList.h b/Source/TablesList.h index 965f00be..5b6214ea 100644 --- a/Source/TablesList.h +++ b/Source/TablesList.h @@ -123,5 +123,6 @@ enum sp_table_types // Setters - (void)setContentRequiresReload:(BOOL)reload; - (void)setStatusRequiresReload:(BOOL)reload; +- (BOOL)selectTableOrViewWithName:(NSString *)theName; @end diff --git a/Source/TablesList.m b/Source/TablesList.m index 49b97008..bba94272 100644 --- a/Source/TablesList.m +++ b/Source/TablesList.m @@ -1051,6 +1051,30 @@ statusLoaded = !reload; } +/** + * Select a table or view using the provided name; returns YES if the + * supplied name could be selected, or NO if not. + */ +- (BOOL)selectTableOrViewWithName:(NSString *)theName +{ + int i, tableType, itemIndex = NSNotFound; + + // Loop through the tables/views to find the desired item + for (i = 0; i < [tables count]; i++) { + tableType = [[tableTypes objectAtIndex:i] intValue]; + if (tableType != SP_TABLETYPE_TABLE && tableType != SP_TABLETYPE_VIEW) continue; + if ([[tables objectAtIndex:i] isEqualToString:theName]) { + itemIndex = i; + break; + } + } + + if (itemIndex == NSNotFound) return NO; + + [tablesListView selectRowIndexes:[NSIndexSet indexSetWithIndex:itemIndex] byExtendingSelection:NO]; + return YES; +} + #pragma mark Datasource methods /** diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index 08a8e134..b160cb33 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -99,6 +99,10 @@ 58CDB3400FCE13EF00F8ACA3 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5EAC0FC0EC87FF900CC579C /* Security.framework */; }; 58CDB3410FCE141900F8ACA3 /* TunnelPassphraseRequester.m in Sources */ = {isa = PBXBuildFile; fileRef = 58CDB3310FCE139C00F8ACA3 /* TunnelPassphraseRequester.m */; }; 58CDB3420FCE142500F8ACA3 /* KeyChain.m in Sources */ = {isa = PBXBuildFile; fileRef = 17E641740EF01F80001BC333 /* KeyChain.m */; }; + 58D2E229101222670063EF1D /* SPTextAndLinkCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 58D2E227101222670063EF1D /* SPTextAndLinkCell.m */; }; + 58D2E22E101222870063EF1D /* link-arrow-clicked.png in Resources */ = {isa = PBXBuildFile; fileRef = 58D2E22B101222870063EF1D /* link-arrow-clicked.png */; }; + 58D2E22F101222870063EF1D /* link-arrow-highlighted.png in Resources */ = {isa = PBXBuildFile; fileRef = 58D2E22C101222870063EF1D /* link-arrow-highlighted.png */; }; + 58D2E230101222870063EF1D /* link-arrow.png in Resources */ = {isa = PBXBuildFile; fileRef = 58D2E22D101222870063EF1D /* link-arrow.png */; }; 58FEF16D0F23D66600518E8E /* SPSQLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 58FEF16C0F23D66600518E8E /* SPSQLParser.m */; }; 58FEF57E0F3B4E9700518E8E /* SPTableData.m in Sources */ = {isa = PBXBuildFile; fileRef = 58FEF57D0F3B4E9700518E8E /* SPTableData.m */; }; 8D15AC340486D014006FF6A4 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A7FEA54F5311CA2CBB /* Cocoa.framework */; }; @@ -387,6 +391,11 @@ 58CDB32F0FCE138D00F8ACA3 /* SPSSHTunnel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPSSHTunnel.m; sourceTree = "<group>"; }; 58CDB3310FCE139C00F8ACA3 /* TunnelPassphraseRequester.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TunnelPassphraseRequester.m; sourceTree = "<group>"; }; 58CDB3360FCE13C900F8ACA3 /* TunnelPassphraseRequester */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = TunnelPassphraseRequester; sourceTree = BUILT_PRODUCTS_DIR; }; + 58D2E227101222670063EF1D /* SPTextAndLinkCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTextAndLinkCell.m; sourceTree = "<group>"; }; + 58D2E228101222670063EF1D /* SPTextAndLinkCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTextAndLinkCell.h; sourceTree = "<group>"; }; + 58D2E22B101222870063EF1D /* link-arrow-clicked.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "link-arrow-clicked.png"; sourceTree = "<group>"; }; + 58D2E22C101222870063EF1D /* link-arrow-highlighted.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "link-arrow-highlighted.png"; sourceTree = "<group>"; }; + 58D2E22D101222870063EF1D /* link-arrow.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "link-arrow.png"; sourceTree = "<group>"; }; 58FEF16B0F23D66600518E8E /* SPSQLParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSQLParser.h; sourceTree = "<group>"; }; 58FEF16C0F23D66600518E8E /* SPSQLParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPSQLParser.m; sourceTree = "<group>"; }; 58FEF57C0F3B4E9700518E8E /* SPTableData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableData.h; sourceTree = "<group>"; }; @@ -756,6 +765,8 @@ 5841423E0F97E11000A34B47 /* NoodleLineNumberView.m */, BC1847E80FE6EC8400094BFB /* SPEditSheetTextView.h */, BC1847E90FE6EC8400094BFB /* SPEditSheetTextView.m */, + 58D2E227101222670063EF1D /* SPTextAndLinkCell.m */, + 58D2E228101222670063EF1D /* SPTextAndLinkCell.h */, ); name = GUI; sourceTree = "<group>"; @@ -802,6 +813,9 @@ 17E6419D0EF02036001BC333 /* grabber-horizontal.png */, 17E6419E0EF02036001BC333 /* grabber-vertical.png */, 17E6419F0EF02036001BC333 /* hideconsole.tiff */, + 58D2E22D101222870063EF1D /* link-arrow.png */, + 58D2E22B101222870063EF1D /* link-arrow-clicked.png */, + 58D2E22C101222870063EF1D /* link-arrow-highlighted.png */, 17E641A20EF02036001BC333 /* logo-48.png */, 17E641AE0EF02036001BC333 /* selectall.tiff */, 17E641AF0EF02036001BC333 /* selectnone.tiff */, @@ -1164,6 +1178,9 @@ 5822CAE110011C8000DCC3D6 /* ConnectionView.xib in Resources */, BC1E55C4100DC92200AAE9F0 /* table-view-small-square.tiff in Resources */, BCA6F631100FA7D700E80253 /* FieldEditorSheet.xib in Resources */, + 58D2E22E101222870063EF1D /* link-arrow-clicked.png in Resources */, + 58D2E22F101222870063EF1D /* link-arrow-highlighted.png in Resources */, + 58D2E230101222870063EF1D /* link-arrow.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1274,6 +1291,7 @@ 5822C9B51000DB2400DCC3D6 /* SPConnectionController.m in Sources */, BC8C8532100E0A8000D7A129 /* SPTableView.m in Sources */, BC9F0881100FCF2C00A80D32 /* SPFieldEditorController.m in Sources */, + 58D2E229101222670063EF1D /* SPTextAndLinkCell.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; |