html_dialog_window_controller.mm revision ddb351dbec246cf1fab5ec20d2d5520909041de1
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#import "chrome/browser/ui/cocoa/html_dialog_window_controller.h"
6
7#include "base/logging.h"
8#include "base/memory/scoped_nsobject.h"
9#include "base/sys_string_conversions.h"
10#include "chrome/browser/profiles/profile.h"
11#import "chrome/browser/ui/browser_dialogs.h"
12#import "chrome/browser/ui/cocoa/browser_command_executor.h"
13#import "chrome/browser/ui/cocoa/chrome_event_processing_window.h"
14#include "chrome/browser/ui/webui/html_dialog_tab_contents_delegate.h"
15#include "chrome/browser/ui/webui/html_dialog_ui.h"
16#include "content/browser/tab_contents/tab_contents.h"
17#include "content/common/native_web_keyboard_event.h"
18#include "ui/base/keycodes/keyboard_codes.h"
19#include "ui/gfx/size.h"
20
21// Thin bridge that routes notifications to
22// HtmlDialogWindowController's member variables.
23class HtmlDialogWindowDelegateBridge : public HtmlDialogUIDelegate,
24                                       public HtmlDialogTabContentsDelegate {
25public:
26  // All parameters must be non-NULL/non-nil.
27  HtmlDialogWindowDelegateBridge(HtmlDialogWindowController* controller,
28                                 Profile* profile,
29                                 HtmlDialogUIDelegate* delegate);
30
31  virtual ~HtmlDialogWindowDelegateBridge();
32
33  // Called when the window is directly closed, e.g. from the close
34  // button or from an accelerator.
35  void WindowControllerClosed();
36
37  // HtmlDialogUIDelegate declarations.
38  virtual bool IsDialogModal() const;
39  virtual std::wstring GetDialogTitle() const;
40  virtual GURL GetDialogContentURL() const;
41  virtual void GetWebUIMessageHandlers(
42      std::vector<WebUIMessageHandler*>* handlers) const;
43  virtual void GetDialogSize(gfx::Size* size) const;
44  virtual std::string GetDialogArgs() const;
45  virtual void OnDialogClosed(const std::string& json_retval);
46  virtual void OnCloseContents(TabContents* source, bool* out_close_dialog) { }
47  virtual bool ShouldShowDialogTitle() const { return true; }
48
49  // HtmlDialogTabContentsDelegate declarations.
50  virtual void MoveContents(TabContents* source, const gfx::Rect& pos);
51  virtual void HandleKeyboardEvent(const NativeWebKeyboardEvent& event);
52
53private:
54  HtmlDialogWindowController* controller_;  // weak
55  HtmlDialogUIDelegate* delegate_;  // weak, owned by controller_
56
57  // Calls delegate_'s OnDialogClosed() exactly once, nulling it out
58  // afterwards so that no other HtmlDialogUIDelegate calls are sent
59  // to it.  Returns whether or not the OnDialogClosed() was actually
60  // called on the delegate.
61  bool DelegateOnDialogClosed(const std::string& json_retval);
62
63  DISALLOW_COPY_AND_ASSIGN(HtmlDialogWindowDelegateBridge);
64};
65
66// ChromeEventProcessingWindow expects its controller to implement the
67// BrowserCommandExecutor protocol.
68@interface HtmlDialogWindowController (InternalAPI) <BrowserCommandExecutor>
69
70// BrowserCommandExecutor methods.
71- (void)executeCommand:(int)command;
72
73@end
74
75namespace browser {
76
77gfx::NativeWindow ShowHtmlDialog(gfx::NativeWindow parent, Profile* profile,
78                                 HtmlDialogUIDelegate* delegate) {
79  // NOTE: Use the parent parameter once we implement modal dialogs.
80  return [HtmlDialogWindowController showHtmlDialog:delegate profile:profile];
81}
82
83}  // namespace html_dialog_window_controller
84
85HtmlDialogWindowDelegateBridge::HtmlDialogWindowDelegateBridge(
86    HtmlDialogWindowController* controller, Profile* profile,
87    HtmlDialogUIDelegate* delegate)
88    : HtmlDialogTabContentsDelegate(profile),
89      controller_(controller), delegate_(delegate) {
90  DCHECK(controller_);
91  DCHECK(delegate_);
92}
93
94HtmlDialogWindowDelegateBridge::~HtmlDialogWindowDelegateBridge() {}
95
96void HtmlDialogWindowDelegateBridge::WindowControllerClosed() {
97  Detach();
98  controller_ = nil;
99  DelegateOnDialogClosed("");
100}
101
102bool HtmlDialogWindowDelegateBridge::DelegateOnDialogClosed(
103    const std::string& json_retval) {
104  if (delegate_) {
105    HtmlDialogUIDelegate* real_delegate = delegate_;
106    delegate_ = NULL;
107    real_delegate->OnDialogClosed(json_retval);
108    return true;
109  }
110  return false;
111}
112
113// HtmlDialogUIDelegate definitions.
114
115// All of these functions check for NULL first since delegate_ is set
116// to NULL when the window is closed.
117
118bool HtmlDialogWindowDelegateBridge::IsDialogModal() const {
119  // TODO(akalin): Support modal dialog boxes.
120  if (delegate_ && delegate_->IsDialogModal()) {
121    LOG(WARNING) << "Modal HTML dialogs are not supported yet";
122  }
123  return false;
124}
125
126std::wstring HtmlDialogWindowDelegateBridge::GetDialogTitle() const {
127  return delegate_ ? delegate_->GetDialogTitle() : L"";
128}
129
130GURL HtmlDialogWindowDelegateBridge::GetDialogContentURL() const {
131  return delegate_ ? delegate_->GetDialogContentURL() : GURL();
132}
133
134void HtmlDialogWindowDelegateBridge::GetWebUIMessageHandlers(
135    std::vector<WebUIMessageHandler*>* handlers) const {
136  if (delegate_) {
137    delegate_->GetWebUIMessageHandlers(handlers);
138  } else {
139    // TODO(akalin): Add this clause in the windows version.  Also
140    // make sure that everything expects handlers to be non-NULL and
141    // document it.
142    handlers->clear();
143  }
144}
145
146void HtmlDialogWindowDelegateBridge::GetDialogSize(gfx::Size* size) const {
147  if (delegate_) {
148    delegate_->GetDialogSize(size);
149  } else {
150    *size = gfx::Size();
151  }
152}
153
154std::string HtmlDialogWindowDelegateBridge::GetDialogArgs() const {
155  return delegate_ ? delegate_->GetDialogArgs() : "";
156}
157
158void HtmlDialogWindowDelegateBridge::OnDialogClosed(
159    const std::string& json_retval) {
160  Detach();
161  // [controller_ close] should be called at most once, too.
162  if (DelegateOnDialogClosed(json_retval)) {
163    [controller_ close];
164  }
165  controller_ = nil;
166}
167
168void HtmlDialogWindowDelegateBridge::MoveContents(TabContents* source,
169                                                  const gfx::Rect& pos) {
170  // TODO(akalin): Actually set the window bounds.
171}
172
173// A simplified version of BrowserWindowCocoa::HandleKeyboardEvent().
174// We don't handle global keyboard shortcuts here, but that's fine since
175// they're all browser-specific. (This may change in the future.)
176void HtmlDialogWindowDelegateBridge::HandleKeyboardEvent(
177    const NativeWebKeyboardEvent& event) {
178  if (event.skip_in_browser || event.type == NativeWebKeyboardEvent::Char)
179    return;
180
181  // Close ourselves if the user hits Esc or Command-. .  The normal
182  // way to do this is to implement (void)cancel:(int)sender, but
183  // since we handle keyboard events ourselves we can't do that.
184  //
185  // According to experiments, hitting Esc works regardless of the
186  // presence of other modifiers (as long as it's not an app-level
187  // shortcut, e.g. Commmand-Esc for Front Row) but no other modifiers
188  // can be present for Command-. to work.
189  //
190  // TODO(thakis): It would be nice to get cancel: to work somehow.
191  // Bug: http://code.google.com/p/chromium/issues/detail?id=32828 .
192  if (event.type == NativeWebKeyboardEvent::RawKeyDown &&
193      ((event.windowsKeyCode == ui::VKEY_ESCAPE) ||
194       (event.windowsKeyCode == ui::VKEY_OEM_PERIOD &&
195        event.modifiers == NativeWebKeyboardEvent::MetaKey))) {
196    [controller_ close];
197    return;
198  }
199
200  ChromeEventProcessingWindow* event_window =
201      static_cast<ChromeEventProcessingWindow*>([controller_ window]);
202  DCHECK([event_window isKindOfClass:[ChromeEventProcessingWindow class]]);
203  [event_window redispatchKeyEvent:event.os_event];
204}
205
206@implementation HtmlDialogWindowController (InternalAPI)
207
208// This gets called whenever a chrome-specific keyboard shortcut is performed
209// in the HTML dialog window.  We simply swallow all those events.
210- (void)executeCommand:(int)command {}
211
212@end
213
214@implementation HtmlDialogWindowController
215
216// NOTE(akalin): We'll probably have to add the parentWindow parameter back
217// in once we implement modal dialogs.
218
219+ (NSWindow*)showHtmlDialog:(HtmlDialogUIDelegate*)delegate
220                    profile:(Profile*)profile {
221  HtmlDialogWindowController* htmlDialogWindowController =
222    [[HtmlDialogWindowController alloc] initWithDelegate:delegate
223                                                 profile:profile];
224  [htmlDialogWindowController loadDialogContents];
225  [htmlDialogWindowController showWindow:nil];
226  return [htmlDialogWindowController window];
227}
228
229- (id)initWithDelegate:(HtmlDialogUIDelegate*)delegate
230               profile:(Profile*)profile {
231  DCHECK(delegate);
232  DCHECK(profile);
233
234  gfx::Size dialogSize;
235  delegate->GetDialogSize(&dialogSize);
236  NSRect dialogRect = NSMakeRect(0, 0, dialogSize.width(), dialogSize.height());
237  // TODO(akalin): Make the window resizable (but with the minimum size being
238  // dialog_size and always on top (but not modal) to match the Windows
239  // behavior.  On the other hand, the fact that HTML dialogs on Windows
240  // are resizable could just be an accident.  Investigate futher...
241  NSUInteger style = NSTitledWindowMask | NSClosableWindowMask;
242  scoped_nsobject<ChromeEventProcessingWindow> window(
243      [[ChromeEventProcessingWindow alloc]
244           initWithContentRect:dialogRect
245                     styleMask:style
246                       backing:NSBackingStoreBuffered
247                         defer:YES]);
248  if (!window.get()) {
249    return nil;
250  }
251  self = [super initWithWindow:window];
252  if (!self) {
253    return nil;
254  }
255  [window setWindowController:self];
256  [window setDelegate:self];
257  [window setTitle:base::SysWideToNSString(delegate->GetDialogTitle())];
258  [window center];
259  delegate_.reset(new HtmlDialogWindowDelegateBridge(self, profile, delegate));
260  return self;
261}
262
263- (void)loadDialogContents {
264  tabContents_.reset(new TabContents(
265      delegate_->profile(), NULL, MSG_ROUTING_NONE, NULL, NULL));
266  [[self window] setContentView:tabContents_->GetNativeView()];
267  tabContents_->set_delegate(delegate_.get());
268
269  // This must be done before loading the page; see the comments in
270  // HtmlDialogUI.
271  HtmlDialogUI::GetPropertyAccessor().SetProperty(tabContents_->property_bag(),
272                                                  delegate_.get());
273
274  tabContents_->controller().LoadURL(delegate_->GetDialogContentURL(),
275                                      GURL(), PageTransition::START_PAGE);
276
277  // TODO(akalin): add accelerator for ESC to close the dialog box.
278  //
279  // TODO(akalin): Figure out why implementing (void)cancel:(id)sender
280  // to do the above doesn't work.
281}
282
283- (void)windowWillClose:(NSNotification*)notification {
284  delegate_->WindowControllerClosed();
285  [self autorelease];
286}
287
288@end
289