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