Home | History | Annotate | Download | only in cocoa
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/ui/cocoa/js_modal_dialog_cocoa.h"
      6 
      7 #import <Cocoa/Cocoa.h>
      8 
      9 #include "base/logging.h"
     10 #import "base/mac/cocoa_protocols.h"
     11 #include "base/sys_string_conversions.h"
     12 #import "chrome/browser/chrome_browser_application_mac.h"
     13 #include "chrome/browser/ui/app_modal_dialogs/js_modal_dialog.h"
     14 #include "grit/app_strings.h"
     15 #include "grit/generated_resources.h"
     16 #include "ui/base/l10n/l10n_util_mac.h"
     17 #include "ui/base/message_box_flags.h"
     18 
     19 // Helper object that receives the notification that the dialog/sheet is
     20 // going away. Is responsible for cleaning itself up.
     21 @interface JavaScriptAppModalDialogHelper : NSObject<NSAlertDelegate> {
     22  @private
     23   NSAlert* alert_;
     24   NSTextField* textField_;  // WEAK; owned by alert_
     25 }
     26 
     27 - (NSAlert*)alert;
     28 - (NSTextField*)textField;
     29 - (void)alertDidEnd:(NSAlert *)alert
     30          returnCode:(int)returnCode
     31         contextInfo:(void*)contextInfo;
     32 
     33 @end
     34 
     35 @implementation JavaScriptAppModalDialogHelper
     36 
     37 - (NSAlert*)alert {
     38   alert_ = [[NSAlert alloc] init];
     39   return alert_;
     40 }
     41 
     42 - (NSTextField*)textField {
     43   textField_ = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 300, 22)];
     44   [[textField_ cell] setLineBreakMode:NSLineBreakByTruncatingTail];
     45   [alert_ setAccessoryView:textField_];
     46   [textField_ release];
     47 
     48   return textField_;
     49 }
     50 
     51 - (void)dealloc {
     52   [alert_ release];
     53   [super dealloc];
     54 }
     55 
     56 // |contextInfo| is the JSModalDialogCocoa that owns us.
     57 - (void)alertDidEnd:(NSAlert*)alert
     58          returnCode:(int)returnCode
     59         contextInfo:(void*)contextInfo {
     60   scoped_ptr<JSModalDialogCocoa> native_dialog(
     61       reinterpret_cast<JSModalDialogCocoa*>(contextInfo));
     62   std::wstring input;
     63   if (textField_)
     64     input = base::SysNSStringToWide([textField_ stringValue]);
     65   bool shouldSuppress = false;
     66   if ([alert showsSuppressionButton])
     67     shouldSuppress = [[alert suppressionButton] state] == NSOnState;
     68   switch (returnCode) {
     69     case NSAlertFirstButtonReturn:  {  // OK
     70       native_dialog->dialog()->OnAccept(input, shouldSuppress);
     71       break;
     72     }
     73     case NSAlertSecondButtonReturn:  {  // Cancel
     74       // If the user wants to stay on this page, stop quitting (if a quit is in
     75       // progress).
     76       if (native_dialog->dialog()->is_before_unload_dialog())
     77         chrome_browser_application_mac::CancelTerminate();
     78       native_dialog->dialog()->OnCancel(shouldSuppress);
     79       break;
     80     }
     81     case NSRunStoppedResponse: {  // Window was closed underneath us
     82       // Need to call OnCancel() because there is some cleanup that needs
     83       // to be done.  It won't call back to the javascript since the
     84       // JavaScriptAppModalDialog knows that the TabContents was destroyed.
     85       native_dialog->dialog()->OnCancel(shouldSuppress);
     86       break;
     87     }
     88     default:  {
     89       NOTREACHED();
     90     }
     91   }
     92 }
     93 @end
     94 
     95 ////////////////////////////////////////////////////////////////////////////////
     96 // JSModalDialogCocoa, public:
     97 
     98 JSModalDialogCocoa::JSModalDialogCocoa(JavaScriptAppModalDialog* dialog)
     99     : dialog_(dialog),
    100       helper_(NULL) {
    101   // Determine the names of the dialog buttons based on the flags. "Default"
    102   // is the OK button. "Other" is the cancel button. We don't use the
    103   // "Alternate" button in NSRunAlertPanel.
    104   NSString* default_button = l10n_util::GetNSStringWithFixup(IDS_APP_OK);
    105   NSString* other_button = l10n_util::GetNSStringWithFixup(IDS_APP_CANCEL);
    106   bool text_field = false;
    107   bool one_button = false;
    108   switch (dialog_->dialog_flags()) {
    109     case ui::MessageBoxFlags::kIsJavascriptAlert:
    110       one_button = true;
    111       break;
    112     case ui::MessageBoxFlags::kIsJavascriptConfirm:
    113       if (dialog_->is_before_unload_dialog()) {
    114         default_button = l10n_util::GetNSStringWithFixup(
    115             IDS_BEFOREUNLOAD_MESSAGEBOX_OK_BUTTON_LABEL);
    116         other_button = l10n_util::GetNSStringWithFixup(
    117             IDS_BEFOREUNLOAD_MESSAGEBOX_CANCEL_BUTTON_LABEL);
    118       }
    119       break;
    120     case ui::MessageBoxFlags::kIsJavascriptPrompt:
    121       text_field = true;
    122       break;
    123 
    124     default:
    125       NOTREACHED();
    126   }
    127 
    128   // Create a helper which will receive the sheet ended selector. It will
    129   // delete itself when done. It doesn't need anything passed to its init
    130   // as it will get a contextInfo parameter.
    131   helper_.reset([[JavaScriptAppModalDialogHelper alloc] init]);
    132 
    133   // Show the modal dialog.
    134   alert_ = [helper_ alert];
    135   NSTextField* field = nil;
    136   if (text_field) {
    137     field = [helper_ textField];
    138     [field setStringValue:base::SysWideToNSString(
    139         dialog_->default_prompt_text())];
    140   }
    141   [alert_ setDelegate:helper_];
    142   [alert_ setInformativeText:base::SysWideToNSString(dialog_->message_text())];
    143   [alert_ setMessageText:base::SysWideToNSString(dialog_->title())];
    144   [alert_ addButtonWithTitle:default_button];
    145   if (!one_button) {
    146     NSButton* other = [alert_ addButtonWithTitle:other_button];
    147     [other setKeyEquivalent:@"\e"];
    148   }
    149   if (dialog_->display_suppress_checkbox()) {
    150     [alert_ setShowsSuppressionButton:YES];
    151     NSString* suppression_title = l10n_util::GetNSStringWithFixup(
    152         IDS_JAVASCRIPT_MESSAGEBOX_SUPPRESS_OPTION);
    153     [[alert_ suppressionButton] setTitle:suppression_title];
    154   }
    155 }
    156 
    157 JSModalDialogCocoa::~JSModalDialogCocoa() {
    158 }
    159 
    160 ////////////////////////////////////////////////////////////////////////////////
    161 // JSModalDialogCocoa, NativeAppModalDialog implementation:
    162 
    163 int JSModalDialogCocoa::GetAppModalDialogButtons() const {
    164   // From the above, it is the case that if there is 1 button, it is always the
    165   // OK button.  The second button, if it exists, is always the Cancel button.
    166   int num_buttons = [[alert_ buttons] count];
    167   switch (num_buttons) {
    168     case 1:
    169       return ui::MessageBoxFlags::DIALOGBUTTON_OK;
    170     case 2:
    171       return ui::MessageBoxFlags::DIALOGBUTTON_OK |
    172              ui::MessageBoxFlags::DIALOGBUTTON_CANCEL;
    173     default:
    174       NOTREACHED();
    175       return 0;
    176   }
    177 }
    178 
    179 void JSModalDialogCocoa::ShowAppModalDialog() {
    180   [alert_
    181       beginSheetModalForWindow:nil  // nil here makes it app-modal
    182                  modalDelegate:helper_.get()
    183                 didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)
    184                    contextInfo:this];
    185 
    186   if ([alert_ accessoryView])
    187     [[alert_ window] makeFirstResponder:[alert_ accessoryView]];
    188 }
    189 
    190 void JSModalDialogCocoa::ActivateAppModalDialog() {
    191 }
    192 
    193 void JSModalDialogCocoa::CloseAppModalDialog() {
    194   DCHECK([alert_ isKindOfClass:[NSAlert class]]);
    195 
    196   // Note: the call below will delete |this|,
    197   // see JavaScriptAppModalDialogHelper's alertDidEnd.
    198   [NSApp endSheet:[alert_ window]];
    199 }
    200 
    201 void JSModalDialogCocoa::AcceptAppModalDialog() {
    202   NSButton* first = [[alert_ buttons] objectAtIndex:0];
    203   [first performClick:nil];
    204 }
    205 
    206 void JSModalDialogCocoa::CancelAppModalDialog() {
    207   DCHECK([[alert_ buttons] count] >= 2);
    208   NSButton* second = [[alert_ buttons] objectAtIndex:1];
    209   [second performClick:nil];
    210 }
    211 
    212 ////////////////////////////////////////////////////////////////////////////////
    213 // NativeAppModalDialog, public:
    214 
    215 // static
    216 NativeAppModalDialog* NativeAppModalDialog::CreateNativeJavaScriptPrompt(
    217     JavaScriptAppModalDialog* dialog,
    218     gfx::NativeWindow parent_window) {
    219   return new JSModalDialogCocoa(dialog);
    220 }
    221