| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2010 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/gtk/bookmark_utils_gtk.h" | |
| 6 | |
| 7 #include "app/gtk_dnd_util.h" | |
| 8 #include "app/l10n_util.h" | |
| 9 #include "app/resource_bundle.h" | |
| 10 #include "base/pickle.h" | |
| 11 #include "base/string16.h" | |
| 12 #include "base/string_util.h" | |
| 13 #include "base/utf_string_conversions.h" | |
| 14 #include "chrome/browser/bookmarks/bookmark_node_data.h" | |
| 15 #include "chrome/browser/bookmarks/bookmark_model.h" | |
| 16 #include "chrome/browser/bookmarks/bookmark_utils.h" | |
| 17 #include "chrome/browser/gtk/gtk_chrome_button.h" | |
| 18 #include "chrome/browser/gtk/gtk_theme_provider.h" | |
| 19 #include "chrome/browser/gtk/gtk_util.h" | |
| 20 #include "chrome/browser/profiles/profile.h" | |
| 21 #include "gfx/canvas_skia_paint.h" | |
| 22 #include "gfx/font.h" | |
| 23 #include "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 GtkThemeProvider* 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 = GtkThemeProvider::GetDefaultFavicon(native); | |
| 169 g_object_ref(pixbuf); | |
| 170 } | |
| 171 } else { | |
| 172 pixbuf = GtkThemeProvider::GetFolderIcon(native); | |
| 173 g_object_ref(pixbuf); | |
| 174 } | |
| 175 | |
| 176 return pixbuf; | |
| 177 } | |
| 178 | |
| 179 GtkWidget* GetDragRepresentation(GdkPixbuf* pixbuf, | |
| 180 const string16& title, | |
| 181 GtkThemeProvider* 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(BrowserThemeProvider::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 BrowserThemeProvider::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 GtkThemeProvider* 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, GtkThemeProvider* 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 const std::string& url = node->GetURL().possibly_invalid_spec(); | |
| 246 const std::string& title = UTF16ToUTF8(node->GetTitle()); | |
| 247 | |
| 248 std::string truncated_url = UTF16ToUTF8(l10n_util::TruncateString( | |
| 249 UTF8ToUTF16(url), kMaxTooltipURLLength)); | |
| 250 gchar* escaped_url_cstr = g_markup_escape_text(truncated_url.c_str(), | |
| 251 truncated_url.size()); | |
| 252 std::string escaped_url(escaped_url_cstr); | |
| 253 g_free(escaped_url_cstr); | |
| 254 | |
| 255 std::string tooltip; | |
| 256 if (url == title || title.empty()) { | |
| 257 return escaped_url; | |
| 258 } else { | |
| 259 std::string truncated_title = UTF16ToUTF8(l10n_util::TruncateString( | |
| 260 node->GetTitle(), kMaxTooltipTitleLength)); | |
| 261 gchar* escaped_title_cstr = g_markup_escape_text(truncated_title.c_str(), | |
| 262 truncated_title.size()); | |
| 263 std::string escaped_title(escaped_title_cstr); | |
| 264 g_free(escaped_title_cstr); | |
| 265 | |
| 266 if (!escaped_url.empty()) | |
| 267 return std::string("<b>") + escaped_title + "</b>\n" + escaped_url; | |
| 268 else | |
| 269 return std::string("<b>") + escaped_title + "</b>"; | |
| 270 } | |
| 271 } | |
| 272 | |
| 273 const BookmarkNode* BookmarkNodeForWidget(GtkWidget* widget) { | |
| 274 return reinterpret_cast<const BookmarkNode*>( | |
| 275 g_object_get_data(G_OBJECT(widget), bookmark_utils::kBookmarkNode)); | |
| 276 } | |
| 277 | |
| 278 void SetButtonTextColors(GtkWidget* label, GtkThemeProvider* provider) { | |
| 279 if (provider->UseGtkTheme()) { | |
| 280 gtk_util::SetLabelColor(label, NULL); | |
| 281 } else { | |
| 282 GdkColor color = provider->GetGdkColor( | |
| 283 BrowserThemeProvider::COLOR_BOOKMARK_TEXT); | |
| 284 gtk_widget_modify_fg(label, GTK_STATE_NORMAL, &color); | |
| 285 gtk_widget_modify_fg(label, GTK_STATE_INSENSITIVE, &color); | |
| 286 | |
| 287 // Because the prelight state is a white image that doesn't change by the | |
| 288 // theme, force the text color to black when it would be used. | |
| 289 gtk_widget_modify_fg(label, GTK_STATE_ACTIVE, >k_util::kGdkBlack); | |
| 290 gtk_widget_modify_fg(label, GTK_STATE_PRELIGHT, >k_util::kGdkBlack); | |
| 291 } | |
| 292 } | |
| 293 | |
| 294 // DnD-related ----------------------------------------------------------------- | |
| 295 | |
| 296 int GetCodeMask(bool folder) { | |
| 297 int rv = gtk_dnd_util::CHROME_BOOKMARK_ITEM; | |
| 298 if (!folder) { | |
| 299 rv |= gtk_dnd_util::TEXT_URI_LIST | | |
| 300 gtk_dnd_util::TEXT_PLAIN | | |
| 301 gtk_dnd_util::NETSCAPE_URL; | |
| 302 } | |
| 303 return rv; | |
| 304 } | |
| 305 | |
| 306 void WriteBookmarkToSelection(const BookmarkNode* node, | |
| 307 GtkSelectionData* selection_data, | |
| 308 guint target_type, | |
| 309 Profile* profile) { | |
| 310 DCHECK(node); | |
| 311 std::vector<const BookmarkNode*> nodes; | |
| 312 nodes.push_back(node); | |
| 313 WriteBookmarksToSelection(nodes, selection_data, target_type, profile); | |
| 314 } | |
| 315 | |
| 316 void WriteBookmarksToSelection(const std::vector<const BookmarkNode*>& nodes, | |
| 317 GtkSelectionData* selection_data, | |
| 318 guint target_type, | |
| 319 Profile* profile) { | |
| 320 switch (target_type) { | |
| 321 case gtk_dnd_util::CHROME_BOOKMARK_ITEM: { | |
| 322 BookmarkNodeData data(nodes); | |
| 323 Pickle pickle; | |
| 324 data.WriteToPickle(profile, &pickle); | |
| 325 | |
| 326 gtk_selection_data_set(selection_data, selection_data->target, | |
| 327 kBitsInAByte, | |
| 328 static_cast<const guchar*>(pickle.data()), | |
| 329 pickle.size()); | |
| 330 break; | |
| 331 } | |
| 332 case gtk_dnd_util::NETSCAPE_URL: { | |
| 333 // _NETSCAPE_URL format is URL + \n + title. | |
| 334 std::string utf8_text = nodes[0]->GetURL().spec() + "\n" + | |
| 335 UTF16ToUTF8(nodes[0]->GetTitle()); | |
| 336 gtk_selection_data_set(selection_data, | |
| 337 selection_data->target, | |
| 338 kBitsInAByte, | |
| 339 reinterpret_cast<const guchar*>(utf8_text.c_str()), | |
| 340 utf8_text.length()); | |
| 341 break; | |
| 342 } | |
| 343 case gtk_dnd_util::TEXT_URI_LIST: { | |
| 344 gchar** uris = reinterpret_cast<gchar**>(malloc(sizeof(gchar*) * | |
| 345 (nodes.size() + 1))); | |
| 346 for (size_t i = 0; i < nodes.size(); ++i) { | |
| 347 // If the node is a folder, this will be empty. TODO(estade): figure out | |
| 348 // if there are any ramifications to passing an empty URI. After a | |
| 349 // little testing, it seems fine. | |
| 350 const GURL& url = nodes[i]->GetURL(); | |
| 351 // This const cast should be safe as gtk_selection_data_set_uris() | |
| 352 // makes copies. | |
| 353 uris[i] = const_cast<gchar*>(url.spec().c_str()); | |
| 354 } | |
| 355 uris[nodes.size()] = NULL; | |
| 356 | |
| 357 gtk_selection_data_set_uris(selection_data, uris); | |
| 358 free(uris); | |
| 359 break; | |
| 360 } | |
| 361 case gtk_dnd_util::TEXT_PLAIN: { | |
| 362 gtk_selection_data_set_text(selection_data, | |
| 363 nodes[0]->GetURL().spec().c_str(), -1); | |
| 364 break; | |
| 365 } | |
| 366 default: { | |
| 367 DLOG(ERROR) << "Unsupported drag get type!"; | |
| 368 } | |
| 369 } | |
| 370 } | |
| 371 | |
| 372 std::vector<const BookmarkNode*> GetNodesFromSelection( | |
| 373 GdkDragContext* context, | |
| 374 GtkSelectionData* selection_data, | |
| 375 guint target_type, | |
| 376 Profile* profile, | |
| 377 gboolean* delete_selection_data, | |
| 378 gboolean* dnd_success) { | |
| 379 if (delete_selection_data) | |
| 380 *delete_selection_data = FALSE; | |
| 381 if (dnd_success) | |
| 382 *dnd_success = FALSE; | |
| 383 | |
| 384 if (selection_data && selection_data->length > 0) { | |
| 385 if (context && delete_selection_data && context->action == GDK_ACTION_MOVE) | |
| 386 *delete_selection_data = TRUE; | |
| 387 | |
| 388 switch (target_type) { | |
| 389 case gtk_dnd_util::CHROME_BOOKMARK_ITEM: { | |
| 390 if (dnd_success) | |
| 391 *dnd_success = TRUE; | |
| 392 Pickle pickle(reinterpret_cast<char*>(selection_data->data), | |
| 393 selection_data->length); | |
| 394 BookmarkNodeData drag_data; | |
| 395 drag_data.ReadFromPickle(&pickle); | |
| 396 return drag_data.GetNodes(profile); | |
| 397 } | |
| 398 default: { | |
| 399 DLOG(ERROR) << "Unsupported drag received type: " << target_type; | |
| 400 } | |
| 401 } | |
| 402 } | |
| 403 | |
| 404 return std::vector<const BookmarkNode*>(); | |
| 405 } | |
| 406 | |
| 407 bool CreateNewBookmarkFromNamedUrl(GtkSelectionData* selection_data, | |
| 408 BookmarkModel* model, const BookmarkNode* parent, int idx) { | |
| 409 GURL url; | |
| 410 string16 title; | |
| 411 if (!gtk_dnd_util::ExtractNamedURL(selection_data, &url, &title)) | |
| 412 return false; | |
| 413 | |
| 414 model->AddURL(parent, idx, title, url); | |
| 415 return true; | |
| 416 } | |
| 417 | |
| 418 bool CreateNewBookmarksFromURIList(GtkSelectionData* selection_data, | |
| 419 BookmarkModel* model, const BookmarkNode* parent, int idx) { | |
| 420 std::vector<GURL> urls; | |
| 421 gtk_dnd_util::ExtractURIList(selection_data, &urls); | |
| 422 for (size_t i = 0; i < urls.size(); ++i) { | |
| 423 std::string title = GetNameForURL(urls[i]); | |
| 424 model->AddURL(parent, idx++, UTF8ToUTF16(title), urls[i]); | |
| 425 } | |
| 426 return true; | |
| 427 } | |
| 428 | |
| 429 bool CreateNewBookmarkFromNetscapeURL(GtkSelectionData* selection_data, | |
| 430 BookmarkModel* model, const BookmarkNode* parent, int idx) { | |
| 431 GURL url; | |
| 432 string16 title; | |
| 433 if (!gtk_dnd_util::ExtractNetscapeURL(selection_data, &url, &title)) | |
| 434 return false; | |
| 435 | |
| 436 model->AddURL(parent, idx, title, url); | |
| 437 return true; | |
| 438 } | |
| 439 | |
| 440 } // namespace bookmark_utils | |
| OLD | NEW |