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 #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