diff options
author | nealsid <nealsid@4c0a9323-5329-0410-9bdc-e9ce6186880e> | 2009-07-21 00:10:57 +0000 |
---|---|---|
committer | nealsid <nealsid@4c0a9323-5329-0410-9bdc-e9ce6186880e> | 2009-07-21 00:10:57 +0000 |
commit | 22734848ea2124d2e8c75a472f71ae20a69fb116 (patch) | |
tree | 5afa6c3c84b50c60ae8438da402edd940c3bb949 /src/client/mac/sender | |
parent | Fix for http://breakpad.appspot.com/18009 - run dump_syms on both PPC & i386 ... (diff) | |
download | breakpad-22734848ea2124d2e8c75a472f71ae20a69fb116.tar.xz |
Port fixes from internal Google Breakpad to SVN.
A=preston, nealsid
R=Stuart, Preston
git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@360 4c0a9323-5329-0410-9bdc-e9ce6186880e
Diffstat (limited to 'src/client/mac/sender')
-rw-r--r-- | src/client/mac/sender/Breakpad.nib/classes.nib | 14 | ||||
-rw-r--r-- | src/client/mac/sender/Breakpad.nib/info.nib | 6 | ||||
-rw-r--r-- | src/client/mac/sender/Breakpad.nib/keyedobjects.nib | bin | 13747 -> 14674 bytes | |||
-rw-r--r-- | src/client/mac/sender/English.lproj/Localizable.strings | bin | 1488 -> 2270 bytes | |||
-rw-r--r-- | src/client/mac/sender/crash_report_sender.h | 49 | ||||
-rw-r--r-- | src/client/mac/sender/crash_report_sender.m | 215 | ||||
-rw-r--r-- | src/client/mac/sender/goArrow.png | bin | 0 -> 3591 bytes |
7 files changed, 246 insertions, 38 deletions
diff --git a/src/client/mac/sender/Breakpad.nib/classes.nib b/src/client/mac/sender/Breakpad.nib/classes.nib index ebc5ab40..c9d43a38 100644 --- a/src/client/mac/sender/Breakpad.nib/classes.nib +++ b/src/client/mac/sender/Breakpad.nib/classes.nib @@ -5,6 +5,14 @@ <key>IBClasses</key> <array> <dict> + <key>CLASS</key> + <string>LengthLimitingTextField</string> + <key>LANGUAGE</key> + <string>ObjC</string> + <key>SUPERCLASS</key> + <string>NSTextField</string> + </dict> + <dict> <key>ACTIONS</key> <dict> <key>cancel</key> @@ -26,10 +34,14 @@ <string>NSButton</string> <key>commentMessage_</key> <string>NSTextField</string> + <key>commentsEntryField_</key> + <string>LengthLimitingTextField</string> + <key>countdownLabel_</key> + <string>NSTextField</string> <key>dialogTitle_</key> <string>NSTextField</string> <key>emailEntryField_</key> - <string>NSView</string> + <string>LengthLimitingTextField</string> <key>emailLabel_</key> <string>NSTextField</string> <key>emailMessage_</key> diff --git a/src/client/mac/sender/Breakpad.nib/info.nib b/src/client/mac/sender/Breakpad.nib/info.nib index 21bd6319..e39a8e54 100644 --- a/src/client/mac/sender/Breakpad.nib/info.nib +++ b/src/client/mac/sender/Breakpad.nib/info.nib @@ -3,17 +3,17 @@ <plist version="1.0"> <dict> <key>IBFramework Version</key> - <string>672</string> + <string>676</string> <key>IBLastKnownRelativeProjectPath</key> <string>../Breakpad.xcodeproj</string> <key>IBOldestOS</key> <integer>5</integer> <key>IBOpenObjects</key> <array> - <integer>2</integer> + <integer>132</integer> </array> <key>IBSystem Version</key> - <string>9G55</string> + <string>9J61</string> <key>targetFramework</key> <string>IBCocoaFramework</string> </dict> diff --git a/src/client/mac/sender/Breakpad.nib/keyedobjects.nib b/src/client/mac/sender/Breakpad.nib/keyedobjects.nib Binary files differindex d0cbd3f2..e370206c 100644 --- a/src/client/mac/sender/Breakpad.nib/keyedobjects.nib +++ b/src/client/mac/sender/Breakpad.nib/keyedobjects.nib diff --git a/src/client/mac/sender/English.lproj/Localizable.strings b/src/client/mac/sender/English.lproj/Localizable.strings Binary files differindex efc832ac..70626567 100644 --- a/src/client/mac/sender/English.lproj/Localizable.strings +++ b/src/client/mac/sender/English.lproj/Localizable.strings diff --git a/src/client/mac/sender/crash_report_sender.h b/src/client/mac/sender/crash_report_sender.h index f62e7613..ca5b3079 100644 --- a/src/client/mac/sender/crash_report_sender.h +++ b/src/client/mac/sender/crash_report_sender.h @@ -41,6 +41,24 @@ extern NSString *const kGoogleServerType; extern NSString *const kSocorroServerType; extern NSString *const kDefaultServerType; + +// We're sublcassing NSTextField in order to override a particular +// method (see the implementation) that lets us reject changes if they +// are longer than a particular length. Bindings would normally solve +// this problem, but when we implemented a validation method, and +// returned NO for strings that were too long, the UI was not updated +// right away, which was a poor user experience. The UI would be +// updated as soon as the text field lost first responder status, +// which isn't soon enough. It is a known bug that the UI KVO didn't +// work in the middle of a validation. +@interface LengthLimitingTextField : NSTextField { + @private + unsigned int maximumLength_; +} + +- (void) setMaximumLength:(unsigned int)maxLength; +@end + @interface Reporter : NSObject { @public IBOutlet NSWindow *alertWindow_; // The alert window @@ -50,26 +68,34 @@ extern NSString *const kDefaultServerType; 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_; + IBOutlet NSTextField *dialogTitle_; + IBOutlet NSTextField *commentMessage_; + IBOutlet NSTextField *emailMessage_; + IBOutlet NSTextField *emailLabel_; + IBOutlet NSTextField *privacyLinkLabel_; + IBOutlet NSButton *sendButton_; + IBOutlet NSButton *cancelButton_; + IBOutlet LengthLimitingTextField *emailEntryField_; + IBOutlet LengthLimitingTextField *commentsEntryField_; + IBOutlet NSTextField *countdownLabel_; + IBOutlet NSView *privacyLinkArrow_; // Text field bindings, for user input. NSString *commentsValue_; // Comments from the user NSString *emailValue_; // Email from the user - + NSString *countdownMessage_; // Message indicating time + // left for input. @private int configFile_; // File descriptor for config file NSMutableDictionary *parameters_; // Key value pairs of data (STRONG) NSData *minidumpContents_; // The data in the minidump (STRONG) NSData *logFileData_; // An NSdata for the tar, // bz2'd log file. + NSTimeInterval remainingDialogTime_; // Keeps track of how long + // we have until we cancel + // the dialog + NSTimer *messageTimer_; // Timer we use to update + // the dialog NSMutableDictionary *serverDictionary_; // The dictionary mapping a // server type name to a // dictionary of server @@ -107,4 +133,7 @@ extern NSString *const kDefaultServerType; - (NSString *)emailValue; - (void)setEmailValue:(NSString *)value; +- (NSString *)countdownMessage; +- (void)setCountdownMessage:(NSString *)value; + @end diff --git a/src/client/mac/sender/crash_report_sender.m b/src/client/mac/sender/crash_report_sender.m index 5d76290c..31c35fe5 100644 --- a/src/client/mac/sender/crash_report_sender.m +++ b/src/client/mac/sender/crash_report_sender.m @@ -42,8 +42,11 @@ #define kLastSubmission @"LastSubmission" const int kMinidumpFileLengthLimit = 800000; +const int kUserCommentsMaxLength = 1500; +const int kEmailMaxLength = 64; -#define kApplePrefsSyncExcludeAllKey @"com.apple.PreferenceSync.ExcludeAllSyncKeys" +#define kApplePrefsSyncExcludeAllKey \ + @"com.apple.PreferenceSync.ExcludeAllSyncKeys" NSString *const kGoogleServerType = @"google"; NSString *const kSocorroServerType = @"socorro"; @@ -174,6 +177,9 @@ NSString *const kDefaultServerType = @"google"; // Returns YES if we should send the report without asking the user first. - (BOOL)shouldSubmitSilently; +// Returns YES if the minidump was generated on demand. +- (BOOL)isOnDemand; + // Returns YES if we should ask the user to provide comments. - (BOOL)shouldRequestComments; @@ -187,11 +193,11 @@ NSString *const kDefaultServerType = @"google"; // Returns the short description of the crash, suitable for use as a dialog // title (e.g., "The application Foo has quit unexpectedly"). -- (NSString*)shortCrashDialogMessage; +- (NSString*)shortDialogMessage; // Return explanatory text about the crash and the reporter, suitable for the // body text of a dialog. -- (NSString*)explanatoryCrashDialogText; +- (NSString*)explanatoryDialogText; // Returns the amount of time the UI should be shown before timing out. - (NSTimeInterval)messageTimeout; @@ -207,7 +213,7 @@ NSString *const kDefaultServerType = @"google"; - (void)removeEmailPrompt; // Run an alert window with the given timeout. Returns -// NSAlertButtonDefault if the timeout is exceeded. A timeout of 0 +// NSRunStoppedResponse if the timeout is exceeded. A timeout of 0 // queues the message immediately in the modal run loop. - (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout; @@ -235,6 +241,16 @@ NSString *const kDefaultServerType = @"google"; // will be uploaded to the crash server. - (void)addServerParameter:(id)value forKey:(NSString *)key; +// This method is used to periodically update the UI with how many +// seconds are left in the dialog display. +- (void)updateSecondsLeftInDialogDisplay:(NSTimer*)theTimer; + +// When we receive this notification, it means that the user has +// begun editing the email address or comments field, and we disable +// the timers so that the user has as long as they want to type +// in their comments/email. +- (void)controlTextDidBeginEditing:(NSNotification *)aNotification; + @end @implementation Reporter @@ -261,6 +277,7 @@ NSString *const kDefaultServerType = @"google"; - (id)initWithConfigurationFD:(int)fd { if ((self = [super init])) { configFile_ = fd; + remainingDialogTime_ = 0; } // Because the reporter is embedded in the framework (and many copies @@ -545,8 +562,8 @@ NSString *const kDefaultServerType = @"google"; } } else { // Create an alert panel to tell the user something happened - NSPanel* alert = NSGetAlertPanel([self shortCrashDialogMessage], - [self explanatoryCrashDialogText], + NSPanel* alert = NSGetAlertPanel([self shortDialogMessage], + [self explanatoryDialogText], NSLocalizedString(@"sendReportButton", @""), NSLocalizedString(@"cancelButton", @""), nil); @@ -566,11 +583,11 @@ NSString *const kDefaultServerType = @"google"; // "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]]; + [dialogTitle_ setStringValue:[self shortDialogMessage]]; // Localize the explanatory text field. [commentMessage_ setStringValue:[NSString stringWithFormat:@"%@\n\n%@", - [self explanatoryCrashDialogText], + [self explanatoryDialogText], NSLocalizedString(@"commentsMsg", @"")]]; float commentHeightDelta = [commentMessage_ breakpad_adjustHeightToFit]; [headerBox_ breakpad_shiftVertically:commentHeightDelta]; @@ -615,9 +632,13 @@ NSString *const kDefaultServerType = @"google"; - (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout { // Queue a |stopModal| message to be performed in |timeout| seconds. if (timeout > 0.001) { - [NSApp performSelector:@selector(stopModal) - withObject:nil - afterDelay:timeout]; + remainingDialogTime_ = timeout; + SEL updateSelector = @selector(updateSecondsLeftInDialogDisplay:); + messageTimer_ = [NSTimer scheduledTimerWithTimeInterval:1.0 + target:self + selector:updateSelector + userInfo:nil + repeats:YES]; } // Run the window modally and wait for either a |stopModal| message or a @@ -625,12 +646,6 @@ NSString *const kDefaultServerType = @"google"; [NSApp activateIgnoringOtherApps:YES]; int returnMethod = [NSApp runModalForWindow:window]; - // Cancel the pending |stopModal| message. - if (returnMethod != NSRunStoppedResponse) { - [NSObject cancelPreviousPerformRequestsWithTarget:NSApp - selector:@selector(stopModal) - object:nil]; - } return returnMethod; } @@ -667,8 +682,10 @@ NSString *const kDefaultServerType = @"google"; textView:(NSTextView*)textView doCommandBySelector:(SEL)commandSelector { BOOL result = NO; - // If the user has entered text, don't end editing on "return" - if (commandSelector == @selector(insertNewline:) + // If the user has entered text on the comment field, don't end + // editing on "return". + if (control == commentsEntryField_ && + commandSelector == @selector(insertNewline:) && [[textView string] length] > 0) { [textView insertNewlineIgnoringFieldEditor:self]; result = YES; @@ -676,6 +693,50 @@ doCommandBySelector:(SEL)commandSelector { return result; } +- (void)controlTextDidBeginEditing:(NSNotification *)aNotification { + [messageTimer_ invalidate]; + [self setCountdownMessage:@""]; +} + +- (void)updateSecondsLeftInDialogDisplay:(NSTimer*)theTimer { + remainingDialogTime_ -= 1; + + NSString *countdownMessage; + NSString *formatString; + + int displayedTimeLeft; // This can be either minutes or seconds. + + if (remainingDialogTime_ > 59) { + // calculate minutes remaining for UI purposes + displayedTimeLeft = (remainingDialogTime_ / 60); + + if (displayedTimeLeft == 1) { + formatString = NSLocalizedString(@"countdownMsgMinuteSingular", @""); + } else { + formatString = NSLocalizedString(@"countdownMsgMinutesPlural", @""); + } + } else { + displayedTimeLeft = remainingDialogTime_; + if (remainingDialogTime_ == 1) { + formatString = NSLocalizedString(@"countdownMsgSecondSingular", @""); + } else { + formatString = NSLocalizedString(@"countdownMsgSecondsPlural", @""); + } + } + countdownMessage = [NSString stringWithFormat:formatString, + displayedTimeLeft]; + if (remainingDialogTime_ <= 30) { + [countdownLabel_ setTextColor:[NSColor redColor]]; + } + [self setCountdownMessage:countdownMessage]; + if (remainingDialogTime_ <= 0) { + [messageTimer_ invalidate]; + [NSApp stopModal]; + } +} + + + #pragma mark Accessors #pragma mark - //============================================================================= @@ -702,6 +763,17 @@ doCommandBySelector:(SEL)commandSelector { } } +- (NSString *)countdownMessage { + return [[countdownMessage_ retain] autorelease]; +} + +- (void)setCountdownMessage:(NSString *)value { + if (countdownMessage_ != value) { + [countdownMessage_ release]; + countdownMessage_ = [value copy]; + } +} + #pragma mark - //============================================================================= - (BOOL)reportIntervalElapsed { @@ -730,6 +802,11 @@ doCommandBySelector:(SEL)commandSelector { return YES; } +- (BOOL)isOnDemand { + return [[parameters_ objectForKey:@BREAKPAD_ON_DEMAND] + isEqualToString:@"YES"]; +} + - (BOOL)shouldSubmitSilently { return [[parameters_ objectForKey:@BREAKPAD_SKIP_CONFIRM] isEqualToString:@"YES"]; @@ -745,21 +822,40 @@ doCommandBySelector:(SEL)commandSelector { isEqualToString:@"YES"]; } -- (NSString*)shortCrashDialogMessage { +- (NSString*)shortDialogMessage { NSString *displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT_DISPLAY]; if (![displayName length]) displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT]; - return [NSString stringWithFormat:NSLocalizedString(@"headerFmt", @""), - displayName]; + if ([self isOnDemand]) { + return [NSString + stringWithFormat:NSLocalizedString(@"noCrashDialogHeader", @""), + displayName]; + } else { + return [NSString + stringWithFormat:NSLocalizedString(@"crashDialogHeader", @""), + displayName]; + } } -- (NSString*)explanatoryCrashDialogText { +- (NSString*)explanatoryDialogText { + NSString *displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT_DISPLAY]; + if (![displayName length]) + displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT]; + NSString *vendor = [parameters_ objectForKey:@BREAKPAD_VENDOR]; if (![vendor length]) vendor = @"unknown vendor"; - return [NSString stringWithFormat:NSLocalizedString(@"msgFmt", @""), vendor]; + if ([self isOnDemand]) { + return [NSString + stringWithFormat:NSLocalizedString(@"noCrashDialogMsg", @""), + vendor, displayName]; + } else { + return [NSString + stringWithFormat:NSLocalizedString(@"crashDialogMsg", @""), + vendor]; + } } - (NSTimeInterval)messageTimeout { @@ -940,6 +1036,77 @@ doCommandBySelector:(SEL)commandSelector { [super dealloc]; } +- (void)awakeFromNib { + [emailEntryField_ setMaximumLength:kEmailMaxLength]; + [commentsEntryField_ setMaximumLength:kUserCommentsMaxLength]; +} + +@end + +//============================================================================= +@implementation LengthLimitingTextField + +- (void) setMaximumLength:(unsigned int)maxLength { + maximumLength_ = maxLength; +} + +// This is the method we're overriding in NSTextField, which lets us +// limit the user's input if it makes the string too long. +- (BOOL) textView:(NSTextView *)textView +shouldChangeTextInRange:(NSRange)affectedCharRange + replacementString:(NSString *)replacementString { + + // Sometimes the range comes in invalid, so reject if we can't + // figure out if the replacement text is too long. + if (affectedCharRange.location == NSNotFound) { + return NO; + } + // Figure out what the new string length would be, taking into + // account user selections. + int newStringLength = + [[textView string] length] - affectedCharRange.length + + [replacementString length]; + if (newStringLength > maximumLength_) { + return NO; + } else { + return YES; + } +} + +// Cut, copy, and paste have to be caught specifically since there is no menu. +- (BOOL)performKeyEquivalent:(NSEvent*)event { + // Only handle the key equivalent if |self| is the text field with focus. + NSText* fieldEditor = [self currentEditor]; + if (fieldEditor != nil) { + // Check for a single "Command" modifier + unsigned int modifiers = [event modifierFlags]; + modifiers &= NSDeviceIndependentModifierFlagsMask; + if (modifiers == NSCommandKeyMask) { + // Now, check for Select All, Cut, Copy, or Paste key equivalents. + NSString* characters = [event characters]; + // Select All is Command-A. + if ([characters isEqualToString:@"a"]) { + [fieldEditor selectAll:self]; + return YES; + // Cut is Command-X. + } else if ([characters isEqualToString:@"x"]) { + [fieldEditor cut:self]; + return YES; + // Copy is Command-C. + } else if ([characters isEqualToString:@"c"]) { + [fieldEditor copy:self]; + return YES; + // Paste is Command-V. + } else if ([characters isEqualToString:@"v"]) { + [fieldEditor paste:self]; + return YES; + } + } + } + // Let the super class handle the rest (e.g. Command-Period will cancel). + return [super performKeyEquivalent:event]; +} + @end //============================================================================= diff --git a/src/client/mac/sender/goArrow.png b/src/client/mac/sender/goArrow.png Binary files differnew file mode 100644 index 00000000..f318a567 --- /dev/null +++ b/src/client/mac/sender/goArrow.png |