)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
{
int originalRow;
int destinationRow;
NSMutableDictionary *draggedRow;
if ( aTableView == queryFavoritesView ) {
originalRow = [[[info draggingPasteboard] stringForType:@"SequelProPasteboard"] intValue];
destinationRow = row;
if ( destinationRow > originalRow )
destinationRow--;
draggedRow = [queryFavorites objectAtIndex:originalRow];
[queryFavorites removeObjectAtIndex:originalRow];
[queryFavorites insertObject:draggedRow atIndex:destinationRow];
[queryFavoritesView reloadData];
[queryFavoritesView selectRowIndexes:[NSIndexSet indexSetWithIndex:destinationRow] byExtendingSelection:NO];
return YES;
} else {
return NO;
}
}*/
#pragma mark -
#pragma mark TableView delegate methods
/**
* Show the table cell content as tooltip
* - for text displays line breaks and tabs as well
* - if blob data can be interpret as image data display the image as transparent thumbnail
* (up to now using base64 encoded HTML data)
*/
- (NSString *)tableView:(NSTableView *)aTableView toolTipForCell:(SPTextAndLinkCell *)aCell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)row mouseLocation:(NSPoint)mouseLocation
{
if([[aCell stringValue] length] < 2 || [tableDocumentInstance isWorking]) return nil;
NSImage *image;
NSPoint pos = [NSEvent mouseLocation];
pos.y -= 20;
// Try to get the original data. If not possible return nil.
// @try clause is used due to the multifarious cases of
// possible exceptions (eg for reloading tables etc.)
id theValue;
@try{
theValue = SPDataStorageObjectAtRowAndColumn(resultData, row, [[aTableColumn identifier] integerValue]);
}
@catch(id ae) {
return nil;
}
// Get the original data for trying to display the blob data as an image
if ([theValue isKindOfClass:[NSData class]]) {
image = [[[NSImage alloc] initWithData:theValue] autorelease];
if(image) {
[SPTooltip showWithObject:image atLocation:pos ofType:@"image"];
return nil;
}
}
// Show the cell string value as tooltip (including line breaks and tabs)
// by using the cell's font
[SPTooltip showWithObject:[aCell stringValue]
atLocation:pos
ofType:@"text"
displayOptions:[NSDictionary dictionaryWithObjectsAndKeys:
[[aCell font] familyName], @"fontname",
[NSString stringWithFormat:@"%f",[[aCell font] pointSize]], @"fontsize",
nil]];
return nil;
}
/*
* Double-click action on a field
*/
- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
// Only allow editing if a task is not active
if ([tableDocumentInstance isWorking]) return NO;
// Check if the field can identified bijectively
if ( aTableView == customQueryView ) {
NSDictionary *columnDefinition;
BOOL noTableName = NO;
BOOL isFieldEditable;
BOOL isBlob;
NSInteger numberOfPossibleUpdateRows = -1;
// Retrieve the column defintion
for(id c in cqColumnDefinition) {
if([[c objectForKey:@"datacolumnindex"] isEqualToNumber:[aTableColumn identifier]]) {
columnDefinition = [NSDictionary dictionaryWithDictionary:c];
break;
}
}
// Check if current field is a blob
if([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"textdata"]
|| [[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"blobdata"])
isBlob = YES;
else
isBlob = NO;
// Resolve the original table name for current column if AS was used
NSString *tableForColumn = [columnDefinition objectForKey:@"org_table"];
// Get the database name which the field belongs to
NSString *dbForColumn = [columnDefinition objectForKey:@"db"];
// No table/database name found indicates that the field's column contains data from more than one table as for UNION
// or the field data are not bound to any table as in SELECT 1 or if column database is unset
if(!tableForColumn || ![tableForColumn length] || ![dbForColumn length])
noTableName = YES;
if(!noTableName) {
// if table and database name are given check if field can be identified unambiguously
fieldIDQueryString = [self argumentForRow:rowIndex ofTable:tableForColumn andDatabase:[columnDefinition objectForKey:@"db"]];
// Actual check whether field can be identified bijectively
numberOfPossibleUpdateRows = [[[[mySQLConnection queryString:[NSString stringWithFormat:@"SELECT COUNT(1) FROM %@.%@ %@", [[columnDefinition objectForKey:@"db"] backtickQuotedString], [tableForColumn backtickQuotedString], fieldIDQueryString]] fetchRowAsArray] objectAtIndex:0] integerValue];
isFieldEditable = (numberOfPossibleUpdateRows == 1) ? YES : NO;
if(!isFieldEditable)
if(numberOfPossibleUpdateRows == 0)
[errorText setStringValue:[NSString stringWithFormat:@"Field is not editable. No matching record found. Try to add the primary key field or more fields in your SELECT statement for table '%@' to identify field origin unambiguously.", tableForColumn]];
else
[errorText setStringValue:[NSString stringWithFormat:@"Field is not editable. Couldn't identify field origin unambiguously (%ld match%@).", (long)numberOfPossibleUpdateRows, (numberOfPossibleUpdateRows>1)?@"es":@""]];
} else {
// no table/databse name are given
isFieldEditable = NO;
fieldIDQueryString = nil;
[errorText setStringValue:NSLocalizedString(@"Field is not editable. Field has no or multiple table or database origin(s).",@"field is not editable due to no table/database")];
}
SPFieldEditorController *fieldEditor = [[SPFieldEditorController alloc] init];
// Remember edited row for reselecting and setting the scroll view after reload
editedRow = rowIndex;
editedScrollViewRect = [customQueryScrollView documentVisibleRect];
// Set max text length
if ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"string"]
&& [columnDefinition valueForKey:@"char_length"])
[fieldEditor setTextMaxLength:[[columnDefinition valueForKey:@"char_length"] integerValue]];
id originalData = [resultData cellDataAtRow:rowIndex column:[[aTableColumn identifier] integerValue]];
if ([originalData isNSNull]) originalData = [prefs objectForKey:SPNullValue];
id editData = [[fieldEditor editWithObject:originalData
fieldName:[columnDefinition objectForKey:@"name"]
usingEncoding:[mySQLConnection encoding]
isObjectBlob:isBlob
isEditable:isFieldEditable
withWindow:tableWindow] retain];
if ( editData )
[self tableView:aTableView setObjectValue:[editData copy] forTableColumn:aTableColumn row:rowIndex];
[fieldEditor release];
if ( editData ) [editData release];
return NO;
} else {
return YES;
}
}
/**
* Prevent the selection of rows while the table is still loading
*/
- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex
{
return tableRowsSelectable;
}
#pragma mark -
#pragma mark TableView notifications
/**
* Saves the new column size in the preferences for columns which map to fields
*/
- (void)tableViewColumnDidResize:(NSNotification *)aNotification
{
// Abort if still loading the table
if (![cqColumnDefinition count]) return;
// Retrieve the original index of the column from the identifier
NSInteger columnIndex = [[[[aNotification userInfo] objectForKey:@"NSTableColumn"] identifier] integerValue];
NSDictionary *columnDefinition = NSArrayObjectAtIndex(cqColumnDefinition, columnIndex);
// Don't save if the column doesn't map to an underlying SQL field
if (![columnDefinition objectForKey:@"org_name"] || ![(NSString *)[columnDefinition objectForKey:@"org_name"] length])
return;
NSMutableDictionary *tableColumnWidths;
NSString *host_db = [NSString stringWithFormat:@"%@@%@", [columnDefinition objectForKey:@"db"], [tableDocumentInstance host]];
NSString *table = [columnDefinition objectForKey:@"org_table"];
NSString *col = [columnDefinition objectForKey:@"org_name"];
// Retrieve or instantiate the tableColumnWidths object
if ([prefs objectForKey:SPTableColumnWidths] != nil) {
tableColumnWidths = [NSMutableDictionary dictionaryWithDictionary:[prefs objectForKey:SPTableColumnWidths]];
} else {
tableColumnWidths = [NSMutableDictionary dictionary];
}
// Edit or create database object
if ([tableColumnWidths objectForKey:host_db] == nil) {
[tableColumnWidths setObject:[NSMutableDictionary dictionary] forKey:host_db];
} else {
[tableColumnWidths setObject:[NSMutableDictionary dictionaryWithDictionary:[tableColumnWidths objectForKey:host_db]] forKey:host_db];
}
// Edit or create table object
if ([[tableColumnWidths objectForKey:host_db] objectForKey:table] == nil) {
[[tableColumnWidths objectForKey:host_db] setObject:[NSMutableDictionary dictionary] forKey:table];
} else {
[[tableColumnWidths objectForKey:host_db] setObject:[NSMutableDictionary dictionaryWithDictionary:[[tableColumnWidths objectForKey:host_db] objectForKey:table]] forKey:table];
}
// Save the column size
[[[tableColumnWidths objectForKey:host_db] objectForKey:table] setObject:[NSNumber numberWithDouble:[(NSTableColumn *)[[aNotification userInfo] objectForKey:@"NSTableColumn"] width]] forKey:col];
[prefs setObject:tableColumnWidths forKey:SPTableColumnWidths];
}
#pragma mark -
#pragma mark TextView delegate methods
/*
* Traps enter key and performs query instead of inserting a line break if aTextView == textView
* closes valueSheet if aTextView == valueTextField
*/
- (BOOL)textView:(NSTextView *)aTextView doCommandBySelector:(SEL)aSelector
{
if ( aTextView == textView ) {
if ( [aTextView methodForSelector:aSelector] == [aTextView methodForSelector:@selector(insertNewline:)] &&
[[[NSApp currentEvent] characters] isEqualToString:@"\003"] )
{
[self runAllQueries:self];
return YES;
} else {
return NO;
}
} else if ( aTextView == valueTextField ) {
if ( [aTextView methodForSelector:aSelector] == [aTextView methodForSelector:@selector(insertNewline:)] )
{
[self closeSheet:self];
return YES;
} else {
return NO;
}
}
return NO;
}
#pragma mark -
#pragma mark TextView notifications
- (NSRange)textView:(NSTextView *)aTextView willChangeSelectionFromCharacterRange:(NSRange)oldSelectedCharRange toCharacterRange:(NSRange)newSelectedCharRange
{
// Check if snippet session is still valid
if(!newSelectedCharRange.length && [textView isSnippetMode]) [textView checkForCaretInsideSnippet];
return newSelectedCharRange;
}
/*
* A notification posted when the selection changes within the text view;
* used to control the run-currentrun-selection button state and action.
*/
- (void)textViewDidChangeSelection:(NSNotification *)aNotification
{
// Ensure that the notification is from the custom query text view
if ( [aNotification object] != textView ) return;
BOOL isLookBehind = YES;
NSRange currentSelection = [textView selectedRange];
NSUInteger caretPosition = currentSelection.location;
NSRange qRange = [self queryRangeAtPosition:caretPosition lookBehind:&isLookBehind];
if(qRange.length)
currentQueryRange = qRange;
else
currentQueryRange = NSMakeRange(0, 0);
[textView setQueryRange:qRange];
[textView setNeedsDisplay:YES];
// disable "Comment Current Query" menu item if no current query is selectable
[commentCurrentQueryMenuItem setEnabled:(currentQueryRange.length) ? YES : NO];
// If no text is selected, disable the button and action menu.
if ( caretPosition == NSNotFound ) {
selectionButtonCanBeEnabled = NO;
[runSelectionButton setEnabled:NO];
[runSelectionMenuItem setEnabled:NO];
return;
}
// If the current selection is a single caret position, update the button based on
// whether the caret is inside a valid query.
if (!currentSelection.length) {
[runSelectionButton setTitle:NSLocalizedString(@"Run Current", @"Title of button to run current query in custom query view")];
[runSelectionMenuItem setTitle:NSLocalizedString(@"Run Current Query", @"Title of action menu item to run current query in custom query view")];
// If a valid query is present at the cursor position, enable the button
if (qRange.length) {
if (isLookBehind) {
[runSelectionButton setTitle:NSLocalizedString(@"Run Previous", @"Title of button to run query just before text caret in custom query view")];
[runSelectionMenuItem setTitle:NSLocalizedString(@"Run Previous Query", @"Title of action menu item to run query just before text caret in custom query view")];
}
selectionButtonCanBeEnabled = YES;
if (![tableDocumentInstance isWorking]) {
[runSelectionButton setEnabled:YES];
[runSelectionMenuItem setEnabled:YES];
}
} else {
selectionButtonCanBeEnabled = NO;
[runSelectionButton setEnabled:NO];
[runSelectionMenuItem setEnabled:NO];
}
[commentLineOrSelectionMenuItem setTitle:NSLocalizedString(@"Comment Line", @"Title of action menu item to comment line")];
// For selection ranges, enable the button.
} else {
selectionButtonCanBeEnabled = YES;
[runSelectionButton setTitle:NSLocalizedString(@"Run Selection", @"Title of button to run selected text in custom query view")];
[runSelectionMenuItem setTitle:NSLocalizedString(@"Run Selected Text", @"Title of action menu item to run selected text in custom query view")];
[commentLineOrSelectionMenuItem setTitle:NSLocalizedString(@"Comment Selection", @"Title of action menu item to comment selection")];
if (![tableDocumentInstance isWorking]) {
[runSelectionButton setEnabled:YES];
[runSelectionMenuItem setEnabled:YES];
}
}
if(!historyItemWasJustInserted)
currentHistoryOffsetIndex = -1;
}
#pragma mark -
#pragma mark TextField delegate methods
/**
* Called whenever the user changes the name of the new query favorite or
* the user changed the query favorite search string.
*/
- (void)controlTextDidChange:(NSNotification *)notification
{
if ([notification object] == queryFavoriteNameTextField)
[saveQueryFavoriteButton setEnabled:[[queryFavoriteNameTextField stringValue] length]];
else if ([notification object] == queryFavoritesSearchField){
[self filterQueryFavorites:nil];
}
else if ([notification object] == queryHistorySearchField) {
[self filterQueryHistory:nil];
}
}
#pragma mark -
#pragma mark SplitView delegate methods
/*
* Tells the splitView that it can collapse views
*/
- (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview
{
return YES;
}
/*
* Defines max position of splitView
*/
- (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset
{
if ( offset == 0 ) {
return proposedMax - 100;
} else {
return proposedMax - 73;
}
}
/*
* Defines min position of splitView
*/
- (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset
{
if ( offset == 0 ) {
return proposedMin + 100;
} else {
return proposedMin + 100;
}
}
#pragma mark -
#pragma mark MySQL Help
/*
* Set the MySQL version as X.Y for Help window title and online search
*/
- (void)setMySQLversion:(NSString *)theVersion
{
mySQLversion = [[theVersion substringToIndex:3] retain];
[textView setConnection:mySQLConnection withVersion:[[[mySQLversion componentsSeparatedByString:@"."] objectAtIndex:0] integerValue]];
}
/*
* Return the Help window.
*/
- (NSWindow *)helpWebViewWindow
{
return helpWebViewWindow;
}
/*
* Show the data for "HELP 'searchString'".
*/
- (void)showHelpFor:(NSString *)searchString addToHistory:(BOOL)addToHistory calledByAutoHelp:(BOOL)autoHelp
{
if(![searchString length]) return;
NSString *helpString = [self getHTMLformattedMySQLHelpFor:searchString calledByAutoHelp:autoHelp];
if(autoHelp && [helpString isEqualToString:SP_HELP_NOT_AVAILABLE]) {
[helpWebViewWindow orderOut:nil];
return;
}
// Order out resp. init the Help window if not visible
if(![helpWebViewWindow isVisible])
{
// set title of the Help window
[helpWebViewWindow setTitle:[NSString stringWithFormat:@"%@ (%@ %@)", NSLocalizedString(@"MySQL Help", @"mysql help"), NSLocalizedString(@"version", @"version"), mySQLversion]];
// init goback/forward buttons
if([[helpWebView backForwardList] backListCount] < 1)
{
[helpNavigator setEnabled:NO forSegment:SP_HELP_GOBACK_BUTTON];
[helpNavigator setEnabled:NO forSegment:SP_HELP_GOFORWARD_BUTTON];
} else {
[helpNavigator setEnabled:[[helpWebView backForwardList] backListCount] forSegment:SP_HELP_GOBACK_BUTTON];
[helpNavigator setEnabled:[[helpWebView backForwardList] forwardListCount] forSegment:SP_HELP_GOFORWARD_BUTTON];
}
// set default to search in MySQL help
helpTarget = SP_HELP_SEARCH_IN_MYSQL;
[helpTargetSelector setSelectedSegment:SP_HELP_SEARCH_IN_MYSQL];
[self helpTargetValidation];
// order out Help window if Help is available
if(![helpString isEqualToString:SP_HELP_NOT_AVAILABLE])
[helpWebViewWindow orderFront:helpWebView];
}
// close Help window if no Help available
if([helpString isEqualToString:SP_HELP_NOT_AVAILABLE])
[helpWebViewWindow close];
if(![helpString length]) return;
// add searchString to history list
if(addToHistory)
{
WebHistoryItem *aWebHistoryItem = [[WebHistoryItem alloc] initWithURLString:[NSString stringWithFormat:@"applewebdata://%@", searchString] title:searchString lastVisitedTimeInterval:[[NSDate date] timeIntervalSinceDate:[NSDate distantFuture]]];
[[helpWebView backForwardList] addItem:aWebHistoryItem];
[aWebHistoryItem release];
}
// validate goback/forward buttons
[helpNavigator setEnabled:[[helpWebView backForwardList] backListCount] forSegment:SP_HELP_GOBACK_BUTTON];
[helpNavigator setEnabled:[[helpWebView backForwardList] forwardListCount] forSegment:SP_HELP_GOFORWARD_BUTTON];
// load HTML formatted help into the webview
[[helpWebView mainFrame] loadHTMLString:helpString baseURL:nil];
}
/*
* Show the data for "HELP 'search word'" according to helpTarget
*/
- (IBAction)showHelpForSearchString:(id)sender
{
NSString *searchString = [[helpSearchField stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
switch(helpTarget)
{
case SP_HELP_SEARCH_IN_PAGE:
if(![helpWebView searchFor:searchString direction:YES caseSensitive:NO wrap:YES])
if([searchString length]) NSBeep();
break;
case SP_HELP_SEARCH_IN_WEB:
if(![searchString length])
break;
[self openMySQLonlineDocumentationWithString:searchString];
break;
case SP_HELP_SEARCH_IN_MYSQL:
[self showHelpFor:searchString addToHistory:YES calledByAutoHelp:NO];
break;
}
}
/*
* Show the Help for the selected text in the webview
*/
- (IBAction)showHelpForWebViewSelection:(id)sender
{
[self showHelpFor:[[helpWebView selectedDOMRange] text] addToHistory:YES calledByAutoHelp:NO];
}
/*
* Show MySQL's online documentation for the selected text in the webview
*/
- (IBAction)searchInDocForWebViewSelection:(id)sender
{
NSString *searchString = [[[helpWebView selectedDOMRange] text] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if(![searchString length])
{
NSBeep();
return;
}
[self openMySQLonlineDocumentationWithString:searchString];
}
/*
* Show the data for "HELP 'currentWord'"
*/
- (IBAction)showHelpForCurrentWord:(id)sender
{
NSString *searchString = [[sender string] substringWithRange:[sender getRangeForCurrentWord]];
[self showHelpFor:searchString addToHistory:YES calledByAutoHelp:NO];
}
/*
* Find Next/Previous in current page
*/
- (IBAction)helpSearchFindNextInPage:(id)sender
{
if(helpTarget == SP_HELP_SEARCH_IN_PAGE)
if(![helpWebView searchFor:[[helpSearchField stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] direction:YES caseSensitive:NO wrap:YES])
NSBeep();
}
- (IBAction)helpSearchFindPreviousInPage:(id)sender
{
if(helpTarget == SP_HELP_SEARCH_IN_PAGE)
if(![helpWebView searchFor:[[helpSearchField stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] direction:NO caseSensitive:NO wrap:YES])
NSBeep();
}
/*
* Navigation for back/TOC/forward
*/
- (IBAction)helpSegmentDispatcher:(id)sender
{
switch([helpNavigator selectedSegment])
{
case SP_HELP_GOBACK_BUTTON:
[helpWebView goBack];
break;
case SP_HELP_SHOW_TOC_BUTTON:
[self showHelpFor:SP_HELP_TOC_SEARCH_STRING addToHistory:YES calledByAutoHelp:NO];
break;
case SP_HELP_GOFORWARD_BUTTON:
[helpWebView goForward];
break;
}
// validate goback and goforward buttons according history
[helpNavigator setEnabled:[[helpWebView backForwardList] backListCount] forSegment:SP_HELP_GOBACK_BUTTON];
[helpNavigator setEnabled:[[helpWebView backForwardList] forwardListCount] forSegment:SP_HELP_GOFORWARD_BUTTON];
}
/*
* Set helpTarget according user choice via mouse and keyboard short-cuts.
*/
- (IBAction)helpSelectHelpTargetMySQL:(id)sender
{
helpTarget = SP_HELP_SEARCH_IN_MYSQL;
[helpTargetSelector setSelectedSegment:SP_HELP_SEARCH_IN_MYSQL];
[self helpTargetValidation];
}
- (IBAction)helpSelectHelpTargetPage:(id)sender
{
helpTarget = SP_HELP_SEARCH_IN_PAGE;
[helpTargetSelector setSelectedSegment:SP_HELP_SEARCH_IN_PAGE];
[self helpTargetValidation];
}
- (IBAction)helpSelectHelpTargetWeb:(id)sender
{
helpTarget = SP_HELP_SEARCH_IN_WEB;
[helpTargetSelector setSelectedSegment:SP_HELP_SEARCH_IN_WEB];
[self helpTargetValidation];
}
- (IBAction)helpTargetDispatcher:(id)sender
{
helpTarget = [helpTargetSelector selectedSegment];
[self helpTargetValidation];
}
- (IBAction)showCompletionList:(id)sender
{
NSRange insertRange = NSMakeRange([textView selectedRange].location, 0);
switch([sender tag]) {
case 8000:
[textView showCompletionListFor:@"$SP_ASLIST_ALL_DATABASES" atRange:insertRange fuzzySearch:NO];
break;
case 8001:
[textView showCompletionListFor:@"$SP_ASLIST_ALL_TABLES" atRange:insertRange fuzzySearch:NO];
break;
case 8002:
[textView showCompletionListFor:@"$SP_ASLIST_ALL_FIELDS" atRange:insertRange fuzzySearch:NO];
break;
}
}
/*
* Show the data for "HELP 'currentWord' invoked by autohelp"
*/
- (void)showAutoHelpForCurrentWord:(id)sender
{
NSString *searchString = [[sender string] substringWithRange:[sender getRangeForCurrentWord]];
[self showHelpFor:searchString addToHistory:YES calledByAutoHelp:YES];
}
/*
* Control the help search field behaviour.
*/
- (void)helpTargetValidation
{
switch(helpTarget)
{
case SP_HELP_SEARCH_IN_PAGE:
case SP_HELP_SEARCH_IN_WEB:
[helpSearchFieldCell setSendsWholeSearchString:YES];
break;
case SP_HELP_SEARCH_IN_MYSQL:
[helpSearchFieldCell setSendsWholeSearchString:NO];
break;
}
}
- (void)openMySQLonlineDocumentationWithString:(NSString *)searchString
{
NSString *version = nil;
if([[mySQLversion stringByReplacingOccurrencesOfString:@"." withString:@""] integerValue] < 42)
version = @"41";
else
version = [mySQLversion stringByReplacingOccurrencesOfString:@"." withString:@""];
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:
[[NSString stringWithFormat:
SP_MYSQL_DEV_SEARCH_URL,
searchString,
version]
stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]]];
}
/*
* Return the help string HTML formatted from executing "HELP 'searchString'".
* If more than one help topic was found return a link list.
*/
- (NSString *)getHTMLformattedMySQLHelpFor:(NSString *)searchString calledByAutoHelp:(BOOL)autoHelp
{
if(![searchString length]) return @"";
NSRange aRange;
MCPResult *theResult = nil;
NSDictionary *tableDetails;
NSMutableString *theHelp = [NSMutableString string];
[theHelp setString:@""];
// search via: HELP 'searchString'
theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"HELP '%@'", [searchString stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"]]];
if (![[mySQLConnection getLastErrorMessage] isEqualToString:@""])
{
// if an error or HELP is not supported fall back to online search but
// don't open it if autoHelp is enabled
if(!autoHelp)
[self openMySQLonlineDocumentationWithString:searchString];
[helpWebViewWindow close];
return SP_HELP_NOT_AVAILABLE;
}
// nothing found?
if(![theResult numOfRows]) {
// try to search via: HELP 'searchString%'
theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"HELP '%@%%'", [searchString stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"]]];
// really nothing found?
if(![theResult numOfRows])
return @"";
}
tableDetails = [[NSDictionary alloc] initWithDictionary:[theResult fetchRowAsDictionary]];
if ([tableDetails objectForKey:@"description"]) { // one single help topic found
if ([tableDetails objectForKey:@"name"]) {
[theHelp appendString:@""];
}
if ([tableDetails objectForKey:@"description"]) {
NSMutableString *desc = [NSMutableString string];
NSError *err1 = NULL;
NSString *aUrl;
[desc setString:[[[tableDetails objectForKey:@"description"] copy] autorelease]];
//[desc replaceOccurrencesOfString:[searchString uppercaseString] withString:[NSString stringWithFormat:@"%@", [searchString uppercaseString]] options:NSLiteralSearch range:NSMakeRange(0,[desc length])];
// detect and generate http links
aRange = NSMakeRange(0,0);
NSInteger safeCnt = 0; // safety counter - not more than 200 loops allowed
while(1){
aRange = [desc rangeOfRegex:@"\\s((https?|ftp|file)://.*?html)" options:RKLNoOptions inRange:NSMakeRange(aRange.location+aRange.length, [desc length]-aRange.location-aRange.length) capture:1 error:&err1];
if(aRange.location != NSNotFound) {
aUrl = [desc substringWithRange:aRange];
[desc replaceCharactersInRange:aRange withString:[NSString stringWithFormat:@"%@", aUrl, aUrl]];
}
else
break;
safeCnt++;
if(safeCnt > 200)
break;
}
// detect and generate mysql links for "[HELP keyword]"
aRange = NSMakeRange(0,0);
safeCnt = 0;
while(1){
// TODO how to catch in HELP 'grant' last see [HELP SHOW GRANTS] ?? it's ridiculous
aRange = [desc rangeOfRegex:@"\\[HELP ([^ ]*?)\\]" options:RKLNoOptions inRange:NSMakeRange(aRange.location+aRange.length+53, [desc length]-53-aRange.location-aRange.length) capture:1 error:&err1];
if(aRange.location != NSNotFound) {
aUrl = [[desc substringWithRange:aRange] stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
[desc replaceCharactersInRange:aRange withString:[NSString stringWithFormat:@"%@", NSLocalizedString(@"Show MySQL help for", @"show mysql help for"), aUrl, aUrl, aUrl]];
}
else
break;
safeCnt++;
if(safeCnt > 200)
break;
}
// detect and generate mysql links for capitalzed letters
// aRange = NSMakeRange(0,0);
// safeCnt = 0;
// while(1){
// aRange = [desc rangeOfRegex:@"(?%@", NSLocalizedString(@"Show MySQL help for", @"show mysql help for"), aUrl, aUrl, aUrl]];
// }
// else
// break;
// safeCnt++;
// if(safeCnt > 200)
// break;
// }
[theHelp appendString:@""];
[theHelp appendString:desc];
[theHelp appendString:@"
"];
}
// are examples available?
if([tableDetails objectForKey:@"example"]){
NSString *examples = [[[tableDetails objectForKey:@"example"] copy] autorelease];
if([examples length]){
[theHelp appendString:@"
Example:
"];
[theHelp appendString:examples];
[theHelp appendString:@"
"];
}
}
} else { // list all found topics
NSInteger i;
NSInteger r = [theResult numOfRows];
if (r) [theResult dataSeek:0];
// check if HELP 'contents' is called
if(![searchString isEqualToString:SP_HELP_TOC_SEARCH_STRING])
[theHelp appendString:[NSString stringWithFormat:@"
%@ “%@”
", NSLocalizedString(@"Help topics for", @"help topics for"), searchString]];
else
[theHelp appendString:[NSString stringWithFormat:@"
%@:
", NSLocalizedString(@"MySQL Help – Categories", @"mysql help categories"), searchString]];
// iterate through all found rows and print them as HTML ul/li list
[theHelp appendString:@""];
for ( i = 0 ; i < r ; i++ ) {
NSArray *anArray = [theResult fetchRowAsArray];
NSString *topic = [anArray objectAtIndex:[anArray count]-2];
[theHelp appendString:
[NSString stringWithFormat:@"- %@
", NSLocalizedString(@"Show MySQL help for", @"show mysql help for"), topic, topic, topic]];
}
[theHelp appendString:@"
"];
}
[tableDetails release];
return [NSString stringWithFormat:helpHTMLTemplate, theHelp];
}
//////////////////////////////
// WebView delegate methods //
//////////////////////////////
/*
* Link detector: If user clicked at an http link open it in the default browser,
* otherwise search for it in the MySQL help. Additionally handle back/forward events from
* keyboard and context menu.
*/
- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id)listener
{
NSInteger navigationType = [[actionInformation objectForKey:WebActionNavigationTypeKey] integerValue];
if([[[request URL] scheme] isEqualToString:@"applewebdata"] && navigationType == WebNavigationTypeLinkClicked){
[self showHelpFor:[[[request URL] path] lastPathComponent] addToHistory:YES calledByAutoHelp:NO];
[listener ignore];
} else {
if (navigationType == WebNavigationTypeOther) {
// catch reload event
// if([[[actionInformation objectForKey:WebActionOriginalURLKey] absoluteString] isEqualToString:@"about:blank"])
// [listener use];
// else
[listener use];
} else if (navigationType == WebNavigationTypeLinkClicked) {
// show http in browser
[[NSWorkspace sharedWorkspace] openURL:[actionInformation objectForKey:WebActionOriginalURLKey]];
[listener ignore];
} else if (navigationType == WebNavigationTypeBackForward) {
// catch back/forward events from contextual menu
[self showHelpFor:[[[[actionInformation objectForKey:WebActionOriginalURLKey] absoluteString] lastPathComponent] stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding] addToHistory:NO calledByAutoHelp:NO];
[listener ignore];
} else if (navigationType == WebNavigationTypeReload) {
// just in case
[listener ignore];
} else {
// Ignore WebNavigationTypeFormSubmitted, WebNavigationTypeFormResubmitted.
[listener ignore];
}
}
}
/*
* Manage contextual menu in helpWebView
* Ignore "Reload", "Open Link", "Open Link in new Window", "Download link" etc.
*/
- (NSArray *)webView:(WebView *)sender contextMenuItemsForElement:(NSDictionary *)element defaultMenuItems:(NSArray *)defaultMenuItems
{
NSMutableArray *webViewMenuItems = [[defaultMenuItems mutableCopy] autorelease];
if (webViewMenuItems)
{
// Remove all needless default menu items
NSEnumerator *itemEnumerator = [defaultMenuItems objectEnumerator];
NSMenuItem *menuItem = nil;
while (menuItem = [itemEnumerator nextObject])
{
NSInteger tag = [menuItem tag];
switch (tag)
{
case 2000: // WebMenuItemTagOpenLink
case WebMenuItemTagOpenLinkInNewWindow:
case WebMenuItemTagDownloadLinkToDisk:
case WebMenuItemTagOpenImageInNewWindow:
case WebMenuItemTagDownloadImageToDisk:
case WebMenuItemTagCopyImageToClipboard:
case WebMenuItemTagOpenFrameInNewWindow:
case WebMenuItemTagStop:
case WebMenuItemTagReload:
case WebMenuItemTagCut:
case WebMenuItemTagPaste:
case WebMenuItemTagSpellingGuess:
case WebMenuItemTagNoGuessesFound:
case WebMenuItemTagIgnoreSpelling:
case WebMenuItemTagLearnSpelling:
case WebMenuItemTagOther:
case WebMenuItemTagOpenWithDefaultApplication:
[webViewMenuItems removeObjectIdenticalTo: menuItem];
break;
}
}
}
// Add two menu items for a selection if no link is given
if(webViewMenuItems
&& [[element objectForKey:@"WebElementIsSelected"] boolValue]
&& ![[element objectForKey:@"WebElementLinkIsLive"] boolValue])
{
NSMenuItem *searchInMySQL;
NSMenuItem *searchInMySQLonline;
[webViewMenuItems insertObject:[NSMenuItem separatorItem] atIndex:0];
searchInMySQLonline = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Search in MySQL Documentation", @"Search in MySQL Documentation") action:@selector(searchInDocForWebViewSelection:) keyEquivalent:@""];
[searchInMySQLonline setEnabled:YES];
[searchInMySQLonline setTarget:self];
[webViewMenuItems insertObject:searchInMySQLonline atIndex:0];
[searchInMySQLonline release];
searchInMySQL = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Search in MySQL Help", @"Search in MySQL Help") action:@selector(showHelpForWebViewSelection:) keyEquivalent:@""];
[searchInMySQL setEnabled:YES];
[searchInMySQL setTarget:self];
[webViewMenuItems insertObject:searchInMySQL atIndex:0];
[searchInMySQL release];
}
return webViewMenuItems;
}
#pragma mark -
#pragma mark Query favorites manager delegate methods
/**
* Rebuild history popup menu.
*/
- (void)historyItemsHaveBeenUpdated:(id)manager
{
// Abort if the connection has been closed already - sign of a closed window
if (![mySQLConnection isConnected]) return;
// Refresh history popup menu
NSMenu* historyMenu = [queryHistoryButton menu];
while([queryHistoryButton numberOfItems] > 7)
[queryHistoryButton removeItemAtIndex:[queryHistoryButton numberOfItems]-1];
NSUInteger numberOfHistoryItems = [[SPQueryController sharedQueryController] numberOfHistoryItemsForFileURL:[tableDocumentInstance fileURL]];
if(numberOfHistoryItems>0)
for(id historyMenuItem in [[SPQueryController sharedQueryController] historyMenuItemsForFileURL:[tableDocumentInstance fileURL]])
[historyMenu addItem:historyMenuItem];
}
/**
* Called by the query favorites manager whenever the query favorites have been updated.
*/
- (void)queryFavoritesHaveBeenUpdated:(id)manager
{
NSMenuItem *headerMenuItem;
NSMenu *menu = [queryFavoritesButton menu];
// Remove all favorites beginning from the end
while([queryFavoritesButton numberOfItems] > 7)
[queryFavoritesButton removeItemAtIndex:[queryFavoritesButton numberOfItems]-1];
// Build document-based list
headerMenuItem = [[NSMenuItem alloc] initWithTitle:
[[[[tableDocumentInstance fileURL] absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] lastPathComponent]
action:NULL keyEquivalent:@""];
[headerMenuItem setTag:SP_FAVORITE_HEADER_MENUITEM_TAG];
[headerMenuItem setToolTip:[NSString stringWithFormat:@"‘%@’ based favorites",
[[[[tableDocumentInstance fileURL] absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] lastPathComponent]]];
[headerMenuItem setIndentationLevel:0];
[menu addItem:headerMenuItem];
[headerMenuItem release];
for (NSDictionary *favorite in [[SPQueryController sharedQueryController] favoritesForFileURL:[tableDocumentInstance fileURL]]) {
if (![favorite isKindOfClass:[NSDictionary class]] || ![favorite objectForKey:@"name"]) continue;
NSMutableParagraphStyle *paraStyle = [[[NSMutableParagraphStyle alloc] init] autorelease];
[paraStyle setTabStops:[NSArray array]];
[paraStyle addTabStop:[[[NSTextTab alloc] initWithType:NSRightTabStopType location:190.0] autorelease]];
NSDictionary *attributes = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:paraStyle, [NSFont systemFontOfSize:11], nil] forKeys:[NSArray arrayWithObjects:NSParagraphStyleAttributeName, NSFontAttributeName, nil]];
NSAttributedString *titleString = [[[NSAttributedString alloc]
initWithString:([favorite objectForKey:@"tabtrigger"] && [(NSString*)[favorite objectForKey:@"tabtrigger"] length]) ? [NSString stringWithFormat:@"%@\t%@⇥", [favorite objectForKey:@"name"], [favorite objectForKey:@"tabtrigger"]] : [favorite objectForKey:@"name"]
attributes:attributes] autorelease];
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"" action:NULL keyEquivalent:@""];
[item setToolTip:[NSString stringWithString:[favorite objectForKey:@"query"]]];
[item setAttributedTitle:titleString];
[item setIndentationLevel:1];
[menu addItem:item];
[item release];
}
// Build global list
headerMenuItem = [[NSMenuItem alloc] initWithTitle:@"Global" action:NULL keyEquivalent:@""];
[headerMenuItem setTag:SP_FAVORITE_HEADER_MENUITEM_TAG];
[headerMenuItem setToolTip:@"Globally stored favorites"];
[headerMenuItem setIndentationLevel:0];
[menu addItem:headerMenuItem];
[headerMenuItem release];
for (NSDictionary *favorite in [prefs objectForKey:SPQueryFavorites]) {
if (![favorite isKindOfClass:[NSDictionary class]] || ![favorite objectForKey:@"name"]) continue;
NSMutableParagraphStyle *paraStyle = [[[NSMutableParagraphStyle alloc] init] autorelease];
[paraStyle setTabStops:[NSArray array]];
[paraStyle addTabStop:[[[NSTextTab alloc] initWithType:NSRightTabStopType location:190.0] autorelease]];
NSDictionary *attributes = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:paraStyle, [NSFont systemFontOfSize:11], nil] forKeys:[NSArray arrayWithObjects:NSParagraphStyleAttributeName, NSFontAttributeName, nil]];
NSAttributedString *titleString = [[[NSAttributedString alloc]
initWithString:([favorite objectForKey:@"tabtrigger"] && [(NSString*)[favorite objectForKey:@"tabtrigger"] length]) ? [NSString stringWithFormat:@"%@\t%@⇥", [favorite objectForKey:@"name"], [favorite objectForKey:@"tabtrigger"]] : [favorite objectForKey:@"name"]
attributes:attributes] autorelease];
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"" action:NULL keyEquivalent:@""];
[item setToolTip:[NSString stringWithString:[favorite objectForKey:@"query"]]];
[item setAttributedTitle:titleString];
[item setIndentationLevel:1];
[menu addItem:item];
[item release];
}
}
#pragma mark -
#pragma mark Task interaction
/**
* Disable all content interactive elements during an ongoing task.
*/
- (void) startDocumentTaskForTab:(NSNotification *)aNotification
{
isWorking = YES;
// Only proceed if this view is selected.
if (![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarCustomQuery])
return;
tableRowsSelectable = NO;
[runSelectionButton setEnabled:NO];
[runSelectionMenuItem setEnabled:NO];
[runAllButton setEnabled:NO];
[runAllMenuItem setEnabled:NO];
}
/**
* Enable all content interactive elements after an ongoing task.
*/
- (void) endDocumentTaskForTab:(NSNotification *)aNotification
{
isWorking = NO;
// Only proceed if this view is selected.
if (![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarCustomQuery])
return;
if (selectionButtonCanBeEnabled) {
[runSelectionButton setEnabled:YES];
[runSelectionMenuItem setEnabled:YES];
}
tableRowsSelectable = YES;
[runAllButton setEnabled:YES];
[runAllMenuItem setEnabled:YES];
}
#pragma mark -
#pragma mark Other
/**
* Returns the number of queries.
*/
- (NSUInteger)numberOfQueries
{
return numberOfQueries;
}
- (NSString *)buildHistoryString
{
return [[[SPQueryController sharedQueryController] historyForFileURL:[tableDocumentInstance fileURL]] componentsJoinedByString:@";\n"];
}
/**
* Add a query string to the file/global history, via the query controller.
* Single argument allows calls on the main thread.
*/
- (void)addHistoryEntry:(NSString *)entryString
{
[[SPQueryController sharedQueryController] addHistory:entryString forFileURL:[tableDocumentInstance fileURL]];
}
/*
* This method is called as part of Key Value Observing which is used to watch for prefernce changes which effect the interface.
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
// Display table veiew vertical gridlines preference changed
if ([keyPath isEqualToString:SPDisplayTableViewVerticalGridlines]) {
[customQueryView setGridStyleMask:([[change objectForKey:NSKeyValueChangeNewKey] boolValue]) ? NSTableViewSolidVerticalGridLineMask : NSTableViewGridNone];
}
// Result Table Font preference changed
else if ([keyPath isEqualToString:SPGlobalResultTableFont]) {
NSFont *tableFont = [NSUnarchiver unarchiveObjectWithData:[change objectForKey:NSKeyValueChangeNewKey]];
[customQueryView setRowHeight:2.0f+NSSizeToCGSize([[NSString stringWithString:@"{ǞṶḹÜ∑zgyf"] sizeWithAttributes:[NSDictionary dictionaryWithObject:tableFont forKey:NSFontAttributeName]]).height];
[customQueryView setFont:tableFont];
[customQueryView reloadData];
}
}
/**
* Called when the save query favorite/clear history sheet is dismissed.
*/
- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo
{
if ([contextInfo isEqualToString:@"clearHistory"]) {
if (returnCode == NSOKButton) {
// Remove items in the query controller
[[SPQueryController sharedQueryController] replaceHistoryByArray:[NSMutableArray array] forFileURL:[tableDocumentInstance fileURL]];
}
return;
}
if ([contextInfo isEqualToString:@"addAllToNewQueryFavorite"] || [contextInfo isEqualToString:@"addSelectionToNewQueryFavorite"]) {
if (returnCode == NSOKButton) {
// Add the new query favorite directly the user's preferences here instead of asking the manager to do it
// as it may not have been fully initialized yet.
NSMutableArray *favorites = [NSMutableArray arrayWithArray:[prefs objectForKey:SPQueryFavorites]];
// What should be saved
NSString *queryToBeAddded;
if([contextInfo isEqualToString:@"addSelectionToNewQueryFavorite"]) {
// First check for a selection
if([textView selectedRange].length)
queryToBeAddded = [[textView string] substringWithRange:[textView selectedRange]];
// then for a current query
else if(currentQueryRange.length)
queryToBeAddded = [[textView string] substringWithRange:currentQueryRange];
// otherwise take the entire string
else
queryToBeAddded = [textView string];
} else {
queryToBeAddded = [textView string];
}
if([saveQueryFavoriteGlobal state] == NSOnState) {
[favorites addObject:[NSMutableDictionary dictionaryWithObjects:
[NSArray arrayWithObjects:[queryFavoriteNameTextField stringValue], queryToBeAddded, nil]
forKeys:[NSArray arrayWithObjects:@"name", @"query", nil]]];
[prefs setObject:favorites forKey:SPQueryFavorites];
} else {
[[SPQueryController sharedQueryController] addFavorite:[NSMutableDictionary dictionaryWithObjects:
[NSArray arrayWithObjects:[queryFavoriteNameTextField stringValue], [[queryToBeAddded mutableCopy] autorelease], nil]
forKeys:[NSArray arrayWithObjects:@"name", @"query", nil]] forFileURL:[tableDocumentInstance fileURL]];
}
[saveQueryFavoriteGlobal setState:NSOffState];
[self queryFavoritesHaveBeenUpdated:nil];
}
}
[queryFavoriteNameTextField setStringValue:@""];
}
- (void)savePanelDidEnd:(NSSavePanel *)panel returnCode:(NSInteger)returnCode contextInfo:(id)contextInfo
{
if([contextInfo isEqualToString:@"saveHistory"]) {
if (returnCode == NSOKButton) {
NSError *error = nil;
[prefs setInteger:[[encodingPopUp selectedItem] tag] forKey:SPLastSQLFileEncoding];
[prefs synchronize];
[[self buildHistoryString] writeToFile:[panel filename]
atomically:YES
encoding:[[encodingPopUp selectedItem] tag]
error:&error];
if (error) [[NSAlert alertWithError:error] runModal];
}
}
}
/**
* Menu item validation.
*/
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
// Control "Save ... to Favorites"
if ( [menuItem tag] == SP_SAVE_SELECTION_FAVORTITE_MENUITEM_TAG ) {
if ([[textView string] length] < 1) return NO;
if([textView selectedRange].length)
[menuItem setTitle:NSLocalizedString(@"Save Selection to Favorites",@"Save Selection to Favorites")];
else if(currentQueryRange.length)
[menuItem setTitle:NSLocalizedString(@"Save Current Query to Favorites",@"Save Current Query to Favorites")];
else
[menuItem setTitle:NSLocalizedString(@"Save All to Favorites",@"Save All to Favorites")];
}
// Control "Save All to Favorites"
if ( [menuItem tag] == SP_SAVE_ALL_FAVORTITE_MENUITEM_TAG ) {
if ([[textView string] length] < 1) return NO;
}
// Avoid selecting button list headers
else if ( [menuItem tag] == SP_FAVORITE_HEADER_MENUITEM_TAG ) {
return NO;
}
// Control Clear History menu item title according to isUntitled
else if ( [menuItem tag] == SP_HISTORY_CLEAR_MENUITEM_TAG ) {
if ( [tableDocumentInstance isUntitled] ) {
[menuItem setTitle:NSLocalizedString(@"Clear Global History", @"clear global history menu item title")];
[menuItem setToolTip:NSLocalizedString(@"Clear the global history list", @"clear the global history list tooltip message")];
} else {
[menuItem setTitle:[NSString stringWithFormat:NSLocalizedString(@"Clear History for “%@”", @"clear history for “%@” menu title"), [tableDocumentInstance displayName]]];
[menuItem setToolTip:NSLocalizedString(@"Clear the document-based history list", @"clear the document-based history list tooltip message")];
}
}
// Check for History items
else if ( [menuItem tag] >= SP_HISTORY_COPY_MENUITEM_TAG && [menuItem tag] <= SP_HISTORY_CLEAR_MENUITEM_TAG ) {
return ([queryHistoryButton numberOfItems]-7);
}
return YES;
}
#pragma mark -
- (id)init
{
if ((self = [super init])) {
usedQuery = [[NSString stringWithString:@""] retain];
sortField = nil;
isDesc = NO;
sortColumn = nil;
selectionButtonCanBeEnabled = NO;
cqColumnDefinition = nil;
favoritesManager = nil;
tableRowsSelectable = YES;
selectionIndexToRestore = nil;
selectionViewportToRestore = NSZeroRect;
// init helpHTMLTemplate
NSError *error;
helpHTMLTemplate = [[NSString alloc]
initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:SPHTMLHelpTemplate ofType:@"html"]
encoding:NSUTF8StringEncoding
error:&error];
// an error occurred while reading
if (helpHTMLTemplate == nil) {
NSLog(@"%@", [NSString stringWithFormat:@"Error reading “%@.html”!
%@", SPHTMLHelpTemplate, [error localizedFailureReason]]);
NSBeep();
}
// init search history
[helpWebView setMaintainsBackForwardList:YES];
[[helpWebView backForwardList] setCapacity:20];
// init tableView's data source
resultDataCount = 0;
resultData = [[SPDataStorage alloc] init];
editedRow = -1;
currentHistoryOffsetIndex = -1;
historyItemWasJustInserted = NO;
prefs = [NSUserDefaults standardUserDefaults];
}
return self;
}
/**
* Filters the query favorites menu.
*/
- (IBAction)filterQueryFavorites:(id)sender
{
NSUInteger i;
NSMenu *menu = [queryFavoritesButton menu];
NSString *searchPattern = [queryFavoritesSearchField stringValue];
for (i = 7; i < [menu numberOfItems]; i++)
{
[[menu itemAtIndex:i] setHidden:([[menu itemAtIndex:i] tag] != SP_FAVORITE_HEADER_MENUITEM_TAG
&& ![[[menu itemAtIndex:i] title] isMatchedByRegex:[NSString stringWithFormat:@"(?i).*%@.*", searchPattern]])];
}
}
/**
* Filters the query history menu.
*/
- (IBAction)filterQueryHistory:(id)sender
{
NSMenu *menu = [queryHistoryButton menu];
NSUInteger numberOfItems = [menu numberOfItems];
NSUInteger i;
NSString *searchPattern = [queryHistorySearchField stringValue];
NSArray *history = [[SPQueryController sharedQueryController] historyForFileURL:[tableDocumentInstance fileURL]];
for (i = 7; i < numberOfItems; i++)
{
[[menu itemAtIndex:i] setHidden:(![[history objectAtIndex:i-7] isMatchedByRegex:[NSString stringWithFormat:@"(?i).*%@.*", searchPattern]])];
}
}
/**
* Abort editing of the Favorite and History search field editors if user presses ARROW UP or DOWN
* to allow to navigate through the menu item list.
*/
- (BOOL)control:(NSControl*)control textView:(NSTextView*)textView doCommandBySelector:(SEL)commandSelector
{
if(control == queryHistorySearchField || control == queryFavoritesSearchField) {
if(commandSelector == @selector(moveDown:) || commandSelector == @selector(moveUp:)) {
[queryHistorySearchField abortEditing];
[queryFavoritesSearchField abortEditing];
// Send moveDown/Up to the popup menu
NSEvent *arrowEvent;
if(commandSelector == @selector(moveDown:))
arrowEvent = [NSEvent keyEventWithType:NSKeyDown location:NSMakePoint(0,0) modifierFlags:0 timestamp:0 windowNumber:[tableWindow windowNumber] context:[NSGraphicsContext currentContext] characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:0x7D];
else
arrowEvent = [NSEvent keyEventWithType:NSKeyDown location:NSMakePoint(0,0) modifierFlags:0 timestamp:0 windowNumber:[tableWindow windowNumber] context:[NSGraphicsContext currentContext] characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:0x7E];
[[NSApplication sharedApplication] postEvent:arrowEvent atStart:NO];
return YES;
}
}
return NO;
}
// - (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item
// {
// // Set the focus at the search field
// // TODO : but no way out; always selecting first/last menu item
// // because after setting focus to search field NSMenu selectedItemIndex is -1
// if(item == queryHistorySearchMenuItem) {
// [queryHistorySearchField selectText:nil];
// }
//
// }
/**
* Setup various interface controls.
*/
- (void)awakeFromNib
{
// Set pre-defined menu tags
[queryFavoritesSaveAsMenuItem setTag:SP_SAVE_SELECTION_FAVORTITE_MENUITEM_TAG];
[queryFavoritesSaveAllMenuItem setTag:SP_SAVE_ALL_FAVORTITE_MENUITEM_TAG];
// Set the structure and index view's vertical gridlines if required
[customQueryView setGridStyleMask:([prefs boolForKey:SPDisplayTableViewVerticalGridlines]) ? NSTableViewSolidVerticalGridLineMask : NSTableViewGridNone];
// Add observers for document task activity
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(startDocumentTaskForTab:)
name:SPDocumentTaskStartNotification
object:tableDocumentInstance];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(endDocumentTaskForTab:)
name:SPDocumentTaskEndNotification
object:tableDocumentInstance];
[prefs addObserver:self forKeyPath:SPGlobalResultTableFont options:NSKeyValueObservingOptionNew context:NULL];
}
/**
* Dealloc.
*/
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[prefs removeObserver:self forKeyPath:SPGlobalResultTableFont];
[usedQuery release];
[resultData release];
[favoritesManager release];
if (helpHTMLTemplate) [helpHTMLTemplate release];
if (mySQLversion) [mySQLversion release];
if (sortField) [sortField release];
if (cqColumnDefinition) [cqColumnDefinition release];
if (selectionIndexToRestore) [selectionIndexToRestore release];
if (currentQueryRanges) [currentQueryRanges release];
[super dealloc];
}
@end