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"
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,
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
,
63 GtkThemeService
* provider
,
65 GtkWidget
* former_child
= gtk_bin_get_child(GTK_BIN(button
));
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.
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
{
115 DragRepresentationData(GdkPixbuf
* favicon
,
116 const base::string16
& text
,
120 text_color(text_color
) {
121 g_object_ref(favicon
);
124 ~DragRepresentationData() {
125 g_object_unref(favicon
);
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
);
139 cairo_set_operator(cr
, CAIRO_OPERATOR_CLEAR
);
142 cairo_set_operator(cr
, CAIRO_OPERATOR_SOURCE
);
143 gdk_cairo_set_source_pixbuf(cr
, data
->favicon
, 0, 0);
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
);
163 void OnDragIconDestroy(GtkWidget
* drag_icon
, DragRepresentationData
* data
) {
164 g_object_unref(drag_icon
);
170 const char kBookmarkNode
[] = "bookmark-node";
172 GdkPixbuf
* GetPixbufForNode(const BookmarkNode
* node
,
173 BookmarkModel
* model
,
177 if (node
->is_url()) {
178 const gfx::Image
& favicon
= model
->GetFavicon(node
);
179 if (!favicon
.IsEmpty()) {
180 pixbuf
= favicon
.CopyGdkPixbuf();
182 pixbuf
= GtkThemeService::GetDefaultFavicon(native
).ToGdkPixbuf();
183 g_object_ref(pixbuf
);
186 pixbuf
= GtkThemeService::GetFolderIcon(native
).ToGdkPixbuf();
187 g_object_ref(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(
202 provider
->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT
));
203 g_signal_connect(window
, "expose-event", G_CALLBACK(OnDragIconExpose
),
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());
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
);
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
);
243 void ConfigureButtonForNode(const BookmarkNode
* node
,
244 BookmarkModel
* model
,
246 GtkThemeService
* provider
) {
248 GetPixbufForNode(node
, model
, provider
->UsingNativeTheme());
249 PackButton(pixbuf
, node
->GetTitle(), node
!= model
->other_node(), provider
,
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
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
));
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
);
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
;
317 rv
|= ui::TEXT_URI_LIST
|
325 void WriteBookmarkToSelection(const BookmarkNode
* node
,
326 GtkSelectionData
* selection_data
,
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
,
339 switch (target_type
) {
340 case ui::CHROME_BOOKMARK_ITEM
: {
341 BookmarkNodeData
data(nodes
);
343 data
.WriteToPickle(profile
, &pickle
);
345 gtk_selection_data_set(selection_data
,
346 gtk_selection_data_get_target(selection_data
),
348 static_cast<const guchar
*>(pickle
.data()),
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
),
359 reinterpret_cast<const guchar
*>(utf8_text
.c_str()),
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()
373 uris
[i
] = const_cast<gchar
*>(url
.spec().c_str());
375 uris
[nodes
.size()] = NULL
;
377 gtk_selection_data_set_uris(selection_data
, uris
);
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(),
386 gtk_selection_data_set(selection_data
,
387 GetAtomForTarget(ui::TEXT_HTML
),
389 reinterpret_cast<const guchar
*>(utf8_html
.data()),
393 case ui::TEXT_PLAIN
: {
394 gtk_selection_data_set_text(selection_data
,
395 nodes
[0]->url().spec().c_str(), -1);
399 DLOG(ERROR
) << "Unsupported drag get type!";
404 std::vector
<const BookmarkNode
*> GetNodesFromSelection(
405 GdkDragContext
* context
,
406 GtkSelectionData
* selection_data
,
409 gboolean
* delete_selection_data
,
410 gboolean
* dnd_success
) {
411 if (delete_selection_data
)
412 *delete_selection_data
= FALSE
;
414 *dnd_success
= FALSE
;
416 if (selection_data
) {
417 gint length
= gtk_selection_data_get_length(selection_data
);
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
: {
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
);
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
,
448 base::string16 title
;
449 if (!ui::ExtractNamedURL(selection_data
, &url
, &title
))
452 model
->AddURL(parent
, idx
, title
, url
);
456 bool CreateNewBookmarksFromURIList(GtkSelectionData
* selection_data
,
457 BookmarkModel
* model
,
458 const BookmarkNode
* parent
,
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
]);
469 bool CreateNewBookmarkFromNetscapeURL(GtkSelectionData
* selection_data
,
470 BookmarkModel
* model
,
471 const BookmarkNode
* parent
,
474 base::string16 title
;
475 if (!ui::ExtractNetscapeURL(selection_data
, &url
, &title
))
478 model
->AddURL(parent
, idx
, title
, url
);
482 base::string16
GetNameForURL(const GURL
& url
) {
483 if (url
.is_valid()) {
484 return net::GetSuggestedFilename(url
,
491 return l10n_util::GetStringUTF16(IDS_APP_UNTITLED_SHORTCUT_FILE_NAME
);