aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrowanbeentje <rowan@beent.je>2009-07-18 16:02:04 +0000
committerrowanbeentje <rowan@beent.je>2009-07-18 16:02:04 +0000
commit11e10321f97577204b74f84b528029490e64ef47 (patch)
tree624f2897d1937f02996471b681a485d6358a8535
parent4aa27ac121818e97bb70f28943d11590226a64ce (diff)
downloadsequelpro-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.pngbin0 -> 287 bytes
-rw-r--r--Resources/Images/link-arrow-highlighted.pngbin0 -> 249 bytes
-rw-r--r--Resources/Images/link-arrow.pngbin0 -> 287 bytes
-rw-r--r--Source/CustomQuery.m3
-rw-r--r--Source/SPTextAndLinkCell.h52
-rw-r--r--Source/SPTextAndLinkCell.m202
-rw-r--r--Source/TableContent.h6
-rw-r--r--Source/TableContent.m169
-rw-r--r--Source/TablesList.h1
-rw-r--r--Source/TablesList.m24
-rw-r--r--sequel-pro.xcodeproj/project.pbxproj18
11 files changed, 419 insertions, 56 deletions
diff --git a/Resources/Images/link-arrow-clicked.png b/Resources/Images/link-arrow-clicked.png
new file mode 100644
index 00000000..541f9b04
--- /dev/null
+++ b/Resources/Images/link-arrow-clicked.png
Binary files differ
diff --git a/Resources/Images/link-arrow-highlighted.png b/Resources/Images/link-arrow-highlighted.png
new file mode 100644
index 00000000..5ce39b58
--- /dev/null
+++ b/Resources/Images/link-arrow-highlighted.png
Binary files differ
diff --git a/Resources/Images/link-arrow.png b/Resources/Images/link-arrow.png
new file mode 100644
index 00000000..3b5759c4
--- /dev/null
+++ b/Resources/Images/link-arrow.png
Binary files differ
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;
};