root/chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. AsVoid
  2. PackButton
  3. OnDragIconExpose
  4. OnDragIconDestroy
  5. GetPixbufForNode
  6. GetDragRepresentation
  7. GetDragRepresentationForNode
  8. ConfigureButtonForNode
  9. ConfigureAppsShortcutButton
  10. BuildTooltipFor
  11. BuildMenuLabelFor
  12. BookmarkNodeForWidget
  13. SetButtonTextColors
  14. GetCodeMask
  15. WriteBookmarkToSelection
  16. WriteBookmarksToSelection
  17. GetNodesFromSelection
  18. CreateNewBookmarkFromNamedUrl
  19. CreateNewBookmarksFromURIList
  20. CreateNewBookmarkFromNetscapeURL
  21. GetNameForURL

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.h"

#include "base/pickle.h"
#include "base/strings/string16.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
#include "chrome/browser/bookmarks/bookmark_node_data.h"
#include "chrome/browser/bookmarks/bookmark_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/ui/gtk/gtk_chrome_button.h"
#include "chrome/browser/ui/gtk/gtk_theme_service.h"
#include "chrome/browser/ui/gtk/gtk_util.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "grit/ui_strings.h"
#include "net/base/net_util.h"
#include "ui/base/dragdrop/gtk_dnd_util.h"
#include "ui/base/gtk/gtk_hig_constants.h"
#include "ui/base/gtk/gtk_screen_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas_skia_paint.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/text_elider.h"

namespace {

// Spacing between the favicon and the text.
const int kBarButtonPadding = 4;

// Used in gtk_selection_data_set(). (I assume from this parameter that gtk has
// to some really exotic hardware...)
const int kBitsInAByte = 8;

// Maximum number of characters on a bookmark button.
const size_t kMaxCharsOnAButton = 15;

// Maximum number of characters on a menu label.
const int kMaxCharsOnAMenuLabel = 50;

// Padding between the chrome button highlight border and the contents (favicon,
// text).
const int kButtonPaddingTop = 0;
const int kButtonPaddingBottom = 0;
const int kButtonPaddingLeft = 5;
const int kButtonPaddingRight = 0;

void* AsVoid(const BookmarkNode* node) {
  return const_cast<BookmarkNode*>(node);
}

// Creates the widget hierarchy for a bookmark button.
void PackButton(GdkPixbuf* pixbuf,
                const base::string16& title,
                bool ellipsize,
                GtkThemeService* provider,
                GtkWidget* button) {
  GtkWidget* former_child = gtk_bin_get_child(GTK_BIN(button));
  if (former_child)
    gtk_container_remove(GTK_CONTAINER(button), former_child);

  // We pack the button manually (rather than using gtk_button_set_*) so that
  // we can have finer control over its label.
  GtkWidget* image = gtk_image_new_from_pixbuf(pixbuf);

  GtkWidget* box = gtk_hbox_new(FALSE, kBarButtonPadding);
  gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 0);

  std::string label_string = base::UTF16ToUTF8(title);
  if (!label_string.empty()) {
    GtkWidget* label = gtk_label_new(label_string.c_str());
    // Until we switch to vector graphics, force the font size.
    if (!provider->UsingNativeTheme())
      gtk_util::ForceFontSizePixels(label, 13.4);  // 13.4px == 10pt @ 96dpi

    // Ellipsize long bookmark names.
    if (ellipsize) {
      gtk_label_set_max_width_chars(GTK_LABEL(label), kMaxCharsOnAButton);
      gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_END);
    }

    gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0);
    SetButtonTextColors(label, provider);
  }

  GtkWidget* alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
  // If we are not showing the label, don't set any padding, so that the icon
  // will just be centered.
  if (label_string.c_str()) {
    gtk_alignment_set_padding(GTK_ALIGNMENT(alignment),
        kButtonPaddingTop, kButtonPaddingBottom,
        kButtonPaddingLeft, kButtonPaddingRight);
  }
  gtk_container_add(GTK_CONTAINER(alignment), box);
  gtk_container_add(GTK_CONTAINER(button), alignment);

  gtk_widget_show_all(alignment);
}

const int kDragRepresentationWidth = 140;

struct DragRepresentationData {
 public:
  GdkPixbuf* favicon;
  base::string16 text;
  SkColor text_color;

  DragRepresentationData(GdkPixbuf* favicon,
                         const base::string16& text,
                         SkColor text_color)
      : favicon(favicon),
        text(text),
        text_color(text_color) {
    g_object_ref(favicon);
  }

  ~DragRepresentationData() {
    g_object_unref(favicon);
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(DragRepresentationData);
};

gboolean OnDragIconExpose(GtkWidget* sender,
                          GdkEventExpose* event,
                          DragRepresentationData* data) {
  // Clear the background.
  cairo_t* cr = gdk_cairo_create(event->window);
  gdk_cairo_rectangle(cr, &event->area);
  cairo_clip(cr);
  cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
  cairo_paint(cr);

  cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
  gdk_cairo_set_source_pixbuf(cr, data->favicon, 0, 0);
  cairo_paint(cr);
  cairo_destroy(cr);

  GtkAllocation allocation;
  gtk_widget_get_allocation(sender, &allocation);

  // Paint the title text.
  gfx::CanvasSkiaPaint canvas(event, false);
  int text_x = gdk_pixbuf_get_width(data->favicon) + kBarButtonPadding;
  int text_width = allocation.width - text_x;
  const gfx::Rect rect(text_x, 0, text_width, allocation.height);
  canvas.DrawStringRectWithFlags(data->text, gfx::FontList(), data->text_color,
                                 rect, gfx::Canvas::NO_SUBPIXEL_RENDERING);

  return TRUE;
}

void OnDragIconDestroy(GtkWidget* drag_icon, DragRepresentationData* data) {
  g_object_unref(drag_icon);
  delete data;
}

}  // namespace

const char kBookmarkNode[] = "bookmark-node";

GdkPixbuf* GetPixbufForNode(const BookmarkNode* node,
                            BookmarkModel* model,
                            bool native) {
  GdkPixbuf* pixbuf;

  if (node->is_url()) {
    const gfx::Image& favicon = model->GetFavicon(node);
    if (!favicon.IsEmpty()) {
      pixbuf = favicon.CopyGdkPixbuf();
    } else {
      pixbuf = GtkThemeService::GetDefaultFavicon(native).ToGdkPixbuf();
      g_object_ref(pixbuf);
    }
  } else {
    pixbuf = GtkThemeService::GetFolderIcon(native).ToGdkPixbuf();
    g_object_ref(pixbuf);
  }

  return pixbuf;
}

GtkWidget* GetDragRepresentation(GdkPixbuf* pixbuf,
                                 const base::string16& title,
                                 GtkThemeService* provider) {
  GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP);

  if (ui::IsScreenComposited() &&
      gtk_util::AddWindowAlphaChannel(window)) {
    DragRepresentationData* data = new DragRepresentationData(
        pixbuf, title,
        provider->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT));
    g_signal_connect(window, "expose-event", G_CALLBACK(OnDragIconExpose),
                     data);
    g_object_ref(window);
    g_signal_connect(window, "destroy", G_CALLBACK(OnDragIconDestroy), data);

    gtk_widget_set_size_request(window, kDragRepresentationWidth,
                                gfx::FontList().GetHeight());
  } else {
    if (!provider->UsingNativeTheme()) {
      GdkColor color = provider->GetGdkColor(
          ThemeProperties::COLOR_TOOLBAR);
      gtk_widget_modify_bg(window, GTK_STATE_NORMAL, &color);
    }
    gtk_widget_realize(window);

    GtkWidget* frame = gtk_frame_new(NULL);
    gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
    gtk_container_add(GTK_CONTAINER(window), frame);

    GtkWidget* floating_button = provider->BuildChromeButton();
    PackButton(pixbuf, title, true, provider, floating_button);
    gtk_container_add(GTK_CONTAINER(frame), floating_button);
    gtk_widget_show_all(frame);
  }

  return window;
}

GtkWidget* GetDragRepresentationForNode(const BookmarkNode* node,
                                        BookmarkModel* model,
                                        GtkThemeService* provider) {
  GdkPixbuf* pixbuf = GetPixbufForNode(
      node, model, provider->UsingNativeTheme());
  GtkWidget* widget = GetDragRepresentation(pixbuf, node->GetTitle(), provider);
  g_object_unref(pixbuf);
  return widget;
}

void ConfigureButtonForNode(const BookmarkNode* node,
                            BookmarkModel* model,
                            GtkWidget* button,
                            GtkThemeService* provider) {
  GdkPixbuf* pixbuf =
      GetPixbufForNode(node, model, provider->UsingNativeTheme());
  PackButton(pixbuf, node->GetTitle(), node != model->other_node(), provider,
             button);
  g_object_unref(pixbuf);

  std::string tooltip = BuildTooltipFor(node);
  if (!tooltip.empty())
    gtk_widget_set_tooltip_markup(button, tooltip.c_str());

  g_object_set_data(G_OBJECT(button), kBookmarkNode, AsVoid(node));
}

void ConfigureAppsShortcutButton(GtkWidget* button, GtkThemeService* provider) {
  GdkPixbuf* pixbuf = ui::ResourceBundle::GetSharedInstance().
      GetNativeImageNamed(IDR_BOOKMARK_BAR_APPS_SHORTCUT,
                          ui::ResourceBundle::RTL_ENABLED).ToGdkPixbuf();
  const base::string16& label = l10n_util::GetStringUTF16(
      IDS_BOOKMARK_BAR_APPS_SHORTCUT_NAME);
  PackButton(pixbuf, label, false, provider, button);
}

std::string BuildTooltipFor(const BookmarkNode* node) {
  if (node->is_folder())
    return std::string();

  return gtk_util::BuildTooltipTitleFor(node->GetTitle(), node->url());
}

std::string BuildMenuLabelFor(const BookmarkNode* node) {
  // This breaks on word boundaries. Ideally we would break on character
  // boundaries.
  std::string elided_name = base::UTF16ToUTF8(
      gfx::TruncateString(node->GetTitle(), kMaxCharsOnAMenuLabel));

  if (elided_name.empty()) {
    elided_name = base::UTF16ToUTF8(gfx::TruncateString(
        base::UTF8ToUTF16(node->url().possibly_invalid_spec()),
        kMaxCharsOnAMenuLabel));
  }

  return elided_name;
}

const BookmarkNode* BookmarkNodeForWidget(GtkWidget* widget) {
  return reinterpret_cast<const BookmarkNode*>(
      g_object_get_data(G_OBJECT(widget), kBookmarkNode));
}

void SetButtonTextColors(GtkWidget* label, GtkThemeService* provider) {
  if (provider->UsingNativeTheme()) {
    gtk_util::SetLabelColor(label, NULL);
  } else {
    GdkColor color = provider->GetGdkColor(
        ThemeProperties::COLOR_BOOKMARK_TEXT);
    gtk_widget_modify_fg(label, GTK_STATE_NORMAL, &color);
    gtk_widget_modify_fg(label, GTK_STATE_INSENSITIVE, &color);

    // Because the prelight state is a white image that doesn't change by the
    // theme, force the text color to black when it would be used.
    gtk_widget_modify_fg(label, GTK_STATE_ACTIVE, &ui::kGdkBlack);
    gtk_widget_modify_fg(label, GTK_STATE_PRELIGHT, &ui::kGdkBlack);
  }
}

// DnD-related -----------------------------------------------------------------

int GetCodeMask(bool folder) {
  int rv = ui::CHROME_BOOKMARK_ITEM;
  if (!folder) {
    rv |= ui::TEXT_URI_LIST |
          ui::TEXT_HTML |
          ui::TEXT_PLAIN |
          ui::NETSCAPE_URL;
  }
  return rv;
}

void WriteBookmarkToSelection(const BookmarkNode* node,
                              GtkSelectionData* selection_data,
                              guint target_type,
                              Profile* profile) {
  DCHECK(node);
  std::vector<const BookmarkNode*> nodes;
  nodes.push_back(node);
  WriteBookmarksToSelection(nodes, selection_data, target_type, profile);
}

void WriteBookmarksToSelection(const std::vector<const BookmarkNode*>& nodes,
                               GtkSelectionData* selection_data,
                               guint target_type,
                               Profile* profile) {
  switch (target_type) {
    case ui::CHROME_BOOKMARK_ITEM: {
      BookmarkNodeData data(nodes);
      Pickle pickle;
      data.WriteToPickle(profile, &pickle);

      gtk_selection_data_set(selection_data,
                             gtk_selection_data_get_target(selection_data),
                             kBitsInAByte,
                             static_cast<const guchar*>(pickle.data()),
                             pickle.size());
      break;
    }
    case ui::NETSCAPE_URL: {
      // _NETSCAPE_URL format is URL + \n + title.
      std::string utf8_text = nodes[0]->url().spec() + "\n" +
          base::UTF16ToUTF8(nodes[0]->GetTitle());
      gtk_selection_data_set(selection_data,
                             gtk_selection_data_get_target(selection_data),
                             kBitsInAByte,
                             reinterpret_cast<const guchar*>(utf8_text.c_str()),
                             utf8_text.length());
      break;
    }
    case ui::TEXT_URI_LIST: {
      gchar** uris = reinterpret_cast<gchar**>(malloc(sizeof(gchar*) *
                                               (nodes.size() + 1)));
      for (size_t i = 0; i < nodes.size(); ++i) {
        // If the node is a folder, this will be empty. TODO(estade): figure out
        // if there are any ramifications to passing an empty URI. After a
        // little testing, it seems fine.
        const GURL& url = nodes[i]->url();
        // This const cast should be safe as gtk_selection_data_set_uris()
        // makes copies.
        uris[i] = const_cast<gchar*>(url.spec().c_str());
      }
      uris[nodes.size()] = NULL;

      gtk_selection_data_set_uris(selection_data, uris);
      free(uris);
      break;
    }
    case ui::TEXT_HTML: {
      std::string utf8_title = base::UTF16ToUTF8(nodes[0]->GetTitle());
      std::string utf8_html = base::StringPrintf("<a href=\"%s\">%s</a>",
                                                 nodes[0]->url().spec().c_str(),
                                                 utf8_title.c_str());
      gtk_selection_data_set(selection_data,
                             GetAtomForTarget(ui::TEXT_HTML),
                             kBitsInAByte,
                             reinterpret_cast<const guchar*>(utf8_html.data()),
                             utf8_html.size());
      break;
    }
    case ui::TEXT_PLAIN: {
      gtk_selection_data_set_text(selection_data,
                                  nodes[0]->url().spec().c_str(), -1);
      break;
    }
    default: {
      DLOG(ERROR) << "Unsupported drag get type!";
    }
  }
}

std::vector<const BookmarkNode*> GetNodesFromSelection(
    GdkDragContext* context,
    GtkSelectionData* selection_data,
    guint target_type,
    Profile* profile,
    gboolean* delete_selection_data,
    gboolean* dnd_success) {
  if (delete_selection_data)
    *delete_selection_data = FALSE;
  if (dnd_success)
    *dnd_success = FALSE;

  if (selection_data) {
    gint length = gtk_selection_data_get_length(selection_data);
    if (length > 0) {
      if (context && delete_selection_data &&
          context->action == GDK_ACTION_MOVE)
        *delete_selection_data = TRUE;

      switch (target_type) {
        case ui::CHROME_BOOKMARK_ITEM: {
          if (dnd_success)
            *dnd_success = TRUE;
          Pickle pickle(reinterpret_cast<const char*>(
              gtk_selection_data_get_data(selection_data)), length);
          BookmarkNodeData drag_data;
          drag_data.ReadFromPickle(&pickle);
          return drag_data.GetNodes(profile);
        }
        default: {
          DLOG(ERROR) << "Unsupported drag received type: " << target_type;
        }
      }
    }
  }

  return std::vector<const BookmarkNode*>();
}

bool CreateNewBookmarkFromNamedUrl(GtkSelectionData* selection_data,
                                   BookmarkModel* model,
                                   const BookmarkNode* parent,
                                   int idx) {
  GURL url;
  base::string16 title;
  if (!ui::ExtractNamedURL(selection_data, &url, &title))
    return false;

  model->AddURL(parent, idx, title, url);
  return true;
}

bool CreateNewBookmarksFromURIList(GtkSelectionData* selection_data,
                                   BookmarkModel* model,
                                   const BookmarkNode* parent,
                                   int idx) {
  std::vector<GURL> urls;
  ui::ExtractURIList(selection_data, &urls);
  for (size_t i = 0; i < urls.size(); ++i) {
    base::string16 title = GetNameForURL(urls[i]);
    model->AddURL(parent, idx++, title, urls[i]);
  }
  return true;
}

bool CreateNewBookmarkFromNetscapeURL(GtkSelectionData* selection_data,
                                      BookmarkModel* model,
                                      const BookmarkNode* parent,
                                      int idx) {
  GURL url;
  base::string16 title;
  if (!ui::ExtractNetscapeURL(selection_data, &url, &title))
    return false;

  model->AddURL(parent, idx, title, url);
  return true;
}

base::string16 GetNameForURL(const GURL& url) {
  if (url.is_valid()) {
    return net::GetSuggestedFilename(url,
                                     std::string(),
                                     std::string(),
                                     std::string(),
                                     std::string(),
                                     std::string());
  } else {
    return l10n_util::GetStringUTF16(IDS_APP_UNTITLED_SHORTCUT_FILE_NAME);
  }
}

/* [<][>][^][v][top][bottom][index][help] */