blob: 3b5c3b52cb612d07865c454ba4cd841510670178 [file] [log] [blame]
[email protected]5c311352010-05-07 19:36:151// Copyright (c) 2010 The Chromium Authors. All rights reserved.
[email protected]e5f72c72009-06-05 19:15:332// 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/gtk/bookmark_utils_gtk.h"
6
[email protected]f29bbb62009-08-18 15:37:207#include "app/gtk_dnd_util.h"
[email protected]8098d2d72009-08-27 22:56:368#include "app/l10n_util.h"
[email protected]e5f72c72009-06-05 19:15:339#include "app/resource_bundle.h"
[email protected]e5f72c72009-06-05 19:15:3310#include "base/pickle.h"
[email protected]aa7dc4f2009-06-29 18:21:3311#include "base/string_util.h"
[email protected]e5f72c72009-06-05 19:15:3312#include "chrome/browser/bookmarks/bookmark_drag_data.h"
13#include "chrome/browser/bookmarks/bookmark_model.h"
[email protected]903e7a82009-07-28 00:45:3514#include "chrome/browser/bookmarks/bookmark_utils.h"
[email protected]aa7dc4f2009-06-29 18:21:3315#include "chrome/browser/gtk/gtk_chrome_button.h"
[email protected]3c78ae922009-07-07 00:37:0816#include "chrome/browser/gtk/gtk_theme_provider.h"
[email protected]16d51df2010-03-02 09:16:4417#include "chrome/browser/gtk/gtk_util.h"
[email protected]e5f72c72009-06-05 19:15:3318#include "chrome/browser/profile.h"
[email protected]3024338792010-06-23 23:04:2319#include "gfx/canvas_skia_paint.h"
[email protected]36622b12010-04-30 23:35:4420#include "gfx/font.h"
[email protected]5c7293a2010-03-17 06:40:5721#include "gfx/gtk_util.h"
[email protected]e5f72c72009-06-05 19:15:3322
[email protected]aa7dc4f2009-06-29 18:21:3323namespace {
24
[email protected]36622b12010-04-30 23:35:4425// Spacing between the favicon and the text.
26const int kBarButtonPadding = 4;
27
[email protected]e5f72c72009-06-05 19:15:3328// Used in gtk_selection_data_set(). (I assume from this parameter that gtk has
29// to some really exotic hardware...)
30const int kBitsInAByte = 8;
31
[email protected]aa7dc4f2009-06-29 18:21:3332// Maximum number of characters on a bookmark button.
33const size_t kMaxCharsOnAButton = 15;
34
[email protected]8098d2d72009-08-27 22:56:3635// Max size of each component of the button tooltips.
36const size_t kMaxTooltipTitleLength = 100;
37const size_t kMaxTooltipURLLength = 400;
38
[email protected]aa7dc4f2009-06-29 18:21:3339// Only used for the background of the drag widget.
40const GdkColor kBackgroundColor = GDK_COLOR_RGB(0xe6, 0xed, 0xf4);
41
[email protected]9f040e432009-07-10 18:23:5842// Padding between the chrome button highlight border and the contents (favicon,
43// text).
[email protected]9f040e432009-07-10 18:23:5844const int kButtonPaddingTop = 0;
45const int kButtonPaddingBottom = 0;
[email protected]ce04ada2010-06-16 20:36:1546const int kButtonPaddingLeft = 5;
[email protected]9f040e432009-07-10 18:23:5847const int kButtonPaddingRight = 0;
48
[email protected]aa7dc4f2009-06-29 18:21:3349void* AsVoid(const BookmarkNode* node) {
50 return const_cast<BookmarkNode*>(node);
51}
52
[email protected]36622b12010-04-30 23:35:4453// Creates the widget hierarchy for a bookmark button.
54void PackButton(GdkPixbuf* pixbuf, const std::wstring& title, bool ellipsize,
55 GtkThemeProvider* provider, GtkWidget* button) {
56 GtkWidget* former_child = gtk_bin_get_child(GTK_BIN(button));
57 if (former_child)
58 gtk_container_remove(GTK_CONTAINER(button), former_child);
59
60 // We pack the button manually (rather than using gtk_button_set_*) so that
61 // we can have finer control over its label.
62 GtkWidget* image = gtk_image_new_from_pixbuf(pixbuf);
63
64 GtkWidget* box = gtk_hbox_new(FALSE, kBarButtonPadding);
65 gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 0);
66
67 std::string label_string = WideToUTF8(title);
68 if (!label_string.empty()) {
69 GtkWidget* label = gtk_label_new(label_string.c_str());
70 // Until we switch to vector graphics, force the font size.
71 gtk_util::ForceFontSizePixels(label, 13.4); // 13.4px == 10pt @ 96dpi
72
73 // Ellipsize long bookmark names.
74 if (ellipsize) {
75 gtk_label_set_max_width_chars(GTK_LABEL(label), kMaxCharsOnAButton);
76 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_END);
77 }
78
79 gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0);
80 bookmark_utils::SetButtonTextColors(label, provider);
81 }
82
83 GtkWidget* alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
84 // If we are not showing the label, don't set any padding, so that the icon
85 // will just be centered.
86 if (label_string.c_str()) {
87 gtk_alignment_set_padding(GTK_ALIGNMENT(alignment),
88 kButtonPaddingTop, kButtonPaddingBottom,
89 kButtonPaddingLeft, kButtonPaddingRight);
90 }
91 gtk_container_add(GTK_CONTAINER(alignment), box);
92 gtk_container_add(GTK_CONTAINER(button), alignment);
93
94 gtk_widget_show_all(alignment);
95}
96
97const int kDragRepresentationWidth = 140;
98
99struct DragRepresentationData {
100 public:
101 GdkPixbuf* favicon;
102 std::wstring text;
103 SkColor text_color;
104
105 DragRepresentationData(GdkPixbuf* favicon,
106 const std::wstring& text,
107 SkColor text_color)
108 : favicon(favicon),
109 text(text),
110 text_color(text_color) {
111 g_object_ref(favicon);
112 }
113
114 ~DragRepresentationData() {
115 g_object_unref(favicon);
116 }
117
118 private:
119 DISALLOW_COPY_AND_ASSIGN(DragRepresentationData);
120};
121
122gboolean OnDragIconExpose(GtkWidget* sender,
123 GdkEventExpose* event,
124 DragRepresentationData* data) {
125 // Clear the background.
126 cairo_t* cr = gdk_cairo_create(event->window);
127 gdk_cairo_rectangle(cr, &event->area);
128 cairo_clip(cr);
129 cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
130 cairo_paint(cr);
131
132 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
133 gdk_cairo_set_source_pixbuf(cr, data->favicon, 0, 0);
134 cairo_paint(cr);
135 cairo_destroy(cr);
136
137 // Paint the title text.
[email protected]3024338792010-06-23 23:04:23138 gfx::CanvasSkiaPaint canvas(event, false);
[email protected]36622b12010-04-30 23:35:44139 int text_x = gdk_pixbuf_get_width(data->favicon) + kBarButtonPadding;
140 int text_width = sender->allocation.width - text_x;
141 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
142 const gfx::Font& base_font = rb.GetFont(ResourceBundle::BaseFont);
143 canvas.DrawStringInt(data->text,
144 base_font, data->text_color,
145 text_x, 0, text_width, sender->allocation.height);
146
147 return TRUE;
148}
149
150void OnDragIconDestroy(GtkWidget* drag_icon,
151 DragRepresentationData* data) {
152 g_object_unref(drag_icon);
153 delete data;
154}
155
[email protected]aa7dc4f2009-06-29 18:21:33156} // namespace
157
[email protected]e5f72c72009-06-05 19:15:33158namespace bookmark_utils {
159
[email protected]aa7dc4f2009-06-29 18:21:33160const char kBookmarkNode[] = "bookmark-node";
161
[email protected]94d88ad32009-08-08 02:18:31162GdkPixbuf* GetPixbufForNode(const BookmarkNode* node, BookmarkModel* model,
163 bool native) {
[email protected]e5f72c72009-06-05 19:15:33164 GdkPixbuf* pixbuf;
165
166 if (node->is_url()) {
167 if (model->GetFavIcon(node).width() != 0) {
168 pixbuf = gfx::GdkPixbufFromSkBitmap(&model->GetFavIcon(node));
169 } else {
[email protected]54a10292009-09-11 01:15:04170 pixbuf = GtkThemeProvider::GetDefaultFavicon(native);
[email protected]e5f72c72009-06-05 19:15:33171 g_object_ref(pixbuf);
172 }
173 } else {
[email protected]54a10292009-09-11 01:15:04174 pixbuf = GtkThemeProvider::GetFolderIcon(native);
[email protected]e5f72c72009-06-05 19:15:33175 g_object_ref(pixbuf);
176 }
177
178 return pixbuf;
179}
180
[email protected]36622b12010-04-30 23:35:44181GtkWidget* GetDragRepresentation(GdkPixbuf* pixbuf,
182 const std::wstring& title,
[email protected]5fdafb22009-07-13 23:23:08183 GtkThemeProvider* provider) {
[email protected]aa7dc4f2009-06-29 18:21:33184 GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP);
[email protected]36622b12010-04-30 23:35:44185
186 if (gtk_util::IsScreenComposited() &&
187 gtk_util::AddWindowAlphaChannel(window)) {
188 DragRepresentationData* data = new DragRepresentationData(
189 pixbuf, title,
190 provider->GetColor(BrowserThemeProvider::COLOR_BOOKMARK_TEXT));
191 g_signal_connect(window, "expose-event", G_CALLBACK(OnDragIconExpose),
192 data);
193 g_object_ref(window);
194 g_signal_connect(window, "destroy", G_CALLBACK(OnDragIconDestroy), data);
195
196 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
197 const gfx::Font& base_font = rb.GetFont(ResourceBundle::BaseFont);
198 gtk_widget_set_size_request(window, kDragRepresentationWidth,
199 base_font.height());
200 } else {
201 if (!provider->UseGtkTheme()) {
202 // TODO(erg): Theme wise, which color should I be picking here?
203 // COLOR_BUTTON_BACKGROUND doesn't match the default theme!
204 gtk_widget_modify_bg(window, GTK_STATE_NORMAL, &kBackgroundColor);
205 }
206 gtk_widget_realize(window);
207
208 GtkWidget* frame = gtk_frame_new(NULL);
209 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
210 gtk_container_add(GTK_CONTAINER(window), frame);
211
212 GtkWidget* floating_button = provider->BuildChromeButton();
213 PackButton(pixbuf, title, true, provider, floating_button);
214 gtk_container_add(GTK_CONTAINER(frame), floating_button);
215 gtk_widget_show_all(frame);
[email protected]3c78ae922009-07-07 00:37:08216 }
[email protected]aa7dc4f2009-06-29 18:21:33217
218 return window;
219}
220
[email protected]36622b12010-04-30 23:35:44221GtkWidget* GetDragRepresentationForNode(const BookmarkNode* node,
222 BookmarkModel* model,
223 GtkThemeProvider* provider) {
224 GdkPixbuf* pixbuf = GetPixbufForNode(node, model, provider->UseGtkTheme());
225 GtkWidget* widget = GetDragRepresentation(pixbuf, node->GetTitle(), provider);
226 g_object_unref(pixbuf);
227 return widget;
228}
229
[email protected]aa7dc4f2009-06-29 18:21:33230void ConfigureButtonForNode(const BookmarkNode* node, BookmarkModel* model,
[email protected]5fdafb22009-07-13 23:23:08231 GtkWidget* button, GtkThemeProvider* provider) {
[email protected]36622b12010-04-30 23:35:44232 GdkPixbuf* pixbuf = bookmark_utils::GetPixbufForNode(node, model,
233 provider->UseGtkTheme());
234 PackButton(pixbuf, node->GetTitle(), node != model->other_node(),
235 provider, button);
236 g_object_unref(pixbuf);
[email protected]2fc90932009-07-02 20:33:40237
[email protected]aa7dc4f2009-06-29 18:21:33238 std::string tooltip = BuildTooltipFor(node);
239 if (!tooltip.empty())
[email protected]8098d2d72009-08-27 22:56:36240 gtk_widget_set_tooltip_markup(button, tooltip.c_str());
[email protected]aa7dc4f2009-06-29 18:21:33241
[email protected]aa7dc4f2009-06-29 18:21:33242 g_object_set_data(G_OBJECT(button), bookmark_utils::kBookmarkNode,
243 AsVoid(node));
244}
245
246std::string BuildTooltipFor(const BookmarkNode* node) {
[email protected]8098d2d72009-08-27 22:56:36247 const std::string& url = node->GetURL().possibly_invalid_spec();
248 const std::string& title = WideToUTF8(node->GetTitle());
249
250 std::string truncated_url = WideToUTF8(l10n_util::TruncateString(
251 UTF8ToWide(url), kMaxTooltipURLLength));
252 gchar* escaped_url_cstr = g_markup_escape_text(truncated_url.c_str(),
253 truncated_url.size());
254 std::string escaped_url(escaped_url_cstr);
255 g_free(escaped_url_cstr);
256
257 std::string tooltip;
[email protected]2edf3232009-09-16 23:33:34258 if (url == title || title.empty()) {
[email protected]8098d2d72009-08-27 22:56:36259 return escaped_url;
260 } else {
261 std::string truncated_title = WideToUTF8(l10n_util::TruncateString(
262 node->GetTitle(), kMaxTooltipTitleLength));
263 gchar* escaped_title_cstr = g_markup_escape_text(truncated_title.c_str(),
264 truncated_title.size());
265 std::string escaped_title(escaped_title_cstr);
266 g_free(escaped_title_cstr);
267
[email protected]a24004b2009-09-03 17:24:54268 if (!escaped_url.empty())
269 return std::string("<b>") + escaped_title + "</b>\n" + escaped_url;
270 else
271 return std::string("<b>") + escaped_title + "</b>";
[email protected]8098d2d72009-08-27 22:56:36272 }
[email protected]aa7dc4f2009-06-29 18:21:33273}
274
275const BookmarkNode* BookmarkNodeForWidget(GtkWidget* widget) {
276 return reinterpret_cast<const BookmarkNode*>(
277 g_object_get_data(G_OBJECT(widget), bookmark_utils::kBookmarkNode));
278}
279
[email protected]5fdafb22009-07-13 23:23:08280void SetButtonTextColors(GtkWidget* label, GtkThemeProvider* provider) {
281 if (provider->UseGtkTheme()) {
[email protected]c7e082e2009-08-03 18:43:28282 gtk_util::SetLabelColor(label, NULL);
[email protected]3c78ae922009-07-07 00:37:08283 } else {
[email protected]5fdafb22009-07-13 23:23:08284 GdkColor color = provider->GetGdkColor(
[email protected]3c78ae922009-07-07 00:37:08285 BrowserThemeProvider::COLOR_BOOKMARK_TEXT);
[email protected]c7e082e2009-08-03 18:43:28286 gtk_util::SetLabelColor(label, &color);
[email protected]3c78ae922009-07-07 00:37:08287 }
[email protected]aa7dc4f2009-06-29 18:21:33288}
289
[email protected]e5f72c72009-06-05 19:15:33290// DnD-related -----------------------------------------------------------------
291
[email protected]2ca8a062010-03-18 17:10:18292int GetCodeMask(bool folder) {
293 int rv = gtk_dnd_util::CHROME_BOOKMARK_ITEM;
294 if (!folder) {
295 rv |= gtk_dnd_util::TEXT_URI_LIST |
296 gtk_dnd_util::TEXT_PLAIN |
297 gtk_dnd_util::NETSCAPE_URL;
298 }
299 return rv;
300}
301
[email protected]b3c33d462009-06-26 22:29:20302void WriteBookmarkToSelection(const BookmarkNode* node,
[email protected]e5f72c72009-06-05 19:15:33303 GtkSelectionData* selection_data,
304 guint target_type,
305 Profile* profile) {
306 DCHECK(node);
[email protected]b3c33d462009-06-26 22:29:20307 std::vector<const BookmarkNode*> nodes;
[email protected]d99bf742009-06-25 00:17:01308 nodes.push_back(node);
309 WriteBookmarksToSelection(nodes, selection_data, target_type, profile);
310}
311
[email protected]b3c33d462009-06-26 22:29:20312void WriteBookmarksToSelection(const std::vector<const BookmarkNode*>& nodes,
[email protected]d99bf742009-06-25 00:17:01313 GtkSelectionData* selection_data,
314 guint target_type,
315 Profile* profile) {
[email protected]e5f72c72009-06-05 19:15:33316 switch (target_type) {
[email protected]6e643432010-03-10 18:32:14317 case gtk_dnd_util::CHROME_BOOKMARK_ITEM: {
[email protected]d99bf742009-06-25 00:17:01318 BookmarkDragData data(nodes);
[email protected]e5f72c72009-06-05 19:15:33319 Pickle pickle;
320 data.WriteToPickle(profile, &pickle);
321
322 gtk_selection_data_set(selection_data, selection_data->target,
323 kBitsInAByte,
324 static_cast<const guchar*>(pickle.data()),
325 pickle.size());
326 break;
327 }
[email protected]6e643432010-03-10 18:32:14328 case gtk_dnd_util::NETSCAPE_URL: {
[email protected]1a4a5cc2010-01-20 00:27:42329 // _NETSCAPE_URL format is URL + \n + title.
330 std::string utf8_text = nodes[0]->GetURL().spec() + "\n" + UTF16ToUTF8(
331 nodes[0]->GetTitleAsString16());
332 gtk_selection_data_set(selection_data,
333 selection_data->target,
334 kBitsInAByte,
335 reinterpret_cast<const guchar*>(utf8_text.c_str()),
336 utf8_text.length());
337 break;
338 }
[email protected]6e643432010-03-10 18:32:14339 case gtk_dnd_util::TEXT_URI_LIST: {
[email protected]3f873e1c2009-07-08 01:01:53340 gchar** uris = reinterpret_cast<gchar**>(malloc(sizeof(gchar*) *
341 (nodes.size() + 1)));
342 for (size_t i = 0; i < nodes.size(); ++i) {
343 // If the node is a folder, this will be empty. TODO(estade): figure out
344 // if there are any ramifications to passing an empty URI. After a
[email protected]903e7a82009-07-28 00:45:35345 // little testing, it seems fine.
[email protected]3f873e1c2009-07-08 01:01:53346 const GURL& url = nodes[i]->GetURL();
347 // This const cast should be safe as gtk_selection_data_set_uris()
348 // makes copies.
349 uris[i] = const_cast<gchar*>(url.spec().c_str());
350 }
351 uris[nodes.size()] = NULL;
352
353 gtk_selection_data_set_uris(selection_data, uris);
354 free(uris);
355 break;
356 }
[email protected]6e643432010-03-10 18:32:14357 case gtk_dnd_util::TEXT_PLAIN: {
[email protected]3462f902009-12-02 00:40:29358 gtk_selection_data_set_text(selection_data,
359 nodes[0]->GetURL().spec().c_str(), -1);
360 break;
[email protected]3462f902009-12-02 00:40:29361 }
[email protected]e5f72c72009-06-05 19:15:33362 default: {
363 DLOG(ERROR) << "Unsupported drag get type!";
364 }
365 }
366}
367
[email protected]b3c33d462009-06-26 22:29:20368std::vector<const BookmarkNode*> GetNodesFromSelection(
[email protected]e5f72c72009-06-05 19:15:33369 GdkDragContext* context,
370 GtkSelectionData* selection_data,
371 guint target_type,
372 Profile* profile,
373 gboolean* delete_selection_data,
374 gboolean* dnd_success) {
[email protected]2ca8a062010-03-18 17:10:18375 if (delete_selection_data)
376 *delete_selection_data = FALSE;
377 if (dnd_success)
378 *dnd_success = FALSE;
[email protected]e5f72c72009-06-05 19:15:33379
[email protected]5c311352010-05-07 19:36:15380 if (selection_data && selection_data->length > 0) {
[email protected]2ca8a062010-03-18 17:10:18381 if (context && delete_selection_data && context->action == GDK_ACTION_MOVE)
[email protected]e5f72c72009-06-05 19:15:33382 *delete_selection_data = TRUE;
[email protected]e5f72c72009-06-05 19:15:33383
384 switch (target_type) {
[email protected]6e643432010-03-10 18:32:14385 case gtk_dnd_util::CHROME_BOOKMARK_ITEM: {
[email protected]2ca8a062010-03-18 17:10:18386 if (dnd_success)
387 *dnd_success = TRUE;
[email protected]e5f72c72009-06-05 19:15:33388 Pickle pickle(reinterpret_cast<char*>(selection_data->data),
389 selection_data->length);
390 BookmarkDragData drag_data;
391 drag_data.ReadFromPickle(&pickle);
392 return drag_data.GetNodes(profile);
393 }
394 default: {
395 DLOG(ERROR) << "Unsupported drag received type: " << target_type;
396 }
397 }
398 }
399
[email protected]b3c33d462009-06-26 22:29:20400 return std::vector<const BookmarkNode*>();
[email protected]e5f72c72009-06-05 19:15:33401}
402
[email protected]cd69661782009-07-17 00:17:56403bool CreateNewBookmarkFromNamedUrl(GtkSelectionData* selection_data,
404 BookmarkModel* model, const BookmarkNode* parent, int idx) {
[email protected]00d83802009-08-11 21:32:30405 GURL url;
406 string16 title;
[email protected]6e643432010-03-10 18:32:14407 if (!gtk_dnd_util::ExtractNamedURL(selection_data, &url, &title))
[email protected]00d83802009-08-11 21:32:30408 return false;
409
410 model->AddURL(parent, idx, UTF16ToWideHack(title), url);
411 return true;
[email protected]cd69661782009-07-17 00:17:56412}
413
[email protected]903e7a82009-07-28 00:45:35414bool CreateNewBookmarksFromURIList(GtkSelectionData* selection_data,
415 BookmarkModel* model, const BookmarkNode* parent, int idx) {
[email protected]00d83802009-08-11 21:32:30416 std::vector<GURL> urls;
[email protected]6e643432010-03-10 18:32:14417 gtk_dnd_util::ExtractURIList(selection_data, &urls);
[email protected]00d83802009-08-11 21:32:30418 for (size_t i = 0; i < urls.size(); ++i) {
419 std::string title = GetNameForURL(urls[i]);
420 model->AddURL(parent, idx++, UTF8ToWide(title), urls[i]);
[email protected]903e7a82009-07-28 00:45:35421 }
[email protected]903e7a82009-07-28 00:45:35422 return true;
423}
424
[email protected]e5f72c72009-06-05 19:15:33425} // namespace bookmark_utils