Home | History | Annotate | Download | only in bookmarks
      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 
     48 void* AsVoid(const BookmarkNode* node) {
     49   return const_cast<BookmarkNode*>(node);
     50 }
     51 
     52 // Creates the widget hierarchy for a bookmark 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 
    104   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 
    113   ~DragRepresentationData() {
    114     g_object_unref(favicon);
    115   }
    116 
    117  private:
    118   DISALLOW_COPY_AND_ASSIGN(DragRepresentationData);
    119 };
    120 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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