Home | History | Annotate | Download | only in chromeos
      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/chromeos/native_dialog_window.h"
      6 
      7 #include <gtk/gtk.h>
      8 
      9 #include "base/logging.h"
     10 #include "base/utf_string_conversions.h"
     11 #include "chrome/browser/chromeos/frame/bubble_window.h"
     12 #include "chrome/browser/ui/views/window.h"
     13 #include "ui/base/gtk/gtk_signal.h"
     14 #include "views/controls/native/native_view_host.h"
     15 #include "views/window/dialog_delegate.h"
     16 #include "views/window/non_client_view.h"
     17 #include "views/window/window.h"
     18 
     19 namespace {
     20 
     21 const int kDialogPadding = 3;
     22 
     23 const char kNativeDialogHost[] = "_chromeos_native_dialog_host_";
     24 
     25 // TODO(xiyuan): Use gtk_window_get_default_widget with GTK 2.14+.
     26 // Gets the default widget of given dialog.
     27 GtkWidget* GetDialogDefaultWidget(GtkDialog* dialog) {
     28   GtkWidget* default_widget = NULL;
     29 
     30   GList* children = gtk_container_get_children(
     31       GTK_CONTAINER(dialog->action_area));
     32 
     33   GList* current = children;
     34   while (current) {
     35     GtkWidget* widget = reinterpret_cast<GtkWidget*>(current->data);
     36     if (GTK_WIDGET_HAS_DEFAULT(widget)) {
     37       default_widget = widget;
     38       break;
     39     }
     40 
     41     current = g_list_next(current);
     42   }
     43 
     44   g_list_free(children);
     45 
     46   return default_widget;
     47 }
     48 
     49 }  // namespace
     50 
     51 namespace chromeos {
     52 
     53 class NativeDialogHost : public views::View,
     54                          public views::DialogDelegate {
     55  public:
     56   NativeDialogHost(gfx::NativeView native_dialog,
     57                    int flags,
     58                    const gfx::Size& size,
     59                    const gfx::Size& min_size);
     60   ~NativeDialogHost();
     61 
     62   // views::DialogDelegate implementation:
     63   virtual bool CanResize() const { return flags_ & DIALOG_FLAG_RESIZEABLE; }
     64   virtual int GetDialogButtons() const { return 0; }
     65   virtual std::wstring GetWindowTitle() const { return title_; }
     66   virtual views::View* GetContentsView() { return this; }
     67   virtual bool IsModal() const { return flags_ & DIALOG_FLAG_MODAL; }
     68   virtual void WindowClosing();
     69 
     70  protected:
     71   CHROMEGTK_CALLBACK_0(NativeDialogHost, void, OnCheckResize);
     72   CHROMEGTK_CALLBACK_0(NativeDialogHost, void, OnDialogDestroy);
     73 
     74   // views::View implementation:
     75   virtual gfx::Size GetPreferredSize();
     76   virtual void Layout();
     77   virtual void ViewHierarchyChanged(bool is_add,
     78                                     views::View* parent,
     79                                     views::View* child);
     80  private:
     81   // Init and attach to native dialog.
     82   void Init();
     83 
     84   // Check and apply minimum size restriction.
     85   void CheckSize();
     86 
     87   // The GtkDialog whose vbox will be displayed in this view.
     88   gfx::NativeView dialog_;
     89 
     90   // NativeViewHost for the dialog's contents.
     91   views::NativeViewHost* contents_view_;
     92 
     93   std::wstring title_;
     94   int flags_;
     95   gfx::Size size_;
     96   gfx::Size preferred_size_;
     97   gfx::Size min_size_;
     98 
     99   int destroy_signal_id_;
    100 
    101   DISALLOW_IMPLICIT_CONSTRUCTORS(NativeDialogHost);
    102 };
    103 
    104 ///////////////////////////////////////////////////////////////////////////////
    105 // NativeDialogHost, public:
    106 
    107 NativeDialogHost::NativeDialogHost(gfx::NativeView native_dialog,
    108                                    int flags,
    109                                    const gfx::Size& size,
    110                                    const gfx::Size& min_size)
    111     : dialog_(native_dialog),
    112       contents_view_(NULL),
    113       flags_(flags),
    114       size_(size),
    115       preferred_size_(size),
    116       min_size_(min_size),
    117       destroy_signal_id_(0) {
    118   const char* title = gtk_window_get_title(GTK_WINDOW(dialog_));
    119   if (title)
    120     UTF8ToWide(title, strlen(title), &title_);
    121 
    122   destroy_signal_id_ = g_signal_connect(dialog_, "destroy",
    123       G_CALLBACK(&OnDialogDestroyThunk), this);
    124 }
    125 
    126 NativeDialogHost::~NativeDialogHost() {
    127 }
    128 
    129 void NativeDialogHost::OnCheckResize(GtkWidget* widget) {
    130   // Do auto height resize only when we are asked to do so.
    131   if (size_.height() == 0) {
    132     gfx::NativeView contents = contents_view_->native_view();
    133 
    134     // Check whether preferred height has changed. We keep the current width
    135     // unchanged and pass "-1" as height to let gtk calculate a proper height.
    136     gtk_widget_set_size_request(contents, width(), -1);
    137     GtkRequisition requsition = { 0 };
    138     gtk_widget_size_request(contents, &requsition);
    139 
    140     if (preferred_size_.height() != requsition.height) {
    141       preferred_size_.set_width(requsition.width);
    142       preferred_size_.set_height(requsition.height);
    143       CheckSize();
    144       SizeToPreferredSize();
    145 
    146       gfx::Size window_size = window()->non_client_view()->GetPreferredSize();
    147       gfx::Rect window_bounds = window()->GetBounds();
    148       window_bounds.set_width(window_size.width());
    149       window_bounds.set_height(window_size.height());
    150       window()->SetWindowBounds(window_bounds, NULL);
    151     }
    152   }
    153 }
    154 
    155 void NativeDialogHost::OnDialogDestroy(GtkWidget* widget) {
    156   dialog_ = NULL;
    157   destroy_signal_id_ = 0;
    158   window()->CloseWindow();
    159 }
    160 
    161 ///////////////////////////////////////////////////////////////////////////////
    162 // NativeDialogHost, views::DialogDelegate implementation:
    163 void NativeDialogHost::WindowClosing() {
    164   if (dialog_) {
    165     // Disconnect the "destroy" signal because we are about to destroy
    166     // the dialog ourselves and no longer interested in it.
    167     g_signal_handler_disconnect(G_OBJECT(dialog_), destroy_signal_id_);
    168     gtk_dialog_response(GTK_DIALOG(dialog_), GTK_RESPONSE_DELETE_EVENT);
    169   }
    170 }
    171 
    172 ///////////////////////////////////////////////////////////////////////////////
    173 // NativeDialogHost, views::View implementation:
    174 
    175 gfx::Size NativeDialogHost::GetPreferredSize() {
    176   return preferred_size_;
    177 }
    178 
    179 void NativeDialogHost::Layout() {
    180   contents_view_->SetBounds(0, 0, width(), height());
    181 }
    182 
    183 void NativeDialogHost::ViewHierarchyChanged(bool is_add,
    184                                             views::View* parent,
    185                                             views::View* child) {
    186   if (is_add && child == this)
    187     Init();
    188 }
    189 
    190 ///////////////////////////////////////////////////////////////////////////////
    191 // NativeDialogHost, private:
    192 void NativeDialogHost::Init() {
    193   if (contents_view_)
    194     return;
    195 
    196   // Get default widget of the dialog.
    197   GtkWidget* default_widget = GetDialogDefaultWidget(GTK_DIALOG(dialog_));
    198 
    199   // Get focus widget of the dialog.
    200   GtkWidget* focus_widget = gtk_window_get_focus(GTK_WINDOW(dialog_));
    201 
    202   // Create a GtkAlignment as dialog contents container.
    203   GtkWidget* contents = gtk_alignment_new(0.5, 0.5, 1.0, 1.0);
    204   gtk_alignment_set_padding(GTK_ALIGNMENT(contents),
    205       kDialogPadding, kDialogPadding,
    206       kDialogPadding, kDialogPadding);
    207 
    208   // Move dialog contents into our container.
    209   GtkWidget* dialog_contents = GTK_DIALOG(dialog_)->vbox;
    210   g_object_ref(dialog_contents);
    211   gtk_container_remove(GTK_CONTAINER(dialog_), dialog_contents);
    212   gtk_container_add(GTK_CONTAINER(contents), dialog_contents);
    213   g_object_unref(dialog_contents);
    214   gtk_widget_hide(dialog_);
    215 
    216   g_object_set_data(G_OBJECT(dialog_), kNativeDialogHost,
    217       reinterpret_cast<gpointer>(this));
    218 
    219   gtk_widget_show_all(contents);
    220 
    221   contents_view_ = new views::NativeViewHost();
    222   // TODO(xiyuan): Find a better way to get proper background.
    223   contents_view_->set_background(views::Background::CreateSolidBackground(
    224       BubbleWindow::kBackgroundColor));
    225   AddChildView(contents_view_);
    226   contents_view_->Attach(contents);
    227 
    228   g_signal_connect(window()->GetNativeWindow(), "check-resize",
    229       G_CALLBACK(&OnCheckResizeThunk), this);
    230 
    231   const int padding = 2 * kDialogPadding;
    232   // Use gtk's default size if size is not specified.
    233   if (size_.IsEmpty()) {
    234     // Use given width or height if given.
    235     if (size_.width() || size_.height()) {
    236       int width = size_.width() == 0 ? -1 : size_.width() + padding;
    237       int height = size_.height() == 0 ? -1 : size_.height() + padding;
    238       gtk_widget_set_size_request(contents, width, height);
    239     }
    240 
    241     GtkRequisition requsition = { 0 };
    242     gtk_widget_size_request(contents, &requsition);
    243     preferred_size_.set_width(requsition.width);
    244     preferred_size_.set_height(requsition.height);
    245   } else {
    246     preferred_size_.set_width(size_.width() + padding);
    247     preferred_size_.set_height(size_.height() + padding);
    248   }
    249 
    250   CheckSize();
    251 
    252   if (default_widget)
    253     gtk_widget_grab_default(default_widget);
    254 
    255   if (focus_widget)
    256     gtk_widget_grab_focus(focus_widget);
    257 }
    258 
    259 void NativeDialogHost::CheckSize() {
    260   // Apply the minimum size.
    261   if (preferred_size_.width() < min_size_.width())
    262     preferred_size_.set_width(min_size_.width());
    263   if (preferred_size_.height() < min_size_.height())
    264     preferred_size_.set_height(min_size_.height());
    265 }
    266 
    267 void ShowNativeDialog(gfx::NativeWindow parent,
    268                       gfx::NativeView native_dialog,
    269                       int flags,
    270                       const gfx::Size& size,
    271                       const gfx::Size& min_size) {
    272   NativeDialogHost* native_dialog_host =
    273       new NativeDialogHost(native_dialog, flags, size, min_size);
    274   browser::CreateViewsWindow(parent, gfx::Rect(), native_dialog_host);
    275   native_dialog_host->window()->Show();
    276 }
    277 
    278 gfx::NativeWindow GetNativeDialogWindow(gfx::NativeView native_dialog) {
    279   NativeDialogHost* host = reinterpret_cast<NativeDialogHost*>(
    280       g_object_get_data(G_OBJECT(native_dialog), kNativeDialogHost));
    281   return host ? host->window()->GetNativeWindow() : NULL;
    282 }
    283 
    284 gfx::Rect GetNativeDialogContentsBounds(gfx::NativeView native_dialog) {
    285   NativeDialogHost* host = reinterpret_cast<NativeDialogHost*>(
    286       g_object_get_data(G_OBJECT(native_dialog), kNativeDialogHost));
    287   return host ? host->bounds() : gfx::Rect();
    288 }
    289 
    290 }  // namespace chromeos
    291