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 NSString *SOPEXApplicationTabIdentifier = @"application";
54 NSString *SOPEXWOTabIdentifier = @"wo";
55 NSString *SOPEXWOXTabIdentifier = @"wox";
56 NSString *SOPEXHTMLTabIdentifier = @"html";
57 NSString *SOPEXHTTPTabIdentifier = @"http";
59 static NGLogger *logger = nil;
63 static BOOL didInit = NO;
67 lm = [NGLoggerManager defaultLoggerManager];
68 logger = [lm loggerForDefaultKey:@"SOPEXDebugEnabled"];
77 [NSBundle loadNibNamed:@"SOPEXBrowserController" owner:self];
78 NSAssert(self->mainWindow != nil,
79 @"Problem loading SOPEXBrowserController.nib!");
84 - (oneway void)release {
86 /* This seems to be triggered by a bug in WebKit by the resource load
87 delegate, after a successful load - it might be another problem,
91 [self errorWithFormat:@"%s THIS SHOULD NEVER HAPPEN!!", __PRETTY_FUNCTION__];
97 [self->responseHeaderValues release];
98 [self->connection release];
99 [self->toolbarController release];
100 [self->document release];
107 - (void)awakeFromNib {
108 [self->webView setGroupName:@"WebUI"];
109 self->responseHeaderValues = [[NSMutableArray alloc] initWithCapacity:20];
111 self->toolbarController = [[SOPEXToolbarController alloc]
112 initWithIdentifier:@"SOPEXWebUI"
115 [self setStatus:nil];
116 [self viewApplication:nil];
122 - (void)setWebConnection:(SOPEXWebConnection *)_conn {
123 ASSIGN(self->connection, _conn);
125 [self debugWithFormat:@"%s connection: %@",
133 - (IBAction)orderFront:(id)sender {
134 [self->mainWindow makeKeyAndOrderFront:sender];
137 - (IBAction)reload:(id)sender {
141 rq = [NSURLRequest requestWithURL:[self->connection url] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10.0];
143 [self debugWithFormat:@"%s request: %@", __PRETTY_FUNCTION__, rq];
144 [[self->webView mainFrame] loadRequest:rq];
147 [self->webView reload:self];
149 [self viewApplication:sender];
152 - (IBAction)back:(id)sender {
153 [self->webView goBack];
156 - (IBAction)viewApplication:(id)sender {
157 [self _selectTabWithIdentifier:SOPEXApplicationTabIdentifier];
160 - (IBAction)viewSource:(id)sender {
161 NSString *componentName;
163 componentName = [[self->document path] lastPathComponent];
164 if([componentName hasSuffix:@"wo"]) {
165 [self->woComponentNameField setStringValue:componentName];
166 [self _selectTabWithIdentifier:SOPEXWOTabIdentifier];
169 [self->woxNameField setStringValue:componentName];
170 [self _selectTabWithIdentifier:SOPEXWOXTabIdentifier];
174 - (IBAction)viewHTML:(id)sender {
175 WebDataSource *dataSource;
176 id <WebDocumentRepresentation> representation;
179 dataSource = [[self->webView mainFrame] dataSource];
180 NSAssert(dataSource != nil, @"dataSource not yet committed?!");
181 NSAssert([dataSource isLoading] == NO, @"dataSource not finished loading?!");
183 representation = [dataSource representation];
185 if([representation canProvideDocumentSource])
186 source = [representation documentSource];
190 [self->htmlView setString:source];
191 [self _selectTabWithIdentifier:SOPEXHTMLTabIdentifier];
194 - (IBAction)viewHTTP:(id)sender {
195 WebDataSource *dataSource;
196 NSHTTPURLResponse *response;
197 NSDictionary *headerFields;
201 dataSource = [[self->webView mainFrame] dataSource];
202 response = (NSHTTPURLResponse *)[dataSource response];
204 headerFields = [response allHeaderFields];
205 headers = [headerFields allKeys];
206 count = [headers count];
208 [self->responseHeaderValues removeAllObjects];
210 for(i = 0; i < count; i++) {
211 NSString *header, *value;
212 NSDictionary *headerValueInfo;
214 header = [headers objectAtIndex:i];
215 value = [headerFields objectForKey:header];
216 headerValueInfo = [[NSDictionary alloc] initWithObjectsAndKeys:value,
217 @"value", header, @"header", nil];
218 [self->responseHeaderValues addObject:headerValueInfo];
219 [headerValueInfo release];
222 [self->responseHeaderInfoTableView reloadData];
223 [self _selectTabWithIdentifier:SOPEXHTTPTabIdentifier];
226 - (void)_selectTabWithIdentifier:(NSString *)identifier {
227 [self->tabView selectTabViewItemWithIdentifier:identifier];
230 - (IBAction)saveDocument:(id)sender {
231 if(self->document == nil)
233 if([self->document hasChanges]) {
234 if(![self->document performSave]) {
238 [self->mainWindow setDocumentEdited:NO];
242 - (IBAction)revertDocumentToSaved:(id)sender {
243 [self->document revertChanges];
244 [self->mainWindow setDocumentEdited:NO];
247 - (IBAction)toggleToolbar:(id)sender {
248 if([self->mainWindow toolbar] == nil)
249 [self->toolbarController applyOnWindow:self->mainWindow];
251 [self->mainWindow setToolbar:nil];
254 - (IBAction)editInXcode:(id)sender {
257 path = [self->document path];
258 [[NSWorkspace sharedWorkspace] openFile:path withApplication:@"Xcode" andDeactivate:YES];
264 - (BOOL)validateToolbarItem:(NSToolbarItem *)theItem {
265 return [self validateMenuItem:(id <NSMenuItem>)theItem];
268 - (BOOL)validateMenuItem:(id <NSMenuItem>)_item {
269 SEL action = [_item action];
273 NSLog(@"%s action:%@", __PRETTY_FUNCTION__, NSStringFromSelector(action));
275 if(action == @selector(back:))
276 return [self->webView canGoBack];
277 else if(action == @selector(saveDocument:) ||
278 action == @selector(revertDocumentToSaved:))
280 return self->document == nil ? NO : [self->document hasChanges];
283 tabId = [[self->tabView selectedTabViewItem] identifier];
284 if(action == @selector(viewApplication:)) {
285 BOOL isOn = [tabId isEqualToString:SOPEXApplicationTabIdentifier];
286 [_item setState:isOn ? NSOnState : NSOffState];
288 else if(action == @selector(viewSource:)) {
289 BOOL isOn = ([tabId isEqualToString:SOPEXWOXTabIdentifier] ||
290 [tabId isEqualToString:SOPEXWOTabIdentifier]);
291 [_item setState:isOn ? NSOnState : NSOffState];
293 return self->document != nil ? YES : NO;
295 else if(action == @selector(viewHTML:)) {
297 WebDataSource *dataSource;
299 isOn = [tabId isEqualToString:SOPEXHTMLTabIdentifier];
300 [_item setState:isOn ? NSOnState : NSOffState];
301 dataSource = [[self->webView mainFrame] dataSource];
302 if(dataSource == nil)
304 return [dataSource isLoading] == NO;
306 else if(action == @selector(viewHTTP:)) {
307 BOOL isOn = [tabId isEqualToString:SOPEXHTTPTabIdentifier];
308 [_item setState:isOn ? NSOnState : NSOffState];
314 /* SOPEXDocumentController PROTOCOL */
316 - (NSTextView *)document:(SOPEXDocument *)_document
317 textViewForType:(NSString *)_fileType
319 if([_document isKindOfClass:[SOPEXWODocument class]]) {
320 if([_fileType isEqualToString:@"html"])
321 return self->woSourceView;
322 return self->woDefinitionView;
324 return self->woxSourceView;
327 - (void)document:(SOPEXDocument *)document
328 didValidateWithError:(NSError *)error
329 forType:(NSString *)fileType
331 [self viewSource:self];
337 - (void)setStatus:(NSString *)_msg {
338 [self setStatus:_msg isError:NO];
341 - (void)setStatus:(NSString *)_msg isError:(BOOL)isError {
344 [self->statusBarTextField setStringValue:_msg];
347 - (void)flushDocument {
348 [self->document release];
349 self->document = nil;
350 [self->mainWindow setDocumentEdited:NO];
353 - (void)createDocumentFromResponse {
354 WebDataSource *dataSource;
355 NSHTTPURLResponse *response;
356 NSDictionary *headerFields;
357 NSString *templatePath;
359 dataSource = [[self->webView mainFrame] dataSource];
360 response = (NSHTTPURLResponse *)[dataSource response];
362 if([response isKindOfClass:[NSHTTPURLResponse class]]) {
363 headerFields = [response allHeaderFields];
364 // NOTE: WebKit cuddly-capses header keys!
365 templatePath = [headerFields objectForKey:@"X-Sope-Template-Path"];
366 if(templatePath == nil)
369 if([templatePath hasSuffix:@"wo"])
370 self->document = [[SOPEXWODocument alloc] initWithPath:templatePath
373 self->document = [[SOPEXWOXDocument alloc] initWithPath:templatePath
379 /* window delegate */
382 - (BOOL)windowShouldClose:(id)sender {
385 [self debugWithFormat:@"%s sender:%@", __PRETTY_FUNCTION__, sender];
388 if(sender != self->mainWindow)
391 if(self->document != nil && [self->document hasChanges]) {
395 panel = NSGetAlertPanel(
396 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"),
397 NSLocalizedString(@"If you don\\u2019t save, your changes will be lost.", "Message of the alert sheet when unsaved changes are about to be lost"),
398 NSLocalizedString(@"Save", "Default button text for the alert sheet"),
399 NSLocalizedString(@"Don\\u2019t save", "Alternate button text for the alert sheet"),
400 NSLocalizedString(@"Cancel", "Other button text for the alert sheet")
403 rc = SOPEXRunSheetModalForWindow(panel, self->mainWindow);
404 NSReleaseAlertPanel(panel);
406 // NSAlertOtherReturn == Cancel
407 // NSAlertAlternateReturn == Don't save
408 // NSAlertDefaultReturn == Save
410 if(rc == NSAlertOtherReturn)
412 if(rc == NSAlertDefaultReturn)
413 [self saveDocument:self];
414 [self flushDocument];
419 /* tableview datasource */
421 - (int)numberOfRowsInTableView:(NSTableView *)_tableView {
422 return [self->responseHeaderValues count];
425 - (id)tableView:(NSTableView *)_tableView
426 objectValueForTableColumn:(NSTableColumn *)_tableColumn
429 return [[self->responseHeaderValues objectAtIndex:_rowIndex]
430 objectForKey:[_tableColumn identifier]];
434 /* WebResourceLoadDelegate */
436 - (id)webView:(WebView *)_sender
437 identifierForInitialRequest:(NSURLRequest *)_rq
438 fromDataSource:(WebDataSource *)_ds
440 return [[_rq URL] absoluteString];
443 - (NSURLRequest *)webView:(WebView *)_sender
445 willSendRequest:(NSURLRequest *)_rq
446 redirectResponse:(NSURLResponse *)redirectResponse
447 fromDataSource:(WebDataSource *)_ds
449 /* use that to patch resource requests to local files ;-) */
454 [self debugWithFormat:@"%s: %@ request: %@ url: %@",
460 if (![self->connection shouldRewriteRequestURL:url])
463 if ((rurl = [self->connection rewriteRequestURL:url]) == nil)
465 if ([rurl isEqual:url])
468 return [NSURLRequest requestWithURL:rurl
469 cachePolicy:NSURLRequestUseProtocolCachePolicy
470 timeoutInterval:5.0];
473 - (void)webView:(WebView *)_sender
475 didReceiveContentLength:(unsigned)_length
476 fromDataSource:(WebDataSource *)_ds
478 //NSLog(@"%s: %@ len: %d", __PRETTY_FUNCTION__, _rid, _length);
481 - (void)webView:(WebView *)_sender
483 didFinishLoadingFromDataSource:(WebDataSource *)_ds
485 NSURLResponse *r = [_ds response];
488 [self debugWithFormat:@"%s: %@ ds: %@\n data-len: %i\n response: %@\n "
489 @"type: %@\n enc: %@",
490 __PRETTY_FUNCTION__, _rid, _ds,
491 [[_ds data] length], r, [r MIMEType],
492 [r textEncodingName]];
494 [self->connection processResponse:[_ds response] data:[_ds data]];
497 - (void)webView:(WebView *)_sender
499 didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)_c
500 fromDataSource:(WebDataSource *)_ds
503 [self debugWithFormat:@"%s: %@ ds: %@", __PRETTY_FUNCTION__, _rid, _ds];
506 - (void)webView:(WebView *)_sender
507 resource:(id)_identifier
508 didReceiveResponse:(NSURLResponse *)_response
509 fromDataSource:(WebDataSource *)_ds
512 [self debugWithFormat:@"%s: view: %@\n resource: %@\n received: %@\n"
513 @" datasource: %@\n data-len: %i%s",
515 _sender, _identifier, _response, _ds,
517 [_ds isLoading]? " LOADING" : ""];
522 /* WebFrameLoadDelegate */
525 - (void)webView:(WebView *)sender
526 didStartProvisionalLoadForFrame:(WebFrame *)frame
528 if(self->document != nil && [self->document hasChanges]) {
532 panel = NSGetAlertPanel(
533 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"),
534 NSLocalizedString(@"If you don\\u2019t save, your changes will be lost.", "Message of the alert sheet when unsaved changes are about to be lost"),
535 NSLocalizedString(@"Save", "Default button text for the alert sheet"),
536 NSLocalizedString(@"Don\\u2019t save", "Alternate button text for the alert sheet"),
539 rc = SOPEXRunSheetModalForWindow(panel, self->mainWindow);
540 NSReleaseAlertPanel(panel);
542 // NSAlertOtherReturn == Cancel
543 // NSAlertAlternateReturn == Don't save
544 // NSAlertDefaultReturn == Save
546 if(rc == NSAlertDefaultReturn)
547 [self saveDocument:self];
549 [self flushDocument];
550 [self->progressIndicator startAnimation:self];
553 - (void)webView:(WebView *)sender
554 didReceiveTitle:(NSString *)_title
555 forFrame:(WebFrame *)_frame
557 [self->mainWindow setTitle:_title];
561 - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {
562 [self->progressIndicator stopAnimation:self];
565 #warning ** setFavIcon enabled ... doesnt really make sense
566 [self->mainWindow setFavIcon:[sender pageIcon]];
568 [self createDocumentFromResponse];
571 - (void)webView:(WebView *)sender
572 didFailLoadWithError:(NSError *)error
573 forFrame:(WebFrame *)frame
575 [self->progressIndicator stopAnimation:self];
576 [self setStatus:[error localizedDescription] isError:YES];
579 - (void)webView:(WebView *)sender
580 didFailProvisionalLoadWithError:(NSError *)error
581 forFrame:(WebFrame *)frame
583 [self webView:sender didFailLoadWithError:error forFrame:frame];
587 /* WebView UI delegate */
589 - (BOOL)webViewIsStatusBarVisible:(WebView *)sender {
593 - (void)webView:(WebView *)sender
594 mouseDidMoveOverElement:(NSDictionary *)_info
595 modifierFlags:(unsigned int)_flags
600 NSLog(@"%s _info:%@", __PRETTY_FUNCTION__, _info);
604 url = [_info objectForKey:WebElementImageURLKey];
609 NSMutableString *status;
611 altString = [_info objectForKey:WebElementImageAltStringKey];
612 imageRect = [[_info objectForKey:WebElementImageRectKey] rectValue];
613 image = [_info objectForKey:WebElementImageKey];
615 status = [NSMutableString string];
618 altString = [url absoluteString];
620 url = [_info objectForKey:WebElementLinkURLKey];
622 [status appendFormat:@"%@ ", [url absoluteString]];
624 [status appendFormat:@"[%@] (w:%.0f h:%.0f)",
626 imageRect.size.width, imageRect.size.height];
627 if(NSEqualSizes([image size], imageRect.size) == NO) {
628 NSSize size = [image size];
629 [status appendFormat:@" -> scaled from (w:%.0f h:%.0f)!", size.width, size.height];
632 [self setStatus:status];
636 url = [_info objectForKey:WebElementLinkURLKey];
638 [self setStatus:[url absoluteString]];
642 [self setStatus:nil];