Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / gtk / bookmarks / bookmark_utils_gtk.cc
blobab8a690ea6d3cb2fb32ef932e78913fded31cd33
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.
5 #include "chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.h"
7 #include "base/pickle.h"
8 #include "base/strings/string16.h"
9 #include "base/strings/stringprintf.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/bookmarks/bookmark_model.h"
12 #include "chrome/browser/bookmarks/bookmark_node_data.h"
13 #include "chrome/browser/bookmarks/bookmark_utils.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/themes/theme_properties.h"
16 #include "chrome/browser/ui/gtk/gtk_chrome_button.h"
17 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
18 #include "chrome/browser/ui/gtk/gtk_util.h"
19 #include "grit/generated_resources.h"
20 #include "grit/theme_resources.h"
21 #include "grit/ui_strings.h"
22 #include "net/base/net_util.h"
23 #include "ui/base/dragdrop/gtk_dnd_util.h"
24 #include "ui/base/gtk/gtk_hig_constants.h"
25 #include "ui/base/gtk/gtk_screen_util.h"
26 #include "ui/base/l10n/l10n_util.h"
27 #include "ui/base/resource/resource_bundle.h"
28 #include "ui/gfx/canvas_skia_paint.h"
29 #include "ui/gfx/font.h"
30 #include "ui/gfx/image/image.h"
31 #include "ui/gfx/text_elider.h"
33 namespace {
35 // Spacing between the favicon and the text.
36 const int kBarButtonPadding = 4;
38 // Used in gtk_selection_data_set(). (I assume from this parameter that gtk has
39 // to some really exotic hardware...)
40 const int kBitsInAByte = 8;
42 // Maximum number of characters on a bookmark button.
43 const size_t kMaxCharsOnAButton = 15;
45 // Maximum number of characters on a menu label.
46 const int kMaxCharsOnAMenuLabel = 50;
48 // Padding between the chrome button highlight border and the contents (favicon,
49 // text).
50 const int kButtonPaddingTop = 0;
51 const int kButtonPaddingBottom = 0;
52 const int kButtonPaddingLeft = 5;
53 const int kButtonPaddingRight = 0;
55 void* AsVoid(const BookmarkNode* node) {
56 return const_cast<BookmarkNode*>(node);
59 // Creates the widget hierarchy for a bookmark button.
60 void PackButton(GdkPixbuf* pixbuf,
61 const base::string16& title,
62 bool ellipsize,
63 GtkThemeService* provider,
64 GtkWidget* button) {
65 GtkWidget* former_child = gtk_bin_get_child(GTK_BIN(button));
66 if (former_child)
67 gtk_container_remove(GTK_CONTAINER(button), former_child);
69 // We pack the button manually (rather than using gtk_button_set_*) so that
70 // we can have finer control over its label.
71 GtkWidget* image = gtk_image_new_from_pixbuf(pixbuf);
73 GtkWidget* box = gtk_hbox_new(FALSE, kBarButtonPadding);
74 gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 0);
76 std::string label_string = base::UTF16ToUTF8(title);
77 if (!label_string.empty()) {
78 GtkWidget* label = gtk_label_new(label_string.c_str());
79 // Until we switch to vector graphics, force the font size.
80 if (!provider->UsingNativeTheme())
81 gtk_util::ForceFontSizePixels(label, 13.4); // 13.4px == 10pt @ 96dpi
83 // Ellipsize long bookmark names.
84 if (ellipsize) {
85 gtk_label_set_max_width_chars(GTK_LABEL(label), kMaxCharsOnAButton);
86 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_END);
89 gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0);
90 SetButtonTextColors(label, provider);
93 GtkWidget* alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
94 // If we are not showing the label, don't set any padding, so that the icon
95 // will just be centered.
96 if (label_string.c_str()) {
97 gtk_alignment_set_padding(GTK_ALIGNMENT(alignment),
98 kButtonPaddingTop, kButtonPaddingBottom,
99 kButtonPaddingLeft, kButtonPaddingRight);
101 gtk_container_add(GTK_CONTAINER(alignment), box);
102 gtk_container_add(GTK_CONTAINER(button), alignment);
104 gtk_widget_show_all(alignment);
107 const int kDragRepresentationWidth = 140;
109 struct DragRepresentationData {
110 public:
111 GdkPixbuf* favicon;
112 base::string16 text;
113 SkColor text_color;
115 DragRepresentationData(GdkPixbuf* favicon,
116 const base::string16& text,
117 SkColor text_color)
118 : favicon(favicon),
119 text(text),
120 text_color(text_color) {
121 g_object_ref(favicon);
124 ~DragRepresentationData() {
125 g_object_unref(favicon);
128 private:
129 DISALLOW_COPY_AND_ASSIGN(DragRepresentationData);
132 gboolean OnDragIconExpose(GtkWidget* sender,
133 GdkEventExpose* event,
134 DragRepresentationData* data) {
135 // Clear the background.
136 cairo_t* cr = gdk_cairo_create(event->window);
137 gdk_cairo_rectangle(cr, &event->area);
138 cairo_clip(cr);
139 cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
140 cairo_paint(cr);
142 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
143 gdk_cairo_set_source_pixbuf(cr, data->favicon, 0, 0);
144 cairo_paint(cr);
145 cairo_destroy(cr);
147 GtkAllocation allocation;
148 gtk_widget_get_allocation(sender, &allocation);
150 // Paint the title text.
151 gfx::CanvasSkiaPaint canvas(event, false);
152 int text_x = gdk_pixbuf_get_width(data->favicon) + kBarButtonPadding;
153 int text_width = allocation.width - text_x;
154 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
155 const gfx::Font& base_font = rb.GetFont(ui::ResourceBundle::BaseFont);
156 canvas.DrawStringInt(data->text, base_font, data->text_color,
157 text_x, 0, text_width, allocation.height,
158 gfx::Canvas::NO_SUBPIXEL_RENDERING);
160 return TRUE;
163 void OnDragIconDestroy(GtkWidget* drag_icon, DragRepresentationData* data) {
164 g_object_unref(drag_icon);
165 delete data;
168 } // namespace
170 const char kBookmarkNode[] = "bookmark-node";
172 GdkPixbuf* GetPixbufForNode(const BookmarkNode* node,
173 BookmarkModel* model,
174 bool native) {
175 GdkPixbuf* pixbuf;
177 if (node->is_url()) {
178 const gfx::Image& favicon = model->GetFavicon(node);
179 if (!favicon.IsEmpty()) {
180 pixbuf = favicon.CopyGdkPixbuf();
181 } else {
182 pixbuf = GtkThemeService::GetDefaultFavicon(native).ToGdkPixbuf();
183 g_object_ref(pixbuf);
185 } else {
186 pixbuf = GtkThemeService::GetFolderIcon(native).ToGdkPixbuf();
187 g_object_ref(pixbuf);
190 return pixbuf;
193 GtkWidget* GetDragRepresentation(GdkPixbuf* pixbuf,
194 const base::string16& title,
195 GtkThemeService* provider) {
196 GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP);
198 if (ui::IsScreenComposited() &&
199 gtk_util::AddWindowAlphaChannel(window)) {
200 DragRepresentationData* data = new DragRepresentationData(
201 pixbuf, title,
202 provider->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT));
203 g_signal_connect(window, "expose-event", G_CALLBACK(OnDragIconExpose),
204 data);
205 g_object_ref(window);
206 g_signal_connect(window, "destroy", G_CALLBACK(OnDragIconDestroy), data);
208 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
209 const gfx::Font& base_font = rb.GetFont(ui::ResourceBundle::BaseFont);
210 gtk_widget_set_size_request(window, kDragRepresentationWidth,
211 base_font.GetHeight());
212 } else {
213 if (!provider->UsingNativeTheme()) {
214 GdkColor color = provider->GetGdkColor(
215 ThemeProperties::COLOR_TOOLBAR);
216 gtk_widget_modify_bg(window, GTK_STATE_NORMAL, &color);
218 gtk_widget_realize(window);
220 GtkWidget* frame = gtk_frame_new(NULL);
221 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
222 gtk_container_add(GTK_CONTAINER(window), frame);
224 GtkWidget* floating_button = provider->BuildChromeButton();
225 PackButton(pixbuf, title, true, provider, floating_button);
226 gtk_container_add(GTK_CONTAINER(frame), floating_button);
227 gtk_widget_show_all(frame);
230 return window;
233 GtkWidget* GetDragRepresentationForNode(const BookmarkNode* node,
234 BookmarkModel* model,
235 GtkThemeService* provider) {
236 GdkPixbuf* pixbuf = GetPixbufForNode(
237 node, model, provider->UsingNativeTheme());
238 GtkWidget* widget = GetDragRepresentation(pixbuf, node->GetTitle(), provider);
239 g_object_unref(pixbuf);
240 return widget;
243 void ConfigureButtonForNode(const BookmarkNode* node,
244 BookmarkModel* model,
245 GtkWidget* button,
246 GtkThemeService* provider) {
247 GdkPixbuf* pixbuf =
248 GetPixbufForNode(node, model, provider->UsingNativeTheme());
249 PackButton(pixbuf, node->GetTitle(), node != model->other_node(), provider,
250 button);
251 g_object_unref(pixbuf);
253 std::string tooltip = BuildTooltipFor(node);
254 if (!tooltip.empty())
255 gtk_widget_set_tooltip_markup(button, tooltip.c_str());
257 g_object_set_data(G_OBJECT(button), kBookmarkNode, AsVoid(node));
260 void ConfigureAppsShortcutButton(GtkWidget* button, GtkThemeService* provider) {
261 GdkPixbuf* pixbuf = ui::ResourceBundle::GetSharedInstance().
262 GetNativeImageNamed(IDR_BOOKMARK_BAR_APPS_SHORTCUT,
263 ui::ResourceBundle::RTL_ENABLED).ToGdkPixbuf();
264 const base::string16& label = l10n_util::GetStringUTF16(
265 IDS_BOOKMARK_BAR_APPS_SHORTCUT_NAME);
266 PackButton(pixbuf, label, false, provider, button);
269 std::string BuildTooltipFor(const BookmarkNode* node) {
270 if (node->is_folder())
271 return std::string();
273 return gtk_util::BuildTooltipTitleFor(node->GetTitle(), node->url());
276 std::string BuildMenuLabelFor(const BookmarkNode* node) {
277 // This breaks on word boundaries. Ideally we would break on character
278 // boundaries.
279 std::string elided_name = base::UTF16ToUTF8(
280 gfx::TruncateString(node->GetTitle(), kMaxCharsOnAMenuLabel));
282 if (elided_name.empty()) {
283 elided_name = base::UTF16ToUTF8(gfx::TruncateString(
284 base::UTF8ToUTF16(node->url().possibly_invalid_spec()),
285 kMaxCharsOnAMenuLabel));
288 return elided_name;
291 const BookmarkNode* BookmarkNodeForWidget(GtkWidget* widget) {
292 return reinterpret_cast<const BookmarkNode*>(
293 g_object_get_data(G_OBJECT(widget), kBookmarkNode));
296 void SetButtonTextColors(GtkWidget* label, GtkThemeService* provider) {
297 if (provider->UsingNativeTheme()) {
298 gtk_util::SetLabelColor(label, NULL);
299 } else {
300 GdkColor color = provider->GetGdkColor(
301 ThemeProperties::COLOR_BOOKMARK_TEXT);
302 gtk_widget_modify_fg(label, GTK_STATE_NORMAL, &color);
303 gtk_widget_modify_fg(label, GTK_STATE_INSENSITIVE, &color);
305 // Because the prelight state is a white image that doesn't change by the
306 // theme, force the text color to black when it would be used.
307 gtk_widget_modify_fg(label, GTK_STATE_ACTIVE, &ui::kGdkBlack);
308 gtk_widget_modify_fg(label, GTK_STATE_PRELIGHT, &ui::kGdkBlack);
312 // DnD-related -----------------------------------------------------------------
314 int GetCodeMask(bool folder) {
315 int rv = ui::CHROME_BOOKMARK_ITEM;
316 if (!folder) {
317 rv |= ui::TEXT_URI_LIST |
318 ui::TEXT_HTML |
319 ui::TEXT_PLAIN |
320 ui::NETSCAPE_URL;
322 return rv;
325 void WriteBookmarkToSelection(const BookmarkNode* node,
326 GtkSelectionData* selection_data,
327 guint target_type,
328 Profile* profile) {
329 DCHECK(node);
330 std::vector<const BookmarkNode*> nodes;
331 nodes.push_back(node);
332 WriteBookmarksToSelection(nodes, selection_data, target_type, profile);
335 void WriteBookmarksToSelection(const std::vector<const BookmarkNode*>& nodes,
336 GtkSelectionData* selection_data,
337 guint target_type,
338 Profile* profile) {
339 switch (target_type) {
340 case ui::CHROME_BOOKMARK_ITEM: {
341 BookmarkNodeData data(nodes);
342 Pickle pickle;
343 data.WriteToPickle(profile, &pickle);
345 gtk_selection_data_set(selection_data,
346 gtk_selection_data_get_target(selection_data),
347 kBitsInAByte,
348 static_cast<const guchar*>(pickle.data()),
349 pickle.size());
350 break;
352 case ui::NETSCAPE_URL: {
353 // _NETSCAPE_URL format is URL + \n + title.
354 std::string utf8_text = nodes[0]->url().spec() + "\n" +
355 base::UTF16ToUTF8(nodes[0]->GetTitle());
356 gtk_selection_data_set(selection_data,
357 gtk_selection_data_get_target(selection_data),
358 kBitsInAByte,
359 reinterpret_cast<const guchar*>(utf8_text.c_str()),
360 utf8_text.length());
361 break;
363 case ui::TEXT_URI_LIST: {
364 gchar** uris = reinterpret_cast<gchar**>(malloc(sizeof(gchar*) *
365 (nodes.size() + 1)));
366 for (size_t i = 0; i < nodes.size(); ++i) {
367 // If the node is a folder, this will be empty. TODO(estade): figure out
368 // if there are any ramifications to passing an empty URI. After a
369 // little testing, it seems fine.
370 const GURL& url = nodes[i]->url();
371 // This const cast should be safe as gtk_selection_data_set_uris()
372 // makes copies.
373 uris[i] = const_cast<gchar*>(url.spec().c_str());
375 uris[nodes.size()] = NULL;
377 gtk_selection_data_set_uris(selection_data, uris);
378 free(uris);
379 break;
381 case ui::TEXT_HTML: {
382 std::string utf8_title = base::UTF16ToUTF8(nodes[0]->GetTitle());
383 std::string utf8_html = base::StringPrintf("<a href=\"%s\">%s</a>",
384 nodes[0]->url().spec().c_str(),
385 utf8_title.c_str());
386 gtk_selection_data_set(selection_data,
387 GetAtomForTarget(ui::TEXT_HTML),
388 kBitsInAByte,
389 reinterpret_cast<const guchar*>(utf8_html.data()),
390 utf8_html.size());
391 break;
393 case ui::TEXT_PLAIN: {
394 gtk_selection_data_set_text(selection_data,
395 nodes[0]->url().spec().c_str(), -1);
396 break;
398 default: {
399 DLOG(ERROR) << "Unsupported drag get type!";
404 std::vector<const BookmarkNode*> GetNodesFromSelection(
405 GdkDragContext* context,
406 GtkSelectionData* selection_data,
407 guint target_type,
408 Profile* profile,
409 gboolean* delete_selection_data,
410 gboolean* dnd_success) {
411 if (delete_selection_data)
412 *delete_selection_data = FALSE;
413 if (dnd_success)
414 *dnd_success = FALSE;
416 if (selection_data) {
417 gint length = gtk_selection_data_get_length(selection_data);
418 if (length > 0) {
419 if (context && delete_selection_data &&
420 context->action == GDK_ACTION_MOVE)
421 *delete_selection_data = TRUE;
423 switch (target_type) {
424 case ui::CHROME_BOOKMARK_ITEM: {
425 if (dnd_success)
426 *dnd_success = TRUE;
427 Pickle pickle(reinterpret_cast<const char*>(
428 gtk_selection_data_get_data(selection_data)), length);
429 BookmarkNodeData drag_data;
430 drag_data.ReadFromPickle(&pickle);
431 return drag_data.GetNodes(profile);
433 default: {
434 DLOG(ERROR) << "Unsupported drag received type: " << target_type;
440 return std::vector<const BookmarkNode*>();
443 bool CreateNewBookmarkFromNamedUrl(GtkSelectionData* selection_data,
444 BookmarkModel* model,
445 const BookmarkNode* parent,
446 int idx) {
447 GURL url;
448 base::string16 title;
449 if (!ui::ExtractNamedURL(selection_data, &url, &title))
450 return false;
452 model->AddURL(parent, idx, title, url);
453 return true;
456 bool CreateNewBookmarksFromURIList(GtkSelectionData* selection_data,
457 BookmarkModel* model,
458 const BookmarkNode* parent,
459 int idx) {
460 std::vector<GURL> urls;
461 ui::ExtractURIList(selection_data, &urls);
462 for (size_t i = 0; i < urls.size(); ++i) {
463 base::string16 title = GetNameForURL(urls[i]);
464 model->AddURL(parent, idx++, title, urls[i]);
466 return true;
469 bool CreateNewBookmarkFromNetscapeURL(GtkSelectionData* selection_data,
470 BookmarkModel* model,
471 const BookmarkNode* parent,
472 int idx) {
473 GURL url;
474 base::string16 title;
475 if (!ui::ExtractNetscapeURL(selection_data, &url, &title))
476 return false;
478 model->AddURL(parent, idx, title, url);
479 return true;
482 base::string16 GetNameForURL(const GURL& url) {
483 if (url.is_valid()) {
484 return net::GetSuggestedFilename(url,
485 std::string(),
486 std::string(),
487 std::string(),
488 std::string(),
489 std::string());
490 } else {
491 return l10n_util::GetStringUTF16(IDS_APP_UNTITLED_SHORTCUT_FILE_NAME);