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/ui/gtk/gtk_floating_container.h"
6
7#include <gtk/gtk.h>
8#include <gtk/gtkmarshal.h>
9#include <gtk/gtkprivate.h>
10
11#include <algorithm>
12
13namespace {
14
15enum {
16  SET_FLOATING_POSITION,
17  LAST_SIGNAL
18};
19
20enum {
21  CHILD_PROP_0,
22  CHILD_PROP_X,
23  CHILD_PROP_Y
24};
25
26// Returns the GtkFloatingContainerChild associated with |widget| (or NULL if
27// |widget| not found).
28GtkFloatingContainerChild* GetChild(GtkFloatingContainer* container,
29                                    GtkWidget* widget) {
30  for (GList* floating_children = container->floating_children;
31       floating_children; floating_children = g_list_next(floating_children)) {
32    GtkFloatingContainerChild* child =
33        reinterpret_cast<GtkFloatingContainerChild*>(floating_children->data);
34
35    if (child->widget == widget)
36      return child;
37  }
38
39  return NULL;
40}
41
42}  // namespace
43
44G_BEGIN_DECLS
45
46static void gtk_floating_container_remove(GtkContainer* container,
47                                          GtkWidget* widget);
48static void gtk_floating_container_forall(GtkContainer* container,
49                                          gboolean include_internals,
50                                          GtkCallback callback,
51                                          gpointer callback_data);
52static void gtk_floating_container_size_request(GtkWidget* widget,
53                                                GtkRequisition* requisition);
54static void gtk_floating_container_size_allocate(GtkWidget* widget,
55                                                 GtkAllocation* allocation);
56static void gtk_floating_container_set_child_property(GtkContainer* container,
57                                                      GtkWidget* child,
58                                                      guint property_id,
59                                                      const GValue* value,
60                                                      GParamSpec* pspec);
61static void gtk_floating_container_get_child_property(GtkContainer* container,
62                                                      GtkWidget* child,
63                                                      guint property_id,
64                                                      GValue* value,
65                                                      GParamSpec* pspec);
66
67static guint floating_container_signals[LAST_SIGNAL] = { 0 };
68
69G_DEFINE_TYPE(GtkFloatingContainer, gtk_floating_container, GTK_TYPE_BIN)
70
71static void gtk_floating_container_class_init(
72    GtkFloatingContainerClass *klass) {
73  GtkObjectClass* object_class =
74      reinterpret_cast<GtkObjectClass*>(klass);
75
76  GtkWidgetClass* widget_class =
77      reinterpret_cast<GtkWidgetClass*>(klass);
78  widget_class->size_request = gtk_floating_container_size_request;
79  widget_class->size_allocate = gtk_floating_container_size_allocate;
80
81  GtkContainerClass* container_class =
82      reinterpret_cast<GtkContainerClass*>(klass);
83  container_class->remove = gtk_floating_container_remove;
84  container_class->forall = gtk_floating_container_forall;
85
86  container_class->set_child_property =
87      gtk_floating_container_set_child_property;
88  container_class->get_child_property =
89      gtk_floating_container_get_child_property;
90
91  gtk_container_class_install_child_property(
92      container_class,
93      CHILD_PROP_X,
94      g_param_spec_int("x",
95                       "X position",
96                       "X position of child widget",
97                       G_MININT,
98                       G_MAXINT,
99                       0,
100                       static_cast<GParamFlags>(GTK_PARAM_READWRITE)));
101
102  gtk_container_class_install_child_property(
103      container_class,
104      CHILD_PROP_Y,
105      g_param_spec_int("y",
106                       "Y position",
107                       "Y position of child widget",
108                       G_MININT,
109                       G_MAXINT,
110                       0,
111                       static_cast<GParamFlags>(GTK_PARAM_READWRITE)));
112
113  floating_container_signals[SET_FLOATING_POSITION] =
114      g_signal_new("set-floating-position",
115                   G_OBJECT_CLASS_TYPE(object_class),
116                   static_cast<GSignalFlags>(G_SIGNAL_RUN_FIRST |
117                                             G_SIGNAL_ACTION),
118                   0,
119                   NULL, NULL,
120                   gtk_marshal_VOID__BOXED,
121                   G_TYPE_NONE, 1,
122                   GDK_TYPE_RECTANGLE | G_SIGNAL_TYPE_STATIC_SCOPE);
123}
124
125static void gtk_floating_container_init(GtkFloatingContainer* container) {
126  GTK_WIDGET_SET_FLAGS(container, GTK_NO_WINDOW);
127
128  container->floating_children = NULL;
129}
130
131static void gtk_floating_container_remove(GtkContainer* container,
132                                          GtkWidget* widget) {
133  g_return_if_fail(GTK_IS_WIDGET(widget));
134
135  GtkBin* bin = GTK_BIN(container);
136  if (bin->child == widget) {
137    ((GTK_CONTAINER_CLASS(gtk_floating_container_parent_class))->remove)
138        (container, widget);
139  } else {
140    // Handle the other case where it's in our |floating_children| list.
141    GtkFloatingContainer* floating = GTK_FLOATING_CONTAINER(container);
142    GList* children = floating->floating_children;
143    gboolean removed_child = false;
144    while (children) {
145      GtkFloatingContainerChild* child =
146          reinterpret_cast<GtkFloatingContainerChild*>(children->data);
147
148      if (child->widget == widget) {
149        removed_child = true;
150        gboolean was_visible = GTK_WIDGET_VISIBLE(widget);
151
152        gtk_widget_unparent(widget);
153
154        floating->floating_children =
155            g_list_remove_link(floating->floating_children, children);
156        g_list_free(children);
157        g_free(child);
158
159        if (was_visible && GTK_WIDGET_VISIBLE(container))
160          gtk_widget_queue_resize(GTK_WIDGET(container));
161
162        break;
163      }
164      children = children->next;
165    }
166
167    g_return_if_fail(removed_child);
168  }
169}
170
171static void gtk_floating_container_forall(GtkContainer* container,
172                                          gboolean include_internals,
173                                          GtkCallback callback,
174                                          gpointer callback_data) {
175  g_return_if_fail(container != NULL);
176  g_return_if_fail(callback != NULL);
177
178  // Let GtkBin do its part of the forall.
179  ((GTK_CONTAINER_CLASS(gtk_floating_container_parent_class))->forall)
180      (container, include_internals, callback, callback_data);
181
182  GtkFloatingContainer* floating = GTK_FLOATING_CONTAINER(container);
183  GList* children = floating->floating_children;
184  while (children) {
185    GtkFloatingContainerChild* child =
186        reinterpret_cast<GtkFloatingContainerChild*>(children->data);
187    children = children->next;
188
189    (*callback)(child->widget, callback_data);
190  }
191}
192
193static void gtk_floating_container_size_request(GtkWidget* widget,
194                                                GtkRequisition* requisition) {
195  GtkBin* bin = GTK_BIN(widget);
196  if (bin && bin->child) {
197    gtk_widget_size_request(bin->child, requisition);
198  } else {
199    requisition->width = 0;
200    requisition->height = 0;
201  }
202}
203
204static void gtk_floating_container_size_allocate(GtkWidget* widget,
205                                                 GtkAllocation* allocation) {
206  widget->allocation = *allocation;
207
208  if (!GTK_WIDGET_NO_WINDOW(widget) && GTK_WIDGET_REALIZED(widget)) {
209    gdk_window_move_resize(widget->window,
210                           allocation->x,
211                           allocation->y,
212                           allocation->width,
213                           allocation->height);
214  }
215
216  // Give the same allocation to our GtkBin component.
217  GtkBin* bin = GTK_BIN(widget);
218  if (bin->child) {
219    gtk_widget_size_allocate(bin->child, allocation);
220  }
221
222  // We need to give whoever is pulling our strings a chance to set the "x" and
223  // "y" properties on all of our children.
224  g_signal_emit(widget, floating_container_signals[SET_FLOATING_POSITION], 0,
225                allocation);
226
227  // Our allocation has been set. We've asked our controller to place the other
228  // widgets. Pass out allocations to all our children based on where they want
229  // to be.
230  GtkFloatingContainer* container = GTK_FLOATING_CONTAINER(widget);
231  GList* children = container->floating_children;
232  GtkAllocation child_allocation;
233  GtkRequisition child_requisition;
234  while (children) {
235    GtkFloatingContainerChild* child =
236        reinterpret_cast<GtkFloatingContainerChild*>(children->data);
237    children = children->next;
238
239    if (GTK_WIDGET_VISIBLE(child->widget)) {
240      gtk_widget_size_request(child->widget, &child_requisition);
241      child_allocation.x = allocation->x + child->x;
242      child_allocation.y = allocation->y + child->y;
243      child_allocation.width = std::max(1, std::min(child_requisition.width,
244                                                    allocation->width));
245      child_allocation.height = std::max(1, std::min(child_requisition.height,
246                                                     allocation->height));
247      gtk_widget_size_allocate(child->widget, &child_allocation);
248    }
249  }
250}
251
252static void gtk_floating_container_set_child_property(GtkContainer* container,
253                                                      GtkWidget* child,
254                                                      guint property_id,
255                                                      const GValue* value,
256                                                      GParamSpec* pspec) {
257  GtkFloatingContainerChild* floating_child =
258      GetChild(GTK_FLOATING_CONTAINER(container), child);
259  g_return_if_fail(floating_child);
260
261  switch (property_id) {
262    case CHILD_PROP_X:
263      floating_child->x = g_value_get_int(value);
264      gtk_widget_child_notify(child, "x");
265      break;
266    case CHILD_PROP_Y:
267      floating_child->y = g_value_get_int(value);
268      gtk_widget_child_notify(child, "y");
269      break;
270    default:
271      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID(
272          container, property_id, pspec);
273      break;
274  };
275}
276
277static void gtk_floating_container_get_child_property(GtkContainer* container,
278                                                      GtkWidget* child,
279                                                      guint property_id,
280                                                      GValue* value,
281                                                      GParamSpec* pspec) {
282  GtkFloatingContainerChild* floating_child =
283      GetChild(GTK_FLOATING_CONTAINER(container), child);
284  g_return_if_fail(floating_child);
285
286  switch (property_id) {
287    case CHILD_PROP_X:
288      g_value_set_int(value, floating_child->x);
289      break;
290    case CHILD_PROP_Y:
291      g_value_set_int(value, floating_child->y);
292      break;
293    default:
294      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID(
295          container, property_id, pspec);
296      break;
297  };
298}
299
300GtkWidget* gtk_floating_container_new() {
301  return GTK_WIDGET(g_object_new(GTK_TYPE_FLOATING_CONTAINER, NULL));
302}
303
304void gtk_floating_container_add_floating(GtkFloatingContainer* container,
305                                         GtkWidget* widget) {
306  g_return_if_fail(GTK_IS_FLOATING_CONTAINER(container));
307  g_return_if_fail(GTK_IS_WIDGET(widget));
308
309  GtkFloatingContainerChild* child_info = g_new(GtkFloatingContainerChild, 1);
310  child_info->widget = widget;
311  child_info->x = 0;
312  child_info->y = 0;
313
314  gtk_widget_set_parent(widget, GTK_WIDGET(container));
315
316  container->floating_children =
317      g_list_append(container->floating_children, child_info);
318}
319
320G_END_DECLS
321