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. 23 class HtmlDialogWindowDelegateBridge : public HtmlDialogUIDelegate, 24 public HtmlDialogTabContentsDelegate { 25 public: 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 53 private: 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 75 namespace browser { 76 77 gfx::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 85 HtmlDialogWindowDelegateBridge::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 94 HtmlDialogWindowDelegateBridge::~HtmlDialogWindowDelegateBridge() {} 95 96 void HtmlDialogWindowDelegateBridge::WindowControllerClosed() { 97 Detach(); 98 controller_ = nil; 99 DelegateOnDialogClosed(""); 100 } 101 102 bool 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 118 bool 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 126 std::wstring HtmlDialogWindowDelegateBridge::GetDialogTitle() const { 127 return delegate_ ? delegate_->GetDialogTitle() : L""; 128 } 129 130 GURL HtmlDialogWindowDelegateBridge::GetDialogContentURL() const { 131 return delegate_ ? delegate_->GetDialogContentURL() : GURL(); 132 } 133 134 void 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 146 void HtmlDialogWindowDelegateBridge::GetDialogSize(gfx::Size* size) const { 147 if (delegate_) { 148 delegate_->GetDialogSize(size); 149 } else { 150 *size = gfx::Size(); 151 } 152 } 153 154 std::string HtmlDialogWindowDelegateBridge::GetDialogArgs() const { 155 return delegate_ ? delegate_->GetDialogArgs() : ""; 156 } 157 158 void 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 168 void 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.) 176 void 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