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