- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / gtk / bookmarks / bookmark_utils_gtk.cc
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/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"
32
33 namespace {
34
35 // Spacing between the favicon and the text.
36 const int kBarButtonPadding = 4;
37
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;
41
42 // Maximum number of characters on a bookmark button.
43 const size_t kMaxCharsOnAButton = 15;
44
45 // Maximum number of characters on a menu label.
46 const int kMaxCharsOnAMenuLabel = 50;
47
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;
54
55 void* AsVoid(const BookmarkNode* node) {
56   return const_cast<BookmarkNode*>(node);
57 }
58
59 // Creates the widget hierarchy for a bookmark button.
60 void PackButton(GdkPixbuf* pixbuf,
61                 const 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);
68
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);
72
73   GtkWidget* box = gtk_hbox_new(FALSE, kBarButtonPadding);
74   gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 0);
75
76   std::string label_string = 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
82
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);
87     }
88
89     gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0);
90     SetButtonTextColors(label, provider);
91   }
92
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);
100   }
101   gtk_container_add(GTK_CONTAINER(alignment), box);
102   gtk_container_add(GTK_CONTAINER(button), alignment);
103
104   gtk_widget_show_all(alignment);
105 }
106
107 const int kDragRepresentationWidth = 140;
108
109 struct DragRepresentationData {
110  public:
111   GdkPixbuf* favicon;
112   string16 text;
113   SkColor text_color;
114
115   DragRepresentationData(GdkPixbuf* favicon,
116                          const string16& text,
117                          SkColor text_color)
118       : favicon(favicon),
119         text(text),
120         text_color(text_color) {
121     g_object_ref(favicon);
122   }
123
124   ~DragRepresentationData() {
125     g_object_unref(favicon);
126   }
127
128  private:
129   DISALLOW_COPY_AND_ASSIGN(DragRepresentationData);
130 };
131
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);
141
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);
146
147   GtkAllocation allocation;
148   gtk_widget_get_allocation(sender, &allocation);
149
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);
159
160   return TRUE;
161 }
162
163 void OnDragIconDestroy(GtkWidget* drag_icon, DragRepresentationData* data) {
164   g_object_unref(drag_icon);
165   delete data;
166 }
167
168 }  // namespace
169
170 const char kBookmarkNode[] = "bookmark-node";
171
172 GdkPixbuf* GetPixbufForNode(const BookmarkNode* node,
173                             BookmarkModel* model,
174                             bool native) {
175   GdkPixbuf* pixbuf;
176
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);
184     }
185   } else {
186     pixbuf = GtkThemeService::GetFolderIcon(native).ToGdkPixbuf();
187     g_object_ref(pixbuf);
188   }
189
190   return pixbuf;
191 }
192
193 GtkWidget* GetDragRepresentation(GdkPixbuf* pixbuf,
194                                  const string16& title,
195                                  GtkThemeService* provider) {
196   GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP);
197
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);
207
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);
217     }
218     gtk_widget_realize(window);
219
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);
223
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);
228   }
229
230   return window;
231 }
232
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;
241 }
242
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);
252
253   std::string tooltip = BuildTooltipFor(node);
254   if (!tooltip.empty())
255     gtk_widget_set_tooltip_markup(button, tooltip.c_str());
256
257   g_object_set_data(G_OBJECT(button), kBookmarkNode, AsVoid(node));
258 }
259
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 string16& label = l10n_util::GetStringUTF16(
265       IDS_BOOKMARK_BAR_APPS_SHORTCUT_NAME);
266   PackButton(pixbuf, label, false, provider, button);
267 }
268
269 std::string BuildTooltipFor(const BookmarkNode* node) {
270   if (node->is_folder())
271     return std::string();
272
273   return gtk_util::BuildTooltipTitleFor(node->GetTitle(), node->url());
274 }
275
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 = UTF16ToUTF8(
280       gfx::TruncateString(node->GetTitle(), kMaxCharsOnAMenuLabel));
281
282   if (elided_name.empty()) {
283     elided_name = UTF16ToUTF8(gfx::TruncateString(
284         UTF8ToUTF16(node->url().possibly_invalid_spec()),
285         kMaxCharsOnAMenuLabel));
286   }
287
288   return elided_name;
289 }
290
291 const BookmarkNode* BookmarkNodeForWidget(GtkWidget* widget) {
292   return reinterpret_cast<const BookmarkNode*>(
293       g_object_get_data(G_OBJECT(widget), kBookmarkNode));
294 }
295
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);
304
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);
309   }
310 }
311
312 // DnD-related -----------------------------------------------------------------
313
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;
321   }
322   return rv;
323 }
324
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);
333 }
334
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);
344
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;
351     }
352     case ui::NETSCAPE_URL: {
353       // _NETSCAPE_URL format is URL + \n + title.
354       std::string utf8_text = nodes[0]->url().spec() + "\n" +
355           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;
362     }
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());
374       }
375       uris[nodes.size()] = NULL;
376
377       gtk_selection_data_set_uris(selection_data, uris);
378       free(uris);
379       break;
380     }
381     case ui::TEXT_HTML: {
382       std::string utf8_title = 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;
392     }
393     case ui::TEXT_PLAIN: {
394       gtk_selection_data_set_text(selection_data,
395                                   nodes[0]->url().spec().c_str(), -1);
396       break;
397     }
398     default: {
399       DLOG(ERROR) << "Unsupported drag get type!";
400     }
401   }
402 }
403
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;
415
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;
422
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);
432         }
433         default: {
434           DLOG(ERROR) << "Unsupported drag received type: " << target_type;
435         }
436       }
437     }
438   }
439
440   return std::vector<const BookmarkNode*>();
441 }
442
443 bool CreateNewBookmarkFromNamedUrl(GtkSelectionData* selection_data,
444                                    BookmarkModel* model,
445                                    const BookmarkNode* parent,
446                                    int idx) {
447   GURL url;
448   string16 title;
449   if (!ui::ExtractNamedURL(selection_data, &url, &title))
450     return false;
451
452   model->AddURL(parent, idx, title, url);
453   return true;
454 }
455
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     string16 title = GetNameForURL(urls[i]);
464     model->AddURL(parent, idx++, title, urls[i]);
465   }
466   return true;
467 }
468
469 bool CreateNewBookmarkFromNetscapeURL(GtkSelectionData* selection_data,
470                                       BookmarkModel* model,
471                                       const BookmarkNode* parent,
472                                       int idx) {
473   GURL url;
474   string16 title;
475   if (!ui::ExtractNetscapeURL(selection_data, &url, &title))
476     return false;
477
478   model->AddURL(parent, idx, title, url);
479   return true;
480 }
481
482 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);
492   }
493 }