blob: 61ed3c866f0e3912dd7730e3de4e50f2ff331a7d [file] [log] [blame]
// 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 <gtk/gtk.h>
#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<GtkWidget*>(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<gpointer>(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<NativeDialogHost*>(
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<NativeDialogHost*>(
g_object_get_data(G_OBJECT(native_dialog), kNativeDialogHost));
return host ? host->bounds() : gfx::Rect();
}
} // namespace chromeos