]> err.no Git - sope/blob - sopex/SOPEX/SOPEXBrowserController.m
fixed some NGMail framework build issue
[sope] / sopex / SOPEX / SOPEXBrowserController.m
1 /*
2   Copyright (C) 2004 Marcus Mueller <znek@mulle-kybernetik.com>
3
4   This file is part of OpenGroupware.org.
5
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
9   later version.
10
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.
15
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
19   02111-1307, USA.
20 */
21
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"
36 #include "common.h"
37
38 #define DNC [NSNotificationCenter defaultCenter]
39 #define UD [NSUserDefaults standardUserDefaults]
40
41
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;
48 @end
49
50
51 @implementation SOPEXBrowserController
52
53 static unsigned controllersCount        = 0;
54
55 NSString *SOPEXApplicationTabIdentifier = @"application";
56 NSString *SOPEXWOTabIdentifier          = @"wo";
57 NSString *SOPEXWOXTabIdentifier         = @"wox";
58 NSString *SOPEXHTMLTabIdentifier        = @"html";
59 NSString *SOPEXHTTPTabIdentifier        = @"http";
60
61 static NGLogger *logger = nil;
62
63 + (void)initialize {
64   NGLoggerManager *lm;
65   static BOOL     didInit = NO;
66   
67   if(didInit) return;
68   didInit = YES;
69   lm      = [NGLoggerManager defaultLoggerManager];
70   logger  = [lm loggerForDefaultKey:@"SOPEXDebugEnabled"];
71 }
72
73 + (BOOL)hasActiveControllers {
74   return controllersCount != 0;
75 }
76
77 /* init & dealloc */
78
79 - (id)init {
80   self = [super init];
81   if(self) {
82     [NSBundle loadNibNamed:@"SOPEXBrowserController" owner:self];
83     NSAssert(self->mainWindow != nil,
84              @"Problem loading SOPEXBrowserController.nib!");
85     controllersCount += 1;
86   }
87   return self;
88 }
89
90 - (oneway void)release {
91 #warning !! FIXME
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,
94      though.
95   */
96 #if 0
97    [self errorWithFormat:@"%s THIS SHOULD NEVER HAPPEN!!", __PRETTY_FUNCTION__];
98 #endif
99 }
100
101 - (void)dealloc
102 {
103   [self->responseHeaderValues release];
104   [self->connection release];
105   [self->toolbarController release];
106   [self->document release];
107   [super dealloc];
108 }
109
110
111 /* setup */
112
113 - (void)awakeFromNib {
114   [self->webView setGroupName:@"WebUI"];
115   self->responseHeaderValues = [[NSMutableArray alloc] initWithCapacity:20];
116
117   self->toolbarController = [[SOPEXToolbarController alloc]
118     initWithIdentifier:@"SOPEXWebUI"
119     target:self];
120
121   [self setStatus:nil];
122   [self viewApplication:nil];
123 }
124
125
126 /* accessors */
127
128 - (void)setWebConnection:(SOPEXWebConnection *)_conn {
129   ASSIGN(self->connection, _conn);
130   if(logger)
131     [self debugWithFormat:@"%s connection: %@",
132                             __PRETTY_FUNCTION__,
133                             self->connection];
134 }
135
136
137 /* actions */
138
139 - (IBAction)orderFront:(id)sender {
140   [self->mainWindow makeKeyAndOrderFront:sender];
141 }
142
143 - (IBAction)reload:(id)sender {
144   if(sender == nil) {
145     NSURLRequest *rq;
146     
147     rq = [NSURLRequest requestWithURL:[self->connection url] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10.0];
148     if(logger)
149       [self debugWithFormat:@"%s request: %@", __PRETTY_FUNCTION__, rq];
150     [[self->webView mainFrame] loadRequest:rq];
151   }
152   else {
153     [self->webView reload:self];
154   }
155   [self viewApplication:sender];
156 }
157
158 - (IBAction)back:(id)sender {
159   [self->webView goBack];
160 }
161
162 - (IBAction)viewApplication:(id)sender {
163   [self _selectTabWithIdentifier:SOPEXApplicationTabIdentifier];
164 }
165
166 - (IBAction)viewSource:(id)sender {
167   NSString *componentName;
168   
169   componentName = [[self->document path] lastPathComponent];
170   if([componentName hasSuffix:@"wo"]) {
171     [self->woComponentNameField setStringValue:componentName];
172     [self _selectTabWithIdentifier:SOPEXWOTabIdentifier];
173   }
174   else {
175     [self->woxNameField setStringValue:componentName];
176     [self _selectTabWithIdentifier:SOPEXWOXTabIdentifier];
177   }
178 }
179
180 - (IBAction)viewHTML:(id)sender {
181   WebDataSource *dataSource;
182   id <WebDocumentRepresentation> representation;
183   NSString *source;
184   
185   dataSource = [[self->webView mainFrame] dataSource];
186   NSAssert(dataSource != nil, @"dataSource not yet committed?!");
187   NSAssert([dataSource isLoading] == NO, @"dataSource not finished loading?!");
188   
189   representation = [dataSource representation];
190   
191   if([representation canProvideDocumentSource])
192     source = [representation documentSource];
193   else
194     source = @"";
195   
196   [self->htmlView setString:source];
197   [self _selectTabWithIdentifier:SOPEXHTMLTabIdentifier];
198 }
199
200 - (IBAction)viewHTTP:(id)sender {
201   WebDataSource *dataSource;
202   NSHTTPURLResponse *response;
203   NSDictionary *headerFields;
204   NSArray *headers;
205   int count, i;
206   
207   dataSource = [[self->webView mainFrame] dataSource];
208   response = (NSHTTPURLResponse *)[dataSource response];
209   
210   headerFields = [response allHeaderFields];
211   headers = [headerFields allKeys];
212   count = [headers count];
213   
214   [self->responseHeaderValues removeAllObjects];
215   
216   for(i = 0; i < count; i++) {
217     NSString *header, *value;
218     NSDictionary *headerValueInfo;
219     
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];
226   }
227   
228   [self->responseHeaderInfoTableView reloadData];
229   [self _selectTabWithIdentifier:SOPEXHTTPTabIdentifier];
230 }
231
232 - (void)_selectTabWithIdentifier:(NSString *)identifier {
233   [self->tabView selectTabViewItemWithIdentifier:identifier];
234 }
235
236 - (IBAction)saveDocument:(id)sender {
237   if(self->document == nil)
238     return;
239   if([self->document hasChanges]) {
240     if(![self->document performSave]) {
241       NSBeep();
242       return;
243     }
244     [self->mainWindow setDocumentEdited:NO];
245   }
246 }
247
248 - (IBAction)revertDocumentToSaved:(id)sender {
249   [self->document revertChanges];
250   [self->mainWindow setDocumentEdited:NO];
251 }
252
253 - (IBAction)toggleToolbar:(id)sender {
254   if([self->mainWindow toolbar] == nil)
255     [self->toolbarController applyOnWindow:self->mainWindow];
256   else
257     [self->mainWindow setToolbar:nil];
258 }
259
260 - (IBAction)editInXcode:(id)sender {
261   NSString *path;
262   
263   path = [self->document path];
264   [[NSWorkspace sharedWorkspace] openFile:path withApplication:@"Xcode" andDeactivate:YES]; 
265 }
266
267
268 /* menu & toolbar */
269
270 - (BOOL)validateToolbarItem:(NSToolbarItem *)theItem {
271   return [self validateMenuItem:(id <NSMenuItem>)theItem];
272 }
273
274 - (BOOL)validateMenuItem:(id <NSMenuItem>)_item {
275   SEL action = [_item action];
276   NSString *tabId;
277
278 #if 0
279   NSLog(@"%s action:%@", __PRETTY_FUNCTION__, NSStringFromSelector(action));
280 #endif
281   if(action == @selector(back:))
282     return [self->webView canGoBack];
283   else if(action == @selector(saveDocument:) ||
284           action == @selector(revertDocumentToSaved:))
285   {
286     return self->document == nil ? NO : [self->document hasChanges];
287   }
288
289   tabId = [[self->tabView selectedTabViewItem] identifier];
290   if(action == @selector(viewApplication:)) {
291     BOOL isOn = [tabId isEqualToString:SOPEXApplicationTabIdentifier];
292     [_item setState:isOn ? NSOnState : NSOffState];
293   }
294   else if(action == @selector(viewSource:)) {
295     BOOL isOn = ([tabId isEqualToString:SOPEXWOXTabIdentifier] ||
296                  [tabId isEqualToString:SOPEXWOTabIdentifier]);
297     [_item setState:isOn ? NSOnState : NSOffState];
298
299     return self->document != nil ? YES : NO;
300   }
301   else if(action == @selector(viewHTML:)) {
302     BOOL isOn;
303     WebDataSource *dataSource;
304
305     isOn = [tabId isEqualToString:SOPEXHTMLTabIdentifier];
306     [_item setState:isOn ? NSOnState : NSOffState];
307     dataSource = [[self->webView mainFrame] dataSource];
308     if(dataSource == nil)
309       return NO;
310     return [dataSource isLoading] == NO;
311   }
312   else if(action == @selector(viewHTTP:)) {
313     BOOL isOn = [tabId isEqualToString:SOPEXHTTPTabIdentifier];
314     [_item setState:isOn ? NSOnState : NSOffState];
315   }
316   return YES;
317 }
318
319
320 /* SOPEXDocumentController PROTOCOL */
321
322 - (NSTextView *)document:(SOPEXDocument *)_document
323   textViewForType:(NSString *)_fileType
324 {
325   if([_document isKindOfClass:[SOPEXWODocument class]]) {
326       if([_fileType isEqualToString:@"html"])
327           return self->woSourceView;
328       return self->woDefinitionView;
329   }
330   return self->woxSourceView;
331 }
332
333 - (void)document:(SOPEXDocument *)document
334   didValidateWithError:(NSError *)error
335   forType:(NSString *)fileType
336 {
337   [self viewSource:self];
338 }
339
340
341 /* private api */
342
343 - (void)setStatus:(NSString *)_msg {
344   [self setStatus:_msg isError:NO];
345 }
346
347 - (void)setStatus:(NSString *)_msg isError:(BOOL)isError {
348   if(_msg == nil)
349       _msg = @"";
350   [self->statusBarTextField setStringValue:_msg];
351 }
352
353 - (void)flushDocument {
354   [self->document release];
355   self->document = nil;
356   [self->mainWindow setDocumentEdited:NO];
357 }
358
359 - (void)createDocumentFromResponse {
360   WebDataSource *dataSource;
361   NSHTTPURLResponse *response;
362   NSDictionary *headerFields;
363   NSString *templatePath;
364   
365   dataSource = [[self->webView mainFrame] dataSource];
366   response = (NSHTTPURLResponse *)[dataSource response];
367   
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)
373       return;
374     
375     if([templatePath hasSuffix:@"wo"])
376       self->document = [[SOPEXWODocument alloc] initWithPath:templatePath
377                                                 controller:self];
378     else
379       self->document = [[SOPEXWOXDocument alloc] initWithPath:templatePath
380                                                  controller:self];
381   }
382 }
383
384
385 /* window delegate */
386
387
388 - (BOOL)windowShouldClose:(id)sender {
389 #if 0
390   if(logger)
391     [self debugWithFormat:@"%s sender:%@", __PRETTY_FUNCTION__, sender];
392 #endif
393   
394   if(sender != self->mainWindow)
395     return YES;
396   
397   if(self->document != nil && [self->document hasChanges]) {
398     id panel;
399     int rc;
400     
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")
407       );
408     
409     rc = SOPEXRunSheetModalForWindow(panel, self->mainWindow);
410     NSReleaseAlertPanel(panel);
411     
412     // NSAlertOtherReturn == Cancel
413     // NSAlertAlternateReturn == Don't save
414     // NSAlertDefaultReturn == Save
415     
416     if(rc == NSAlertOtherReturn)
417       return NO;
418     if(rc == NSAlertDefaultReturn)
419       [self saveDocument:self];
420     [self flushDocument];
421   }
422   return YES;
423 }
424
425 - (void)windowWillClose:(NSNotification *)_notif {
426   controllersCount -= 1;
427   [[SOPEXAppController sharedController] browserControllerDidClose:self];
428 }
429
430 /* tableview datasource */
431
432 - (int)numberOfRowsInTableView:(NSTableView *)_tableView {
433   return [self->responseHeaderValues count];
434 }
435
436 - (id)tableView:(NSTableView *)_tableView
437   objectValueForTableColumn:(NSTableColumn *)_tableColumn
438   row:(int)_rowIndex
439 {
440   return [[self->responseHeaderValues objectAtIndex:_rowIndex]
441                                       objectForKey:[_tableColumn identifier]];
442 }
443
444
445 /* WebResourceLoadDelegate */
446
447 - (id)webView:(WebView *)_sender 
448   identifierForInitialRequest:(NSURLRequest *)_rq 
449   fromDataSource:(WebDataSource *)_ds
450 {
451   return [[_rq URL] absoluteString];
452 }
453
454 - (NSURLRequest *)webView:(WebView *)_sender 
455   resource:(id)_id
456   willSendRequest:(NSURLRequest *)_rq
457   redirectResponse:(NSURLResponse *)redirectResponse 
458   fromDataSource:(WebDataSource *)_ds
459 {
460   /* use that to patch resource requests to local files ;-) */
461   NSURL    *url, *rurl;
462   
463   url = [_rq URL];
464   if(logger)
465     [self debugWithFormat:@"%s: %@ request: %@ url: %@",
466                             __PRETTY_FUNCTION__,
467                             _id,
468                             _rq,
469                             url];
470
471   if (![self->connection shouldRewriteRequestURL:url])
472     return _rq;
473   
474   if ((rurl = [self->connection rewriteRequestURL:url]) == nil)
475     return _rq;
476   if ([rurl isEqual:url])
477     return _rq;
478   
479   return [NSURLRequest requestWithURL:rurl
480                        cachePolicy:NSURLRequestUseProtocolCachePolicy
481                        timeoutInterval:5.0];
482 }
483
484 - (void)webView:(WebView *)_sender
485   resource:(id)_rid 
486   didReceiveContentLength:(unsigned)_length 
487   fromDataSource:(WebDataSource *)_ds
488 {
489     //NSLog(@"%s: %@ len: %d", __PRETTY_FUNCTION__, _rid, _length);
490 }
491
492 - (void)webView:(WebView *)_sender
493   resource:(id)_rid 
494   didFinishLoadingFromDataSource:(WebDataSource *)_ds
495 {
496   NSURLResponse *r = [_ds response];
497   
498   if(logger) {
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]];
504   }
505   [self->connection processResponse:[_ds response] data:[_ds data]];
506 }
507
508 - (void)webView:(WebView *)_sender
509   resource:(id)_rid
510   didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)_c 
511   fromDataSource:(WebDataSource *)_ds
512 {
513   if(logger)
514     [self debugWithFormat:@"%s: %@ ds: %@", __PRETTY_FUNCTION__, _rid, _ds];
515 }
516
517 - (void)webView:(WebView *)_sender 
518   resource:(id)_identifier 
519   didReceiveResponse:(NSURLResponse *)_response 
520   fromDataSource:(WebDataSource *)_ds
521 {
522   if (logger) {
523     [self debugWithFormat:@"%s: view: %@\n  resource: %@\n  received: %@\n"
524                             @"  datasource: %@\n  data-len: %i%s",
525                             __PRETTY_FUNCTION__,
526                             _sender, _identifier, _response, _ds, 
527                             [[_ds data] length],
528                             [_ds isLoading]? " LOADING" : ""];
529   }
530 }
531
532
533 /* WebFrameLoadDelegate */
534
535
536 - (void)webView:(WebView *)sender
537   didStartProvisionalLoadForFrame:(WebFrame *)frame
538 {
539   if(self->document != nil && [self->document hasChanges]) {
540     id panel;
541     int rc;
542     
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"),
548       NULL);
549     
550     rc = SOPEXRunSheetModalForWindow(panel, self->mainWindow);
551     NSReleaseAlertPanel(panel);
552     
553     // NSAlertOtherReturn     == Cancel
554     // NSAlertAlternateReturn == Don't save
555     // NSAlertDefaultReturn   == Save
556
557     if(rc == NSAlertDefaultReturn)
558       [self saveDocument:self];
559   }
560   [self flushDocument];
561   [self->progressIndicator startAnimation:self];
562 }
563
564 - (void)webView:(WebView *)sender
565   didReceiveTitle:(NSString *)_title
566   forFrame:(WebFrame *)_frame
567 {
568   [self->mainWindow setTitle:_title];
569 }
570
571
572 - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {
573   [self->progressIndicator stopAnimation:self];
574   
575 #if 0
576 #warning ** setFavIcon enabled ... doesnt really make sense
577   [self->mainWindow setFavIcon:[sender pageIcon]];
578 #endif
579   [self createDocumentFromResponse];
580 }
581
582 - (void)webView:(WebView *)sender
583   didFailLoadWithError:(NSError *)error
584   forFrame:(WebFrame *)frame
585 {
586   [self->progressIndicator stopAnimation:self];
587   [self setStatus:[error localizedDescription] isError:YES];
588 }
589
590 - (void)webView:(WebView *)sender
591   didFailProvisionalLoadWithError:(NSError *)error
592   forFrame:(WebFrame *)frame
593 {
594   [self webView:sender didFailLoadWithError:error forFrame:frame];
595 }
596
597
598 /* WebView UI delegate */
599
600 - (BOOL)webViewIsStatusBarVisible:(WebView *)sender {
601   return YES;
602 }
603
604 - (void)webView:(WebView *)sender
605   mouseDidMoveOverElement:(NSDictionary *)_info
606   modifierFlags:(unsigned int)_flags
607 {
608   NSURL *url;
609   
610 #if 0
611   NSLog(@"%s _info:%@", __PRETTY_FUNCTION__, _info);
612 #endif
613   
614   
615   url = [_info objectForKey:WebElementImageURLKey];
616   if(url != nil) {
617     NSString *altString;
618     NSRect imageRect;
619     NSImage *image;
620     NSMutableString *status;
621     
622     altString = [_info objectForKey:WebElementImageAltStringKey];
623     imageRect = [[_info objectForKey:WebElementImageRectKey] rectValue];
624     image     = [_info objectForKey:WebElementImageKey];
625     
626     status = [NSMutableString string];
627     
628     if(altString == nil)
629       altString = [url absoluteString];
630     
631     url = [_info objectForKey:WebElementLinkURLKey];
632     if(url != nil)
633       [status appendFormat:@"%@    ", [url absoluteString]];
634     
635     [status appendFormat:@"[%@]   (w:%.0f h:%.0f)",
636                            altString,
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];
641     }
642     
643     [self setStatus:status];
644     return;
645   }
646   
647   url = [_info objectForKey:WebElementLinkURLKey];
648   if(url != nil) {
649     [self setStatus:[url absoluteString]];
650     return;
651   }
652   
653   [self setStatus:nil];
654 }
655
656 /* Logging */
657
658 - (id)debugLogger {
659   return logger;
660 }
661
662 @end