/* Copyright (C) 2000-2003 SKYRIX Software AG This file is part of OGo OGo is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. OGo is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with OGo; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // $Id: SOPEXAppController.m 1 2004-08-20 11:17:52Z znek $ // Created by znek on Mon Jan 26 2004. #import "SOPEXAppController.h" #import #import #import #import #import #import "SOPEXConsole.h" #import "SOPEXToolbarController.h" #import "SOPEXWebConnection.h" #import "SOPEXSNSController.h" #import "SOPEXStatisticsController.h" #import "SOPEXConstants.h" #import "WebView+Ext.h" #import "SOPEXBrowserWindow.h" #import "SOPEXRangeUtilities.h" #import "SOPEXDocument.h" #import "SOPEXWOXDocument.h" #import "SOPEXWODocument.h" #import "SOPEXSheetRunner.h" #define DNC [NSNotificationCenter defaultCenter] #define UD [NSUserDefaults standardUserDefaults] #define SNS_CHILD_DEBUG 0 @interface SOPEXAppController (PrivateAPI) - (void)_setup; - (void)_setupDaemonTask; - (void)_launchDaemonTask; - (void)setStatus:(NSString *)_msg; - (void)setStatus:(NSString *)_msg isError:(BOOL)isError; - (void)flushDocument; - (void)createDocumentFromResponse; - (void)_selectTabWithIdentifier:(NSString *)identifier menuItem:(NSMenuItem *)menuItem; @end @implementation SOPEXAppController static BOOL debugOn = NO; static BOOL isInRADMode = YES; + (void)initialize { static BOOL isInitialized = NO; if(isInitialized) return; debugOn = [[NSUserDefaults standardUserDefaults] boolForKey:@"SOPEXDebugEnabled"]; isInitialized = YES; } #pragma mark - #pragma mark ### INIT & DEALLOC ### - (id)init { self = [super init]; if(self) { NSArray *args; // check, if Application has been launched by Finder args = [[NSProcessInfo processInfo] arguments]; if([[args lastObject] hasPrefix:@"-psn_"]) isInRADMode = NO; } return self; } - (void)dealloc { [DNC removeObserver:self]; [self->responseHeaderValues release]; [self->statsController release]; [self->snsd release]; [self->daemonTask release]; [self->console release]; [super dealloc]; } #pragma mark - #pragma mark ### SETUP ### - (void)awakeFromNib { NSString *appName; [self->webView setGroupName:@"WebUI"]; // Fix menu items appName = [[NSProcessInfo processInfo] processName]; [self->aboutMenuItem setTitle:[NSString stringWithFormat:[self->aboutMenuItem title], appName]]; [self->hideMenuItem setTitle:[NSString stringWithFormat:[self->hideMenuItem title], appName]]; [self->quitMenuItem setTitle:[NSString stringWithFormat:[self->quitMenuItem title], appName]]; self->responseHeaderValues = [[NSMutableArray alloc] initWithCapacity:20]; #if 0 [DNC addObserver:self selector:@selector(textViewDidChangeSelection:) name:NSTextViewDidChangeSelectionNotification object:nil]; #endif [self viewApplication:nil]; } - (void)_setup { if(! [self isInRADMode]) { NSMenu *viewMenu; // remove RAD menuItems [self->mainMenu removeItem:self->debugMenuItem]; viewMenu = [self->viewSeparatorMenuItem menu]; [viewMenu removeItem:self->viewSeparatorMenuItem]; [viewMenu removeItem:self->viewApplicationMenuItem]; [viewMenu removeItem:self->viewSourceMenuItem]; [viewMenu removeItem:self->viewHTMLMenuItem]; [viewMenu removeItem:self->viewHTTPMenuItem]; } self->snsd = [[SOPEXSNSController alloc] init]; [self->snsd setDelegate:self]; [self->snsd start]; if(debugOn) NSLog(@"%s snsd's address:%@", __PRETTY_FUNCTION__, [snsd socketAddress]); self->console = [[SOPEXConsole alloc] init]; self->toolbarController = [[SOPEXToolbarController alloc] initWithIdentifier:@"SOPEXWebUI" target:self]; [self _setupDaemonTask]; } - (void)_setupDaemonTask { NSString *daemonPath; NSMutableArray *args; NSString *woPort; self->daemonTask = [[NSTask alloc] init]; daemonPath = [[NSBundle mainBundle] executablePath]; // prepare the task [self->daemonTask setLaunchPath:daemonPath]; [self->daemonTask setStandardInput:[NSPipe new]]; #if(SNS_CHILD_DEBUG == 0) [self->daemonTask setStandardOutput:[NSPipe new]]; [self->daemonTask setStandardError:[NSPipe new]]; #endif // prepare arguments args = [[NSMutableArray alloc] initWithCapacity:20]; [args addObject:SOPEXDaemonFlag]; // this triggers 'daemon' mode [args addObject:@"-WOContactSNS"]; [args addObject:@"YES"]; // increase responsiveness in case we terminate by accident [args addObject:@"-SNSPingInterval"]; [args addObject:@"10"]; #if SNS_CHILD_DEBUG #warning ** ZNeK: Debugging SNS in child [args addObject:@"-SNSLogActivity"]; [args addObject:@"YES"]; #endif [args addObject:@"-SNSPort"]; [args addObject:[snsd socketAddress]]; [args addObject:@"-WOPort"]; woPort = [UD stringForKey:@"WOPort"]; if(woPort != nil) [args addObject:woPort]; else [args addObject:@"auto"]; [self appendToDaemonLaunchArguments:args]; [self->daemonTask setArguments:args]; [args release]; } - (void)_launchDaemonTask { if(debugOn) NSLog(@"%s", __PRETTY_FUNCTION__); [DNC addObserver:self selector:@selector(daemonTaskDidTerminate:) name:NSTaskDidTerminateNotification object:self->daemonTask]; #if(SNS_CHILD_DEBUG == 0) [self->console setStandardOutput:[self->daemonTask standardOutput] standardError:[self->daemonTask standardError]]; #endif [self->daemonTask launch]; } - (void)appendToDaemonLaunchArguments:(NSMutableArray *)_args { // the next entry works, because executable's cwd is the project directory // (set in project's launch options) if([self isInRADMode]) { [_args addObject:@"-WOProjectDirectory"]; [_args addObject:[[NSFileManager defaultManager] currentDirectoryPath]]; // Debugging options [_args addObject:@"-WOCachingEnabled"]; [_args addObject:@"NO"]; [_args addObject:@"-WODebuggingEnabled"]; [_args addObject:@"YES"]; #if 0 [_args addObject:@"-WODebugComponentLookup"]; [_args addObject:@"YES"]; [_args addObject:@"-WODebugResourceLookup"]; [_args addObject:@"YES"]; #endif #if 0 [_args addObject:@"-WOxComponentElemBuilderDebugEnabled"]; [_args addObject:@"YES"]; [_args addObject:@"-WOxElemBuilder_LogAssociationMapping"]; [_args addObject:@"YES"]; [_args addObject:@"-WOxElemBuilder_LogAssociationCreation"]; [_args addObject:@"YES"]; #endif #if 0 #warning ** ZNeK: Profiling information [_args addObject:@"-WOProfileComponents"]; [_args addObject:@"YES"]; [_args addObject:@"-WOProfileElements"]; [_args addObject:@"YES"]; [_args addObject:@"-WOProfileHttpAdaptor"]; [_args addObject:@"YES"]; #endif } } #pragma mark - #pragma mark ### ACCESSORS ### - (BOOL)isInRADMode { return isInRADMode; } #pragma mark - #pragma mark ### ACTIONS ### - (IBAction)restartDaemonTask:(id)sender { [DNC removeObserver:self name:NSTaskDidTerminateNotification object:self->daemonTask]; // we're done writing [[[self->daemonTask standardInput] fileHandleForWriting] closeFile]; [self->daemonTask terminate]; [self->daemonTask waitUntilExit]; [self->daemonTask release]; [self _setupDaemonTask]; [self _launchDaemonTask]; } - (IBAction)stopDaemonTask:(id)sender { if(debugOn) NSLog(@"%s", __PRETTY_FUNCTION__); [self->progressIndicator startAnimation:self]; // we're done writing [[[self->daemonTask standardInput] fileHandleForWriting] closeFile]; [self->daemonTask terminate]; [self->daemonTask waitUntilExit]; [NSApp terminate:self]; } - (IBAction)reload:(id)sender { if(sender == nil) { NSURLRequest *rq; rq = [NSURLRequest requestWithURL:[self->connection url] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10.0]; if(debugOn) NSLog(@"%s request: %@", __PRETTY_FUNCTION__, rq); [[self->webView mainFrame] loadRequest:rq]; } else { [self->webView reload:self]; } [self viewApplication:sender]; } - (IBAction)back:(id)sender { [self->webView goBack]; } - (IBAction)viewApplication:(id)sender { [self _selectTabWithIdentifier:@"application" menuItem:self->viewApplicationMenuItem]; } - (IBAction)viewSource:(id)sender { NSString *componentName; componentName = [[self->document path] lastPathComponent]; if([componentName hasSuffix:@"wo"]) { [self->woComponentNameField setStringValue:componentName]; [self _selectTabWithIdentifier:@"wo" menuItem:self->viewSourceMenuItem]; } else { [self->woxNameField setStringValue:componentName]; [self _selectTabWithIdentifier:@"wox" menuItem:self->viewSourceMenuItem]; } } - (IBAction)viewHTML:(id)sender { WebDataSource *dataSource; id representation; NSString *source; dataSource = [[self->webView mainFrame] dataSource]; NSAssert(dataSource != nil, @"dataSource not yet committed?!"); NSAssert([dataSource isLoading] == NO, @"dataSource not finished loading?!"); representation = [dataSource representation]; if([representation canProvideDocumentSource]) source = [representation documentSource]; else source = @""; [self->htmlView setString:source]; [self _selectTabWithIdentifier:@"html" menuItem:self->viewHTMLMenuItem]; } - (IBAction)viewHTTP:(id)sender { WebDataSource *dataSource; NSHTTPURLResponse *response; NSDictionary *headerFields; NSArray *headers; int count, i; dataSource = [[self->webView mainFrame] dataSource]; response = (NSHTTPURLResponse *)[dataSource response]; headerFields = [response allHeaderFields]; headers = [headerFields allKeys]; count = [headers count]; [self->responseHeaderValues removeAllObjects]; for(i = 0; i < count; i++) { NSString *header, *value; NSDictionary *headerValueInfo; header = [headers objectAtIndex:i]; value = [headerFields objectForKey:header]; headerValueInfo = [[NSDictionary alloc] initWithObjectsAndKeys:value, @"value", header, @"header", nil]; [self->responseHeaderValues addObject:headerValueInfo]; [headerValueInfo release]; } [self->responseHeaderInfoTableView reloadData]; [self _selectTabWithIdentifier:@"http" menuItem:self->viewHTTPMenuItem]; } - (void)_selectTabWithIdentifier:(NSString *)identifier menuItem:(NSMenuItem *)menuItem { [self->tabView selectTabViewItemWithIdentifier:identifier]; if(isInRADMode) { [self->viewApplicationMenuItem setState:menuItem == self->viewApplicationMenuItem ? NSOnState : NSOffState]; [self->viewSourceMenuItem setState:menuItem == self->viewSourceMenuItem ? NSOnState : NSOffState]; [self->viewHTMLMenuItem setState:menuItem == self->viewHTMLMenuItem ? NSOnState : NSOffState]; [self->viewHTTPMenuItem setState:menuItem == self->viewHTTPMenuItem ? NSOnState : NSOffState]; } } - (IBAction)saveDocument:(id)sender { if(self->document != nil) { if([self->document hasChanges]) { if(![self->document performSave]) { NSBeep(); return; } [self->mainWindow setDocumentEdited:NO]; } } } - (IBAction)revertDocumentToSaved:(id)sender { [self->document revertChanges]; [self->mainWindow setDocumentEdited:NO]; } - (IBAction)openConsole:(id)sender { #if(SNS_CHILD_DEBUG == 0) [self->console orderFront:sender]; #endif } - (IBAction)openStatistics:(id)sender { [self->statsController orderFront:sender]; } - (IBAction)clear:(id)sender { [self->console clear:sender]; } - (IBAction)toggleToolbar:(id)sender { if([self->mainWindow toolbar] == nil) [self->toolbarController applyOnWindow:self->mainWindow]; else [self->mainWindow setToolbar:nil]; } - (IBAction)editInXcode:(id)sender { NSString *path; path = [self->document path]; [[NSWorkspace sharedWorkspace] openFile:path withApplication:@"Xcode" andDeactivate:YES]; } #pragma mark ### VALIDATION ### - (BOOL)validateToolbarItem:(NSToolbarItem *)theItem { return [self validateMenuItem:(id )theItem]; } - (BOOL)validateMenuItem:(id )menuItem { SEL action = [menuItem action]; #if 0 NSLog(@"%s action:%@", __PRETTY_FUNCTION__, NSStringFromSelector(action)); #endif if(action == @selector(openStatistics:)) return self->statsController != nil; if(action == @selector(clear:)) return [self->console validateMenuItem:menuItem]; if(action == @selector(back:)) return [self->webView canGoBack]; if(action == @selector(viewHTML:)) { WebDataSource *dataSource = [[self->webView mainFrame] dataSource]; if(dataSource == nil) return NO; return [dataSource isLoading] == NO; } if(action == @selector(viewSource:)) return self->document != nil; if(action == @selector(saveDocument:) || action == @selector(revertDocumentToSaved:)) if(self->document == nil) return NO; else return [self->document hasChanges]; return YES; } #pragma mark - #pragma mark ### SOPEXDocumentController PROTOCOL ### - (NSTextView *)document:(SOPEXDocument *)_document textViewForType:(NSString *)_fileType { if([_document isKindOfClass:[SOPEXWODocument class]]) { if([_fileType isEqualToString:@"html"]) return self->woSourceView; return self->woDefinitionView; } return self->woxSourceView; } - (void)document:(SOPEXDocument *)document didValidateWithError:(NSError *)error forType:(NSString *)fileType { [self viewSource:self]; } #pragma mark - #pragma mark ### NOTIFICATIONS ### - (void)daemonTaskDidTerminate:(NSNotification *)notification { if(debugOn) NSLog(@"%s", __PRETTY_FUNCTION__); [self->progressIndicator stopAnimation:self]; } #pragma mark - #pragma mark ### APPLICATION DELEGATE ### - (void)applicationWillFinishLaunching:(NSNotification *)notification { } - (void)applicationDidFinishLaunching:(NSNotification *)notification { [self setStatus:nil]; [self _setup]; [self _launchDaemonTask]; if(isInRADMode) [self openConsole:self]; } - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app { return YES; } - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app { if(! [self windowShouldClose:self->mainWindow]) return NSTerminateLater; return NSTerminateNow; } - (void)applicationWillTerminate:(NSNotification *)aNotification { [self stopDaemonTask:self]; } #pragma mark - #pragma mark ### TEXTVIEW DELEGATE ### // ZNeK: This is something I'd like to out-source somplace else, but it just doesn't feel right // to do it at the moment. - (void)textViewDidChangeSelection:(NSNotification *)notification { NSTextView *textView = [notification object]; NSEvent *event; NSRange selRange; selRange = [textView selectedRange]; event = [NSApp currentEvent]; if(([event type] == NSLeftMouseUp) && ([event clickCount] == 2)) { NSRange matchRange = SOPEX_findMatchingTagForRangeInString(selRange, [textView string]); if(matchRange.location != NSNotFound) { selRange = NSUnionRange(selRange, matchRange); [textView setSelectedRange:selRange affinity:NSSelectionAffinityUpstream stillSelecting:YES]; [textView scrollRangeToVisible:matchRange]; } } } #pragma mark - #pragma mark ### PRIVATE API ### - (void)setStatus:(NSString *)_msg { [self setStatus:_msg isError:NO]; } - (void)setStatus:(NSString *)_msg isError:(BOOL)isError { if(_msg == nil) _msg = @""; [self->statusBarTextField setStringValue:_msg]; } - (void)flushDocument { [self->document release]; self->document = nil; [self->mainWindow setDocumentEdited:NO]; } - (void)createDocumentFromResponse { WebDataSource *dataSource; NSHTTPURLResponse *response; NSDictionary *headerFields; NSString *templatePath; dataSource = [[self->webView mainFrame] dataSource]; response = (NSHTTPURLResponse *)[dataSource response]; if([response isKindOfClass:[NSHTTPURLResponse class]]) { headerFields = [response allHeaderFields]; // NOTE: WebKit cuddly-capses header keys! templatePath = [headerFields objectForKey:@"X-Sope-Template-Path"]; if(templatePath == nil) return; if([templatePath hasSuffix:@"wo"]) self->document = [[SOPEXWODocument alloc] initWithPath:templatePath controller:self]; else self->document = [[SOPEXWOXDocument alloc] initWithPath:templatePath controller:self]; } } #pragma mark - #pragma mark ### SNS CONTROLLER DELEGATE ### - (void)snsController:(SOPEXSNSController *)controller registerInstance:(NSDictionary *)instanceDescription { NGInternetSocketAddress *applicationAddress; NSString *applicationName, *url; NSBundle *resourceBundle; NSString *path; applicationName = [instanceDescription objectForKey:SNSApplicationNameKey]; applicationAddress = [instanceDescription objectForKey:SNSApplicationAddressKey]; url = [NSString stringWithFormat:@"http://localhost:%d/%@", [applicationAddress port], applicationName]; [self->connection release]; // In Rapid Development mode the mainBundle path is the current working directory, // which is where the source code is located if(isInRADMode) { path = [[NSFileManager defaultManager] currentDirectoryPath]; } else { path = [[NSBundle mainBundle] resourcePath]; } // However, SOPE:X applications have a special WebServerResources folder path = [path stringByAppendingPathComponent:@"WebServerResources"]; resourceBundle = [[[NSBundle alloc] initWithPath:path] autorelease]; self->connection = [(SOPEXWebConnection *)[SOPEXWebConnection alloc] initWithURL:url localResourceBundle:resourceBundle]; if(debugOn) NSLog(@"%s OGo connection: %@", __PRETTY_FUNCTION__, self->connection); self->statsController = [[SOPEXStatisticsController alloc] initWithApplicationURL:url]; if(debugOn) NSLog(@"%s instance:%@", __PRETTY_FUNCTION__, instanceDescription); [self reload:nil]; } - (void)snsController:(SOPEXSNSController *)controller unregisterInstance:(NSDictionary *)instanceDescription { NSLog(@"%s WARNING!! Child did terminate!", __PRETTY_FUNCTION__); } #pragma mark - #pragma mark ### WINDOW DELEGATE ### - (BOOL)windowShouldClose:(id)sender { #if 0 if(debugOn) NSLog(@"%s sender:%@", __PRETTY_FUNCTION__, sender); #endif if(sender != self->mainWindow) return YES; if(self->document != nil && [self->document hasChanges]) { id panel; int rc; panel = NSGetAlertPanel( NSLocalizedString(@"Do you want to save changes to the source code before closing?", "Title of the alert sheet when window should close but changes are still not saved"), NSLocalizedString(@"If you don\\u2019t save, your changes will be lost.", "Message of the alert sheet when unsaved changes are about to be lost"), NSLocalizedString(@"Save", "Default button text for the alert sheet"), NSLocalizedString(@"Don\\u2019t save", "Alternate button text for the alert sheet"), NSLocalizedString(@"Cancel", "Other button text for the alert sheet") ); rc = SOPEXRunSheetModalForWindow(panel, self->mainWindow); NSReleaseAlertPanel(panel); // NSAlertOtherReturn == Cancel // NSAlertAlternateReturn == Don't save // NSAlertDefaultReturn == Save if(rc == NSAlertOtherReturn) return NO; if(rc == NSAlertDefaultReturn) [self saveDocument:self]; [self flushDocument]; } return YES; } - (void)windowWillClose:(NSNotification *)notification { #if 0 if(debugOn) NSLog(@"%s notification:%@", __PRETTY_FUNCTION__, notification); #endif if([notification object] == self->mainWindow) [NSApp terminate:self]; } #pragma mark - #pragma mark ### TableView DATASOURCE ### - (int)numberOfRowsInTableView:(NSTableView *)_tableView { return [self->responseHeaderValues count]; } - (id)tableView:(NSTableView *)_tableView objectValueForTableColumn:(NSTableColumn *)_tableColumn row:(int)_rowIndex { return [[self->responseHeaderValues objectAtIndex:_rowIndex] objectForKey:[_tableColumn identifier]]; } #pragma mark - #pragma mark ### WebResource Load DELEGATE ### - (id)webView:(WebView *)_sender identifierForInitialRequest:(NSURLRequest *)_rq fromDataSource:(WebDataSource *)_ds { return [[_rq URL] absoluteString]; } - (NSURLRequest *)webView:(WebView *)_sender resource:(id)_id willSendRequest:(NSURLRequest *)_rq redirectResponse:(NSURLResponse *)redirectResponse fromDataSource:(WebDataSource *)_ds { /* use that to patch resource requests to local files ;-) */ NSURL *url, *rurl; url = [_rq URL]; if(debugOn) NSLog(@"%s: %@ request: %@ url: %@", __PRETTY_FUNCTION__, _id, _rq, url); if (![self->connection shouldRewriteRequestURL:url]) return _rq; if ((rurl = [self->connection rewriteRequestURL:url]) == nil) return _rq; if ([rurl isEqual:url]) return _rq; return [NSURLRequest requestWithURL:rurl cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:5.0]; } - (void)webView:(WebView *)_sender resource:(id)_rid didReceiveContentLength:(unsigned)_length fromDataSource:(WebDataSource *)_ds { //NSLog(@"%s: %@ len: %d", __PRETTY_FUNCTION__, _rid, _length); } - (void)webView:(WebView *)_sender resource:(id)_rid didFinishLoadingFromDataSource:(WebDataSource *)_ds { NSURLResponse *r = [_ds response]; if(debugOn) { NSLog(@"%s: %@ ds: %@\n data-len: %i\n response: %@\n type: %@\n enc: %@", __PRETTY_FUNCTION__, _rid, _ds, [[_ds data] length], r, [r MIMEType], [r textEncodingName]); } [self->connection processResponse:[_ds response] data:[_ds data]]; } - (void)webView:(WebView *)_sender resource:(id)_rid didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge fromDataSource:(WebDataSource *)_ds { if(debugOn) NSLog(@"%s: %@ ds: %@", __PRETTY_FUNCTION__, _rid, _ds); } - (void)webView:(WebView *)_sender resource:(id)_identifier didReceiveResponse:(NSURLResponse *)_response fromDataSource:(WebDataSource *)_ds { if (debugOn) { NSLog(@"%s: view: %@\n resource: %@\n received: %@\n" @" datasource: %@\n data-len: %i%s", __PRETTY_FUNCTION__, _sender, _identifier, _response, _ds, [[_ds data] length], [_ds isLoading]?" LOADING":""); } } #pragma mark - #pragma mark ### WebFrame Load DELEGATE ### - (void)webView:(WebView *)sender didStartProvisionalLoadForFrame:(WebFrame *)frame { if(self->document != nil && [self->document hasChanges]) { id panel; int rc; panel = NSGetAlertPanel( NSLocalizedString(@"Do you want to save changes to the source code before proceeding?", "Title of the alert sheet when user wants to proceed but changes are still not saved"), NSLocalizedString(@"If you don\\u2019t save, your changes will be lost.", "Message of the alert sheet when unsaved changes are about to be lost"), NSLocalizedString(@"Save", "Default button text for the alert sheet"), NSLocalizedString(@"Don\\u2019t save", "Alternate button text for the alert sheet"), NULL); rc = SOPEXRunSheetModalForWindow(panel, self->mainWindow); NSReleaseAlertPanel(panel); // NSAlertOtherReturn == Cancel // NSAlertAlternateReturn == Don't save // NSAlertDefaultReturn == Save if(rc == NSAlertDefaultReturn) [self saveDocument:self]; } [self flushDocument]; [self->progressIndicator startAnimation:self]; } - (void)webView:(WebView *)sender didReceiveTitle:(NSString *)_title forFrame:(WebFrame *)_frame { [self->mainWindow setTitle:_title]; } - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame { [self->progressIndicator stopAnimation:self]; #if 0 #warning ** setFavIcon enabled ... doesnt really make sense [self->mainWindow setFavIcon:[sender pageIcon]]; #endif [self createDocumentFromResponse]; } - (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame { [self->progressIndicator stopAnimation:self]; [self setStatus:[error localizedDescription] isError:YES]; } - (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame { [self webView:sender didFailLoadWithError:error forFrame:frame]; } #pragma mark - #pragma mark ### WebView UI DELEGATE ### - (BOOL)webViewIsStatusBarVisible:(WebView *)sender { return YES; } - (void)webView:(WebView *)sender mouseDidMoveOverElement:(NSDictionary *)elementInformation modifierFlags:(unsigned int)modifierFlags { NSURL *url; #if 0 NSLog(@"%s elementInformation:%@", __PRETTY_FUNCTION__, elementInformation); #endif url = [elementInformation objectForKey:WebElementImageURLKey]; if(url != nil) { NSString *altString; NSRect imageRect; NSImage *image; NSMutableString *status; altString = [elementInformation objectForKey:WebElementImageAltStringKey]; imageRect = [[elementInformation objectForKey:WebElementImageRectKey] rectValue]; image = [elementInformation objectForKey:WebElementImageKey]; status = [NSMutableString string]; if(altString == nil) altString = [url absoluteString]; url = [elementInformation objectForKey:WebElementLinkURLKey]; if(url != nil) [status appendFormat:@"%@ ", [url absoluteString]]; [status appendFormat:@"[%@] (w:%.0f h:%.0f)", altString, imageRect.size.width, imageRect.size.height]; if(NSEqualSizes([image size], imageRect.size) == NO) { NSSize size = [image size]; [status appendFormat:@" -> scaled from (w:%.0f h:%.0f)!", size.width, size.height]; } [self setStatus:status]; return; } url = [elementInformation objectForKey:WebElementLinkURLKey]; if(url != nil) { [self setStatus:[url absoluteString]]; return; } [self setStatus:nil]; } @end