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
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/
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
15 * The Original Code is Mozilla Toolkit Crash Reporter
17 * The Initial Developer of the Original Code is
19 * Portions created by the Initial Developer are Copyright (C) 2006
20 * the Initial Developer. All Rights Reserved.
23 * Dave Camp <dcamp@mozilla.com>
24 * Ted Mielczarek <ted.mielczarek@gmail.com>
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.
38 * ***** END LICENSE BLOCK ***** */
40 #import <Cocoa/Cocoa.h>
41 #import <CoreFoundation/CoreFoundation.h>
42 #include "crashreporter.h"
43 #include "crashreporter_osx.h"
45 #include <sys/types.h>
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 = "?";
74 static bool RestartApplication()
76 char** argv = reinterpret_cast<char**>(
77 malloc(sizeof(char*) * (gRestartArgs.size() + 1)));
79 if (!argv) return false;
82 for (i = 0; i < gRestartArgs.size(); i++) {
83 argv[i] = (char*)gRestartArgs[i].c_str();
91 (void)execv(argv[0], argv);
100 @implementation CrashReporterUI
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;
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];
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];
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)];
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"];
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];
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"];
183 // use compile-time specified enable percentage
184 submitChecked = ShouldEnableSending();
185 [userDefaults setBool:submitChecked forKey:@"submitReport"];
187 [mSubmitReportButton setState:(submitChecked ? NSOnState : NSOffState)];
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)
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();
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];
240 NSAttributedString* extra = [[NSAttributedString alloc]
241 initWithString:NSSTR("\n" + gStrings[ST_EXTRAREPORTINFO])
242 attributes:normalAttr];
243 [[mViewReportTextView textStorage] appendAttributedString: extra];
247 - (void)maybeSubmitReport
249 if ([mSubmitReportButton state] == NSOnState) {
250 [self setStringFitVertically:mProgressText
251 string:Str(ST_REPORTDURINGSUBMIT)
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];
262 [NSApp terminate:self];
266 - (void)closeMeDown:(id)unused
268 [NSApp terminate:self];
271 -(IBAction)submitReportClicked:(id)sender
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
308 -(IBAction)emailMeClicked:(id)sender
313 -(void)controlTextDidChange:(NSNotification *)note
318 - (void)textDidChange:(NSNotification *)aNotification
320 // update comment parameter
321 if ([[[mCommentText textStorage] mutableString] length] > 0)
322 gQueryParameters["Comments"] = [[[mCommentText textStorage] mutableString]
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) {
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
355 closeFrame.origin.x -= closeFrame.size.width - oldCloseWidth;
358 if (gRestartArgs.size() == 0) {
359 [mRestartButton removeFromSuperview];
361 closeFrame.origin.x = restartFrame.origin.x +
362 (restartFrame.size.width - closeFrame.size.width);
365 closeFrame.origin.x = restartFrame.origin.x;
367 [mCloseButton setFrame: closeFrame];
368 [mCloseButton setKeyEquivalent:@"\r"];
370 [mRestartButton setTitle:Str(ST_RESTART)];
371 // resize "restart" button
372 float oldRestartWidth = restartFrame.size.width;
373 [mRestartButton sizeToFit];
374 restartFrame = [mRestartButton frame];
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;
381 // shift the close button right in RTL
382 closeFrame.origin.x += restartFrame.size.width - oldRestartWidth;
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 +
392 if (neededWidth > windowFrame.size.width) {
393 windowFrame.size.width = neededWidth;
394 [mWindow setFrame:windowFrame display: true animate: NO];
396 [mRestartButton setKeyEquivalent:@"\r"];
399 NSButton *checkboxes[] = {
405 for (int i=0; i<3; i++) {
406 NSRect frame = [checkboxes[i] frame];
407 [checkboxes[i] sizeToFit];
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];
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];
423 // do this down here because we may have made the window wider
425 [self setStringFitVertically:mDescriptionLabel
426 string:Str(ST_CRASHREPORTERDESCRIPTION)
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.
442 for (unsigned int i=0; i<sizeof(views)/sizeof(views[0]); i++) {
443 [views[i] setAutoresizingMask:NSViewMinYMargin];
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];
466 NSRect frame = [mWindow frame];
467 frame.origin.y -= delta;
468 frame.size.height += delta;
469 [mWindow setFrame:frame display: true animate: NO];
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];
504 if ([mSubmitReportButton state] == NSOnState) {
505 [self setStringFitVertically:mProgressText
506 string:Str(ST_REPORTPRESUBMIT)
508 [mProgressText setHidden:NO];
509 // enable all the controls
510 [self enableControls:YES];
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];
522 if ([mIncludeURLButton state] == NSOnState && !gURLParameter.empty()) {
523 gQueryParameters["URL"] = gURLParameter;
525 gQueryParameters.erase("URL");
531 if ([mEmailMeButton state] == NSOnState &&
532 [mSubmitReportButton state] == NSOnState) {
533 NSString* email = [mEmailText stringValue];
534 gQueryParameters["Email"] = [email UTF8String];
535 [mEmailText setEnabled:YES];
537 gQueryParameters.erase("Email");
538 [mEmailText setEnabled:NO];
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)
549 // quit after 5 seconds
550 [self performSelector:@selector(closeMeDown:) withObject:nil
554 [NSThread detachNewThreadSelector:@selector(uploadThread:)
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();
575 NSString* key = NSSTR(i->first);
576 NSString* value = NSSTR(i->second);
577 [parameters setObject: value forKey: key];
580 [mPost addFileAtPath: NSSTR(gDumpFile) name: @"upload_file_minidump"];
581 [mPost setParameters: parameters];
582 [parameters release];
587 -(void)uploadComplete:(NSData*)data
589 NSHTTPURLResponse* response = [mPost response];
594 if (!data || !response || [response statusCode] != 200) {
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());
607 LogMessage("Crash report submitted successfully");
609 NSString* encodingName = [response textEncodingName];
610 NSStringEncoding encoding;
612 encoding = CFStringConvertEncodingToNSStringEncoding(
613 CFStringConvertIANACharSetNameToEncoding((CFStringRef)encodingName));
615 encoding = NSISOLatin1StringEncoding;
617 NSString* r = [[NSString alloc] initWithData: data encoding: encoding];
618 reply = [r UTF8String];
622 SendCompleted(success, reply);
624 [mProgressIndicator stopAnimation:self];
626 [self setStringFitVertically:mProgressText
627 string:Str(ST_REPORTSUBMITSUCCESS)
630 [self setStringFitVertically:mProgressText
631 string:Str(ST_SUBMITFAILED)
634 // quit after 5 seconds
635 [self performSelector:@selector(closeMeDown:) withObject:nil
639 -(void)uploadThread:(HTTPMultipartUpload*)post
641 NSAutoreleasePool* autoreleasepool = [[NSAutoreleasePool alloc] init];
642 NSError* error = nil;
643 NSData* data = [post send: &error];
646 NSString* errorDesc = [error localizedDescription];
647 string message = [errorDesc UTF8String];
648 LogMessage("Crash report submission failed: " + message);
651 [self performSelectorOnMainThread: @selector(uploadComplete:)
655 [autoreleasepool release];
658 // to get auto-quit when we close the window
659 -(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)theApplication
664 -(void)applicationWillTerminate:(NSNotification *)aNotification
666 // since we use [NSApp terminate:] we never return to main,
667 // so do our cleanup here
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];
705 [mPlaceHolderString setAlignment:NSRightTextAlignment
706 range:NSMakeRange(0, [placeholder length])];
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;
729 txtColor = [NSColor textColor];
731 txtColor = [NSColor disabledControlTextColor];
732 NSDictionary *txtDict = [NSDictionary
733 dictionaryWithObjectsAndKeys:txtColor,
734 NSForegroundColorAttributeName, nil];
735 colorString = [[NSAttributedString alloc]
736 initWithString:[self string]
738 [[self textStorage] setAttributedString: colorString];
739 [self setInsertionPointColor:txtColor];
740 [colorString release];
746 [mPlaceHolderString release];
752 /* === Crashreporter UI Functions === */
756 gMainPool = [[NSAutoreleasePool alloc] init];
757 [NSApplication sharedApplication];
759 if (gStrings.find("isRTL") != gStrings.end() &&
760 gStrings["isRTL"] == "yes")
763 [NSBundle loadNibNamed:(gRTLlayout ? @"MainMenuRTL" : @"MainMenu")
774 void UIShowDefaultUI()
776 [gUI showErrorUI: gStrings[ST_CRASHREPORTERDEFAULT]];
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
795 void UIError_impl(const string& message)
798 // UI failed to initialize, printing is the best we can do
799 printf("Error: %s\n", message.c_str());
803 [gUI showErrorUI: message];
807 bool UIGetIniPath(string& path)
815 bool UIGetSettingsPath(const string& vendor,
816 const string& product,
817 string& settingsPath)
820 OSErr err = FSFindFolder(kUserDomain, kApplicationSupportFolderType,
821 kCreateFolder, &foundRef);
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))
840 destPath = [destPath stringByAppendingPathComponent: @"Crash Reports"];
842 settingsPath = [destPath UTF8String];
847 bool UIEnsurePathExists(const string& path)
849 int ret = mkdir(path.c_str(), S_IRWXU);
851 if (ret == -1 && e != EEXIST)
857 bool UIFileExists(const string& path)
860 int ret = stat(path.c_str(), &sb);
861 if (ret == -1 || !(sb.st_mode & S_IFREG))
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