diff --git a/src/client/mac/Breakpad.xcodeproj/project.pbxproj b/src/client/mac/Breakpad.xcodeproj/project.pbxproj index a9ddd017..7d8e5b8d 100644 --- a/src/client/mac/Breakpad.xcodeproj/project.pbxproj +++ b/src/client/mac/Breakpad.xcodeproj/project.pbxproj @@ -26,10 +26,10 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 3329D4ED0FA16D820007BBC5 /* Breakpad.nib in Resources */ = {isa = PBXBuildFile; fileRef = 3329D4EC0FA16D820007BBC5 /* Breakpad.nib */; }; 33880C800F9E097100817F82 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 33880C7E0F9E097100817F82 /* InfoPlist.strings */; }; 4084699D0F5D9CF900FDCA37 /* crash_report_sender.icns in Resources */ = {isa = PBXBuildFile; fileRef = 4084699C0F5D9CF900FDCA37 /* crash_report_sender.icns */; }; 8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; }; - F9267CB30F844BDD00A827EC /* Breakpad.nib in Resources */ = {isa = PBXBuildFile; fileRef = F9267CB10F844BDD00A827EC /* Breakpad.nib */; }; F9286B3A0F7EB25800A4DCC8 /* InspectorMain.mm in Sources */ = {isa = PBXBuildFile; fileRef = F9286B390F7EB25800A4DCC8 /* InspectorMain.mm */; }; F92C53B80ECCE7B3009BE4BA /* Inspector.mm in Sources */ = {isa = PBXBuildFile; fileRef = F92C53B70ECCE7B3009BE4BA /* Inspector.mm */; }; F92C554C0ECCF534009BE4BA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; }; @@ -224,10 +224,10 @@ 0867D6A5FE840307C02AAC07 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 32DBCF5E0370ADEE00C91783 /* Breakpad_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Breakpad_Prefix.pch; path = Framework/Breakpad_Prefix.pch; sourceTree = ""; }; + 3329D4EC0FA16D820007BBC5 /* Breakpad.nib */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = Breakpad.nib; path = sender/Breakpad.nib; sourceTree = ""; }; 33880C7F0F9E097100817F82 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = sender/English.lproj/InfoPlist.strings; sourceTree = ""; }; 4084699C0F5D9CF900FDCA37 /* crash_report_sender.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = crash_report_sender.icns; path = sender/crash_report_sender.icns; sourceTree = ""; }; 8DC2EF5B0486A6940098B216 /* Breakpad.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Breakpad.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F9267CB20F844BDD00A827EC /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = sender/English.lproj/Breakpad.nib; sourceTree = ""; }; F9286B380F7EB25800A4DCC8 /* Inspector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Inspector.h; path = crash_generation/Inspector.h; sourceTree = ""; }; F9286B390F7EB25800A4DCC8 /* InspectorMain.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = InspectorMain.mm; path = crash_generation/InspectorMain.mm; sourceTree = ""; }; F92C53540ECCE349009BE4BA /* Inspector */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = Inspector; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -531,13 +531,13 @@ F92C56A60ECE04B6009BE4BA /* sender */ = { isa = PBXGroup; children = ( - F945849C0F280E3C009A47BF /* Localizable.strings */, - 33880C7E0F9E097100817F82 /* InfoPlist.strings */, - 4084699C0F5D9CF900FDCA37 /* crash_report_sender.icns */, - F92C56A20ECE04A7009BE4BA /* crash_report_sender-Info.plist */, - F9267CB10F844BDD00A827EC /* Breakpad.nib */, F92C56A70ECE04C5009BE4BA /* crash_report_sender.h */, F92C56A80ECE04C5009BE4BA /* crash_report_sender.m */, + F945849C0F280E3C009A47BF /* Localizable.strings */, + 33880C7E0F9E097100817F82 /* InfoPlist.strings */, + 3329D4EC0FA16D820007BBC5 /* Breakpad.nib */, + 4084699C0F5D9CF900FDCA37 /* crash_report_sender.icns */, + F92C56A20ECE04A7009BE4BA /* crash_report_sender-Info.plist */, ); name = sender; sourceTree = ""; @@ -806,8 +806,8 @@ files = ( F945849E0F280E3C009A47BF /* Localizable.strings in Resources */, 4084699D0F5D9CF900FDCA37 /* crash_report_sender.icns in Resources */, - F9267CB30F844BDD00A827EC /* Breakpad.nib in Resources */, 33880C800F9E097100817F82 /* InfoPlist.strings in Resources */, + 3329D4ED0FA16D820007BBC5 /* Breakpad.nib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1078,14 +1078,6 @@ name = InfoPlist.strings; sourceTree = ""; }; - F9267CB10F844BDD00A827EC /* Breakpad.nib */ = { - isa = PBXVariantGroup; - children = ( - F9267CB20F844BDD00A827EC /* English */, - ); - name = Breakpad.nib; - sourceTree = ""; - }; F945849C0F280E3C009A47BF /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( diff --git a/src/client/mac/Framework/Breakpad.h b/src/client/mac/Framework/Breakpad.h index 483b8769..88be8162 100644 --- a/src/client/mac/Framework/Breakpad.h +++ b/src/client/mac/Framework/Breakpad.h @@ -76,9 +76,10 @@ extern "C" { "BreakpadReporterExeLocation" #define BREAKPAD_LOGFILES "BreakpadLogFiles" #define BREAKPAD_LOGFILE_UPLOAD_SIZE "BreakpadLogFileTailSize" -#define BREAKPAD_EMAIL "BreakpadEmail" #define BREAKPAD_REQUEST_COMMENTS "BreakpadRequestComments" #define BREAKPAD_COMMENTS "BreakpadComments" +#define BREAKPAD_REQUEST_EMAIL "BreakpadRequestEmail" +#define BREAKPAD_EMAIL "BreakpadEmail" #define BREAKPAD_SERVER_TYPE "BreakpadServerType" // TODO(nealsid) find a better way to support server-specific // parameters without having to rebuild Breakpad @@ -163,8 +164,12 @@ typedef bool (*BreakpadFilterCallback)(int exception_type, // should be uploaded at crash time. // // BREAKPAD_REQUEST_COMMENTS If true, the message dialog will have a -// text box for the user to enter comments as -// well as a name and email address. +// text box for the user to enter comments. +// Default: NO +// +// BREAKPAD_REQUEST_EMAIL If true and BREAKPAD_REQUEST_COMMENTS is also +// true, the message dialog will have a text +// box for the user to enter their email address. // Default: NO // // BREAKPAD_SERVER_TYPE A parameter that tells Breakpad how to diff --git a/src/client/mac/Framework/Breakpad.mm b/src/client/mac/Framework/Breakpad.mm index 0635e189..ed3d385e 100644 --- a/src/client/mac/Framework/Breakpad.mm +++ b/src/client/mac/Framework/Breakpad.mm @@ -407,6 +407,7 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) { NSString *reportEmail = [parameters objectForKey:@BREAKPAD_EMAIL]; NSString *requestUserText = [parameters objectForKey:@BREAKPAD_REQUEST_COMMENTS]; + NSString *requestEmail = [parameters objectForKey:@BREAKPAD_REQUEST_EMAIL]; NSString *vendor = [parameters objectForKey:@BREAKPAD_VENDOR]; NSString *dumpSubdirectory = @@ -549,8 +550,8 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) { [logFileTailSize UTF8String]); dictionary.SetKeyValue(BREAKPAD_REQUEST_COMMENTS, [requestUserText UTF8String]); - dictionary.SetKeyValue(BREAKPAD_VENDOR, - [vendor UTF8String]); + dictionary.SetKeyValue(BREAKPAD_REQUEST_EMAIL, [requestEmail UTF8String]); + dictionary.SetKeyValue(BREAKPAD_VENDOR, [vendor UTF8String]); dictionary.SetKeyValue(BREAKPAD_DUMP_DIRECTORY, [dumpSubdirectory UTF8String]); diff --git a/src/client/mac/sender/Breakpad.nib/classes.nib b/src/client/mac/sender/Breakpad.nib/classes.nib new file mode 100644 index 00000000..ebc5ab40 --- /dev/null +++ b/src/client/mac/sender/Breakpad.nib/classes.nib @@ -0,0 +1,57 @@ + + + + + IBClasses + + + ACTIONS + + cancel + id + sendReport + id + showPrivacyPolicy + id + + CLASS + Reporter + LANGUAGE + ObjC + OUTLETS + + alertWindow_ + NSWindow + cancelButton_ + NSButton + commentMessage_ + NSTextField + dialogTitle_ + NSTextField + emailEntryField_ + NSView + emailLabel_ + NSTextField + emailMessage_ + NSTextField + emailSectionBox_ + NSBox + headerBox_ + NSBox + preEmailBox_ + NSBox + privacyLinkArrow_ + NSView + privacyLinkLabel_ + NSTextField + sendButton_ + NSButton + + SUPERCLASS + NSObject + + + IBVersion + 1 + + diff --git a/src/client/mac/sender/English.lproj/Breakpad.nib/info.nib b/src/client/mac/sender/Breakpad.nib/info.nib similarity index 84% rename from src/client/mac/sender/English.lproj/Breakpad.nib/info.nib rename to src/client/mac/sender/Breakpad.nib/info.nib index 55565cda..21bd6319 100644 --- a/src/client/mac/sender/English.lproj/Breakpad.nib/info.nib +++ b/src/client/mac/sender/Breakpad.nib/info.nib @@ -3,9 +3,9 @@ IBFramework Version - 670 + 672 IBLastKnownRelativeProjectPath - ../../Breakpad.xcodeproj + ../Breakpad.xcodeproj IBOldestOS 5 IBOpenObjects @@ -13,7 +13,7 @@ 2 IBSystem Version - 9F33 + 9G55 targetFramework IBCocoaFramework diff --git a/src/client/mac/sender/Breakpad.nib/keyedobjects.nib b/src/client/mac/sender/Breakpad.nib/keyedobjects.nib new file mode 100644 index 00000000..d0cbd3f2 Binary files /dev/null and b/src/client/mac/sender/Breakpad.nib/keyedobjects.nib differ diff --git a/src/client/mac/sender/English.lproj/Breakpad.nib/classes.nib b/src/client/mac/sender/English.lproj/Breakpad.nib/classes.nib deleted file mode 100644 index 2636d457..00000000 --- a/src/client/mac/sender/English.lproj/Breakpad.nib/classes.nib +++ /dev/null @@ -1,33 +0,0 @@ - - - - - IBClasses - - - ACTIONS - - cancel - id - sendReport - id - showPrivacyPolicy - id - - CLASS - Reporter - LANGUAGE - ObjC - OUTLETS - - alertWindow - NSWindow - - SUPERCLASS - NSObject - - - IBVersion - 1 - - diff --git a/src/client/mac/sender/English.lproj/Breakpad.nib/keyedobjects.nib b/src/client/mac/sender/English.lproj/Breakpad.nib/keyedobjects.nib deleted file mode 100644 index 50dff43d..00000000 Binary files a/src/client/mac/sender/English.lproj/Breakpad.nib/keyedobjects.nib and /dev/null differ diff --git a/src/client/mac/sender/English.lproj/Localizable.strings b/src/client/mac/sender/English.lproj/Localizable.strings index aa2ca503..efc832ac 100644 Binary files a/src/client/mac/sender/English.lproj/Localizable.strings and b/src/client/mac/sender/English.lproj/Localizable.strings differ diff --git a/src/client/mac/sender/crash_report_sender.h b/src/client/mac/sender/crash_report_sender.h index 955bd86d..b3169538 100644 --- a/src/client/mac/sender/crash_report_sender.h +++ b/src/client/mac/sender/crash_report_sender.h @@ -43,16 +43,25 @@ extern NSString *const kSocorroServerType; extern NSString *const kDefaultServerType; @interface Reporter : NSObject { @public - IBOutlet NSWindow *alertWindow; // The alert window + IBOutlet NSWindow *alertWindow_; // The alert window - // Values bound in the XIB - NSString *headerMessage_; // Message notifying of the - // crash - NSString *reportMessage_; // Message explaining the - // crash report + // Grouping boxes used for resizing. + IBOutlet NSBox *headerBox_; + IBOutlet NSBox *preEmailBox_; + IBOutlet NSBox *emailSectionBox_; + // Localized elements (or things that need to be moved during localization). + IBOutlet NSTextField *dialogTitle_; + IBOutlet NSTextField *commentMessage_; + IBOutlet NSTextField *emailMessage_; + IBOutlet NSTextField *emailLabel_; + IBOutlet NSTextField *privacyLinkLabel_; + IBOutlet NSButton *sendButton_; + IBOutlet NSButton *cancelButton_; + IBOutlet NSView *emailEntryField_; + IBOutlet NSView *privacyLinkArrow_; + + // Text field bindings, for user input. NSString *commentsValue_; // Comments from the user - NSString *emailMessage_; // Message requesting user - // email NSString *emailValue_; // Email from the user @private @@ -90,18 +99,9 @@ extern NSString *const kDefaultServerType; - (BOOL)setPostParametersFromDictionary:(NSMutableDictionary *)crashParameters; // Accessors to make bindings work -- (NSString *)headerMessage; -- (void)setHeaderMessage:(NSString *)value; - -- (NSString *)reportMessage; -- (void)setReportMessage:(NSString *)value; - - (NSString *)commentsValue; - (void)setCommentsValue:(NSString *)value; -- (NSString *)emailMessage; -- (void)setEmailMessage:(NSString *)value; - - (NSString *)emailValue; - (void)setEmailValue:(NSString *)value; diff --git a/src/client/mac/sender/crash_report_sender.m b/src/client/mac/sender/crash_report_sender.m index ebf31c20..43d1da08 100644 --- a/src/client/mac/sender/crash_report_sender.m +++ b/src/client/mac/sender/crash_report_sender.m @@ -49,6 +49,112 @@ NSString *const kGoogleServerType = @"google"; NSString *const kSocorroServerType = @"socorro"; NSString *const kDefaultServerType = @"google"; +#pragma mark - + +@interface NSView (ResizabilityExtentions) +// Shifts the view vertically by the given amount. +- (void)breakpad_shiftVertically:(float)offset; + +// Shifts the view horizontally by the given amount. +- (void)breakpad_shiftHorizontally:(float)offset; +@end + +@implementation NSView (ResizabilityExtentions) +- (void)breakpad_shiftVertically:(float)offset { + NSPoint origin = [self frame].origin; + origin.y += offset; + [self setFrameOrigin:origin]; +} + +- (void)breakpad_shiftHorizontally:(float)offset { + NSPoint origin = [self frame].origin; + origin.x += offset; + [self setFrameOrigin:origin]; +} +@end + +@interface NSWindow (ResizabilityExtentions) +// Adjusts the window height by heightDelta relative to its current height, +// keeping all the content at the same size. +- (void)breakpad_adjustHeight:(float)heightDelta; +@end + +@implementation NSWindow (ResizabilityExtentions) +- (void)breakpad_adjustHeight:(float)heightDelta { + [[self contentView] setAutoresizesSubviews:NO]; + + NSRect windowFrame = [self frame]; + windowFrame.size.height += heightDelta; + [self setFrame:windowFrame display:YES]; + // For some reason the content view is resizing, but not adjusting its origin, + // so correct it manually. + [[self contentView] setFrameOrigin:NSMakePoint(0, 0)]; + + [[self contentView] setAutoresizesSubviews:YES]; +} +@end + +@interface NSTextField (ResizabilityExtentions) +// Grows or shrinks the height of the field to the minimum required to show the +// current text, preserving the existing width and origin. +// Returns the change in height. +- (float)breakpad_adjustHeightToFit; + +// Grows or shrinks the width of the field to the minimum required to show the +// current text, preserving the existing height and origin. +// Returns the change in width. +- (float)breakpad_adjustWidthToFit; +@end + +@implementation NSTextField (ResizabilityExtentions) +- (float)breakpad_adjustHeightToFit { + NSRect oldFrame = [self frame]; + // sizeToFit will blow out the width rather than making the field taller, so + // we do it manually. + NSSize newSize = [[self cell] cellSizeForBounds:oldFrame]; + NSRect newFrame = NSMakeRect(oldFrame.origin.x, oldFrame.origin.y, + NSWidth(oldFrame), newSize.height); + [self setFrame:newFrame]; + + return newSize.height - NSHeight(oldFrame); +} + +- (float)breakpad_adjustWidthToFit { + NSRect oldFrame = [self frame]; + [self sizeToFit]; + return NSWidth([self frame]) - NSWidth(oldFrame); +} +@end + +@interface NSButton (ResizabilityExtentions) +// Resizes to fit the label using IB-style size-to-fit metrics and enforcing a +// minimum width of 70, while preserving the right edge location. +// Returns the change in width. +- (float)breakpad_smartSizeToFit; +@end + +@implementation NSButton (ResizabilityExtentions) +- (float)breakpad_smartSizeToFit { + NSRect oldFrame = [self frame]; + [self sizeToFit]; + NSRect newFrame = [self frame]; + // sizeToFit gives much worse results that IB's Size to Fit option. This is + // the amount of padding IB adds over a sizeToFit, empirically determined. + const float kExtraPaddingAmount = 12; + const float kMinButtonWidth = 70; // The default button size in IB. + newFrame.size.width = NSWidth(newFrame) + kExtraPaddingAmount; + if (NSWidth(newFrame) < kMinButtonWidth) + newFrame.size.width = kMinButtonWidth; + // Preserve the right edge location. + newFrame.origin.x = NSMaxX(oldFrame) - NSWidth(newFrame); + [self setFrame:newFrame]; + return NSWidth(newFrame) - NSWidth(oldFrame); +} +@end + +#pragma mark - + + @interface Reporter(PrivateMethods) + (uid_t)consoleUID; @@ -61,8 +167,44 @@ NSString *const kDefaultServerType = @"google"; - (BOOL)readMinidumpData; - (BOOL)readLogFileData; -- (BOOL)askUserPermissionToSend:(BOOL)shouldSubmitReport; -- (BOOL)shouldSubmitReport; +// Returns YES if it has been long enough since the last report that we should +// submit a report for this crash. +- (BOOL)reportIntervalElapsed; + +// Returns YES if we should send the report without asking the user first. +- (BOOL)shouldSubmitSilently; + +// Returns YES if we should ask the user to provide comments. +- (BOOL)shouldRequestComments; + +// Returns YES if we should ask the user to provide an email address. +- (BOOL)shouldRequestEmail; + +// Shows UI to the user to ask for permission to send and any extra information +// we've been instructed to request. Returns YES if the user allows the report +// to be sent. +- (BOOL)askUserPermissionToSend; + +// Returns the short description of the crash, suitable for use as a dialog +// title (e.g., "The application Foo has quit unexpectedly"). +- (NSString*)shortCrashDialogMessage; + +// Return explanatory text about the crash and the reporter, suitable for the +// body text of a dialog. +- (NSString*)explanatoryCrashDialogText; + +// Returns the amount of time the UI should be shown before timing out. +- (NSTimeInterval)messageTimeout; + +// Preps the comment-prompting alert window for display: +// * localizes all the elements +// * resizes and adjusts layout as necessary for localization +// * removes the email section if includeEmail is NO +- (void)configureAlertWindowIncludingEmail:(BOOL)includeEmail; + +// Rmevoes the email section of the dialog, adjusting the rest of the window +// as necessary. +- (void)removeEmailPrompt; // Run an alert window with the given timeout. Returns // NSAlertButtonDefault if the timeout is exceeded. A timeout of 0 @@ -341,105 +483,39 @@ NSString *const kDefaultServerType = @"google"; } //============================================================================= -- (BOOL)askUserPermissionToSend:(BOOL)shouldSubmitReport { - // Send without confirmation - if ([[parameters_ objectForKey:@BREAKPAD_SKIP_CONFIRM] isEqualToString:@"YES"]) { - GTMLoggerDebug(@"Skipping confirmation and sending report"); - return YES; - } - - // Determine if we should create a text box for user feedback - BOOL shouldRequestComments = - [[parameters_ objectForKey:@BREAKPAD_REQUEST_COMMENTS] - isEqual:@"YES"]; - - NSString *display = [parameters_ objectForKey:@BREAKPAD_PRODUCT_DISPLAY]; - - if (![display length]) - display = [parameters_ objectForKey:@BREAKPAD_PRODUCT]; - - NSString *vendor = [parameters_ objectForKey:@BREAKPAD_VENDOR]; - - if (![vendor length]) - vendor = @"Vendor not specified"; - - NSBundle *bundle = [NSBundle mainBundle]; - [self setHeaderMessage:[NSString stringWithFormat: - NSLocalizedStringFromTableInBundle(@"headerFmt", nil, - bundle, - @""), display]]; - NSString *defaultButtonTitle = nil; - NSString *otherButtonTitle = nil; - - // Get the localized alert strings - // If we're going to submit a report, prompt the user if this is okay. - // Otherwise, just let them know that the app crashed. - - if (shouldSubmitReport) { - NSString *msgFormat = NSLocalizedStringFromTableInBundle(@"msg", - nil, - bundle, @""); - - [self setReportMessage:[NSString stringWithFormat:msgFormat, vendor]]; - - defaultButtonTitle = NSLocalizedStringFromTableInBundle(@"sendReportButton", - nil, bundle, @""); - otherButtonTitle = NSLocalizedStringFromTableInBundle(@"cancelButton", nil, - bundle, @""); - } else { - [self setReportMessage:NSLocalizedStringFromTableInBundle(@"noSendMsg", nil, - bundle, @"")]; - defaultButtonTitle = NSLocalizedStringFromTableInBundle(@"noSendButton", - nil, bundle, @""); - } - - // Get the timeout value for the notification. - NSTimeInterval timeout = [[parameters_ objectForKey:@BREAKPAD_CONFIRM_TIMEOUT] - floatValue]; - // Show the notification for at least one minute (but allow 0, since it means - // no timeout). - if (timeout > 0.001 && timeout < 60.0) { - timeout = 60.0; - } - +- (BOOL)askUserPermissionToSend { // Initialize Cocoa, needed to display the alert NSApplicationLoad(); + // Get the timeout value for the notification. + NSTimeInterval timeout = [self messageTimeout]; + int buttonPressed = NSAlertAlternateReturn; - - if (shouldRequestComments) { + // Determine whether we should create a text box for user feedback. + if ([self shouldRequestComments]) { BOOL didLoadNib = [NSBundle loadNibNamed:@"Breakpad" owner:self]; - if (didLoadNib) { - // Append the request for comments to the |reportMessage| string. - NSString *commentsMessage = - NSLocalizedStringFromTableInBundle(@"commentsMsg", nil, bundle, @""); - [self setReportMessage:[NSString stringWithFormat:@"%@\n\n%@", - [self reportMessage], - commentsMessage]]; + if (!didLoadNib) { + return NO; + } - // Add the request for email address. - [self setEmailMessage: - NSLocalizedStringFromTableInBundle(@"emailMsg", nil, bundle, @"")]; + [self configureAlertWindowIncludingEmail:[self shouldRequestEmail]]; - // Run the alert - buttonPressed = [self runModalWindow:alertWindow withTimeout:timeout]; + buttonPressed = [self runModalWindow:alertWindow_ withTimeout:timeout]; - // Extract info from the user into the parameters_ dictionary - if ([self commentsValue]) { - [parameters_ setObject:[self commentsValue] - forKey:@BREAKPAD_COMMENTS]; - } - if ([self emailValue]) { - [parameters_ setObject:[self emailValue] - forKey:@BREAKPAD_EMAIL]; - } + // Extract info from the user into the parameters_ dictionary + if ([self commentsValue]) { + [parameters_ setObject:[self commentsValue] forKey:@BREAKPAD_COMMENTS]; + } + if ([self emailValue]) { + [parameters_ setObject:[self emailValue] forKey:@BREAKPAD_EMAIL]; } } else { // Create an alert panel to tell the user something happened - NSPanel* alert = NSGetAlertPanel([self headerMessage], - [self reportMessage], - defaultButtonTitle, - otherButtonTitle, nil); + NSPanel* alert = NSGetAlertPanel([self shortCrashDialogMessage], + [self explanatoryCrashDialogText], + NSLocalizedString(@"sendReportButton", @""), + NSLocalizedString(@"cancelButton", @""), + nil); // Pop the alert with an automatic timeout, and wait for the response buttonPressed = [self runModalWindow:alert withTimeout:timeout]; @@ -450,6 +526,58 @@ NSString *const kDefaultServerType = @"google"; return buttonPressed == NSAlertDefaultReturn; } +- (void)configureAlertWindowIncludingEmail:(BOOL)includeEmail { + // Swap in localized values, making size adjustments to impacted elements as + // we go. Remember that the origin is in the bottom left, so elements above + // "fall" as text areas are shrunk from their overly-large IB sizes. + + // Localize the header. No resizing needed, as it has plenty of room. + [dialogTitle_ setStringValue:[self shortCrashDialogMessage]]; + + // Localize the explanatory text field. + [commentMessage_ setStringValue:[NSString stringWithFormat:@"%@\n\n%@", + [self explanatoryCrashDialogText], + NSLocalizedString(@"commentsMsg", @"")]]; + float commentHeightDelta = [commentMessage_ breakpad_adjustHeightToFit]; + [headerBox_ breakpad_shiftVertically:commentHeightDelta]; + [alertWindow_ breakpad_adjustHeight:commentHeightDelta]; + + // Either localize the email explanation field or remove the whole email + // section depending on whether or not we are asking for email. + if (includeEmail) { + [emailMessage_ setStringValue:NSLocalizedString(@"emailMsg", @"")]; + float emailHeightDelta = [emailMessage_ breakpad_adjustHeightToFit]; + [preEmailBox_ breakpad_shiftVertically:emailHeightDelta]; + [alertWindow_ breakpad_adjustHeight:emailHeightDelta]; + } else { + [self removeEmailPrompt]; // Handles necessary resizing. + } + + // Localize the email label, and shift the associated text field. + [emailLabel_ setStringValue:NSLocalizedString(@"emailLabel", @"")]; + float emailLabelWidthDelta = [emailLabel_ breakpad_adjustWidthToFit]; + [emailEntryField_ breakpad_shiftHorizontally:emailLabelWidthDelta]; + + // Localize the privacy policy label, and keep it right-aligned to the arrow. + [privacyLinkLabel_ setStringValue:NSLocalizedString(@"privacyLabel", @"")]; + float privacyLabelWidthDelta = [privacyLinkLabel_ breakpad_adjustWidthToFit]; + [privacyLinkLabel_ breakpad_shiftHorizontally:(-privacyLabelWidthDelta)]; + + // Localize the buttons, and keep the cancel button at the right distance. + [sendButton_ setTitle:NSLocalizedString(@"sendReportButton", @"")]; + float sendButtonWidthDelta = [sendButton_ breakpad_smartSizeToFit]; + [cancelButton_ breakpad_shiftHorizontally:(-sendButtonWidthDelta)]; + [cancelButton_ setTitle:NSLocalizedString(@"cancelButton", @"")]; + [cancelButton_ breakpad_smartSizeToFit]; +} + +- (void)removeEmailPrompt { + [emailSectionBox_ setHidden:YES]; + float emailSectionHeight = NSHeight([emailSectionBox_ frame]); + [preEmailBox_ breakpad_shiftVertically:(-emailSectionHeight)]; + [alertWindow_ breakpad_adjustHeight:(-emailSectionHeight)]; +} + - (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout { // Queue a |stopModal| message to be performed in |timeout| seconds. if (timeout > 0.001) { @@ -473,7 +601,11 @@ NSString *const kDefaultServerType = @"google"; } - (IBAction)sendReport:(id)sender { - [alertWindow orderOut:self]; + // Force the text fields to end editing so text for the currently focused + // field will be commited. + [alertWindow_ makeFirstResponder:alertWindow_]; + + [alertWindow_ orderOut:self]; // Use NSAlertDefaultReturn so that the return value of |runModalWithWindow| // matches the AppKit function NSRunAlertPanel() [NSApp stopModalWithCode:NSAlertDefaultReturn]; @@ -482,7 +614,7 @@ NSString *const kDefaultServerType = @"google"; // UI Button Actions //============================================================================= - (IBAction)cancel:(id)sender { - [alertWindow orderOut:self]; + [alertWindow_ orderOut:self]; // Use NSAlertDefaultReturn so that the return value of |runModalWithWindow| // matches the AppKit function NSRunAlertPanel() [NSApp stopModalWithCode:NSAlertAlternateReturn]; @@ -491,8 +623,7 @@ NSString *const kDefaultServerType = @"google"; - (IBAction)showPrivacyPolicy:(id)sender { // Get the localized privacy policy URL and open it in the default browser. NSURL* privacyPolicyURL = - [NSURL URLWithString:NSLocalizedStringFromTableInBundle( - @"privacyPolicyURL", nil, [NSBundle mainBundle], @"")]; + [NSURL URLWithString:NSLocalizedString(@"privacyPolicyURL", @"")]; [[NSWorkspace sharedWorkspace] openURL:privacyPolicyURL]; } @@ -511,29 +642,9 @@ doCommandBySelector:(SEL)commandSelector { return result; } -// Accessors +#pragma mark Accessors +#pragma mark - //============================================================================= -- (NSString *)headerMessage { - return [[headerMessage_ retain] autorelease]; -} - -- (void)setHeaderMessage:(NSString *)value { - if (headerMessage_ != value) { - [headerMessage_ autorelease]; - headerMessage_ = [value copy]; - } -} - -- (NSString *)reportMessage { - return [[reportMessage_ retain] autorelease]; -} - -- (void)setReportMessage:(NSString *)value { - if (reportMessage_ != value) { - [reportMessage_ autorelease]; - reportMessage_ = [value copy]; - } -} - (NSString *)commentsValue { return [[commentsValue_ retain] autorelease]; @@ -541,35 +652,25 @@ doCommandBySelector:(SEL)commandSelector { - (void)setCommentsValue:(NSString *)value { if (commentsValue_ != value) { - [commentsValue_ autorelease]; + [commentsValue_ release]; commentsValue_ = [value copy]; } } -- (NSString *)emailMessage { - return [[emailMessage_ retain] autorelease]; -} - -- (void)setEmailMessage:(NSString *)value { - if (emailMessage_ != value) { - [emailMessage_ autorelease]; - emailMessage_ = [value copy]; - } -} - - (NSString *)emailValue { return [[emailValue_ retain] autorelease]; } - (void)setEmailValue:(NSString *)value { if (emailValue_ != value) { - [emailValue_ autorelease]; + [emailValue_ release]; emailValue_ = [value copy]; } } +#pragma mark - //============================================================================= -- (BOOL)shouldSubmitReport { +- (BOOL)reportIntervalElapsed { float interval = [[parameters_ objectForKey:@BREAKPAD_REPORT_INTERVAL] floatValue]; NSString *program = [parameters_ objectForKey:@BREAKPAD_PRODUCT]; @@ -595,6 +696,49 @@ doCommandBySelector:(SEL)commandSelector { return YES; } +- (BOOL)shouldSubmitSilently { + return [[parameters_ objectForKey:@BREAKPAD_SKIP_CONFIRM] + isEqualToString:@"YES"]; +} + +- (BOOL)shouldRequestComments { + return [[parameters_ objectForKey:@BREAKPAD_REQUEST_COMMENTS] + isEqualToString:@"YES"]; +} + +- (BOOL)shouldRequestEmail { + return [[parameters_ objectForKey:@BREAKPAD_REQUEST_EMAIL] + isEqualToString:@"YES"]; +} + +- (NSString*)shortCrashDialogMessage { + NSString *displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT_DISPLAY]; + if (![displayName length]) + displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT]; + + return [NSString stringWithFormat:NSLocalizedString(@"headerFmt", @""), + displayName]; +} + +- (NSString*)explanatoryCrashDialogText { + NSString *vendor = [parameters_ objectForKey:@BREAKPAD_VENDOR]; + if (![vendor length]) + vendor = @"unknown vendor"; + + return [NSString stringWithFormat:NSLocalizedString(@"msgFmt", @""), vendor]; +} + +- (NSTimeInterval)messageTimeout { + // Get the timeout value for the notification. + NSTimeInterval timeout = [[parameters_ objectForKey:@BREAKPAD_CONFIRM_TIMEOUT] + floatValue]; + // Require a timeout of at least a minute (except 0, which means no timeout). + if (timeout > 0.001 && timeout < 60.0) { + timeout = 60.0; + } + return timeout; +} + - (void)createServerParameterDictionaries { serverDictionary_ = [[NSMutableDictionary alloc] init]; socorroDictionary_ = [[NSMutableDictionary alloc] init]; @@ -796,12 +940,17 @@ int main(int argc, const char *argv[]) { [reporter readLogFileData]; // only submit a report if we have not recently crashed in the past - BOOL shouldSubmitReport = [reporter shouldSubmitReport]; + BOOL shouldSubmitReport = [reporter reportIntervalElapsed]; BOOL okayToSend = NO; // ask user if we should send if (shouldSubmitReport) { - okayToSend = [reporter askUserPermissionToSend:shouldSubmitReport]; + if ([reporter shouldSubmitSilently]) { + GTMLoggerDebug(@"Skipping confirmation and sending report"); + okayToSend = YES; + } else { + okayToSend = [reporter askUserPermissionToSend]; + } } // If we're running as root, switch over to nobody diff --git a/src/client/mac/testapp/Info.plist b/src/client/mac/testapp/Info.plist index 586fb5eb..2794c44b 100644 --- a/src/client/mac/testapp/Info.plist +++ b/src/client/mac/testapp/Info.plist @@ -36,6 +36,8 @@ NO BreakpadSendAndExit YES + BreakpadRequestEmail + YES BreakpadRequestComments YES BreakpadVendor