aboutsummaryrefslogtreecommitdiffstats
path: root/Source
diff options
context:
space:
mode:
Diffstat (limited to 'Source')
-rw-r--r--Source/SPCustomQuery.h40
-rw-r--r--Source/SPCustomQuery.m531
-rw-r--r--Source/SPDatabaseDocument.h4
-rw-r--r--Source/SPDatabaseDocument.m14
-rw-r--r--Source/SPHelpViewerClient.h66
-rw-r--r--Source/SPHelpViewerClient.m262
-rw-r--r--Source/SPHelpViewerController.h99
-rw-r--r--Source/SPHelpViewerController.m415
-rw-r--r--Source/SPTextView.m3
9 files changed, 876 insertions, 558 deletions
diff --git a/Source/SPCustomQuery.h b/Source/SPCustomQuery.h
index 24695ba2..f06b63dc 100644
--- a/Source/SPCustomQuery.h
+++ b/Source/SPCustomQuery.h
@@ -31,17 +31,6 @@
#import "SPDatabaseContentViewDelegate.h"
-#import <WebKit/WebKit.h>
-
-#define SP_HELP_TOC_SEARCH_STRING @"contents"
-#define SP_HELP_SEARCH_IN_MYSQL 0
-#define SP_HELP_SEARCH_IN_PAGE 1
-#define SP_HELP_SEARCH_IN_WEB 2
-#define SP_HELP_GOBACK_BUTTON 0
-#define SP_HELP_SHOW_TOC_BUTTON 1
-#define SP_HELP_GOFORWARD_BUTTON 2
-#define SP_HELP_NOT_AVAILABLE @"__no_help_available"
-
#define SP_SAVE_ALL_FAVORTITE_MENUITEM_TAG 100001
#define SP_SAVE_SELECTION_FAVORTITE_MENUITEM_TAG 100000
#define SP_FAVORITE_HEADER_MENUITEM_TAG 200000
@@ -122,12 +111,6 @@
#ifndef SP_CODA
IBOutlet NSMenuItem *previousHistoryMenuItem;
IBOutlet NSMenuItem *nextHistoryMenuItem;
- IBOutlet NSWindow *helpWebViewWindow;
- IBOutlet WebView *helpWebView;
- IBOutlet NSSearchField *helpSearchField;
- IBOutlet NSSearchFieldCell *helpSearchFieldCell;
- IBOutlet NSSegmentedControl *helpNavigator;
- IBOutlet NSSegmentedControl *helpTargetSelector;
#endif
IBOutlet NSButton *queryInfoButton;
@@ -145,17 +128,10 @@
NSArray *currentQueryRanges;
BOOL currentQueryBeforeCaret;
- NSString *mySQLversion;
NSTableColumn *sortColumn;
NSUInteger queryStartPosition;
-#ifndef SP_CODA
- NSUInteger helpTarget;
- WebHistory *helpHistory;
- NSString *helpHTMLTemplate;
-#endif
-
SPDataStorage *resultData;
pthread_mutex_t resultDataLock;
NSArray *cqColumnDefinition;
@@ -213,17 +189,6 @@
- (IBAction)chooseQueryHistory:(id)sender;
- (IBAction)closeSheet:(id)sender;
- (IBAction)gearMenuItemSelected:(id)sender;
-#ifndef SP_CODA
-- (IBAction)showHelpForCurrentWord:(id)sender;
-- (IBAction)showHelpForSearchString:(id)sender;
-- (IBAction)helpSegmentDispatcher:(id)sender;
-- (IBAction)helpTargetDispatcher:(id)sender;
-- (IBAction)helpSearchFindNextInPage:(id)sender;
-- (IBAction)helpSearchFindPreviousInPage:(id)sender;
-- (IBAction)helpSelectHelpTargetMySQL:(id)sender;
-- (IBAction)helpSelectHelpTargetPage:(id)sender;
-- (IBAction)helpSelectHelpTargetWeb:(id)sender;
-#endif
- (IBAction)filterQueryFavorites:(id)sender;
- (IBAction)filterQueryHistory:(id)sender;
- (IBAction)saveQueryHistory:(id)sender;
@@ -268,11 +233,6 @@
#ifndef SP_CODA
// MySQL Help
- (void)showAutoHelpForCurrentWord:(id)sender;
-- (NSString *)getHTMLformattedMySQLHelpFor:(NSString *)searchString calledByAutoHelp:(BOOL)autoHelp;
-- (void)showHelpFor:(NSString *)aString addToHistory:(BOOL)addToHistory calledByAutoHelp:(BOOL)autoHelp;
-- (void)helpTargetValidation;
-- (void)openMySQLonlineDocumentationWithString:(NSString *)searchString;
-- (NSWindow *)helpWebViewWindow;
#endif
- (void)setMySQLversion:(NSString *)theVersion;
diff --git a/Source/SPCustomQuery.m b/Source/SPCustomQuery.m
index e8c1d6ad..62ff3e18 100644
--- a/Source/SPCustomQuery.m
+++ b/Source/SPCustomQuery.m
@@ -61,6 +61,8 @@
#import "SPBundleHTMLOutputController.h"
#endif
#import "SPFunctions.h"
+#import "SPHelpViewerClient.h"
+#import "SPHelpViewerController.h"
#import <pthread.h>
#import <SPMySQL/SPMySQL.h>
@@ -68,10 +70,10 @@
@interface SPCustomQuery ()
- (id)_resultDataItemAtRow:(NSInteger)row columnIndex:(NSUInteger)column preserveNULLs:(BOOL)preserveNULLs asPreview:(BOOL)asPreview;
-+ (NSString *)linkToHelpTopic:(NSString *)aTopic;
- (void)documentWillClose:(NSNotification *)notification;
- (void)queryFavoritesHaveBeenUpdated:(NSNotification *)notification;
- (void)historyItemsHaveBeenUpdated:(NSNotification *)notification;
+- (void)helpWindowClosedByUser:(NSNotification *)notification;
@end
@@ -2830,221 +2832,14 @@
#endif
-#pragma mark -
-#pragma mark MySQL Help
-
/**
- * Set the MySQL version as X.Y for Help window title and online search
+ * Set the MySQL version as X.Y
*/
- (void)setMySQLversion:(NSString *)theVersion
{
- mySQLversion = [[theVersion substringToIndex:3] retain];
- [textView setConnection:mySQLConnection withVersion:[[[mySQLversion componentsSeparatedByString:@"."] objectAtIndex:0] integerValue]];
-}
-
-#ifndef SP_CODA
-/**
- * Return the Help window.
- */
-- (NSWindow *)helpWebViewWindow
-{
- return helpWebViewWindow;
+ [textView setConnection:mySQLConnection withVersion:[[[[theVersion substringToIndex:3] componentsSeparatedByString:@"."] objectAtIndex:0] integerValue]];
}
-/**
- * Show the data for "HELP 'searchString'".
- */
-- (void)showHelpFor:(NSString *)searchString addToHistory:(BOOL)addToHistory calledByAutoHelp:(BOOL)autoHelp
-{
-
- // If there's no search string, show nothing if called by autohelp, and the index otherwise
- if (![searchString length]) {
- if (autoHelp) return;
- searchString = SP_HELP_TOC_SEARCH_STRING;
- }
-
- 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];
-}
-#endif
-
- (IBAction)showCompletionList:(id)sender
{
NSRange insertRange = NSMakeRange([textView selectedRange].location, 0);
@@ -3068,294 +2863,22 @@
- (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 = @"4.1";
- else
- version = [NSString stringWithString:mySQLversion];
-
- [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:
- [[NSString stringWithFormat:
- SPMySQLSearchURL,
- version,
- NSLocalizedString(@"en", @"MySQL search language code - eg in http://search.mysql.com/search?q=select&site=refman-50&lr=lang_en"),
- searchString]
- 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 @"";
-
- // Don't escape % when being used as a wildcard, but escape it when it's being used by itself.
- if ([searchString isEqualToString:@"%"]) searchString = @"\\%";
-
- NSRange aRange;
- SPMySQLResult *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 queryErrored])
- {
- // 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 numberOfRows]) {
-
- // try to search via: HELP 'searchString%'
- theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"HELP '%@%%'", [searchString stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"]]];
-
- // really nothing found?
- if(![theResult numberOfRows])
- return [NSString stringWithFormat:@"<em style='color: gray'>%@</em>", NSLocalizedString(@"No results found.", @"Mysql Help Viewer : Search : No results")];
- }
-
- // Ensure rows are returned as strings to prevent data problems with older 4.1 servers
- [theResult setReturnDataAsStrings:YES];
-
- tableDetails = [[NSDictionary alloc] initWithDictionary:[theResult getRowAsDictionary]];
-
- if ([tableDetails objectForKey:@"description"]) { // one single help topic found
- if ([tableDetails objectForKey:@"name"]) {
- [theHelp appendString:@"<h2 class='header'>"];
- [theHelp appendString:[[[tableDetails objectForKey:@"name"] copy] autorelease]];
- [theHelp appendString:@"</h2>"];
-
- }
- 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:@"<span class='searchstring'>%@</span>", [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(NSMaxRange(aRange), [desc length]-aRange.location-aRange.length) capture:1 error:&err1];
- if(aRange.location != NSNotFound) {
- aUrl = [desc substringWithRange:aRange];
- [desc replaceCharactersInRange:aRange withString:[NSString stringWithFormat:@"<a href='%@'>%@</a>", aUrl, aUrl]];
- }
- else
- break;
- safeCnt++;
- if(safeCnt > 200)
- break;
- }
-
- // Detect and generate cross-links. First, handle the old-style [HELP ...] text.
- [desc replaceOccurrencesOfRegex:@"(\\[HELP ([^\\]]*?)\\]" withString:[SPCustomQuery linkToHelpTopic:@"$1"]];
-
- // Handle "see [...]" and "in [...]"-style 5.x links.
- //look-behind won't work here because of the \s+
- [desc replaceOccurrencesOfRegex:@"(See|see|In|in|and)\\s+\\[(?:HELP\\s+)?([^\\]]*?)\\]" withString:[NSString stringWithFormat:@"$1 %@",[SPCustomQuery linkToHelpTopic:@"$2"]]];
-
- [theHelp appendFormat:@"<pre class='description'>%@</pre>", desc];
- }
- // are examples available?
- if([tableDetails objectForKey:@"example"]){
- NSString *examples = [[[tableDetails objectForKey:@"example"] copy] autorelease];
- if([examples length])
- [theHelp appendFormat:@"<br><i><b>%1$@</b></i><br><pre class='example'>%2$@</pre>",NSLocalizedString(@"Example:",@"Mysql Help Viewer : Help Topic: Example section title"), examples];
-
- }
- } else { // list all found topics
-
- // check if HELP 'contents' is called
- if(![searchString isEqualToString:SP_HELP_TOC_SEARCH_STRING])
- [theHelp appendFormat:@"<br><i>%@</i><br>", [NSString stringWithFormat:NSLocalizedString(@"Help topics for “%@”", @"MySQL Help Viewer : Results list : Page title"), searchString]];
- else
- [theHelp appendFormat:@"<br><b>%@:</b><br>", NSLocalizedString(@"MySQL Help – Categories", @"mysql help categories")];
-
- // iterate through all found rows and print them as HTML ul/li list
- [theHelp appendString:@"<ul>"];
- [theResult setDefaultRowReturnType:SPMySQLResultRowAsArray];
- for (NSArray *eachRow in theResult) {
- NSString *topic = [eachRow objectAtIndex:[eachRow count]-2];
- [theHelp appendFormat:@"<li>%@</li>",[SPCustomQuery linkToHelpTopic:topic]];
- }
- [theHelp appendString:@"</ul>"];
- }
-
- [tableDetails release];
-
- return [NSString stringWithFormat:helpHTMLTemplate, theHelp];
-}
-
-+ (NSString *)linkToHelpTopic:(NSString *)aTopic
-{
- NSString *linkTitle = [NSString stringWithFormat:NSLocalizedString(@"Show MySQL help for “%@”", @"MySQL Help Viewer : Results list : Link tooltip"),aTopic];
- return [NSString stringWithFormat:@"<a title='%2$@' href='%1$@' class='internallink'>%1$@</a>", aTopic, linkTitle];
-}
-
-#pragma mark -
-#pragma mark 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<WebPolicyDecisionListener>)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;
+ [[tableDocumentInstance helpViewerClient] showHelpFor:searchString addToHistory:YES calledByAutoHelp:YES];
}
/**
* Detect when the help window is closed (manually) and disable autohelp to ensure it
* isn't reopened on keypresses.
*/
-- (BOOL)windowShouldClose:(id)sender
+- (void)helpWindowClosedByUser:(NSNotification *)notification
{
- if (sender == helpWebViewWindow) {
- [prefs setBool:NO forKey:SPCustomQueryUpdateAutoHelp];
- [prefs synchronize];
- [autohelpMenuItem setState:NSOffState];
- [textView setAutohelp:NO];
- }
+ if ([notification object] != [tableDocumentInstance helpViewerClient]) return;
- return YES;
+ //TODO: this doesn't belong in the document context, since multiple open documents can become out of sync through this
+ [prefs setBool:NO forKey:SPCustomQueryUpdateAutoHelp];
+ [prefs synchronize];
+ [autohelpMenuItem setState:NSOffState];
+ [textView setAutohelp:NO];
}
#endif
@@ -3749,26 +3272,6 @@
selectionIndexToRestore = nil;
selectionViewportToRestore = NSZeroRect;
-#ifndef SP_CODA
- // 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”!<br>%@", SPHTMLHelpTemplate, [error localizedFailureReason]]);
- NSBeep();
- }
-
- // init search history
- [helpWebView setMaintainsBackForwardList:YES];
- [[helpWebView backForwardList] setCapacity:20];
-#endif
-
// init tableView's data source
resultData = [[SPDataStorage alloc] init];
editedRow = -1;
@@ -4003,6 +3506,10 @@
selector:@selector(historyItemsHaveBeenUpdated:)
name:SPHistoryItemsHaveBeenUpdatedNotification
object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(helpWindowClosedByUser:)
+ name:SPUserClosedHelpViewerNotification
+ object:[tableDocumentInstance helpViewerClient]];
#ifndef SP_CODA
[prefs addObserver:self forKeyPath:SPGlobalResultTableFont options:NSKeyValueObservingOptionNew context:NULL];
@@ -4088,10 +3595,6 @@
if(fieldEditor) SPClear(fieldEditor);
-#ifndef SP_CODA
- if (helpHTMLTemplate) SPClear(helpHTMLTemplate);
-#endif
- if (mySQLversion) SPClear(mySQLversion);
if (sortField) SPClear(sortField);
if (cqColumnDefinition) SPClear(cqColumnDefinition);
if (selectionIndexToRestore) SPClear(selectionIndexToRestore);
diff --git a/Source/SPDatabaseDocument.h b/Source/SPDatabaseDocument.h
index 20fb34d1..ceaca039 100644
--- a/Source/SPDatabaseDocument.h
+++ b/Source/SPDatabaseDocument.h
@@ -60,6 +60,7 @@
@class SPExtendedTableInfo;
@class SPTableTriggers;
@class SPTableRelations;
+@class SPHelpViewerClient;
#import "SPDatabaseContentViewDelegate.h"
#import "SPConnectionControllerDelegateProtocol.h"
@@ -92,6 +93,7 @@
IBOutlet id spHistoryControllerInstance;
IBOutlet id exportControllerInstance;
#endif
+ IBOutlet SPHelpViewerClient *helpViewerClientInstance;
IBOutlet id statusTableAccessoryView;
IBOutlet id statusTableView;
@@ -325,6 +327,8 @@
@property (readonly) SPDatabaseStructure *databaseStructureRetrieval;
@property (readonly) int64_t instanceId;
+- (SPHelpViewerClient *)helpViewerClient;
+
#ifndef SP_CODA /* method decls */
- (BOOL)isUntitled;
#endif
diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m
index 4bdd0aad..4909282c 100644
--- a/Source/SPDatabaseDocument.m
+++ b/Source/SPDatabaseDocument.m
@@ -48,7 +48,6 @@
#import "SPSQLParser.h"
#import "SPTableData.h"
#import "SPDatabaseData.h"
-#import "SPDatabaseStructure.h"
#import "SPExtendedTableInfo.h"
#import "SPHistoryController.h"
#import "SPPreferenceController.h"
@@ -85,6 +84,8 @@
#import "ICUTemplateMatcher.h"
#import "SPFavoritesOutlineView.h"
#import "SPSSHTunnel.h"
+#import "SPHelpViewerClient.h"
+#import "SPHelpViewerController.h"
#import <SPMySQL/SPMySQL.h>
@@ -511,6 +512,8 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0;
// Set the custom query editor's MySQL version
[customQueryInstance setMySQLversion:mySQLVersion];
+ [helpViewerClientInstance setConnection:mySQLConnection];
+
#ifndef SP_CODA
[self updateWindowTitle:self];
@@ -2661,6 +2664,11 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0;
}
}
+- (SPHelpViewerClient *)helpViewerClient
+{
+ return helpViewerClientInstance;
+}
+
/**
* Is current document Untitled?
*/
@@ -3602,8 +3610,8 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0;
*/
- (IBAction)showMySQLHelp:(id)sender
{
- [customQueryInstance showHelpFor:SP_HELP_TOC_SEARCH_STRING addToHistory:YES calledByAutoHelp:NO];
- [[customQueryInstance helpWebViewWindow] makeKeyWindow];
+ [helpViewerClientInstance showHelpFor:SPHelpViewerSearchTOC addToHistory:YES calledByAutoHelp:NO];
+ [[helpViewerClientInstance helpWebViewWindow] makeKeyWindow];
}
#endif
diff --git a/Source/SPHelpViewerClient.h b/Source/SPHelpViewerClient.h
new file mode 100644
index 00000000..b169e99f
--- /dev/null
+++ b/Source/SPHelpViewerClient.h
@@ -0,0 +1,66 @@
+//
+// SPHelpViewerClient.h
+// sequel-pro
+//
+// Created by Max Lohrmann on 25.05.18.
+// Copyright (c) 2018 Max Lohrmann. All rights reserved.
+// Parts relocated from existing files. Previous copyright applies.
+//
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// More info at <https://github.com/sequelpro/sequelpro>
+
+@class SPHelpViewerController;
+@class SPMySQLConnection;
+@class MGTemplateEngine;
+
+/**
+ * This is the client side of the Help Viewer window, i.e. this class
+ * can be instantiated from within an xib file as a custom object.
+ *
+ * It also contains the logic to look up the help in the mysql database
+ * using the mySQLConnection (which does not belong into the Help Viewer's
+ * window controller).
+ *
+ * Notifications posted:
+ * * SPUserClosedHelpViewerNotification
+ * When the user triggered closing the help viewer window
+ */
+@interface SPHelpViewerClient : NSObject
+{
+ SPHelpViewerController *controller;
+
+ NSString *helpHTMLTemplate;
+ SPMySQLConnection *mySQLConnection;
+
+ MGTemplateEngine *engine;
+}
+
+- (void)setConnection:(SPMySQLConnection *)theConnection;
+
+- (NSWindow *)helpWebViewWindow;
+
+- (void)showHelpFor:(NSString *)aString addToHistory:(BOOL)addToHistory calledByAutoHelp:(BOOL)autoHelp;
+
+// this is not bound in Interface Builder, but used by the SPTextView context menu
+- (IBAction)showHelpForCurrentWord:(id)sender;
+@end
diff --git a/Source/SPHelpViewerClient.m b/Source/SPHelpViewerClient.m
new file mode 100644
index 00000000..54d8633d
--- /dev/null
+++ b/Source/SPHelpViewerClient.m
@@ -0,0 +1,262 @@
+//
+// SPHelpViewerClient.m
+// sequel-pro
+//
+// Created by Max Lohrmann on 25.05.18.
+// Copyright (c) 2018 Max Lohrmann. All rights reserved.
+// Parts relocated from existing files. Previous copyright applies.
+//
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// More info at <https://github.com/sequelpro/sequelpro>
+
+#import "SPHelpViewerClient.h"
+#import "SPHelpViewerController.h"
+#import <SPMySQL/SPMySQL.h>
+#import "RegexKitLite.h"
+#import "MGTemplateEngine.h"
+#import "ICUTemplateMatcher.h"
+
+@interface SPHelpViewerClient () <SPHelpViewerDataSource>
+
++ (NSString *)linkToHelpTopic:(NSString *)aTopic;
+
+- (void)helpViewerClosed:(NSNotification *)notification;
+
+@end
+
+@implementation SPHelpViewerClient
+
+- (instancetype)init
+{
+ if (self = [super init]) {
+ controller = [[SPHelpViewerController alloc] init];
+ [controller setDataSource:self];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(helpViewerClosed:) name:SPUserClosedHelpViewerNotification object:controller];
+
+ // init helpHTMLTemplate
+ NSError *error;
+
+ helpHTMLTemplate = [[NSString alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:SPHTMLHelpTemplate ofType:@"html"]
+ encoding:NSUTF8StringEncoding
+ error:&error];
+
+ // Set up template engine with your chosen matcher
+ engine = [[MGTemplateEngine alloc] init];
+ [engine setMatcher:[ICUTemplateMatcher matcherWithTemplateEngine:engine]];
+
+ // an error occurred while reading
+ if (helpHTMLTemplate == nil) {
+ helpHTMLTemplate = [@"<html><body>{{body}}</body></html>" copy]; //fallback
+ NSLog(@"%@", [NSString stringWithFormat:@"Error reading “%@.html”!<br>%@", SPHTMLHelpTemplate, [error localizedFailureReason]]);
+ NSBeep();
+ }
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [controller setDataSource:nil]; // we are the (unretained) datasource, but the controller may outlive us (if retained by other objects)
+ [controller close]; // hide the window if it is still visible (can't update anymore without delegate anyway)
+
+ mySQLConnection = nil;
+ SPClear(controller);
+ SPClear(helpHTMLTemplate);
+ SPClear(engine);
+ [super dealloc];
+}
+
+- (void)helpViewerClosed:(NSNotification *)notification
+{
+ //we'll just proxy that notification because outsiders can't/shouldn't access the controller
+ [[NSNotificationCenter defaultCenter] postNotificationName:SPUserClosedHelpViewerNotification object:self];
+}
+
+- (void)openOnlineHelpForTopic:(NSString *)searchString
+{
+ NSString *version = nil;
+ if(![mySQLConnection serverVersionIsGreaterThanOrEqualTo:4 minorVersion:1 releaseVersion:0])
+ version = @"4.1";
+ else
+ version = [NSString stringWithFormat:@"%u.%u",(unsigned int)[mySQLConnection serverMajorVersion], (unsigned int)[mySQLConnection serverMinorVersion]];
+
+ NSString *url = [[NSString stringWithFormat:
+ SPMySQLSearchURL,
+ version,
+ NSLocalizedString(@"en", @"MySQL search language code - eg in http://search.mysql.com/search?q=select&site=refman-50&lr=lang_en"),
+ searchString]
+ stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
+
+ if([url length]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:url]];
+}
+
+- (NSString *)HTMLHelpContentsForSearchString:(NSString *)searchString autoHelp:(BOOL)autoHelp
+{
+ if(![searchString length]) return @"";
+
+ NSMutableString *theTitle = [NSMutableString stringWithFormat:NSLocalizedString(@"Version %@", @"Mysql Help Viewer : window title : mysql server version"),[mySQLConnection serverVersionString]];
+ NSMutableString *theHelp = [NSMutableString string];
+
+ // Don't escape % when being used as a wildcard, but escape it when it's being used by itself.
+ if ([searchString isEqualToString:@"%"]) searchString = @"\\%";
+
+ // search via: HELP 'searchString'
+ SPMySQLResult *theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"HELP %@", [searchString tickQuotedString]]];
+ if ([mySQLConnection queryErrored]) {
+ [theTitle setString:NSLocalizedString(@"Error", @"Mysql Help Viewer : window title : query error")];
+ NSString *errMsg = [NSString stringWithFormat:@"ERROR %lu (%@): %@", (unsigned long)[mySQLConnection lastErrorID], [mySQLConnection lastSqlstate], [mySQLConnection lastErrorMessage]];
+ [theHelp appendFormat:@"<b>%@:</b><br><p class='error'>%@</p>", NSLocalizedString(@"MySQL Help Query Failed", @"Mysql Help Viewer : title of error message"), errMsg];
+ goto generate_help;
+ }
+
+ // nothing found?
+ if(![theResult numberOfRows]) {
+ // try to search via: HELP 'searchString%'
+ theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"HELP %@", [[searchString stringByAppendingString:@"%"] tickQuotedString]]];
+
+ // really nothing found?
+ if(![theResult numberOfRows]) {
+ [theTitle appendFormat:@": %@", NSLocalizedString(@"No Results", @"Mysql Help Viewer : window title : nothing found")];
+ [theHelp appendFormat:@"<em class='nothing'>%@</em>", NSLocalizedString(@"No results found.", @"Mysql Help Viewer : Search : No results")];
+ goto generate_help;
+ }
+ }
+
+ // Ensure rows are returned as strings to prevent data problems with older 4.1 servers
+ [theResult setReturnDataAsStrings:YES];
+
+ NSDictionary *tableDetails = [[NSDictionary alloc] initWithDictionary:[theResult getRowAsDictionary]];
+
+ if ([tableDetails objectForKey:@"description"]) { // one single help topic found
+ if ([tableDetails objectForKey:@"name"]) {
+ [theTitle appendFormat:@": %@", [tableDetails objectForKey:@"name"]];
+ [theHelp appendString:@"<h2 class='header'>"];
+ [theHelp appendString:[tableDetails objectForKey:@"name"]];
+ [theHelp appendString:@"</h2>"];
+ }
+ if ([tableDetails objectForKey:@"description"]) {
+ NSMutableString *desc = [NSMutableString string];
+ NSError *err1 = NULL;
+ NSString *aUrl;
+
+ [desc setString:[tableDetails objectForKey:@"description"]];
+
+ //[desc replaceOccurrencesOfString:[searchString uppercaseString] withString:[NSString stringWithFormat:@"<span class='searchstring'>%@</span>", [searchString uppercaseString]] options:NSLiteralSearch range:NSMakeRange(0,[desc length])];
+
+ // detect and generate http links
+ NSRange 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(NSMaxRange(aRange), [desc length]-aRange.location-aRange.length) capture:1 error:&err1];
+ if(aRange.location != NSNotFound) {
+ aUrl = [desc substringWithRange:aRange];
+ [desc replaceCharactersInRange:aRange withString:[NSString stringWithFormat:@"<a href='%@'>%@</a>", aUrl, aUrl]];
+ }
+ else {
+ break;
+ }
+ safeCnt++;
+ if(safeCnt > 200) break;
+ }
+
+ // Detect and generate cross-links. First, handle the old-style [HELP ...] text.
+ [desc replaceOccurrencesOfRegex:@"(\\[HELP ([^\\]]*?)\\]" withString:[[self class] linkToHelpTopic:@"$1"]];
+
+ // Handle "see [...]" and "in [...]"-style 5.x links.
+ //look-behind won't work here because of the \s+
+ [desc replaceOccurrencesOfRegex:@"(See|see|In|in|and)\\s+\\[(?:HELP\\s+)?([^\\]]*?)\\]" withString:[NSString stringWithFormat:@"$1 %@",[[self class] linkToHelpTopic:@"$2"]]];
+
+ [theHelp appendFormat:@"<pre class='description'>%@</pre>", desc];
+ }
+ // are examples available?
+ if([tableDetails objectForKey:@"example"]){
+ NSString *examples = [[[tableDetails objectForKey:@"example"] copy] autorelease];
+ if([examples length]) [theHelp appendFormat:@"<br><i><b>%1$@</b></i><br><pre class='example'>%2$@</pre>",NSLocalizedString(@"Example:",@"Mysql Help Viewer : Help Topic: Example section title"), examples];
+ }
+ }
+ else { // list all found topics
+ // check if HELP 'contents' is called
+ if(![searchString isEqualToString:SPHelpViewerSearchTOC]) {
+ [theTitle appendString:@": "];
+ [theTitle appendFormat:NSLocalizedString(@"Multiple Results for “%@”", @"Mysql Help Viewer : window title : multiple topics found"), searchString];
+ [theHelp appendFormat:@"<br><i>%@</i><br>", [NSString stringWithFormat:NSLocalizedString(@"Help topics for “%@”", @"MySQL Help Viewer : Results list : Page title"), searchString]];
+ }
+ else {
+ [theTitle appendFormat:@": %@", NSLocalizedString(@"Table of Contents", @"Mysql Help Viewer : window title : TOC")];
+ [theHelp appendFormat:@"<br><b>%@:</b><br>", NSLocalizedString(@"MySQL Help – Categories", @"mysql help categories")];
+ }
+
+ // iterate through all found rows and print them as HTML ul/li list
+ [theHelp appendString:@"<ul>"];
+ [theResult setDefaultRowReturnType:SPMySQLResultRowAsArray];
+ for (NSArray *eachRow in theResult) {
+ NSString *topic = [eachRow objectAtIndex:[eachRow count]-2];
+ [theHelp appendFormat:@"<li>%@</li>",[[self class] linkToHelpTopic:topic]];
+ }
+ [theHelp appendString:@"</ul>"];
+ }
+
+ [tableDetails release];
+
+generate_help:
+ return [engine processTemplate:helpHTMLTemplate withVariables:@{
+ @"title": theTitle,
+ @"body": theHelp,
+ }];
+}
+
++ (NSString *)linkToHelpTopic:(NSString *)aTopic
+{
+ NSString *linkTitle = [NSString stringWithFormat:NSLocalizedString(@"Show MySQL help for “%@”", @"MySQL Help Viewer : Results list : Link tooltip"),aTopic];
+ return [NSString stringWithFormat:@"<a title='%2$@' href='%1$@' class='internallink'>%1$@</a>", aTopic, linkTitle];
+}
+
+- (void)setConnection:(SPMySQLConnection *)theConnection
+{
+ mySQLConnection = theConnection;
+}
+
+/**
+ * Return the Help window.
+ */
+- (NSWindow *)helpWebViewWindow
+{
+ return [controller window];
+}
+
+- (void)showHelpFor:(NSString *)aString addToHistory:(BOOL)addToHistory calledByAutoHelp:(BOOL)autoHelp
+{
+ [controller showHelpFor:aString addToHistory:addToHistory calledByAutoHelp:autoHelp];
+}
+
+/**
+ * Show the data for "HELP 'currentWord'"
+ */
+- (IBAction)showHelpForCurrentWord:(id)sender
+{
+ NSString *searchString = [[sender string] substringWithRange:[sender getRangeForCurrentWord]];
+ [controller showHelpFor:searchString addToHistory:YES calledByAutoHelp:NO];
+}
+
+@end
diff --git a/Source/SPHelpViewerController.h b/Source/SPHelpViewerController.h
new file mode 100644
index 00000000..c4de0d54
--- /dev/null
+++ b/Source/SPHelpViewerController.h
@@ -0,0 +1,99 @@
+//
+// SPHelpViewerController.h
+// sequel-pro
+//
+// Created by Max Lohrmann on 21.05.18.
+// Copyright (c) 2018 Max Lohrmann. All rights reserved.
+// Parts relocated from existing files. Previous copyright applies.
+//
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// More info at <https://github.com/sequelpro/sequelpro>
+
+@class WebView;
+
+//private
+typedef NS_ENUM(NSUInteger, HelpTarget) {
+ HelpTargetMySQL = 0,
+ HelpTargetPage = 1,
+ HelpTargetWeb = 2,
+};
+
+NSString * const SPHelpViewerSearchTOC;
+
+/**
+ * This notification is posted by the SPHelpViewerController when the user
+ * triggered closing the help viewer window (or by -performClose:).
+ * The window is not guaranteed to be off screen already, when the notification is sent.
+ *
+ * It will NOT be sent when the window was closed or hidden by code (including app termination).
+ */
+NSString * const SPUserClosedHelpViewerNotification;
+
+@protocol SPHelpViewerDataSource <NSObject>
+
+@required
+/**
+ * When called with a search string this method should open the user's default browser
+ * with an URL to the MySQL online manual for the page that explains the search string.
+ */
+- (void)openOnlineHelpForTopic:(NSString *)searchString;
+
+/**
+ * This method is called by the SPHelpViewerController when it wants to receive the HTML
+ * page to display in response to a search string.
+ *
+ * The implementation has to handle the magic search string SPHelpViewerSearchTOC to
+ * return a table of contents document.
+ */
+- (NSString *)HTMLHelpContentsForSearchString:(NSString *)searchString autoHelp:(BOOL)autoHelp;
+
+@end
+
+/**
+ * This is the window controller class for the MySQL Help Viewer panel.
+ *
+ * See SPHelpViewerClient for the class that provides data for this controller and which
+ * can be instantiated from within an XIB.
+ *
+ * - Do NOT instantiate this class from within an XIB.
+ * - None of the methods in this class are thread-safe - always use the UI thread!
+ */
+@interface SPHelpViewerController : NSWindowController
+{
+ IBOutlet WebView *helpWebView;
+
+ IBOutlet NSSearchField *helpSearchField;
+ IBOutlet NSSearchFieldCell *helpSearchFieldCell;
+ IBOutlet NSSegmentedControl *helpNavigator;
+ IBOutlet NSSegmentedControl *helpTargetSelector;
+
+ HelpTarget helpTarget;
+
+ id<SPHelpViewerDataSource> dataSource;
+}
+
+@property (assign, nonatomic) id <SPHelpViewerDataSource> dataSource;
+
+- (void)showHelpFor:(NSString *)aString addToHistory:(BOOL)addToHistory calledByAutoHelp:(BOOL)autoHelp;
+
+@end
diff --git a/Source/SPHelpViewerController.m b/Source/SPHelpViewerController.m
new file mode 100644
index 00000000..a2c13000
--- /dev/null
+++ b/Source/SPHelpViewerController.m
@@ -0,0 +1,415 @@
+//
+// SPHelpViewerController.m
+// sequel-pro
+//
+// Created by Max Lohrmann on 21.05.18.
+// Copyright (c) 2018 Max Lohrmann. All rights reserved.
+// Parts relocated from existing files. Previous copyright applies.
+//
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// More info at <https://github.com/sequelpro/sequelpro>
+
+#import "SPHelpViewerController.h"
+
+#import <WebKit/WebKit.h>
+
+NSString * const SPHelpViewerSearchTOC = @"contents";
+
+NSString * const SPUserClosedHelpViewerNotification = @"SPUserClosedHelpViewer";
+
+typedef NS_ENUM(NSInteger, HelpNavButton) {
+ HelpNavButtonGoBack = 0,
+ HelpNavButtonShowTOC = 1,
+ HelpNavButtonGoForward = 2,
+};
+
+static void *HelpViewerControllerKVOContext = &HelpViewerControllerKVOContext;
+
+@interface SPHelpViewerController () <WebPolicyDelegate, WebUIDelegate, NSWindowDelegate>
+- (IBAction)showHelpForSearchString:(id)sender;
+- (IBAction)helpSegmentDispatcher:(id)sender;
+- (IBAction)helpSearchFindNextInPage:(id)sender;
+- (IBAction)helpSearchFindPreviousInPage:(id)sender;
+- (IBAction)helpTargetDispatcher:(id)sender;
+- (IBAction)helpSelectHelpTargetMySQL:(id)sender;
+- (IBAction)helpSelectHelpTargetPage:(id)sender;
+- (IBAction)helpSelectHelpTargetWeb:(id)sender;
+
+- (IBAction)showHelpForWebViewSelection:(id)sender;
+- (IBAction)searchInDocForWebViewSelection:(id)sender;
+- (void)helpTargetValidation;
+- (void)updateWindowTitle;
+@end
+
+#pragma mark -
+
+@implementation SPHelpViewerController
+
+@synthesize dataSource = dataSource;
+
+- (instancetype)init
+{
+ if ((self = [super initWithWindowNibName:@"HelpViewer"])) {
+ //force window to be loaded for simplicity
+ [self window];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [helpWebView removeObserver:self forKeyPath:@"mainFrameTitle"]; //TODO: update to ...context: variant after 10.6
+ [super dealloc];
+}
+
+- (void)windowDidLoad
+{
+ // init search history
+ [helpWebView setMaintainsBackForwardList:YES];
+ [[helpWebView backForwardList] setCapacity:20];
+
+ [self updateWindowTitle];
+
+ [helpWebView addObserver:self forKeyPath:@"mainFrameTitle" options:0 context:HelpViewerControllerKVOContext];
+}
+
+- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context
+{
+ if(context == HelpViewerControllerKVOContext) {
+ if([@"mainFrameTitle" isEqualToString:keyPath] && object == helpWebView) {
+ [self updateWindowTitle];
+ }
+ }
+ else {
+ [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
+ }
+}
+
+- (void)updateWindowTitle
+{
+ NSString *title = NSLocalizedString(@"MySQL Help", @"mysql help");
+
+ NSString *webTitle = [helpWebView mainFrameTitle];
+ if([webTitle length]) title = [title stringByAppendingFormat:@" (%@)", webTitle];
+
+ [[self window] setTitle:title];
+}
+
+#pragma mark -
+#pragma mark MySQL Help
+
+/**
+ * Show the data for "HELP 'searchString'".
+ */
+- (void)showHelpFor:(NSString *)searchString addToHistory:(BOOL)addToHistory calledByAutoHelp:(BOOL)autoHelp
+{
+ // If there's no search string, ignore if called by autohelp, show the index otherwise
+ if (![searchString length]) {
+ if (autoHelp) return;
+ searchString = SPHelpViewerSearchTOC;
+ }
+
+ NSString *helpString = [dataSource HTMLHelpContentsForSearchString:searchString autoHelp:autoHelp];
+
+ // init the Help window if not visible
+ if(![[self window] isVisible]) {
+ // init goback/forward buttons
+ if([[helpWebView backForwardList] backListCount] < 1) {
+ [helpNavigator setEnabled:NO forSegment:HelpNavButtonGoBack];
+ [helpNavigator setEnabled:NO forSegment:HelpNavButtonGoForward];
+ }
+ else {
+ [helpNavigator setEnabled:([[helpWebView backForwardList] backListCount] != 0) forSegment:HelpNavButtonGoBack];
+ [helpNavigator setEnabled:([[helpWebView backForwardList] forwardListCount] != 0) forSegment:HelpNavButtonGoForward];
+ }
+
+ // set default to search in MySQL help
+ helpTarget = HelpTargetMySQL;
+ [helpTargetSelector setSelectedSegment:HelpTargetMySQL];
+ [self helpTargetValidation];
+
+ // show Help window
+ [[self window] orderFront:helpWebView];
+ }
+
+ 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] != 0) forSegment:HelpNavButtonGoBack];
+ [helpNavigator setEnabled:([[helpWebView backForwardList] forwardListCount] != 0) forSegment:HelpNavButtonGoForward];
+
+ // 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 HelpTargetPage:
+ if(![helpWebView searchFor:searchString direction:YES caseSensitive:NO wrap:YES]) {
+ if([searchString length]) NSBeep();
+ }
+ break;
+ case HelpTargetWeb:
+ if(![searchString length]) break;
+ [dataSource openOnlineHelpForTopic:searchString];
+ break;
+ case HelpTargetMySQL:
+ [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;
+ }
+ [dataSource openOnlineHelpForTopic:searchString];
+}
+
+/**
+ * Find Next/Previous in current page
+ */
+- (IBAction)helpSearchFindNextInPage:(id)sender
+{
+ if(helpTarget == HelpTargetPage) {
+ if(![helpWebView searchFor:[[helpSearchField stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] direction:YES caseSensitive:NO wrap:YES]) NSBeep();
+ }
+}
+
+- (IBAction)helpSearchFindPreviousInPage:(id)sender
+{
+ if(helpTarget == HelpTargetPage) {
+ 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((HelpNavButton)[helpNavigator selectedSegment]) {
+ case HelpNavButtonGoBack:
+ [helpWebView goBack];
+ break;
+ case HelpNavButtonShowTOC:
+ [self showHelpFor:SPHelpViewerSearchTOC addToHistory:YES calledByAutoHelp:NO];
+ break;
+ case HelpNavButtonGoForward:
+ [helpWebView goForward];
+ break;
+ }
+
+ // validate goback and goforward buttons according history
+ [helpNavigator setEnabled:([[helpWebView backForwardList] backListCount] != 0) forSegment:HelpNavButtonGoBack];
+ [helpNavigator setEnabled:([[helpWebView backForwardList] forwardListCount] != 0) forSegment:HelpNavButtonGoForward];
+}
+
+/**
+ * Set helpTarget according user choice via mouse and keyboard short-cuts.
+ */
+- (IBAction)helpSelectHelpTargetMySQL:(id)sender
+{
+ helpTarget = HelpTargetMySQL;
+ [helpTargetSelector setSelectedSegment:HelpTargetMySQL];
+ [self helpTargetValidation];
+}
+
+- (IBAction)helpSelectHelpTargetPage:(id)sender
+{
+ helpTarget = HelpTargetPage;
+ [helpTargetSelector setSelectedSegment:HelpTargetPage];
+ [self helpTargetValidation];
+}
+
+- (IBAction)helpSelectHelpTargetWeb:(id)sender
+{
+ helpTarget = HelpTargetWeb;
+ [helpTargetSelector setSelectedSegment:HelpTargetWeb];
+ [self helpTargetValidation];
+}
+
+- (IBAction)helpTargetDispatcher:(id)sender
+{
+ helpTarget = (HelpTarget)[helpTargetSelector selectedSegment];
+ [self helpTargetValidation];
+}
+
+/**
+ * Control the help search field behaviour.
+ */
+- (void)helpTargetValidation
+{
+ switch(helpTarget) {
+ case HelpTargetPage:
+ case HelpTargetWeb:
+ [helpSearchFieldCell setSendsWholeSearchString:YES];
+ break;
+ case HelpTargetMySQL:
+ [helpSearchFieldCell setSendsWholeSearchString:NO];
+ break;
+ }
+}
+
+- (BOOL)windowShouldClose:(NSWindow *)sender
+{
+ // -windowShouldClose: is the only method that will ONLY be invoked when the user closes the window (or by -performClose:)
+ [[NSNotificationCenter defaultCenter] postNotificationName:SPUserClosedHelpViewerNotification object:self];
+ return YES;
+}
+
+#pragma mark -
+#pragma mark 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<WebPolicyDecisionListener>)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 {
+ // Ignore WebNavigationTypeFormSubmitted, WebNavigationTypeFormResubmitted, WebNavigationTypeReload.
+ [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;
+}
+
+@end
diff --git a/Source/SPTextView.m b/Source/SPTextView.m
index de1ddc04..6b98217c 100644
--- a/Source/SPTextView.m
+++ b/Source/SPTextView.m
@@ -50,6 +50,7 @@
#import "SPCopyTable.h"
#import "SPEditorTokens.h"
#import "SPSyntaxParser.h"
+#import "SPHelpViewerClient.h"
#import <SPMySQL/SPMySQL.h>
@@ -1107,7 +1108,7 @@ retry:
*/
- (IBAction) showMySQLHelpForCurrentWord:(id)sender
{
- [customQueryInstance showHelpForCurrentWord:self];
+ [[tableDocumentInstance helpViewerClient] showHelpForCurrentWord:self];
}
#endif