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.
GetDialogDefaultWidget(GtkDialog * 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:
CanResize() const63 virtual bool CanResize() const { return flags_ & DIALOG_FLAG_RESIZEABLE; }
GetDialogButtons() const64 virtual int GetDialogButtons() const { return 0; }
GetWindowTitle() const65 virtual std::wstring GetWindowTitle() const { return title_; }
GetContentsView()66 virtual views::View* GetContentsView() { return this; }
IsModal() const67 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
NativeDialogHost(gfx::NativeView native_dialog,int flags,const gfx::Size & size,const gfx::Size & min_size)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
~NativeDialogHost()126 NativeDialogHost::~NativeDialogHost() {
127 }
128
OnCheckResize(GtkWidget * widget)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
OnDialogDestroy(GtkWidget * widget)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:
WindowClosing()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
GetPreferredSize()175 gfx::Size NativeDialogHost::GetPreferredSize() {
176 return preferred_size_;
177 }
178
Layout()179 void NativeDialogHost::Layout() {
180 contents_view_->SetBounds(0, 0, width(), height());
181 }
182
ViewHierarchyChanged(bool is_add,views::View * parent,views::View * child)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:
Init()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
CheckSize()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
ShowNativeDialog(gfx::NativeWindow parent,gfx::NativeView native_dialog,int flags,const gfx::Size & size,const gfx::Size & min_size)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
GetNativeDialogWindow(gfx::NativeView native_dialog)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
GetNativeDialogContentsBounds(gfx::NativeView native_dialog)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