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
98JSModalDialogCocoa::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
157JSModalDialogCocoa::~JSModalDialogCocoa() {
158}
159
160////////////////////////////////////////////////////////////////////////////////
161// JSModalDialogCocoa, NativeAppModalDialog implementation:
162
163int 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
179void 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
190void JSModalDialogCocoa::ActivateAppModalDialog() {
191}
192
193void 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
201void JSModalDialogCocoa::AcceptAppModalDialog() {
202  NSButton* first = [[alert_ buttons] objectAtIndex:0];
203  [first performClick:nil];
204}
205
206void 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
216NativeAppModalDialog* NativeAppModalDialog::CreateNativeJavaScriptPrompt(
217    JavaScriptAppModalDialog* dialog,
218    gfx::NativeWindow parent_window) {
219  return new JSModalDialogCocoa(dialog);
220}
221