// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/chromeos/native_dialog_window.h" #include #include "base/logging.h" #include "base/utf_string_conversions.h" #include "chrome/browser/chromeos/frame/bubble_window.h" #include "chrome/browser/ui/views/window.h" #include "ui/base/gtk/gtk_signal.h" #include "views/controls/native/native_view_host.h" #include "views/window/dialog_delegate.h" #include "views/window/non_client_view.h" #include "views/window/window.h" namespace { const int kDialogPadding = 3; const char kNativeDialogHost[] = "_chromeos_native_dialog_host_"; // TODO(xiyuan): Use gtk_window_get_default_widget with GTK 2.14+. // Gets the default widget of given dialog. GtkWidget* GetDialogDefaultWidget(GtkDialog* dialog) { GtkWidget* default_widget = NULL; GList* children = gtk_container_get_children( GTK_CONTAINER(dialog->action_area)); GList* current = children; while (current) { GtkWidget* widget = reinterpret_cast(current->data); if (GTK_WIDGET_HAS_DEFAULT(widget)) { default_widget = widget; break; } current = g_list_next(current); } g_list_free(children); return default_widget; } } // namespace namespace chromeos { class NativeDialogHost : public views::View, public views::DialogDelegate { public: NativeDialogHost(gfx::NativeView native_dialog, int flags, const gfx::Size& size, const gfx::Size& min_size); ~NativeDialogHost(); // views::DialogDelegate implementation: virtual bool CanResize() const { return flags_ & DIALOG_FLAG_RESIZEABLE; } virtual int GetDialogButtons() const { return 0; } virtual std::wstring GetWindowTitle() const { return title_; } virtual views::View* GetContentsView() { return this; } virtual bool IsModal() const { return flags_ & DIALOG_FLAG_MODAL; } virtual void WindowClosing(); protected: CHROMEGTK_CALLBACK_0(NativeDialogHost, void, OnCheckResize); CHROMEGTK_CALLBACK_0(NativeDialogHost, void, OnDialogDestroy); // views::View implementation: virtual gfx::Size GetPreferredSize(); virtual void Layout(); virtual void ViewHierarchyChanged(bool is_add, views::View* parent, views::View* child); private: // Init and attach to native dialog. void Init(); // Check and apply minimum size restriction. void CheckSize(); // The GtkDialog whose vbox will be displayed in this view. gfx::NativeView dialog_; // NativeViewHost for the dialog's contents. views::NativeViewHost* contents_view_; std::wstring title_; int flags_; gfx::Size size_; gfx::Size preferred_size_; gfx::Size min_size_; int destroy_signal_id_; DISALLOW_IMPLICIT_CONSTRUCTORS(NativeDialogHost); }; /////////////////////////////////////////////////////////////////////////////// // NativeDialogHost, public: NativeDialogHost::NativeDialogHost(gfx::NativeView native_dialog, int flags, const gfx::Size& size, const gfx::Size& min_size) : dialog_(native_dialog), contents_view_(NULL), flags_(flags), size_(size), preferred_size_(size), min_size_(min_size), destroy_signal_id_(0) { const char* title = gtk_window_get_title(GTK_WINDOW(dialog_)); if (title) UTF8ToWide(title, strlen(title), &title_); destroy_signal_id_ = g_signal_connect(dialog_, "destroy", G_CALLBACK(&OnDialogDestroyThunk), this); } NativeDialogHost::~NativeDialogHost() { } void NativeDialogHost::OnCheckResize(GtkWidget* widget) { // Do auto height resize only when we are asked to do so. if (size_.height() == 0) { gfx::NativeView contents = contents_view_->native_view(); // Check whether preferred height has changed. We keep the current width // unchanged and pass "-1" as height to let gtk calculate a proper height. gtk_widget_set_size_request(contents, width(), -1); GtkRequisition requsition = { 0 }; gtk_widget_size_request(contents, &requsition); if (preferred_size_.height() != requsition.height) { preferred_size_.set_width(requsition.width); preferred_size_.set_height(requsition.height); CheckSize(); SizeToPreferredSize(); gfx::Size window_size = window()->non_client_view()->GetPreferredSize(); gfx::Rect window_bounds = window()->GetBounds(); window_bounds.set_width(window_size.width()); window_bounds.set_height(window_size.height()); window()->SetWindowBounds(window_bounds, NULL); } } } void NativeDialogHost::OnDialogDestroy(GtkWidget* widget) { dialog_ = NULL; destroy_signal_id_ = 0; window()->CloseWindow(); } /////////////////////////////////////////////////////////////////////////////// // NativeDialogHost, views::DialogDelegate implementation: void NativeDialogHost::WindowClosing() { if (dialog_) { // Disconnect the "destroy" signal because we are about to destroy // the dialog ourselves and no longer interested in it. g_signal_handler_disconnect(G_OBJECT(dialog_), destroy_signal_id_); gtk_dialog_response(GTK_DIALOG(dialog_), GTK_RESPONSE_DELETE_EVENT); } } /////////////////////////////////////////////////////////////////////////////// // NativeDialogHost, views::View implementation: gfx::Size NativeDialogHost::GetPreferredSize() { return preferred_size_; } void NativeDialogHost::Layout() { contents_view_->SetBounds(0, 0, width(), height()); } void NativeDialogHost::ViewHierarchyChanged(bool is_add, views::View* parent, views::View* child) { if (is_add && child == this) Init(); } /////////////////////////////////////////////////////////////////////////////// // NativeDialogHost, private: void NativeDialogHost::Init() { if (contents_view_) return; // Get default widget of the dialog. GtkWidget* default_widget = GetDialogDefaultWidget(GTK_DIALOG(dialog_)); // Get focus widget of the dialog. GtkWidget* focus_widget = gtk_window_get_focus(GTK_WINDOW(dialog_)); // Create a GtkAlignment as dialog contents container. GtkWidget* contents = gtk_alignment_new(0.5, 0.5, 1.0, 1.0); gtk_alignment_set_padding(GTK_ALIGNMENT(contents), kDialogPadding, kDialogPadding, kDialogPadding, kDialogPadding); // Move dialog contents into our container. GtkWidget* dialog_contents = GTK_DIALOG(dialog_)->vbox; g_object_ref(dialog_contents); gtk_container_remove(GTK_CONTAINER(dialog_), dialog_contents); gtk_container_add(GTK_CONTAINER(contents), dialog_contents); g_object_unref(dialog_contents); gtk_widget_hide(dialog_); g_object_set_data(G_OBJECT(dialog_), kNativeDialogHost, reinterpret_cast(this)); gtk_widget_show_all(contents); contents_view_ = new views::NativeViewHost(); // TODO(xiyuan): Find a better way to get proper background. contents_view_->set_background(views::Background::CreateSolidBackground( BubbleWindow::kBackgroundColor)); AddChildView(contents_view_); contents_view_->Attach(contents); g_signal_connect(window()->GetNativeWindow(), "check-resize", G_CALLBACK(&OnCheckResizeThunk), this); const int padding = 2 * kDialogPadding; // Use gtk's default size if size is not specified. if (size_.IsEmpty()) { // Use given width or height if given. if (size_.width() || size_.height()) { int width = size_.width() == 0 ? -1 : size_.width() + padding; int height = size_.height() == 0 ? -1 : size_.height() + padding; gtk_widget_set_size_request(contents, width, height); } GtkRequisition requsition = { 0 }; gtk_widget_size_request(contents, &requsition); preferred_size_.set_width(requsition.width); preferred_size_.set_height(requsition.height); } else { preferred_size_.set_width(size_.width() + padding); preferred_size_.set_height(size_.height() + padding); } CheckSize(); if (default_widget) gtk_widget_grab_default(default_widget); if (focus_widget) gtk_widget_grab_focus(focus_widget); } void NativeDialogHost::CheckSize() { // Apply the minimum size. if (preferred_size_.width() < min_size_.width()) preferred_size_.set_width(min_size_.width()); if (preferred_size_.height() < min_size_.height()) preferred_size_.set_height(min_size_.height()); } void ShowNativeDialog(gfx::NativeWindow parent, gfx::NativeView native_dialog, int flags, const gfx::Size& size, const gfx::Size& min_size) { NativeDialogHost* native_dialog_host = new NativeDialogHost(native_dialog, flags, size, min_size); browser::CreateViewsWindow(parent, gfx::Rect(), native_dialog_host); native_dialog_host->window()->Show(); } gfx::NativeWindow GetNativeDialogWindow(gfx::NativeView native_dialog) { NativeDialogHost* host = reinterpret_cast( g_object_get_data(G_OBJECT(native_dialog), kNativeDialogHost)); return host ? host->window()->GetNativeWindow() : NULL; } gfx::Rect GetNativeDialogContentsBounds(gfx::NativeView native_dialog) { NativeDialogHost* host = reinterpret_cast( g_object_get_data(G_OBJECT(native_dialog), kNativeDialogHost)); return host ? host->bounds() : gfx::Rect(); } } // namespace chromeos