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 "ui/gfx/gtk_util.h"
6
7#include <gdk/gdk.h>
8#include <gtk/gtk.h>
9#include <stdlib.h>
10
11#include "base/basictypes.h"
12#include "base/command_line.h"
13#include "base/memory/scoped_ptr.h"
14#include "third_party/skia/include/core/SkBitmap.h"
15#include "third_party/skia/include/core/SkUnPreMultiply.h"
16#include "ui/gfx/rect.h"
17
18namespace {
19
20// A process wide singleton that manages our usage of gdk cursors.
21// gdk_cursor_new() hits the disk in several places and GdkCursor instances can
22// be reused throughout the process.
23class GdkCursorCache {
24 public:
25  GdkCursorCache() {}
26  ~GdkCursorCache() {
27    for (GdkCursorMap::iterator i(cursors_.begin()); i != cursors_.end(); ++i) {
28      gdk_cursor_unref(i->second);
29    }
30    cursors_.clear();
31  }
32
33  GdkCursor* GetCursorImpl(GdkCursorType type) {
34    GdkCursorMap::iterator it = cursors_.find(type);
35    GdkCursor* cursor = NULL;
36    if (it == cursors_.end()) {
37      cursor = gdk_cursor_new(type);
38      cursors_.insert(std::make_pair(type, cursor));
39    } else {
40      cursor = it->second;
41    }
42
43    // It is not necessary to add a reference here. The callers can ref the
44    // cursor if they need it for something.
45    return cursor;
46  }
47
48 private:
49  typedef std::map<GdkCursorType, GdkCursor*> GdkCursorMap;
50  GdkCursorMap cursors_;
51
52  DISALLOW_COPY_AND_ASSIGN(GdkCursorCache);
53};
54
55}  // namespace
56
57namespace gfx {
58
59static void CommonInitFromCommandLine(const CommandLine& command_line,
60                                      void (*init_func)(gint*, gchar***)) {
61  const std::vector<std::string>& args = command_line.argv();
62  int argc = args.size();
63  scoped_ptr<char *[]> argv(new char *[argc + 1]);
64  for (size_t i = 0; i < args.size(); ++i) {
65    // TODO(piman@google.com): can gtk_init modify argv? Just being safe
66    // here.
67    argv[i] = strdup(args[i].c_str());
68  }
69  argv[argc] = NULL;
70  char **argv_pointer = argv.get();
71
72  init_func(&argc, &argv_pointer);
73  for (size_t i = 0; i < args.size(); ++i) {
74    free(argv[i]);
75  }
76}
77
78void GtkInitFromCommandLine(const CommandLine& command_line) {
79  CommonInitFromCommandLine(command_line, gtk_init);
80}
81
82void GdkInitFromCommandLine(const CommandLine& command_line) {
83  CommonInitFromCommandLine(command_line, gdk_init);
84}
85
86GdkPixbuf* GdkPixbufFromSkBitmap(const SkBitmap& bitmap) {
87  if (bitmap.isNull())
88    return NULL;
89
90  SkAutoLockPixels lock_pixels(bitmap);
91
92  int width = bitmap.width();
93  int height = bitmap.height();
94
95  GdkPixbuf* pixbuf = gdk_pixbuf_new(
96      GDK_COLORSPACE_RGB,  // The only colorspace gtk supports.
97      TRUE,  // There is an alpha channel.
98      8,
99      width, height);
100
101  // SkBitmaps are premultiplied, we need to unpremultiply them.
102  const int kBytesPerPixel = 4;
103  uint8* divided = gdk_pixbuf_get_pixels(pixbuf);
104
105  for (int y = 0, i = 0; y < height; y++) {
106    for (int x = 0; x < width; x++) {
107      uint32 pixel = bitmap.getAddr32(0, y)[x];
108
109      int alpha = SkColorGetA(pixel);
110      if (alpha != 0 && alpha != 255) {
111        SkColor unmultiplied = SkUnPreMultiply::PMColorToColor(pixel);
112        divided[i + 0] = SkColorGetR(unmultiplied);
113        divided[i + 1] = SkColorGetG(unmultiplied);
114        divided[i + 2] = SkColorGetB(unmultiplied);
115        divided[i + 3] = alpha;
116      } else {
117        divided[i + 0] = SkColorGetR(pixel);
118        divided[i + 1] = SkColorGetG(pixel);
119        divided[i + 2] = SkColorGetB(pixel);
120        divided[i + 3] = alpha;
121      }
122      i += kBytesPerPixel;
123    }
124  }
125
126  return pixbuf;
127}
128
129void SubtractRectanglesFromRegion(GdkRegion* region,
130                                  const std::vector<Rect>& cutouts) {
131  for (size_t i = 0; i < cutouts.size(); ++i) {
132    GdkRectangle rect = cutouts[i].ToGdkRectangle();
133    GdkRegion* rect_region = gdk_region_rectangle(&rect);
134    gdk_region_subtract(region, rect_region);
135    // TODO(deanm): It would be nice to be able to reuse the GdkRegion here.
136    gdk_region_destroy(rect_region);
137  }
138}
139
140GdkCursor* GetCursor(int type) {
141  CR_DEFINE_STATIC_LOCAL(GdkCursorCache, impl, ());
142  return impl.GetCursorImpl(static_cast<GdkCursorType>(type));
143}
144
145void InitRCStyles() {
146  static const char kRCText[] =
147      // Make our dialogs styled like the GNOME HIG.
148      //
149      // TODO(evanm): content-area-spacing was introduced in a later
150      // version of GTK, so we need to set that manually on all dialogs.
151      // Perhaps it would make sense to have a shared FixupDialog() function.
152      "style \"gnome-dialog\" {\n"
153      "  xthickness = 12\n"
154      "  GtkDialog::action-area-border = 0\n"
155      "  GtkDialog::button-spacing = 6\n"
156      "  GtkDialog::content-area-spacing = 18\n"
157      "  GtkDialog::content-area-border = 12\n"
158      "}\n"
159      // Note we set it at the "application" priority, so users can override.
160      "widget \"GtkDialog\" style : application \"gnome-dialog\"\n"
161
162      // Make our about dialog special, so the image is flush with the edge.
163      "style \"about-dialog\" {\n"
164      "  GtkDialog::action-area-border = 12\n"
165      "  GtkDialog::button-spacing = 6\n"
166      "  GtkDialog::content-area-spacing = 18\n"
167      "  GtkDialog::content-area-border = 0\n"
168      "}\n"
169      "widget \"about-dialog\" style : application \"about-dialog\"\n";
170
171  gtk_rc_parse_string(kRCText);
172}
173
174base::TimeDelta GetCursorBlinkCycle() {
175  // From http://library.gnome.org/devel/gtk/unstable/GtkSettings.html, this is
176  // the default value for gtk-cursor-blink-time.
177  static const gint kGtkDefaultCursorBlinkTime = 1200;
178
179  gint cursor_blink_time = kGtkDefaultCursorBlinkTime;
180  gboolean cursor_blink = TRUE;
181  g_object_get(gtk_settings_get_default(),
182               "gtk-cursor-blink-time", &cursor_blink_time,
183               "gtk-cursor-blink", &cursor_blink,
184               NULL);
185  return cursor_blink ?
186         base::TimeDelta::FromMilliseconds(cursor_blink_time) :
187         base::TimeDelta();
188}
189
190}  // namespace gfx
191