diff options
Diffstat (limited to 'Source/SPHelpViewerController.m')
-rw-r--r-- | Source/SPHelpViewerController.m | 415 |
1 files changed, 415 insertions, 0 deletions
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 |