2 Copyright (C) 2004 Marcus Mueller <znek@mulle-kybernetik.com>
4 This file is part of OpenGroupware.org.
6 OGo is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #import "SOPEXBrowserController.h"
23 #import <WebKit/WebFrame.h>
24 #import <WebKit/WebBackForwardList.h>
25 #import <WebKit/WebDocument.h>
26 #import <WebKit/WebDataSource.h>
27 #import "SOPEXAppController.h"
28 #import "SOPEXToolbarController.h"
29 #import "SOPEXWebConnection.h"
30 #import "WebView+Ext.h"
31 #import "SOPEXBrowserWindow.h"
32 #import "SOPEXDocument.h"
33 #import "SOPEXWOXDocument.h"
34 #import "SOPEXWODocument.h"
35 #import "SOPEXSheetRunner.h"
38 #define DNC [NSNotificationCenter defaultCenter]
39 #define UD [NSUserDefaults standardUserDefaults]
42 @interface SOPEXBrowserController (PrivateAPI)
43 - (void)setStatus:(NSString *)_msg;
44 - (void)setStatus:(NSString *)_msg isError:(BOOL)isError;
45 - (void)flushDocument;
46 - (void)createDocumentFromResponse;
47 - (void)_selectTabWithIdentifier:(NSString *)identifier;
51 @implementation SOPEXBrowserController
53 static unsigned controllersCount = 0;
55 NSString *SOPEXApplicationTabIdentifier = @"application";
56 NSString *SOPEXWOTabIdentifier = @"wo";
57 NSString *SOPEXWOXTabIdentifier = @"wox";
58 NSString *SOPEXHTMLTabIdentifier = @"html";
59 NSString *SOPEXHTTPTabIdentifier = @"http";
61 static NGLogger *logger = nil;
65 static BOOL didInit = NO;
69 lm = [NGLoggerManager defaultLoggerManager];
70 logger = [lm loggerForDefaultKey:@"SOPEXDebugEnabled"];
73 + (BOOL)hasActiveControllers {
74 return controllersCount != 0;
82 [NSBundle loadNibNamed:@"SOPEXBrowserController" owner:self];
83 NSAssert(self->mainWindow != nil,
84 @"Problem loading SOPEXBrowserController.nib!");
85 controllersCount += 1;
90 - (oneway void)release {
92 /* This seems to be triggered by a bug in WebKit by the resource load
93 delegate, after a successful load - it might be another problem,
97 [self errorWithFormat:@"%s THIS SHOULD NEVER HAPPEN!!", __PRETTY_FUNCTION__];
103 [self->responseHeaderValues release];
104 [self->connection release];
105 [self->toolbarController release];
106 [self->document release];
113 - (void)awakeFromNib {
114 [self->webView setGroupName:@"WebUI"];
115 self->responseHeaderValues = [[NSMutableArray alloc] initWithCapacity:20];
117 self->toolbarController = [[SOPEXToolbarController alloc]
118 initWithIdentifier:@"SOPEXWebUI"
121 [self setStatus:nil];
122 [self viewApplication:nil];
128 - (void)setWebConnection:(SOPEXWebConnection *)_conn {
129 ASSIGN(self->connection, _conn);
131 [self debugWithFormat:@"%s connection: %@",
139 - (IBAction)orderFront:(id)sender {
140 [self->mainWindow makeKeyAndOrderFront:sender];
143 - (IBAction)reload:(id)sender {
147 rq = [NSURLRequest requestWithURL:[self->connection url] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10.0];
149 [self debugWithFormat:@"%s request: %@", __PRETTY_FUNCTION__, rq];
150 [[self->webView mainFrame] loadRequest:rq];
153 [self->webView reload:self];
155 [self viewApplication:sender];
158 - (IBAction)back:(id)sender {
159 [self->webView goBack];
162 - (IBAction)viewApplication:(id)sender {
163 [self _selectTabWithIdentifier:SOPEXApplicationTabIdentifier];
166 - (IBAction)viewSource:(id)sender {
167 NSString *componentName;
169 componentName = [[self->document path] lastPathComponent];
170 if([componentName hasSuffix:@"wo"]) {
171 [self->woComponentNameField setStringValue:componentName];
172 [self _selectTabWithIdentifier:SOPEXWOTabIdentifier];
175 [self->woxNameField setStringValue:componentName];
176 [self _selectTabWithIdentifier:SOPEXWOXTabIdentifier];
180 - (IBAction)viewHTML:(id)sender {
181 WebDataSource *dataSource;
182 id <WebDocumentRepresentation> representation;
185 dataSource = [[self->webView mainFrame] dataSource];
186 NSAssert(dataSource != nil, @"dataSource not yet committed?!");
187 NSAssert([dataSource isLoading] == NO, @"dataSource not finished loading?!");
189 representation = [dataSource representation];
191 if([representation canProvideDocumentSource])
192 source = [representation documentSource];
196 [self->htmlView setString:source];
197 [self _selectTabWithIdentifier:SOPEXHTMLTabIdentifier];
200 - (IBAction)viewHTTP:(id)sender {
201 WebDataSource *dataSource;
202 NSHTTPURLResponse *response;
203 NSDictionary *headerFields;
207 dataSource = [[self->webView mainFrame] dataSource];
208 response = (NSHTTPURLResponse *)[dataSource response];
210 headerFields = [response allHeaderFields];
211 headers = [headerFields allKeys];
212 count = [headers count];
214 [self->responseHeaderValues removeAllObjects];
216 for(i = 0; i < count; i++) {
217 NSString *header, *value;
218 NSDictionary *headerValueInfo;
220 header = [headers objectAtIndex:i];
221 value = [headerFields objectForKey:header];
222 headerValueInfo = [[NSDictionary alloc] initWithObjectsAndKeys:value,
223 @"value", header, @"header", nil];
224 [self->responseHeaderValues addObject:headerValueInfo];
225 [headerValueInfo release];
228 [self->responseHeaderInfoTableView reloadData];
229 [self _selectTabWithIdentifier:SOPEXHTTPTabIdentifier];
232 - (void)_selectTabWithIdentifier:(NSString *)identifier {
233 [self->tabView selectTabViewItemWithIdentifier:identifier];
236 - (IBAction)saveDocument:(id)sender {
237 if(self->document == nil)
239 if([self->document hasChanges]) {
240 if(![self->document performSave]) {
244 [self->mainWindow setDocumentEdited:NO];
248 - (IBAction)revertDocumentToSaved:(id)sender {
249 [self->document revertChanges];
250 [self->mainWindow setDocumentEdited:NO];
253 - (IBAction)toggleToolbar:(id)sender {
254 if([self->mainWindow toolbar] == nil)
255 [self->toolbarController applyOnWindow:self->mainWindow];
257 [self->mainWindow setToolbar:nil];
260 - (IBAction)editInXcode:(id)sender {
263 path = [self->document path];
264 [[NSWorkspace sharedWorkspace] openFile:path withApplication:@"Xcode" andDeactivate:YES];
270 - (BOOL)validateToolbarItem:(NSToolbarItem *)theItem {
271 return [self validateMenuItem:(id <NSMenuItem>)theItem];
274 - (BOOL)validateMenuItem:(id <NSMenuItem>)_item {
275 SEL action = [_item action];
279 NSLog(@"%s action:%@", __PRETTY_FUNCTION__, NSStringFromSelector(action));
281 if(action == @selector(back:))
282 return [self->webView canGoBack];
283 else if(action == @selector(saveDocument:) ||
284 action == @selector(revertDocumentToSaved:))
286 return self->document == nil ? NO : [self->document hasChanges];
289 tabId = [[self->tabView selectedTabViewItem] identifier];
290 if(action == @selector(viewApplication:)) {
291 BOOL isOn = [tabId isEqualToString:SOPEXApplicationTabIdentifier];
292 [_item setState:isOn ? NSOnState : NSOffState];
294 else if(action == @selector(viewSource:)) {
295 BOOL isOn = ([tabId isEqualToString:SOPEXWOXTabIdentifier] ||
296 [tabId isEqualToString:SOPEXWOTabIdentifier]);
297 [_item setState:isOn ? NSOnState : NSOffState];
299 return self->document != nil ? YES : NO;
301 else if(action == @selector(viewHTML:)) {
303 WebDataSource *dataSource;
305 isOn = [tabId isEqualToString:SOPEXHTMLTabIdentifier];
306 [_item setState:isOn ? NSOnState : NSOffState];
307 dataSource = [[self->webView mainFrame] dataSource];
308 if(dataSource == nil)
310 return [dataSource isLoading] == NO;
312 else if(action == @selector(viewHTTP:)) {
313 BOOL isOn = [tabId isEqualToString:SOPEXHTTPTabIdentifier];
314 [_item setState:isOn ? NSOnState : NSOffState];
320 /* SOPEXDocumentController PROTOCOL */
322 - (NSTextView *)document:(SOPEXDocument *)_document
323 textViewForType:(NSString *)_fileType
325 if([_document isKindOfClass:[SOPEXWODocument class]]) {
326 if([_fileType isEqualToString:@"html"])
327 return self->woSourceView;
328 return self->woDefinitionView;
330 return self->woxSourceView;
333 - (void)document:(SOPEXDocument *)document
334 didValidateWithError:(NSError *)error
335 forType:(NSString *)fileType
337 [self viewSource:self];
343 - (void)setStatus:(NSString *)_msg {
344 [self setStatus:_msg isError:NO];
347 - (void)setStatus:(NSString *)_msg isError:(BOOL)isError {
350 [self->statusBarTextField setStringValue:_msg];
353 - (void)flushDocument {
354 [self->document release];
355 self->document = nil;
356 [self->mainWindow setDocumentEdited:NO];
359 - (void)createDocumentFromResponse {
360 WebDataSource *dataSource;
361 NSHTTPURLResponse *response;
362 NSDictionary *headerFields;
363 NSString *templatePath;
365 dataSource = [[self->webView mainFrame] dataSource];
366 response = (NSHTTPURLResponse *)[dataSource response];
368 if([response isKindOfClass:[NSHTTPURLResponse class]]) {
369 headerFields = [response allHeaderFields];
370 // NOTE: WebKit cuddly-capses header keys!
371 templatePath = [headerFields objectForKey:@"X-Sope-Template-Path"];
372 if(templatePath == nil)
375 if([templatePath hasSuffix:@"wo"])
376 self->document = [[SOPEXWODocument alloc] initWithPath:templatePath
379 self->document = [[SOPEXWOXDocument alloc] initWithPath:templatePath
385 /* window delegate */
388 - (BOOL)windowShouldClose:(id)sender {
391 [self debugWithFormat:@"%s sender:%@", __PRETTY_FUNCTION__, sender];
394 if(sender != self->mainWindow)
397 if(self->document != nil && [self->document hasChanges]) {
401 panel = NSGetAlertPanel(
402 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"),
403 NSLocalizedString(@"If you don\\u2019t save, your changes will be lost.", "Message of the alert sheet when unsaved changes are about to be lost"),
404 NSLocalizedString(@"Save", "Default button text for the alert sheet"),
405 NSLocalizedString(@"Don\\u2019t save", "Alternate button text for the alert sheet"),
406 NSLocalizedString(@"Cancel", "Other button text for the alert sheet")
409 rc = SOPEXRunSheetModalForWindow(panel, self->mainWindow);
410 NSReleaseAlertPanel(panel);
412 // NSAlertOtherReturn == Cancel
413 // NSAlertAlternateReturn == Don't save
414 // NSAlertDefaultReturn == Save
416 if(rc == NSAlertOtherReturn)
418 if(rc == NSAlertDefaultReturn)
419 [self saveDocument:self];
420 [self flushDocument];
425 - (void)windowWillClose:(NSNotification *)_notif {
426 controllersCount -= 1;
427 [[SOPEXAppController sharedController] browserControllerDidClose:self];
430 /* tableview datasource */
432 - (int)numberOfRowsInTableView:(NSTableView *)_tableView {
433 return [self->responseHeaderValues count];
436 - (id)tableView:(NSTableView *)_tableView
437 objectValueForTableColumn:(NSTableColumn *)_tableColumn
440 return [[self->responseHeaderValues objectAtIndex:_rowIndex]
441 objectForKey:[_tableColumn identifier]];
445 /* WebResourceLoadDelegate */
447 - (id)webView:(WebView *)_sender
448 identifierForInitialRequest:(NSURLRequest *)_rq
449 fromDataSource:(WebDataSource *)_ds
451 return [[_rq URL] absoluteString];
454 - (NSURLRequest *)webView:(WebView *)_sender
456 willSendRequest:(NSURLRequest *)_rq
457 redirectResponse:(NSURLResponse *)redirectResponse
458 fromDataSource:(WebDataSource *)_ds
460 /* use that to patch resource requests to local files ;-) */
465 [self debugWithFormat:@"%s: %@ request: %@ url: %@",
471 if (![self->connection shouldRewriteRequestURL:url])
474 if ((rurl = [self->connection rewriteRequestURL:url]) == nil)
476 if ([rurl isEqual:url])
479 return [NSURLRequest requestWithURL:rurl
480 cachePolicy:NSURLRequestUseProtocolCachePolicy
481 timeoutInterval:5.0];
484 - (void)webView:(WebView *)_sender
486 didReceiveContentLength:(unsigned)_length
487 fromDataSource:(WebDataSource *)_ds
489 //NSLog(@"%s: %@ len: %d", __PRETTY_FUNCTION__, _rid, _length);
492 - (void)webView:(WebView *)_sender
494 didFinishLoadingFromDataSource:(WebDataSource *)_ds
496 NSURLResponse *r = [_ds response];
499 [self debugWithFormat:@"%s: %@ ds: %@\n data-len: %i\n response: %@\n "
500 @"type: %@\n enc: %@",
501 __PRETTY_FUNCTION__, _rid, _ds,
502 [[_ds data] length], r, [r MIMEType],
503 [r textEncodingName]];
505 [self->connection processResponse:[_ds response] data:[_ds data]];
508 - (void)webView:(WebView *)_sender
510 didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)_c
511 fromDataSource:(WebDataSource *)_ds
514 [self debugWithFormat:@"%s: %@ ds: %@", __PRETTY_FUNCTION__, _rid, _ds];
517 - (void)webView:(WebView *)_sender
518 resource:(id)_identifier
519 didReceiveResponse:(NSURLResponse *)_response
520 fromDataSource:(WebDataSource *)_ds
523 [self debugWithFormat:@"%s: view: %@\n resource: %@\n received: %@\n"
524 @" datasource: %@\n data-len: %i%s",
526 _sender, _identifier, _response, _ds,
528 [_ds isLoading]? " LOADING" : ""];
533 /* WebFrameLoadDelegate */
536 - (void)webView:(WebView *)sender
537 didStartProvisionalLoadForFrame:(WebFrame *)frame
539 if(self->document != nil && [self->document hasChanges]) {
543 panel = NSGetAlertPanel(
544 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"),
545 NSLocalizedString(@"If you don\\u2019t save, your changes will be lost.", "Message of the alert sheet when unsaved changes are about to be lost"),
546 NSLocalizedString(@"Save", "Default button text for the alert sheet"),
547 NSLocalizedString(@"Don\\u2019t save", "Alternate button text for the alert sheet"),
550 rc = SOPEXRunSheetModalForWindow(panel, self->mainWindow);
551 NSReleaseAlertPanel(panel);
553 // NSAlertOtherReturn == Cancel
554 // NSAlertAlternateReturn == Don't save
555 // NSAlertDefaultReturn == Save
557 if(rc == NSAlertDefaultReturn)
558 [self saveDocument:self];
560 [self flushDocument];
561 [self->progressIndicator startAnimation:self];
564 - (void)webView:(WebView *)sender
565 didReceiveTitle:(NSString *)_title
566 forFrame:(WebFrame *)_frame
568 [self->mainWindow setTitle:_title];
572 - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {
573 [self->progressIndicator stopAnimation:self];
576 #warning ** setFavIcon enabled ... doesnt really make sense
577 [self->mainWindow setFavIcon:[sender pageIcon]];
579 [self createDocumentFromResponse];
582 - (void)webView:(WebView *)sender
583 didFailLoadWithError:(NSError *)error
584 forFrame:(WebFrame *)frame
586 [self->progressIndicator stopAnimation:self];
587 [self setStatus:[error localizedDescription] isError:YES];
590 - (void)webView:(WebView *)sender
591 didFailProvisionalLoadWithError:(NSError *)error
592 forFrame:(WebFrame *)frame
594 [self webView:sender didFailLoadWithError:error forFrame:frame];
598 /* WebView UI delegate */
600 - (BOOL)webViewIsStatusBarVisible:(WebView *)sender {
604 - (void)webView:(WebView *)sender
605 mouseDidMoveOverElement:(NSDictionary *)_info
606 modifierFlags:(unsigned int)_flags
611 NSLog(@"%s _info:%@", __PRETTY_FUNCTION__, _info);
615 url = [_info objectForKey:WebElementImageURLKey];
620 NSMutableString *status;
622 altString = [_info objectForKey:WebElementImageAltStringKey];
623 imageRect = [[_info objectForKey:WebElementImageRectKey] rectValue];
624 image = [_info objectForKey:WebElementImageKey];
626 status = [NSMutableString string];
629 altString = [url absoluteString];
631 url = [_info objectForKey:WebElementLinkURLKey];
633 [status appendFormat:@"%@ ", [url absoluteString]];
635 [status appendFormat:@"[%@] (w:%.0f h:%.0f)",
637 imageRect.size.width, imageRect.size.height];
638 if(NSEqualSizes([image size], imageRect.size) == NO) {
639 NSSize size = [image size];
640 [status appendFormat:@" -> scaled from (w:%.0f h:%.0f)!", size.width, size.height];
643 [self setStatus:status];
647 url = [_info objectForKey:WebElementLinkURLKey];
649 [self setStatus:[url absoluteString]];
653 [self setStatus:nil];