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