Bug 452317 - FeedConverter.js: QueryInterface should throw NS_ERROR_NO_INTERFACE...
[wine-gecko.git] / toolkit / crashreporter / client / crashreporter_osx.mm
blob453ace46ed5349922ff8da17db84c9a05ac2c9b3
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4  *
5  * The contents of this file are subject to the Mozilla Public License Version
6  * 1.1 (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  * http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the
13  * License.
14  *
15  * The Original Code is Mozilla Toolkit Crash Reporter
16  *
17  * The Initial Developer of the Original Code is
18  *   Mozilla Corporation
19  * Portions created by the Initial Developer are Copyright (C) 2006
20  * the Initial Developer. All Rights Reserved.
21  *
22  * Contributor(s):
23  *   Dave Camp <dcamp@mozilla.com>
24  *   Ted Mielczarek <ted.mielczarek@gmail.com>
25  *
26  * Alternatively, the contents of this file may be used under the terms of
27  * either the GNU General Public License Version 2 or later (the "GPL"), or
28  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29  * in which case the provisions of the GPL or the LGPL are applicable instead
30  * of those above. If you wish to allow use of your version of this file only
31  * under the terms of either the GPL or the LGPL, and not to allow others to
32  * use your version of this file under the terms of the MPL, indicate your
33  * decision by deleting the provisions above and replace them with the notice
34  * and other provisions required by the GPL or the LGPL. If you do not delete
35  * the provisions above, a recipient may use your version of this file under
36  * the terms of any one of the MPL, the GPL or the LGPL.
37  *
38  * ***** END LICENSE BLOCK ***** */
40 #import <Cocoa/Cocoa.h>
41 #import <CoreFoundation/CoreFoundation.h>
42 #include "crashreporter.h"
43 #include "crashreporter_osx.h"
44 #include <sys/stat.h>
45 #include <sys/types.h>
46 #include <fcntl.h>
47 #include <sstream>
49 using std::string;
50 using std::vector;
51 using std::ostringstream;
53 using namespace CrashReporter;
55 static NSAutoreleasePool* gMainPool;
56 static CrashReporterUI* gUI = 0;
57 static string gDumpFile;
58 static StringTable gQueryParameters;
59 static string gURLParameter;
60 static string gSendURL;
61 static vector<string> gRestartArgs;
62 static bool gDidTrySend = false;
63 static bool gRTLlayout = false;
65 #define NSSTR(s) [NSString stringWithUTF8String:(s).c_str()]
67 static NSString* Str(const char* aName)
69   string str = gStrings[aName];
70   if (str.empty()) str = "?";
71   return NSSTR(str);
74 static bool RestartApplication()
76   char** argv = reinterpret_cast<char**>(
77     malloc(sizeof(char*) * (gRestartArgs.size() + 1)));
79   if (!argv) return false;
81   unsigned int i;
82   for (i = 0; i < gRestartArgs.size(); i++) {
83     argv[i] = (char*)gRestartArgs[i].c_str();
84   }
85   argv[i] = 0;
87   pid_t pid = fork();
88   if (pid == -1)
89     return false;
90   else if (pid == 0) {
91     (void)execv(argv[0], argv);
92     _exit(1);
93   }
95   free(argv);
97   return true;
100 @implementation CrashReporterUI
102 -(void)awakeFromNib
104   gUI = self;
105   [mWindow center];
107   [mWindow setTitle:[[NSBundle mainBundle]
108                       objectForInfoDictionaryKey:@"CFBundleName"]];
111 -(void)showCrashUI:(const string&)dumpfile
112    queryParameters:(const StringTable&)queryParameters
113            sendURL:(const string&)sendURL
115   gDumpFile = dumpfile;
116   gQueryParameters = queryParameters;
117   gSendURL = sendURL;
119   [mWindow setTitle:Str(ST_CRASHREPORTERTITLE)];
120   [mHeaderLabel setStringValue:Str(ST_CRASHREPORTERHEADER)];
122   NSRect viewReportFrame = [mViewReportButton frame];
123   [mViewReportButton setTitle:Str(ST_VIEWREPORT)];
124   [mViewReportButton sizeToFit];
125   if (gRTLlayout) {
126     // sizeToFit will keep the left side fixed, so realign
127     float oldWidth = viewReportFrame.size.width;
128     viewReportFrame = [mViewReportButton frame];
129     viewReportFrame.origin.x += oldWidth - viewReportFrame.size.width;
130     [mViewReportButton setFrame: viewReportFrame];
131   }
133   [mSubmitReportButton setTitle:Str(ST_CHECKSUBMIT)];
134   [mIncludeURLButton setTitle:Str(ST_CHECKURL)];
135   [mEmailMeButton setTitle:Str(ST_CHECKEMAIL)];
136   [mViewReportOkButton setTitle:Str(ST_OK)];
138   [mCommentText setPlaceholder:Str(ST_COMMENTGRAYTEXT)];
139   if (gRTLlayout)
140     [mCommentText toggleBaseWritingDirection:self];
141   [[mEmailText cell] setPlaceholderString:Str(ST_EMAILGRAYTEXT)];
143   if (gQueryParameters.find("URL") != gQueryParameters.end()) {
144     // save the URL value in case the checkbox gets unchecked
145     gURLParameter = gQueryParameters["URL"];
146   }
147   else {
148     // no URL specified, hide checkbox
149     [mIncludeURLButton removeFromSuperview];
150     // shrink window to fit
151     NSRect frame = [mWindow frame];
152     NSRect includeURLFrame = [mIncludeURLButton frame];
153     NSRect emailFrame = [mEmailMeButton frame];
154     int buttonMask = [mViewReportButton autoresizingMask];
155     int checkMask = [mSubmitReportButton autoresizingMask];
156     int commentScrollMask = [mCommentScrollView autoresizingMask];
158     [mViewReportButton setAutoresizingMask:NSViewMinYMargin];
159     [mSubmitReportButton setAutoresizingMask:NSViewMinYMargin];
160     [mCommentScrollView setAutoresizingMask:NSViewMinYMargin];
162     // remove all the space in between
163     frame.size.height -= includeURLFrame.origin.y - emailFrame.origin.y;
164     [mWindow setFrame:frame display: true animate:NO];
166     [mViewReportButton setAutoresizingMask:buttonMask];
167     [mSubmitReportButton setAutoresizingMask:checkMask];
168     [mCommentScrollView setAutoresizingMask:commentScrollMask];
169   }
171   // resize some buttons horizontally and possibly some controls vertically
172   [self doInitialResizing];
174   // load default state of submit checkbox
175   // we don't just do this via IB because we want the default to be
176   // off a certain percentage of the time
177   BOOL submitChecked = NO;
178   NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
179   if (nil != [userDefaults objectForKey:@"submitReport"]) {
180     submitChecked =  [userDefaults boolForKey:@"submitReport"];
181   }
182   else {
183     // use compile-time specified enable percentage
184     submitChecked = ShouldEnableSending();
185     [userDefaults setBool:submitChecked forKey:@"submitReport"];
186   }
187   [mSubmitReportButton setState:(submitChecked ? NSOnState : NSOffState)];
188   
189   [self updateSubmit];
190   [self updateURL];
191   [self updateEmail];
193   [mWindow makeKeyAndOrderFront:nil];
196 -(void)showErrorUI:(const string&)message
198   [self setView: mErrorView animate: NO];
200   [mErrorHeaderLabel setStringValue:Str(ST_CRASHREPORTERHEADER)];
201   [self setStringFitVertically:mErrorLabel
202                         string:NSSTR(message)
203                   resizeWindow:YES];
204   [mErrorCloseButton setTitle:Str(ST_OK)];
206   [mErrorCloseButton setKeyEquivalent:@"\r"];
207   [mWindow makeFirstResponder:mErrorCloseButton];
208   [mWindow makeKeyAndOrderFront:nil];
211 -(void)showReportInfo
213   NSDictionary* boldAttr = [NSDictionary
214                             dictionaryWithObject:
215                             [NSFont boldSystemFontOfSize:
216                              [NSFont smallSystemFontSize]]
217                                           forKey:NSFontAttributeName];
218   NSDictionary* normalAttr = [NSDictionary
219                               dictionaryWithObject:
220                               [NSFont systemFontOfSize:
221                                [NSFont smallSystemFontSize]]
222                                             forKey:NSFontAttributeName];
224   [mViewReportTextView setString:@""];
225   for (StringTable::iterator iter = gQueryParameters.begin();
226        iter != gQueryParameters.end();
227        iter++) {
228     NSAttributedString* key = [[NSAttributedString alloc]
229                                initWithString:NSSTR(iter->first + ": ")
230                                attributes:boldAttr];
231     NSAttributedString* value = [[NSAttributedString alloc]
232                                  initWithString:NSSTR(iter->second + "\n")
233                                  attributes:normalAttr];
234     [[mViewReportTextView textStorage] appendAttributedString: key];
235     [[mViewReportTextView textStorage] appendAttributedString: value];
236     [key release];
237     [value release];
238   }
240   NSAttributedString* extra = [[NSAttributedString alloc]
241                                initWithString:NSSTR("\n" + gStrings[ST_EXTRAREPORTINFO])
242                                attributes:normalAttr];
243   [[mViewReportTextView textStorage] appendAttributedString: extra];
244   [extra release];
247 - (void)maybeSubmitReport
249   if ([mSubmitReportButton state] == NSOnState) {
250     [self setStringFitVertically:mProgressText
251                           string:Str(ST_REPORTDURINGSUBMIT)
252                     resizeWindow:YES];
253     // disable all the controls
254     [self enableControls:NO];
255     [mSubmitReportButton setEnabled:NO];
256     [mRestartButton setEnabled:NO];
257     [mCloseButton setEnabled:NO];
258     [mProgressIndicator startAnimation:self];
259     gDidTrySend = true;
260     [self sendReport];
261   } else {
262     [NSApp terminate:self];
263   }
266 - (void)closeMeDown:(id)unused
268   [NSApp terminate:self];
271 -(IBAction)submitReportClicked:(id)sender
273   [self updateSubmit];
274   NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
275   [userDefaults setBool:[mSubmitReportButton state] == NSOnState
276    forKey:@"submitReport"];
279 -(IBAction)viewReportClicked:(id)sender
281   [self showReportInfo];
282   [NSApp beginSheet:mViewReportWindow modalForWindow:mWindow
283    modalDelegate:nil didEndSelector:nil contextInfo:nil];
286 - (IBAction)viewReportOkClicked:(id)sender;
288   [mViewReportWindow orderOut:nil];
289   [NSApp endSheet:mViewReportWindow];
292 -(IBAction)closeClicked:(id)sender
294   [self maybeSubmitReport];
297 -(IBAction)restartClicked:(id)sender
299   RestartApplication();
300   [self maybeSubmitReport];
303 - (IBAction)includeURLClicked:(id)sender
305   [self updateURL];
308 -(IBAction)emailMeClicked:(id)sender
310   [self updateEmail];
313 -(void)controlTextDidChange:(NSNotification *)note
315   [self updateEmail];
318 - (void)textDidChange:(NSNotification *)aNotification
320   // update comment parameter
321   if ([[[mCommentText textStorage] mutableString] length] > 0)
322     gQueryParameters["Comments"] = [[[mCommentText textStorage] mutableString]
323                                     UTF8String];
324   else
325     gQueryParameters.erase("Comments");
328 // Limit the comment field to 500 bytes in UTF-8
329 - (BOOL)textView:(NSTextView *)aTextView shouldChangeTextInRange:(NSRange)affectedCharRange replacementString:(NSString *)replacementString
331   // current string length + replacement text length - replaced range length
332   if (([[aTextView string]
333         lengthOfBytesUsingEncoding:NSUTF8StringEncoding]
334            + [replacementString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]
335            - [[[aTextView string] substringWithRange:affectedCharRange]
336         lengthOfBytesUsingEncoding:NSUTF8StringEncoding])
337             > MAX_COMMENT_LENGTH) {
338     return NO;
339   }
340   return YES;
343 - (void)doInitialResizing
345   NSRect windowFrame = [mWindow frame];
346   NSRect restartFrame = [mRestartButton frame];
347   NSRect closeFrame = [mCloseButton frame];
348   // resize close button to fit text
349   float oldCloseWidth = closeFrame.size.width;
350   [mCloseButton setTitle:Str(ST_QUIT)];
351   [mCloseButton sizeToFit];
352   closeFrame = [mCloseButton frame];
353   // move close button left if it grew
354   if (!gRTLlayout) {
355     closeFrame.origin.x -= closeFrame.size.width - oldCloseWidth;
356   }
358   if (gRestartArgs.size() == 0) {
359     [mRestartButton removeFromSuperview];
360     if (!gRTLlayout) {
361       closeFrame.origin.x = restartFrame.origin.x +
362         (restartFrame.size.width - closeFrame.size.width);
363     }
364     else {
365       closeFrame.origin.x = restartFrame.origin.x;
366     }
367     [mCloseButton setFrame: closeFrame];
368     [mCloseButton setKeyEquivalent:@"\r"];
369   } else {
370     [mRestartButton setTitle:Str(ST_RESTART)];
371     // resize "restart" button
372     float oldRestartWidth = restartFrame.size.width;
373     [mRestartButton sizeToFit];
374     restartFrame = [mRestartButton frame];
375     if (!gRTLlayout) {
376       // move left by the amount that the button grew
377       restartFrame.origin.x -= restartFrame.size.width - oldRestartWidth;
378       closeFrame.origin.x -= restartFrame.size.width - oldRestartWidth;
379     }
380     else {
381       // shift the close button right in RTL
382       closeFrame.origin.x += restartFrame.size.width - oldRestartWidth;
383     }
384     [mRestartButton setFrame: restartFrame];
385     [mCloseButton setFrame: closeFrame];
386     // possibly resize window if both buttons no longer fit
387     // leave 20 px from either side of the window, and 12 px
388     // between the buttons
389     float neededWidth = closeFrame.size.width + restartFrame.size.width +
390                         2*20 + 12;
391     
392     if (neededWidth > windowFrame.size.width) {
393       windowFrame.size.width = neededWidth;
394       [mWindow setFrame:windowFrame display: true animate: NO];
395     }
396     [mRestartButton setKeyEquivalent:@"\r"];
397   }
399   NSButton *checkboxes[] = {
400     mSubmitReportButton,
401     mIncludeURLButton,
402     mEmailMeButton
403   };
405   for (int i=0; i<3; i++) {
406     NSRect frame = [checkboxes[i] frame];
407     [checkboxes[i] sizeToFit];
408     if (gRTLlayout) {
409       // sizeToFit will keep the left side fixed, so realign
410       float oldWidth = frame.size.width;
411       frame = [checkboxes[i] frame];
412       frame.origin.x += oldWidth - frame.size.width;
413       [checkboxes[i] setFrame: frame];
414     }
415     // keep existing spacing on left side, + 20 px spare on right
416     float neededWidth = frame.origin.x + frame.size.width + 20;
417     if (neededWidth > windowFrame.size.width) {
418       windowFrame.size.width = neededWidth;
419       [mWindow setFrame:windowFrame display: true animate: NO];
420     }
421   }
423   // do this down here because we may have made the window wider
424   // up above
425   [self setStringFitVertically:mDescriptionLabel
426                         string:Str(ST_CRASHREPORTERDESCRIPTION)
427                   resizeWindow:YES];
429   // now pin all the controls (except quit/submit) in place,
430   // if we lengthen the window after this, it's just to lengthen
431   // the progress text, so nothing above that text should move.
432   NSView* views[] = {
433     mSubmitReportButton,
434     mViewReportButton,
435     mCommentScrollView,
436     mIncludeURLButton,
437     mEmailMeButton,
438     mEmailText,
439     mProgressIndicator,
440     mProgressText
441   };
442   for (unsigned int i=0; i<sizeof(views)/sizeof(views[0]); i++) {
443     [views[i] setAutoresizingMask:NSViewMinYMargin];
444   }
447 -(float)setStringFitVertically:(NSControl*)control
448                         string:(NSString*)str
449                   resizeWindow:(BOOL)resizeWindow
451   // hack to make the text field grow vertically
452   NSRect frame = [control frame];
453   float oldHeight = frame.size.height;
455   frame.size.height = 10000;
456   NSSize oldCellSize = [[control cell] cellSizeForBounds: frame];
457   [control setStringValue: str];
458   NSSize newCellSize = [[control cell] cellSizeForBounds: frame];
460   float delta = newCellSize.height - oldCellSize.height;
461   frame.origin.y -= delta;
462   frame.size.height = oldHeight + delta;
463   [control setFrame: frame];
465   if (resizeWindow) {
466     NSRect frame = [mWindow frame];
467     frame.origin.y -= delta;
468     frame.size.height += delta;
469     [mWindow setFrame:frame display: true animate: NO];
470   }
472   return delta;
475 -(void)setView: (NSView*)v animate: (BOOL)animate
477   NSRect frame = [mWindow frame];
479   NSRect oldViewFrame = [[mWindow contentView] frame];
480   NSRect newViewFrame = [v frame];
482   frame.origin.y += oldViewFrame.size.height - newViewFrame.size.height;
483   frame.size.height -= oldViewFrame.size.height - newViewFrame.size.height;
485   frame.origin.x += oldViewFrame.size.width - newViewFrame.size.width;
486   frame.size.width -= oldViewFrame.size.width - newViewFrame.size.width;
488   [mWindow setContentView:v];
489   [mWindow setFrame:frame display:true animate:animate];
492 - (void)enableControls:(BOOL)enabled
494   [mViewReportButton setEnabled:enabled];
495   [mIncludeURLButton setEnabled:enabled];
496   [mEmailMeButton setEnabled:enabled];
497   [mCommentText setEnabled:enabled];
498   [mCommentScrollView setHasVerticalScroller:enabled];
499   [self updateEmail];
502 -(void)updateSubmit
504   if ([mSubmitReportButton state] == NSOnState) {
505     [self setStringFitVertically:mProgressText
506                           string:Str(ST_REPORTPRESUBMIT)
507                     resizeWindow:YES];
508     [mProgressText setHidden:NO];
509     // enable all the controls
510     [self enableControls:YES];
511   }
512   else {
513     // not submitting, disable all the controls under
514     // the submit checkbox, and hide the status text
515     [mProgressText setHidden:YES];
516     [self enableControls:NO];
517   }
520 -(void)updateURL
522   if ([mIncludeURLButton state] == NSOnState && !gURLParameter.empty()) {
523     gQueryParameters["URL"] = gURLParameter;
524   } else {
525     gQueryParameters.erase("URL");
526   }
529 -(void)updateEmail
531   if ([mEmailMeButton state] == NSOnState &&
532       [mSubmitReportButton state] == NSOnState) {
533     NSString* email = [mEmailText stringValue];
534     gQueryParameters["Email"] = [email UTF8String];
535     [mEmailText setEnabled:YES];
536   } else {
537     gQueryParameters.erase("Email");
538     [mEmailText setEnabled:NO];
539   }
542 -(void)sendReport
544   if (![self setupPost]) {
545     LogMessage("Crash report submission failed: could not set up POST data");
546    [self setStringFitVertically:mProgressText
547                           string:Str(ST_SUBMITFAILED)
548                     resizeWindow:YES];
549    // quit after 5 seconds
550    [self performSelector:@selector(closeMeDown:) withObject:nil
551     afterDelay:5.0];
552   }
554   [NSThread detachNewThreadSelector:@selector(uploadThread:)
555             toTarget:self
556             withObject:mPost];
559 -(bool)setupPost
561   NSURL* url = [NSURL URLWithString:NSSTR(gSendURL)];
562   if (!url) return false;
564   mPost = [[HTTPMultipartUpload alloc] initWithURL: url];
565   if (!mPost) return false;
567   NSMutableDictionary* parameters =
568     [[NSMutableDictionary alloc] initWithCapacity: gQueryParameters.size()];
569   if (!parameters) return false;
571   StringTable::const_iterator end = gQueryParameters.end();
572   for (StringTable::const_iterator i = gQueryParameters.begin();
573        i != end;
574        i++) {
575     NSString* key = NSSTR(i->first);
576     NSString* value = NSSTR(i->second);
577     [parameters setObject: value forKey: key];
578   }
580   [mPost addFileAtPath: NSSTR(gDumpFile) name: @"upload_file_minidump"];
581   [mPost setParameters: parameters];
582   [parameters release];
584   return true;
587 -(void)uploadComplete:(NSData*)data
589   NSHTTPURLResponse* response = [mPost response];
590   [mPost release];
592   bool success;
593   string reply;
594   if (!data || !response || [response statusCode] != 200) {
595     success = false;
596     reply = "";
598     // if data is nil, we probably logged an error in uploadThread
599     if (data != nil && response != nil) {
600       ostringstream message;
601       message << "Crash report submission failed: server returned status "
602               << [response statusCode];
603       LogMessage(message.str());
604     }
605   } else {
606     success = true;
607     LogMessage("Crash report submitted successfully");
609     NSString* encodingName = [response textEncodingName];
610     NSStringEncoding encoding;
611     if (encodingName) {
612       encoding = CFStringConvertEncodingToNSStringEncoding(
613         CFStringConvertIANACharSetNameToEncoding((CFStringRef)encodingName));
614     } else {
615       encoding = NSISOLatin1StringEncoding;
616     }
617     NSString* r = [[NSString alloc] initWithData: data encoding: encoding];
618     reply = [r UTF8String];
619     [r release];
620   }
622   SendCompleted(success, reply);
624   [mProgressIndicator stopAnimation:self];
625   if (success) {
626    [self setStringFitVertically:mProgressText
627                           string:Str(ST_REPORTSUBMITSUCCESS)
628                     resizeWindow:YES];
629   } else {
630    [self setStringFitVertically:mProgressText
631                           string:Str(ST_SUBMITFAILED)
632                     resizeWindow:YES];
633   }
634   // quit after 5 seconds
635   [self performSelector:@selector(closeMeDown:) withObject:nil
636    afterDelay:5.0];
639 -(void)uploadThread:(HTTPMultipartUpload*)post
641   NSAutoreleasePool* autoreleasepool = [[NSAutoreleasePool alloc] init];
642   NSError* error = nil;
643   NSData* data = [post send: &error];
644   if (error) {
645     data = nil;
646     NSString* errorDesc = [error localizedDescription];
647     string message = [errorDesc UTF8String];
648     LogMessage("Crash report submission failed: " + message);
649   }
651   [self performSelectorOnMainThread: @selector(uploadComplete:)
652         withObject: data
653         waitUntilDone: YES];
655   [autoreleasepool release];
658 // to get auto-quit when we close the window
659 -(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)theApplication
661   return YES;
664 -(void)applicationWillTerminate:(NSNotification *)aNotification
666   // since we use [NSApp terminate:] we never return to main,
667   // so do our cleanup here
668   if (!gDidTrySend)
669     DeleteDump();
672 @end
674 @implementation TextViewWithPlaceHolder
676 - (BOOL)becomeFirstResponder
678   [self setNeedsDisplay:YES];
679   return [super becomeFirstResponder];
682 - (void)drawRect:(NSRect)rect
684   [super drawRect:rect];
685   if (mPlaceHolderString && [[self string] isEqualToString:@""] &&
686       self != [[self window] firstResponder])
687     [mPlaceHolderString drawInRect:[self frame]];
690 - (BOOL)resignFirstResponder
692   [self setNeedsDisplay:YES];
693   return [super resignFirstResponder];
696 - (void)setPlaceholder:(NSString*)placeholder
698         NSColor* txtColor = [NSColor disabledControlTextColor];
699   NSDictionary* txtDict = [NSDictionary
700                            dictionaryWithObjectsAndKeys:txtColor,
701                            NSForegroundColorAttributeName, nil];
702   mPlaceHolderString = [[NSMutableAttributedString alloc]
703                        initWithString:placeholder attributes:txtDict];
704   if (gRTLlayout)
705     [mPlaceHolderString setAlignment:NSRightTextAlignment
706      range:NSMakeRange(0, [placeholder length])];
707   
710 - (void)insertTab:(id)sender
712   // don't actually want to insert tabs, just tab to next control
713   [[self window] selectNextKeyView:sender];
716 - (void)insertBacktab:(id)sender
718   [[self window] selectPreviousKeyView:sender];
721 - (void)setEnabled:(BOOL)enabled
723   [self setSelectable:enabled];
724   [self setEditable:enabled];
725   if (![[self string] isEqualToString:@""]) {
726     NSAttributedString* colorString;
727     NSColor* txtColor;
728     if (enabled)
729       txtColor = [NSColor textColor];
730     else
731       txtColor = [NSColor disabledControlTextColor];
732     NSDictionary *txtDict = [NSDictionary
733                              dictionaryWithObjectsAndKeys:txtColor,
734                              NSForegroundColorAttributeName, nil];
735     colorString = [[NSAttributedString alloc]
736                    initWithString:[self string]
737                    attributes:txtDict];
738     [[self textStorage] setAttributedString: colorString];
739     [self setInsertionPointColor:txtColor];
740     [colorString release];
741   }
744 - (void)dealloc
746   [mPlaceHolderString release];
747   [super dealloc];
750 @end
752 /* === Crashreporter UI Functions === */
754 bool UIInit()
756   gMainPool = [[NSAutoreleasePool alloc] init];
757   [NSApplication sharedApplication];
759   if (gStrings.find("isRTL") != gStrings.end() &&
760       gStrings["isRTL"] == "yes")
761     gRTLlayout = true;
763   [NSBundle loadNibNamed:(gRTLlayout ? @"MainMenuRTL" : @"MainMenu")
764                    owner:NSApp];
766   return true;
769 void UIShutdown()
771   [gMainPool release];
774 void UIShowDefaultUI()
776   [gUI showErrorUI: gStrings[ST_CRASHREPORTERDEFAULT]];
777   [NSApp run];
780 bool UIShowCrashUI(const string& dumpfile,
781                    const StringTable& queryParameters,
782                    const string& sendURL,
783                    const vector<string>& restartArgs)
785   gRestartArgs = restartArgs;
787   [gUI showCrashUI: dumpfile
788        queryParameters: queryParameters
789        sendURL: sendURL];
790   [NSApp run];
792   return gDidTrySend;
795 void UIError_impl(const string& message)
797   if (!gUI) {
798     // UI failed to initialize, printing is the best we can do
799     printf("Error: %s\n", message.c_str());
800     return;
801   }
803   [gUI showErrorUI: message];
804   [NSApp run];
807 bool UIGetIniPath(string& path)
809   path = gArgv[0];
810   path.append(".ini");
812   return true;
815 bool UIGetSettingsPath(const string& vendor,
816                        const string& product,
817                        string& settingsPath)
819   FSRef foundRef;
820   OSErr err = FSFindFolder(kUserDomain, kApplicationSupportFolderType,
821                            kCreateFolder, &foundRef);
822   if (err != noErr)
823     return false;
825   unsigned char path[PATH_MAX];
826   FSRefMakePath(&foundRef, path, sizeof(path));
827   NSString* destPath = [NSString stringWithUTF8String:reinterpret_cast<char*>(path)];
829   // Note that MacOS ignores the vendor when creating the profile hierarchy -
830   // all application preferences directories live alongside one another in
831   // ~/Library/Application Support/
832   destPath = [destPath stringByAppendingPathComponent: NSSTR(product)];
833   // Thunderbird stores its profile in ~/Library/Thunderbird,
834   // but we're going to put stuff in ~/Library/Application Support/Thunderbird
835   // anyway, so we have to ensure that path exists.
836   string tempPath = [destPath UTF8String];
837   if (!UIEnsurePathExists(tempPath))
838     return false;
840   destPath = [destPath stringByAppendingPathComponent: @"Crash Reports"];
842   settingsPath = [destPath UTF8String];
844   return true;
847 bool UIEnsurePathExists(const string& path)
849   int ret = mkdir(path.c_str(), S_IRWXU);
850   int e = errno;
851   if (ret == -1 && e != EEXIST)
852     return false;
854   return true;
857 bool UIFileExists(const string& path)
859   struct stat sb;
860   int ret = stat(path.c_str(), &sb);
861   if (ret == -1 || !(sb.st_mode & S_IFREG))
862     return false;
864   return true;
867 bool UIMoveFile(const string& file, const string& newfile)
869   return (rename(file.c_str(), newfile.c_str()) != -1);
872 bool UIDeleteFile(const string& file)
874   return (unlink(file.c_str()) != -1);
877 std::ifstream* UIOpenRead(const string& filename)
879   return new std::ifstream(filename.c_str(), std::ios::in);
882 std::ofstream* UIOpenWrite(const string& filename, bool append) // append=false
884   return new std::ofstream(filename.c_str(),
885                            append ? std::ios::out | std::ios::app
886                                   : std::ios::out);