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
19namespace {
20
21const int kDialogPadding = 3;
22
23const 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.
27GtkWidget* 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
51namespace chromeos {
52
53class 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
107NativeDialogHost::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
126NativeDialogHost::~NativeDialogHost() {
127}
128
129void 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
155void NativeDialogHost::OnDialogDestroy(GtkWidget* widget) {
156  dialog_ = NULL;
157  destroy_signal_id_ = 0;
158  window()->CloseWindow();
159}
160
161///////////////////////////////////////////////////////////////////////////////
162// NativeDialogHost, views::DialogDelegate implementation:
163void 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
175gfx::Size NativeDialogHost::GetPreferredSize() {
176  return preferred_size_;
177}
178
179void NativeDialogHost::Layout() {
180  contents_view_->SetBounds(0, 0, width(), height());
181}
182
183void 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:
192void 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
259void 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
267void 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
278gfx::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
284gfx::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