)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 selectRow:destinationRow byExtendingSelection:NO];
return YES;
} else {
return NO;
}
}
//tableView delegate methods
- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
/*
opens sheet with value when double clicking on a field
*/
{
if ( aTableView == customQueryView ) {
NSArray *theRow;
NSString *theValue;
NSNumber *theIdentifier = [aTableColumn identifier];
//get the value
theRow = [queryResult objectAtIndex:rowIndex];
if ( [[theRow objectAtIndex:[theIdentifier intValue]] isKindOfClass:[NSData class]] ) {
theValue = [[NSString alloc] initWithData:[theRow objectAtIndex:[theIdentifier intValue]]
encoding:[mySQLConnection encoding]];
if (theValue == nil) {
theValue = [[NSString alloc] initWithData:[theRow objectAtIndex:[theIdentifier intValue]]
encoding:NSASCIIStringEncoding];
}
[theValue autorelease];
} else if ( [[theRow objectAtIndex:[theIdentifier intValue]] isMemberOfClass:[NSNull class]] ) {
theValue = [prefs objectForKey:@"NullValue"];
} else {
theValue = [theRow objectAtIndex:[theIdentifier intValue]];
}
[valueTextField setString:[theValue description]];
[valueTextField selectAll:self];
[NSApp beginSheet:valueSheet
modalForWindow:tableWindow modalDelegate:self
didEndSelector:nil contextInfo:nil];
[NSApp runModalForWindow:valueSheet];
[NSApp endSheet:valueSheet];
[valueSheet orderOut:nil];
return NO;
} else {
return YES;
}
}
#pragma mark -
#pragma mark SplitView delegate methods
- (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview
/*
tells the splitView that it can collapse views
*/
{
return YES;
}
- (float)splitView:(NSSplitView *)sender constrainMaxCoordinate:(float)proposedMax ofSubviewAt:(int)offset
/*
defines max position of splitView
*/
{
if ( offset == 0 ) {
return proposedMax - 100;
} else {
return proposedMax - 73;
}
}
- (float)splitView:(NSSplitView *)sender constrainMinCoordinate:(float)proposedMin ofSubviewAt:(int)offset
/*
defines min position of splitView
*/
{
if ( offset == 0 ) {
return proposedMin + 100;
} else {
return proposedMin + 100;
}
}
#pragma mark -
#pragma mark TextView delegate methods
- (BOOL)textView:(NSTextView *)aTextView doCommandBySelector:(SEL)aSelector
/*
traps enter key and
performs query instead of inserting a line break if aTextView == textView
closes valueSheet if aTextView == valueTextField
*/
{
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;
}
/*
* 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;
// If no text is selected, disable the button and action menu.
if ( [textView selectedRange].location == NSNotFound ) {
[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 ([textView selectedRange].length == 0) {
int selectionPosition = [textView selectedRange].location;
int movedRangeStart, movedRangeLength;
BOOL updateQueryButtons = FALSE;
NSRange oldSelection;
NSCharacterSet *whitespaceAndNewlineCharset = [NSCharacterSet whitespaceAndNewlineCharacterSet];
// Retrieve the old selection position
[[[aNotification userInfo] objectForKey:@"NSOldSelectedCharacterRange"] getValue:&oldSelection];
// Only process the query text if the selection previously had length, or moved more than 100 characters,
// or the intervening space contained a semicolon, or typing has been performed with no current query.
// This adds more checks to every keypress, but ensures the majority of the actions don't incur a
// parsing overhead - which is cheap on small text strings but heavy of large queries.
movedRangeStart = (selectionPosition < oldSelection.location)?selectionPosition:oldSelection.location;
movedRangeLength = abs(selectionPosition - oldSelection.location);
if (oldSelection.length > 0) updateQueryButtons = TRUE;
if (!updateQueryButtons && movedRangeLength > 100) updateQueryButtons = TRUE;
if (!updateQueryButtons && oldSelection.location > [[textView string] length]) updateQueryButtons = TRUE;
if (!updateQueryButtons && [[textView string] rangeOfString:@";" options:0 range:NSMakeRange(movedRangeStart, movedRangeLength)].location != NSNotFound) updateQueryButtons = TRUE;
if (!updateQueryButtons && ![runSelectionButton isEnabled] && selectionPosition > oldSelection.location
&& [[[[textView string] substringWithRange:NSMakeRange(movedRangeStart, movedRangeLength)] stringByTrimmingCharactersInSet:whitespaceAndNewlineCharset] length]) updateQueryButtons = TRUE;
if (!updateQueryButtons && [[runSelectionButton title] isEqualToString:NSLocalizedString(@"Run Current", @"Title of button to run current query in custom query view")]) {
int charPosition;
unichar theChar;
for (charPosition = selectionPosition; charPosition > 0; charPosition--) {
theChar = [[textView string] characterAtIndex:charPosition-1];
if (theChar == ';') {
updateQueryButtons = TRUE;
break;
}
if (![whitespaceAndNewlineCharset characterIsMember:theChar]) break;
}
}
if (!updateQueryButtons && [[runSelectionButton title] isEqualToString:NSLocalizedString(@"Run Previous", @"Title of button to run query just before text caret in custom query view")]) {
updateQueryButtons = TRUE;
}
if (updateQueryButtons) {
[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
BOOL isLookBehind = YES;
if ([self queryAtPosition:selectionPosition lookBehind:&isLookBehind]) {
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")];
}
[runSelectionButton setEnabled:YES];
[runSelectionMenuItem setEnabled:YES];
} else {
[runSelectionButton setEnabled:NO];
[runSelectionMenuItem setEnabled:NO];
}
}
// For selection ranges, enable the button.
} else {
[runSelectionButton setTitle:NSLocalizedString(@"Run Selection", @"Title of button to run selected text in custom query view")];
[runSelectionButton setEnabled:YES];
[runSelectionMenuItem setTitle:NSLocalizedString(@"Run Selected Text", @"Title of action menu item to run selected text in custom query view")];
[runSelectionMenuItem setEnabled:YES];
}
}
/*
* Save the custom query editor font if it is changed.
*/
- (void)textViewDidChangeTypingAttributes:(NSNotification *)aNotification
{
// Only save the font if prefs have been loaded, ensuring the saved font has been applied once.
if (prefs) {
[prefs setObject:[NSArchiver archivedDataWithRootObject:[textView font]] forKey:@"CustomQueryEditorFont"];
}
}
#pragma mark -
#pragma mark TableView notifications
/*
* Updates various interface elements based on the current table view selection.
*/
- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
if ([notification object] == queryFavoritesView) {
// Enable/disable buttons
[removeQueryFavoriteButton setEnabled:([queryFavoritesView numberOfSelectedRows] == 1)];
[copyQueryFavoriteButton setEnabled:([queryFavoritesView numberOfSelectedRows] == 1)];
}
}
#pragma mark -
#pragma mark MySQL Help
/*
* Retrieve and show the data for "HELP 'aString'".
*/
- (void)showHelpFor:(NSString *)aString setHistory:(BOOL)setHistory
{
NSString * helpString = [self getHTMLHelpFor:aString];
// Order out the Help window if not visible
if(![helpWebViewWindow isVisible])
{
mySQLversion = [[[(TableDocument *)[[textView window] delegate] mySQLVersion] substringToIndex:3] retain];
[helpWebViewWindow setTitle:[NSString stringWithFormat:@"%@ (%@ %@)", NSLocalizedString(@"MySQL Help", @"mysql help"), NSLocalizedString(@"version", @"version"), mySQLversion]];
[helpWebView setMaintainsBackForwardList:YES];
[[helpWebView backForwardList] setCapacity:20];
if([[helpWebView backForwardList] backListCount] < 1)
{
[helpNavigator setEnabled:NO forSegment:0];
[helpNavigator setEnabled:NO forSegment:2];
} else {
[helpNavigator setEnabled:[[helpWebView backForwardList] backListCount] forSegment:0];
[helpNavigator setEnabled:[[helpWebView backForwardList] forwardListCount] forSegment:2];
}
if(![helpString isEqualToString:@"__no_help_available"])
[helpWebViewWindow orderFront:helpWebView];
helpTarget = 2; // set default to search in MySQL help
[self helpTargetValidation];
}
if(![helpString length]) return;
if(setHistory)
{
WebHistoryItem *aWebHistoryItem = [[WebHistoryItem alloc] initWithURLString:[NSString stringWithFormat:@"applewebdata://%@", aString] title:aString lastVisitedTimeInterval:[[NSDate date] timeIntervalSinceDate:[NSDate distantFuture]]];
[[helpWebView backForwardList] addItem:aWebHistoryItem];
}
[helpNavigator setEnabled:[[helpWebView backForwardList] backListCount] forSegment:0];
[helpNavigator setEnabled:[[helpWebView backForwardList] forwardListCount] forSegment:2];
[[helpWebView mainFrame] loadHTMLString:helpString baseURL:nil];
}
/*
* Retrieve and show the data for "HELP 'search word'" according to helpTarget
*/
- (IBAction)showHelpForSearchString:(id)sender
{
NSString *searchTerm = [[helpSearchField stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
switch(helpTarget)
{
case 0: // page
if(![helpWebView searchFor:searchTerm direction:YES caseSensitive:NO wrap:YES])
if([searchTerm length]) NSBeep();
break;
case 1: // online
// Open MySQL Documentation search in browser
if(![searchTerm length])
break;
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:
[[NSString stringWithFormat:
MYSQL_DEV_SEARCH_URL,
searchTerm,
[mySQLversion stringByReplacingOccurrencesOfString:@"." withString:@""]]
stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]]];
// [[helpWebView mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:
// [[NSString stringWithFormat:
// MYSQL_DEV_SEARCH_URL,
// [[helpSearchField stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]],
// [[mySQLversion substringToIndex:3] stringByReplacingOccurrencesOfString:@"." withString:@""]]
// stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]]]];
break;
case 2: // MySQL
[self showHelpFor:searchTerm setHistory:YES];
break;
}
}
/*
* Retrieve and show the data for "HELP 'currentWord'"
*/
- (IBAction)getHelpForCurrentWord:(id)sender
{
NSString *aString = [[textView string] substringWithRange:[textView getRangeForCurrentWord]];
// if(![aString length]) return;
[self showHelpFor:aString setHistory:YES];
}
/*
* Find Next/Previous in current page
*/
- (IBAction)helpSearchFindNextInPage:(id)sender
{
if(!helpTarget)
if(![helpWebView searchFor:[[helpSearchField stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] direction:YES caseSensitive:NO wrap:YES])
NSBeep();
}
- (IBAction)helpSearchFindPreviousInPage:(id)sender
{
if(!helpTarget)
if(![helpWebView searchFor:[[helpSearchField stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] direction:NO caseSensitive:NO wrap:YES])
NSBeep();
}
/*
* Navigation for back/TOC/forward
*/
- (IBAction)helpSegmentDispachter:(id)sender
{
switch([helpNavigator selectedSegment])
{
case 0:
if([[helpWebView backForwardList] backListCount]) {
[self showHelpFor:[[[[helpWebView backForwardList] backItem] URLString] lastPathComponent] setHistory:NO];
[[helpWebView backForwardList] goBack];
}
break;
case 1: // TOC
[self showHelpFor:@"contents" setHistory:YES];
break;
case 2:
if([[helpWebView backForwardList] forwardListCount]) {
[self showHelpFor:[[[[helpWebView backForwardList] forwardItem] URLString] lastPathComponent] setHistory:NO];
[[helpWebView backForwardList] goForward];
}
break;
}
[helpNavigator setEnabled:[[helpWebView backForwardList] backListCount] forSegment:0];
[helpNavigator setEnabled:[[helpWebView backForwardList] forwardListCount] forSegment:2];
}
/*
* Set helpTarget
*/
- (IBAction)helpTargetPageButton:(id)sender
{
helpTarget = 0;
[self helpTargetValidation];
}
- (IBAction)helpTargetOnlineButton:(id)sender
{
helpTarget = 1;
[self helpTargetValidation];
}
- (IBAction)helpTargetMySQLButton:(id)sender
{
helpTarget = 2;
[self helpTargetValidation];
}
/*
* Control search target buttons and help behaviour
*/
- (void)helpTargetValidation
{
switch(helpTarget)
{
case 0: // page
[helpTargetPageButton setState:NSOnState];
[helpTargetOnlineButton setState:NSOffState];
[helpTargetMySQLButton setState:NSOffState];
[helpSearchFieldCell setSendsWholeSearchString:YES];
break;
case 1: // online
[helpTargetPageButton setState:NSOffState];
[helpTargetOnlineButton setState:NSOnState];
[helpTargetMySQLButton setState:NSOffState];
[helpSearchFieldCell setSendsWholeSearchString:YES];
break;
case 2: // MySQL
[helpTargetPageButton setState:NSOffState];
[helpTargetOnlineButton setState:NSOffState];
[helpTargetMySQLButton setState:NSOnState];
[helpSearchFieldCell setSendsWholeSearchString:NO];
break;
}
}
/*
* Return the help string HTML formatted from executing "HELP 'aString'".
* If more than one help topic was found return a link list.
*/
- (NSString *)getHTMLHelpFor:(NSString *)aString
{
if(![aString length]) return(@"");
CMMCPResult *theResult = nil;
NSDictionary *tableDetails;
NSRange aRange;
NSMutableString *theHelp = [NSMutableString string];
[theHelp setString:
@""
@""
@" "
@""
@""
];
theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"HELP '%@'", aString]];
if ( ![[mySQLConnection getLastErrorMessage] isEqualToString:@""] || ![theResult numOfRows])
{
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:
[[NSString stringWithFormat:
MYSQL_DEV_SEARCH_URL,
aString,
[mySQLversion stringByReplacingOccurrencesOfString:@"." withString:@""]]
stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]]];
return @"__no_help_available";
}
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:[aString uppercaseString] withString:[NSString stringWithFormat:@"%@", [aString uppercaseString]] options:NSLiteralSearch range:NSMakeRange(0,[desc length])];
// detect and generate http links
aRange = NSMakeRange(0,0);
int 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];
[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:@"
"];
}
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
int i;
int r = [theResult numOfRows];
if (r) [theResult dataSeek:0];
if(![aString isEqualToString:@"contents"])
[theHelp appendString:[NSString stringWithFormat:@"
%@ “%@”
", NSLocalizedString(@"Help topics for", @"help topics for"), aString]];
else
[theHelp appendString:[NSString stringWithFormat:@"
%@:
", NSLocalizedString(@"MySQL Help – Categories", @"mysql help categories"), aString]];
[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:@"
"];
}
[theHelp appendString:@""];
[tableDetails release];
return theHelp;
}
/*
* Link detector: If user clicked at an http link open it in the default browser,
* otherwise search for it in the MySQL help.
*/
- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id)listener
{
int navigationType = [[actionInformation objectForKey:WebActionNavigationTypeKey] intValue];
if([[[request URL] scheme] isEqualToString:@"applewebdata"]){
[self showHelpFor:[[[request URL] path] substringWithRange:NSMakeRange(1,[[[request URL] path] length]-1)] setHistory:YES];
[listener ignore];
// WebHistoryItem *aWebHistoryItem = [[WebHistoryItem alloc] initWithURLString:[[request URL] absoluteString] title:[[request URL] path] lastVisitedTimeInterval:[[NSDate date] timeIntervalSinceDate:[NSDate distantFuture]]];
// [[helpWebView backForwardList] addItem:aWebHistoryItem];
} else {
if (navigationType == WebNavigationTypeOther) {
[listener use];
} else if (navigationType == WebNavigationTypeLinkClicked) {
[[NSWorkspace sharedWorkspace] openURL:[actionInformation objectForKey:WebActionOriginalURLKey]];
[listener ignore];
} else {
// Ignore WebNavigationTypeFormSubmitted, WebNavigationTypeBackForward,
// WebNavigationTypeReload and WebNavigationTypeFormResubmitted.
[listener ignore];
}
}
}
/*
* Up to now no contextual menu in helpWebView
*/
- (NSArray *)webView:(WebView *)sender contextMenuItemsForElement:(NSDictionary *)element defaultMenuItems:(NSArray *)defaultMenuItems
{
return nil;
}
#pragma mark -
// Last but not least
- (id)init;
{
self = [super init];
prefs = nil;
usedQuery = [[NSString stringWithString:@""] retain];
return self;
}
- (void)dealloc
{
[queryResult release];
[prefs release];
[queryFavorites release];
[usedQuery release];
[super dealloc];
}
@end