Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / gtk / rounded_window.cc
1 // Copyright (c) 2012 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/rounded_window.h"
6
7 #include <gtk/gtk.h>
8 #include <math.h>
9
10 #include "base/i18n/rtl.h"
11 #include "base/logging.h"
12 #include "chrome/browser/ui/gtk/gtk_util.h"
13 #include "ui/base/gtk/gtk_signal_registrar.h"
14 #include "ui/gfx/gtk_compat.h"
15
16 namespace gtk_util {
17
18 namespace {
19
20 const char* kRoundedData = "rounded-window-data";
21
22 // If the border radius is less than |kMinRoundedBorderSize|, we don't actually
23 // round the corners, we just truncate the corner.
24 const int kMinRoundedBorderSize = 8;
25
26 struct RoundedWindowData {
27   // Expected window size. Used to detect when we need to reshape the window.
28   int expected_width;
29   int expected_height;
30
31   // Color of the border.
32   GdkColor border_color;
33
34   // Radius of the edges in pixels.
35   int corner_size;
36
37   // Which corners should be rounded?
38   int rounded_edges;
39
40   // Which sides of the window should have an internal border?
41   int drawn_borders;
42
43   // Keeps track of attached signal handlers.
44   ui::GtkSignalRegistrar signals;
45 };
46
47 // Callback from GTK to release allocated memory.
48 void FreeRoundedWindowData(gpointer data) {
49   delete static_cast<RoundedWindowData*>(data);
50 }
51
52 enum FrameType {
53   FRAME_MASK,
54   FRAME_STROKE,
55 };
56
57 // Returns a list of points that either form the outline of the status bubble
58 // (|type| == FRAME_MASK) or form the inner border around the inner edge
59 // (|type| == FRAME_STROKE).
60 std::vector<GdkPoint> MakeFramePolygonPoints(RoundedWindowData* data,
61                                              FrameType type) {
62   using gtk_util::MakeBidiGdkPoint;
63   int width = data->expected_width;
64   int height = data->expected_height;
65   int corner_size = data->corner_size;
66
67   std::vector<GdkPoint> points;
68
69   bool ltr = !base::i18n::IsRTL();
70   // If we have a stroke, we have to offset some of our points by 1 pixel.
71   // We have to inset by 1 pixel when we draw horizontal lines that are on the
72   // bottom or when we draw vertical lines that are closer to the end (end is
73   // right for ltr).
74   int y_off = (type == FRAME_MASK) ? 0 : -1;
75   // We use this one for LTR.
76   int x_off_l = ltr ? y_off : 0;
77   // We use this one for RTL.
78   int x_off_r = !ltr ? -y_off : 0;
79
80   // Build up points starting with the bottom left corner and continuing
81   // clockwise.
82
83   // Bottom left corner.
84   if (type == FRAME_MASK ||
85       (data->drawn_borders & (BORDER_LEFT | BORDER_BOTTOM))) {
86     if (data->rounded_edges & ROUNDED_BOTTOM_LEFT) {
87       if (corner_size >= kMinRoundedBorderSize) {
88         // We are careful to only add points that are horizontal or vertically
89         // offset from the previous point (not both).  This avoids rounding
90         // differences when two points are connected.
91         for (int x = 0; x <= corner_size; ++x) {
92           int y = static_cast<int>(sqrt(static_cast<double>(
93               (corner_size * corner_size) - (x * x))));
94           if (x > 0) {
95             points.push_back(MakeBidiGdkPoint(
96                 corner_size - x + x_off_r + 1,
97                 height - (corner_size - y) + y_off, width, ltr));
98           }
99           points.push_back(MakeBidiGdkPoint(
100               corner_size - x + x_off_r,
101               height - (corner_size - y) + y_off, width, ltr));
102         }
103       } else {
104         points.push_back(MakeBidiGdkPoint(
105             corner_size + x_off_l, height + y_off, width, ltr));
106         points.push_back(MakeBidiGdkPoint(
107             x_off_r, height - corner_size, width, ltr));
108       }
109     } else {
110       points.push_back(MakeBidiGdkPoint(x_off_r, height + y_off, width, ltr));
111     }
112   }
113
114   // Top left corner.
115   if (type == FRAME_MASK ||
116       (data->drawn_borders & (BORDER_LEFT | BORDER_TOP))) {
117     if (data->rounded_edges & ROUNDED_TOP_LEFT) {
118       if (corner_size >= kMinRoundedBorderSize) {
119         for (int x = corner_size; x >= 0; --x) {
120           int y = static_cast<int>(sqrt(static_cast<double>(
121               (corner_size * corner_size) - (x * x))));
122           points.push_back(MakeBidiGdkPoint(corner_size - x + x_off_r,
123               corner_size - y, width, ltr));
124           if (x > 0) {
125             points.push_back(MakeBidiGdkPoint(corner_size - x + 1 + x_off_r,
126                 corner_size - y, width, ltr));
127           }
128         }
129       } else {
130         points.push_back(MakeBidiGdkPoint(
131             x_off_r, corner_size - 1, width, ltr));
132         points.push_back(MakeBidiGdkPoint(
133             corner_size + x_off_r - 1, 0, width, ltr));
134       }
135     } else {
136       points.push_back(MakeBidiGdkPoint(x_off_r, 0, width, ltr));
137     }
138   }
139
140   // Top right corner.
141   if (type == FRAME_MASK ||
142       (data->drawn_borders & (BORDER_TOP | BORDER_RIGHT))) {
143     if (data->rounded_edges & ROUNDED_TOP_RIGHT) {
144       if (corner_size >= kMinRoundedBorderSize) {
145         for (int x = 0; x <= corner_size; ++x) {
146           int y = static_cast<int>(sqrt(static_cast<double>(
147               (corner_size * corner_size) - (x * x))));
148           if (x > 0) {
149             points.push_back(MakeBidiGdkPoint(
150                 width - (corner_size - x) + x_off_l - 1,
151                 corner_size - y, width, ltr));
152           }
153           points.push_back(MakeBidiGdkPoint(
154               width - (corner_size - x) + x_off_l,
155               corner_size - y, width, ltr));
156         }
157       } else {
158         points.push_back(MakeBidiGdkPoint(
159             width - corner_size + 1 + x_off_l, 0, width, ltr));
160         points.push_back(MakeBidiGdkPoint(
161             width + x_off_l, corner_size - 1, width, ltr));
162       }
163     } else {
164       points.push_back(MakeBidiGdkPoint(
165           width + x_off_l, 0, width, ltr));
166     }
167   }
168
169   // Bottom right corner.
170   if (type == FRAME_MASK ||
171       (data->drawn_borders & (BORDER_RIGHT | BORDER_BOTTOM))) {
172     if (data->rounded_edges & ROUNDED_BOTTOM_RIGHT) {
173       if (corner_size >= kMinRoundedBorderSize) {
174         for (int x = corner_size; x >= 0; --x) {
175           int y = static_cast<int>(sqrt(static_cast<double>(
176               (corner_size * corner_size) - (x * x))));
177           points.push_back(MakeBidiGdkPoint(
178               width - (corner_size - x) + x_off_l,
179               height - (corner_size - y) + y_off, width, ltr));
180           if (x > 0) {
181             points.push_back(MakeBidiGdkPoint(
182                 width - (corner_size - x) + x_off_l - 1,
183                 height - (corner_size - y) + y_off, width, ltr));
184           }
185         }
186       } else {
187         points.push_back(MakeBidiGdkPoint(
188             width + x_off_l, height - corner_size, width, ltr));
189         points.push_back(MakeBidiGdkPoint(
190             width - corner_size + x_off_r, height + y_off, width, ltr));
191       }
192     } else {
193       points.push_back(MakeBidiGdkPoint(
194           width + x_off_l, height + y_off, width, ltr));
195     }
196   }
197
198   return points;
199 }
200
201 // Set the window shape in needed, lets our owner do some drawing (if it wants
202 // to), and finally draw the border.
203 gboolean OnRoundedWindowExpose(GtkWidget* widget,
204                                GdkEventExpose* event) {
205   RoundedWindowData* data = static_cast<RoundedWindowData*>(
206       g_object_get_data(G_OBJECT(widget), kRoundedData));
207
208   GtkAllocation allocation;
209   gtk_widget_get_allocation(widget, &allocation);
210
211   if (data->expected_width != allocation.width ||
212       data->expected_height != allocation.height) {
213     data->expected_width = allocation.width;
214     data->expected_height = allocation.height;
215
216     // We need to update the shape of the status bubble whenever our GDK
217     // window changes shape.
218     std::vector<GdkPoint> mask_points = MakeFramePolygonPoints(
219         data, FRAME_MASK);
220     GdkRegion* mask_region = gdk_region_polygon(&mask_points[0],
221                                                 mask_points.size(),
222                                                 GDK_EVEN_ODD_RULE);
223     gdk_window_shape_combine_region(gtk_widget_get_window(widget),
224                                     mask_region, 0, 0);
225     gdk_region_destroy(mask_region);
226   }
227
228   GdkDrawable* drawable = GDK_DRAWABLE(event->window);
229   GdkGC* gc = gdk_gc_new(drawable);
230   gdk_gc_set_clip_rectangle(gc, &event->area);
231   gdk_gc_set_rgb_fg_color(gc, &data->border_color);
232
233   // Stroke the frame border.
234   std::vector<GdkPoint> points = MakeFramePolygonPoints(
235       data, FRAME_STROKE);
236   if (data->drawn_borders == BORDER_ALL) {
237     // If we want to have borders everywhere, we need to draw a polygon instead
238     // of a set of lines.
239     gdk_draw_polygon(drawable, gc, FALSE, &points[0], points.size());
240   } else if (!points.empty()) {
241     gdk_draw_lines(drawable, gc, &points[0], points.size());
242   }
243
244   g_object_unref(gc);
245   return FALSE;  // Propagate so our children paint, etc.
246 }
247
248 // On theme changes, window shapes are reset, but we detect whether we need to
249 // reshape a window by whether its allocation has changed so force it to reset
250 // the window shape on next expose.
251 void OnStyleSet(GtkWidget* widget, GtkStyle* previous_style) {
252   DCHECK(widget);
253   RoundedWindowData* data = static_cast<RoundedWindowData*>(
254       g_object_get_data(G_OBJECT(widget), kRoundedData));
255   DCHECK(data);
256   data->expected_width = -1;
257   data->expected_height = -1;
258 }
259
260 }  // namespace
261
262 void ActAsRoundedWindow(
263     GtkWidget* widget, const GdkColor& color, int corner_size,
264     int rounded_edges, int drawn_borders) {
265   DCHECK(widget);
266   DCHECK(!g_object_get_data(G_OBJECT(widget), kRoundedData));
267
268   gtk_widget_set_app_paintable(widget, TRUE);
269
270   RoundedWindowData* data = new RoundedWindowData;
271   data->signals.Connect(widget, "expose-event",
272                         G_CALLBACK(OnRoundedWindowExpose), NULL);
273   data->signals.Connect(widget, "style-set", G_CALLBACK(OnStyleSet), NULL);
274
275   data->expected_width = -1;
276   data->expected_height = -1;
277
278   data->border_color = color;
279   data->corner_size = corner_size;
280
281   data->rounded_edges = rounded_edges;
282   data->drawn_borders = drawn_borders;
283
284   g_object_set_data_full(G_OBJECT(widget), kRoundedData,
285                          data, FreeRoundedWindowData);
286
287   if (gtk_widget_get_visible(widget))
288     gtk_widget_queue_draw(widget);
289 }
290
291 void StopActingAsRoundedWindow(GtkWidget* widget) {
292   g_object_set_data(G_OBJECT(widget), kRoundedData, NULL);
293
294   if (gtk_widget_get_realized(widget))
295     gdk_window_shape_combine_mask(gtk_widget_get_window(widget), NULL, 0, 0);
296
297   if (gtk_widget_get_visible(widget))
298     gtk_widget_queue_draw(widget);
299 }
300
301 bool IsActingAsRoundedWindow(GtkWidget* widget) {
302   return g_object_get_data(G_OBJECT(widget), kRoundedData) != NULL;
303 }
304
305 void SetRoundedWindowEdgesAndBorders(GtkWidget* widget,
306                                      int corner_size,
307                                      int rounded_edges,
308                                      int drawn_borders) {
309   DCHECK(widget);
310   RoundedWindowData* data = static_cast<RoundedWindowData*>(
311       g_object_get_data(G_OBJECT(widget), kRoundedData));
312   DCHECK(data);
313   data->corner_size = corner_size;
314   data->rounded_edges = rounded_edges;
315   data->drawn_borders = drawn_borders;
316 }
317
318 void SetRoundedWindowBorderColor(GtkWidget* widget, GdkColor color) {
319   DCHECK(widget);
320   RoundedWindowData* data = static_cast<RoundedWindowData*>(
321       g_object_get_data(G_OBJECT(widget), kRoundedData));
322   DCHECK(data);
323   data->border_color = color;
324 }
325
326 }  // namespace gtk_util