summaryrefslogtreecommitdiffstats
path: root/chrome/browser/gtk/download_item_gtk.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/gtk/download_item_gtk.cc')
-rw-r--r--chrome/browser/gtk/download_item_gtk.cc870
1 files changed, 870 insertions, 0 deletions
diff --git a/chrome/browser/gtk/download_item_gtk.cc b/chrome/browser/gtk/download_item_gtk.cc
new file mode 100644
index 0000000..6744af0
--- /dev/null
+++ b/chrome/browser/gtk/download_item_gtk.cc
@@ -0,0 +1,870 @@
+// Copyright (c) 2010 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/gtk/download_item_gtk.h"
+
+#include "app/gtk_util.h"
+#include "app/l10n_util.h"
+#include "app/menus/simple_menu_model.h"
+#include "app/resource_bundle.h"
+#include "app/slide_animation.h"
+#include "app/text_elider.h"
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/histogram.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "chrome/browser/browser.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/download/download_item.h"
+#include "chrome/browser/download/download_item_model.h"
+#include "chrome/browser/download/download_manager.h"
+#include "chrome/browser/download/download_shelf.h"
+#include "chrome/browser/download/download_util.h"
+#include "chrome/browser/gtk/custom_drag.h"
+#include "chrome/browser/gtk/download_shelf_gtk.h"
+#include "chrome/browser/gtk/gtk_theme_provider.h"
+#include "chrome/browser/gtk/gtk_util.h"
+#include "chrome/browser/gtk/menu_gtk.h"
+#include "chrome/browser/gtk/nine_box.h"
+#include "chrome/common/notification_service.h"
+#include "gfx/canvas_skia_paint.h"
+#include "gfx/color_utils.h"
+#include "gfx/font.h"
+#include "gfx/skia_utils_gtk.h"
+#include "grit/generated_resources.h"
+#include "grit/theme_resources.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace {
+
+// The width of the |menu_button_| widget. It has to be at least as wide as the
+// bitmap that we use to draw it, i.e. 16, but can be more.
+const int kMenuButtonWidth = 16;
+
+// Padding on left and right of items in dangerous download prompt.
+const int kDangerousElementPadding = 3;
+
+// Amount of space we allot to showing the filename. If the filename is too wide
+// it will be elided.
+const int kTextWidth = 140;
+
+// We only cap the size of the tooltip so we don't crash.
+const int kTooltipMaxWidth = 1000;
+
+// The minimum width we will ever draw the download item. Used as a lower bound
+// during animation. This number comes from the width of the images used to
+// make the download item.
+const int kMinDownloadItemWidth = download_util::kSmallProgressIconSize;
+
+// New download item animation speed in milliseconds.
+const int kNewItemAnimationDurationMs = 800;
+
+// How long the 'download complete' animation should last for.
+const int kCompleteAnimationDurationMs = 2500;
+
+// Width of the body area of the download item.
+// TODO(estade): get rid of the fudge factor. http://crbug.com/18692
+const int kBodyWidth = kTextWidth + 50 + download_util::kSmallProgressIconSize;
+
+// The font size of the text, and that size rounded down to the nearest integer
+// for the size of the arrow in GTK theme mode.
+const double kTextSize = 13.4; // 13.4px == 10pt @ 96dpi
+
+// Darken light-on-dark download status text by 20% before drawing, thus
+// creating a "muted" version of title text for both dark-on-light and
+// light-on-dark themes.
+static const double kDownloadItemLuminanceMod = 0.8;
+
+} // namespace
+
+// DownloadShelfContextMenuGtk -------------------------------------------------
+
+class DownloadShelfContextMenuGtk : public DownloadShelfContextMenu,
+ public MenuGtk::Delegate {
+ public:
+ // The constructor creates the menu and immediately pops it up.
+ // |model| is the download item model associated with this context menu,
+ // |widget| is the button that popped up this context menu, and |e| is
+ // the button press event that caused this menu to be created.
+ DownloadShelfContextMenuGtk(BaseDownloadItemModel* model,
+ DownloadItemGtk* download_item)
+ : DownloadShelfContextMenu(model),
+ download_item_(download_item),
+ method_factory_(this) {
+ }
+
+ ~DownloadShelfContextMenuGtk() {
+ }
+
+ void Popup(GtkWidget* widget, GdkEvent* event) {
+ // Create the menu if we have not created it yet or we created it for
+ // an in-progress download that has since completed.
+ if (download_->state() == DownloadItem::COMPLETE)
+ menu_.reset(new MenuGtk(this, GetFinishedMenuModel()));
+ else
+ menu_.reset(new MenuGtk(this, GetInProgressMenuModel()));
+ menu_->Popup(widget, event);
+ }
+
+ // MenuGtk::Delegate implementation:
+ virtual void StoppedShowing() {
+ download_item_->menu_showing_ = false;
+ gtk_widget_queue_draw(download_item_->menu_button_);
+ }
+
+ private:
+ // The menu we show on Popup(). We keep a pointer to it for a couple reasons:
+ // * we don't want to have to recreate the menu every time it's popped up.
+ // * we have to keep it in scope for longer than the duration of Popup(), or
+ // completing the user-selected action races against the menu's
+ // destruction.
+ scoped_ptr<MenuGtk> menu_;
+
+ // The download item that created us.
+ DownloadItemGtk* download_item_;
+
+ ScopedRunnableMethodFactory<DownloadShelfContextMenuGtk> method_factory_;
+};
+
+// DownloadItemGtk -------------------------------------------------------------
+
+NineBox* DownloadItemGtk::body_nine_box_normal_ = NULL;
+NineBox* DownloadItemGtk::body_nine_box_prelight_ = NULL;
+NineBox* DownloadItemGtk::body_nine_box_active_ = NULL;
+
+NineBox* DownloadItemGtk::menu_nine_box_normal_ = NULL;
+NineBox* DownloadItemGtk::menu_nine_box_prelight_ = NULL;
+NineBox* DownloadItemGtk::menu_nine_box_active_ = NULL;
+
+NineBox* DownloadItemGtk::dangerous_nine_box_ = NULL;
+
+DownloadItemGtk::DownloadItemGtk(DownloadShelfGtk* parent_shelf,
+ BaseDownloadItemModel* download_model)
+ : parent_shelf_(parent_shelf),
+ arrow_(NULL),
+ menu_showing_(false),
+ theme_provider_(GtkThemeProvider::GetFrom(
+ parent_shelf->browser()->profile())),
+ progress_angle_(download_util::kStartAngleDegrees),
+ download_model_(download_model),
+ dangerous_prompt_(NULL),
+ dangerous_label_(NULL),
+ icon_small_(NULL),
+ icon_large_(NULL),
+ creation_time_(base::Time::Now()) {
+ LoadIcon();
+
+ body_.Own(gtk_button_new());
+ gtk_widget_set_app_paintable(body_.get(), TRUE);
+ UpdateTooltip();
+
+ g_signal_connect(body_.get(), "expose-event",
+ G_CALLBACK(OnExposeThunk), this);
+ g_signal_connect(body_.get(), "clicked",
+ G_CALLBACK(OnClickThunk), this);
+ GTK_WIDGET_UNSET_FLAGS(body_.get(), GTK_CAN_FOCUS);
+ // Remove internal padding on the button.
+ GtkRcStyle* no_padding_style = gtk_rc_style_new();
+ no_padding_style->xthickness = 0;
+ no_padding_style->ythickness = 0;
+ gtk_widget_modify_style(body_.get(), no_padding_style);
+ g_object_unref(no_padding_style);
+
+ name_label_ = gtk_label_new(NULL);
+
+ UpdateNameLabel();
+
+ status_label_ = gtk_label_new(NULL);
+ g_signal_connect(status_label_, "destroy",
+ G_CALLBACK(gtk_widget_destroyed), &status_label_);
+ // Left align and vertically center the labels.
+ gtk_misc_set_alignment(GTK_MISC(name_label_), 0, 0.5);
+ gtk_misc_set_alignment(GTK_MISC(status_label_), 0, 0.5);
+ // Until we switch to vector graphics, force the font size.
+ gtk_util::ForceFontSizePixels(name_label_, kTextSize);
+ gtk_util::ForceFontSizePixels(status_label_, kTextSize);
+
+ // Stack the labels on top of one another.
+ GtkWidget* text_stack = gtk_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(text_stack), name_label_, TRUE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(text_stack), status_label_, FALSE, FALSE, 0);
+
+ // We use a GtkFixed because we don't want it to have its own window.
+ // This choice of widget is not critically important though.
+ progress_area_.Own(gtk_fixed_new());
+ gtk_widget_set_size_request(progress_area_.get(),
+ download_util::kSmallProgressIconSize,
+ download_util::kSmallProgressIconSize);
+ gtk_widget_set_app_paintable(progress_area_.get(), TRUE);
+ g_signal_connect(progress_area_.get(), "expose-event",
+ G_CALLBACK(OnProgressAreaExposeThunk), this);
+
+ // Put the download progress icon on the left of the labels.
+ GtkWidget* body_hbox = gtk_hbox_new(FALSE, 0);
+ gtk_container_add(GTK_CONTAINER(body_.get()), body_hbox);
+ gtk_box_pack_start(GTK_BOX(body_hbox), progress_area_.get(), FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(body_hbox), text_stack, TRUE, TRUE, 0);
+
+ menu_button_ = gtk_button_new();
+ gtk_widget_set_app_paintable(menu_button_, TRUE);
+ GTK_WIDGET_UNSET_FLAGS(menu_button_, GTK_CAN_FOCUS);
+ g_signal_connect(menu_button_, "expose-event",
+ G_CALLBACK(OnExposeThunk), this);
+ g_signal_connect(menu_button_, "button-press-event",
+ G_CALLBACK(OnMenuButtonPressEventThunk), this);
+ g_object_set_data(G_OBJECT(menu_button_), "left-align-popup",
+ reinterpret_cast<void*>(true));
+
+ GtkWidget* shelf_hbox = parent_shelf->GetHBox();
+ hbox_.Own(gtk_hbox_new(FALSE, 0));
+ g_signal_connect(hbox_.get(), "expose-event",
+ G_CALLBACK(OnHboxExposeThunk), this);
+ gtk_box_pack_start(GTK_BOX(hbox_.get()), body_.get(), FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(hbox_.get()), menu_button_, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(shelf_hbox), hbox_.get(), FALSE, FALSE, 0);
+ // Insert as the leftmost item.
+ gtk_box_reorder_child(GTK_BOX(shelf_hbox), hbox_.get(), 0);
+
+ get_download()->AddObserver(this);
+
+ new_item_animation_.reset(new SlideAnimation(this));
+ new_item_animation_->SetSlideDuration(kNewItemAnimationDurationMs);
+ gtk_widget_show_all(hbox_.get());
+
+ if (IsDangerous()) {
+ // Hide the download item components for now.
+ gtk_widget_hide(body_.get());
+ gtk_widget_hide(menu_button_);
+
+ // Create an hbox to hold it all.
+ dangerous_hbox_ = gtk_hbox_new(FALSE, kDangerousElementPadding);
+
+ // Add padding at the beginning and end. The hbox will add padding between
+ // the empty labels and the other elements.
+ GtkWidget* empty_label_a = gtk_label_new(NULL);
+ GtkWidget* empty_label_b = gtk_label_new(NULL);
+ gtk_box_pack_start(GTK_BOX(dangerous_hbox_), empty_label_a,
+ FALSE, FALSE, 0);
+ gtk_box_pack_end(GTK_BOX(dangerous_hbox_), empty_label_b,
+ FALSE, FALSE, 0);
+
+ // Create the warning icon.
+ dangerous_image_ = gtk_image_new();
+ gtk_box_pack_start(GTK_BOX(dangerous_hbox_), dangerous_image_,
+ FALSE, FALSE, 0);
+
+ dangerous_label_ = gtk_label_new(NULL);
+ // We pass TRUE, TRUE so that the label will condense to less than its
+ // request when the animation is going on.
+ gtk_box_pack_start(GTK_BOX(dangerous_hbox_), dangerous_label_,
+ TRUE, TRUE, 0);
+
+ // Create the nevermind button.
+ GtkWidget* dangerous_decline = gtk_button_new_with_label(
+ l10n_util::GetStringUTF8(IDS_DISCARD_DOWNLOAD).c_str());
+ g_signal_connect(dangerous_decline, "clicked",
+ G_CALLBACK(OnDangerousDeclineThunk), this);
+ gtk_util::CenterWidgetInHBox(dangerous_hbox_, dangerous_decline, false, 0);
+
+ // Create the ok button.
+ GtkWidget* dangerous_accept = gtk_button_new_with_label(
+ l10n_util::GetStringUTF8(
+ download_model->download()->is_extension_install() ?
+ IDS_CONTINUE_EXTENSION_DOWNLOAD : IDS_SAVE_DOWNLOAD).c_str());
+ g_signal_connect(dangerous_accept, "clicked",
+ G_CALLBACK(OnDangerousAcceptThunk), this);
+ gtk_util::CenterWidgetInHBox(dangerous_hbox_, dangerous_accept, false, 0);
+
+ // Put it in an alignment so that padding will be added on the left and
+ // right.
+ dangerous_prompt_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(dangerous_prompt_),
+ 0, 0, kDangerousElementPadding, kDangerousElementPadding);
+ gtk_container_add(GTK_CONTAINER(dangerous_prompt_), dangerous_hbox_);
+ gtk_box_pack_start(GTK_BOX(hbox_.get()), dangerous_prompt_, FALSE, FALSE,
+ 0);
+ gtk_widget_set_app_paintable(dangerous_prompt_, TRUE);
+ gtk_widget_set_redraw_on_allocate(dangerous_prompt_, TRUE);
+ g_signal_connect(dangerous_prompt_, "expose-event",
+ G_CALLBACK(OnDangerousPromptExposeThunk), this);
+ gtk_widget_show_all(dangerous_prompt_);
+ }
+
+ registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED,
+ NotificationService::AllSources());
+ theme_provider_->InitThemesFor(this);
+
+ // Set the initial width of the widget to be animated.
+ if (IsDangerous()) {
+ gtk_widget_set_size_request(dangerous_hbox_,
+ dangerous_hbox_start_width_, -1);
+ } else {
+ gtk_widget_set_size_request(body_.get(), kMinDownloadItemWidth, -1);
+ }
+
+ new_item_animation_->Show();
+}
+
+DownloadItemGtk::~DownloadItemGtk() {
+ icon_consumer_.CancelAllRequests();
+ StopDownloadProgress();
+ get_download()->RemoveObserver(this);
+
+ // We may free some shelf space for showing more download items.
+ parent_shelf_->MaybeShowMoreDownloadItems();
+
+ hbox_.Destroy();
+ progress_area_.Destroy();
+ body_.Destroy();
+
+ // Make sure this widget has been destroyed and the pointer we hold to it
+ // NULLed.
+ DCHECK(!status_label_);
+}
+
+void DownloadItemGtk::OnDownloadUpdated(DownloadItem* download) {
+ DCHECK_EQ(download, get_download());
+
+ if (dangerous_prompt_ != NULL &&
+ download->safety_state() == DownloadItem::DANGEROUS_BUT_VALIDATED) {
+ // We have been approved.
+ gtk_widget_show_all(hbox_.get());
+ gtk_widget_destroy(dangerous_prompt_);
+ gtk_widget_set_size_request(body_.get(), kBodyWidth, -1);
+ dangerous_prompt_ = NULL;
+
+ // We may free some shelf space for showing more download items.
+ parent_shelf_->MaybeShowMoreDownloadItems();
+ }
+
+ if (download->full_path() != icon_filepath_) {
+ // Turns out the file path is "unconfirmed %d.download" for dangerous
+ // downloads. When the download is confirmed, the file is renamed on
+ // another thread, so reload the icon if the download filename changes.
+ LoadIcon();
+
+ UpdateTooltip();
+ }
+
+ switch (download->state()) {
+ case DownloadItem::REMOVING:
+ parent_shelf_->RemoveDownloadItem(this); // This will delete us!
+ return;
+ case DownloadItem::CANCELLED:
+ StopDownloadProgress();
+ gtk_widget_queue_draw(progress_area_.get());
+ break;
+ case DownloadItem::COMPLETE:
+ if (download->auto_opened()) {
+ parent_shelf_->RemoveDownloadItem(this); // This will delete us!
+ return;
+ }
+ StopDownloadProgress();
+
+ // Set up the widget as a drag source.
+ DownloadItemDrag::SetSource(body_.get(), get_download(), icon_large_);
+
+ complete_animation_.reset(new SlideAnimation(this));
+ complete_animation_->SetSlideDuration(kCompleteAnimationDurationMs);
+ complete_animation_->SetTweenType(Tween::LINEAR);
+ complete_animation_->Show();
+ break;
+ case DownloadItem::IN_PROGRESS:
+ get_download()->is_paused() ?
+ StopDownloadProgress() : StartDownloadProgress();
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ // Now update the status label. We may have already removed it; if so, we
+ // do nothing.
+ if (!status_label_) {
+ return;
+ }
+
+ std::wstring status_text = download_model_->GetStatusText();
+ status_text_ = WideToUTF8(status_text);
+ // Remove the status text label.
+ if (status_text.empty()) {
+ gtk_widget_destroy(status_label_);
+ return;
+ }
+
+ UpdateStatusLabel(status_text_);
+}
+
+void DownloadItemGtk::AnimationProgressed(const Animation* animation) {
+ if (animation == complete_animation_.get()) {
+ gtk_widget_queue_draw(progress_area_.get());
+ } else {
+ if (IsDangerous()) {
+ int progress = static_cast<int>((dangerous_hbox_full_width_ -
+ dangerous_hbox_start_width_) *
+ new_item_animation_->GetCurrentValue());
+ int showing_width = dangerous_hbox_start_width_ + progress;
+ gtk_widget_set_size_request(dangerous_hbox_, showing_width, -1);
+ } else {
+ DCHECK(animation == new_item_animation_.get());
+ int showing_width = std::max(kMinDownloadItemWidth,
+ static_cast<int>(kBodyWidth *
+ new_item_animation_->GetCurrentValue()));
+ gtk_widget_set_size_request(body_.get(), showing_width, -1);
+ }
+ }
+}
+
+void DownloadItemGtk::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ if (type == NotificationType::BROWSER_THEME_CHANGED) {
+ // Our GtkArrow is only visible in gtk mode. Otherwise, we let the custom
+ // rendering code do whatever it wants.
+ if (theme_provider_->UseGtkTheme()) {
+ if (!arrow_) {
+ arrow_ = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
+ gtk_widget_set_size_request(arrow_,
+ static_cast<int>(kTextSize),
+ static_cast<int>(kTextSize));
+ gtk_container_add(GTK_CONTAINER(menu_button_), arrow_);
+ }
+
+ gtk_widget_set_size_request(menu_button_, -1, -1);
+ gtk_widget_show(arrow_);
+ } else {
+ InitNineBoxes();
+
+ gtk_widget_set_size_request(menu_button_, kMenuButtonWidth, 0);
+
+ if (arrow_)
+ gtk_widget_hide(arrow_);
+ }
+
+ UpdateNameLabel();
+ UpdateStatusLabel(status_text_);
+ UpdateDangerWarning();
+ }
+}
+
+DownloadItem* DownloadItemGtk::get_download() {
+ return download_model_->download();
+}
+
+bool DownloadItemGtk::IsDangerous() {
+ return get_download()->safety_state() == DownloadItem::DANGEROUS;
+}
+
+// Download progress animation functions.
+
+void DownloadItemGtk::UpdateDownloadProgress() {
+ progress_angle_ = (progress_angle_ +
+ download_util::kUnknownIncrementDegrees) %
+ download_util::kMaxDegrees;
+ gtk_widget_queue_draw(progress_area_.get());
+}
+
+void DownloadItemGtk::StartDownloadProgress() {
+ if (progress_timer_.IsRunning())
+ return;
+ progress_timer_.Start(
+ base::TimeDelta::FromMilliseconds(download_util::kProgressRateMs), this,
+ &DownloadItemGtk::UpdateDownloadProgress);
+}
+
+void DownloadItemGtk::StopDownloadProgress() {
+ progress_timer_.Stop();
+}
+
+// Icon loading functions.
+
+void DownloadItemGtk::OnLoadSmallIconComplete(IconManager::Handle handle,
+ SkBitmap* icon_bitmap) {
+ icon_small_ = icon_bitmap;
+ gtk_widget_queue_draw(progress_area_.get());
+}
+
+void DownloadItemGtk::OnLoadLargeIconComplete(IconManager::Handle handle,
+ SkBitmap* icon_bitmap) {
+ icon_large_ = icon_bitmap;
+ DownloadItemDrag::SetSource(body_.get(), get_download(), icon_large_);
+}
+
+void DownloadItemGtk::LoadIcon() {
+ icon_consumer_.CancelAllRequests();
+ IconManager* im = g_browser_process->icon_manager();
+ icon_filepath_ = get_download()->full_path();
+ im->LoadIcon(icon_filepath_,
+ IconLoader::SMALL, &icon_consumer_,
+ NewCallback(this, &DownloadItemGtk::OnLoadSmallIconComplete));
+ im->LoadIcon(icon_filepath_,
+ IconLoader::LARGE, &icon_consumer_,
+ NewCallback(this, &DownloadItemGtk::OnLoadLargeIconComplete));
+}
+
+void DownloadItemGtk::UpdateTooltip() {
+ std::wstring elided_filename = gfx::ElideFilename(
+ get_download()->GetFileName(),
+ gfx::Font(), kTooltipMaxWidth);
+ gtk_widget_set_tooltip_text(body_.get(), WideToUTF8(elided_filename).c_str());
+}
+
+void DownloadItemGtk::UpdateNameLabel() {
+ // TODO(estade): This is at best an educated guess, since we don't actually
+ // use gfx::Font() to draw the text. This is why we need to add so
+ // much padding when we set the size request. We need to either use gfx::Font
+ // or somehow extend TextElider.
+ std::wstring elided_filename = gfx::ElideFilename(
+ get_download()->GetFileName(),
+ gfx::Font(), kTextWidth);
+
+ GdkColor color = theme_provider_->GetGdkColor(
+ BrowserThemeProvider::COLOR_BOOKMARK_TEXT);
+ gtk_util::SetLabelColor(name_label_, theme_provider_->UseGtkTheme() ?
+ NULL : &color);
+ gtk_label_set_text(GTK_LABEL(name_label_),
+ WideToUTF8(elided_filename).c_str());
+}
+
+void DownloadItemGtk::UpdateStatusLabel(const std::string& status_text) {
+ if (!status_label_)
+ return;
+
+ GdkColor text_color;
+ if (!theme_provider_->UseGtkTheme()) {
+ SkColor color = theme_provider_->GetColor(
+ BrowserThemeProvider::COLOR_BOOKMARK_TEXT);
+ if (color_utils::RelativeLuminance(color) > 0.5) {
+ color = SkColorSetRGB(
+ static_cast<int>(kDownloadItemLuminanceMod *
+ SkColorGetR(color)),
+ static_cast<int>(kDownloadItemLuminanceMod *
+ SkColorGetG(color)),
+ static_cast<int>(kDownloadItemLuminanceMod *
+ SkColorGetB(color)));
+ }
+
+ // Lighten the color by blending it with the download item body color. These
+ // values are taken from IDR_DOWNLOAD_BUTTON.
+ SkColor blend_color = SkColorSetRGB(241, 245, 250);
+ text_color = gfx::SkColorToGdkColor(
+ color_utils::AlphaBlend(blend_color, color, 77));
+ }
+
+ gtk_util::SetLabelColor(status_label_, theme_provider_->UseGtkTheme() ?
+ NULL : &text_color);
+ gtk_label_set_text(GTK_LABEL(status_label_), status_text.c_str());
+}
+
+void DownloadItemGtk::UpdateDangerWarning() {
+ if (dangerous_prompt_) {
+ // We create |dangerous_warning| as a wide string so we can more easily
+ // calculate its length in characters.
+ std::wstring dangerous_warning;
+ if (get_download()->is_extension_install()) {
+ dangerous_warning =
+ l10n_util::GetString(IDS_PROMPT_DANGEROUS_DOWNLOAD_EXTENSION);
+ } else {
+ std::wstring elided_filename = gfx::ElideFilename(
+ get_download()->original_name(), gfx::Font(), kTextWidth);
+
+ dangerous_warning =
+ l10n_util::GetStringF(IDS_PROMPT_DANGEROUS_DOWNLOAD, elided_filename);
+ }
+
+ if (theme_provider_->UseGtkTheme()) {
+ gtk_image_set_from_stock(GTK_IMAGE(dangerous_image_),
+ GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_SMALL_TOOLBAR);
+
+ gtk_util::SetLabelColor(dangerous_label_, NULL);
+ } else {
+ // Set the warning icon.
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ GdkPixbuf* download_pixbuf = rb.GetPixbufNamed(IDR_WARNING);
+ gtk_image_set_from_pixbuf(GTK_IMAGE(dangerous_image_), download_pixbuf);
+
+ GdkColor color = theme_provider_->GetGdkColor(
+ BrowserThemeProvider::COLOR_BOOKMARK_TEXT);
+ gtk_util::SetLabelColor(dangerous_label_, &color);
+ }
+
+ gtk_label_set_text(GTK_LABEL(dangerous_label_),
+ WideToUTF8(dangerous_warning).c_str());
+
+ // Until we switch to vector graphics, force the font size.
+ gtk_util::ForceFontSizePixels(dangerous_label_, kTextSize);
+
+ // Get the label width when displaying in one line, and reduce it to 60% to
+ // wrap the label into two lines.
+ gtk_widget_set_size_request(dangerous_label_, -1, -1);
+ gtk_label_set_line_wrap(GTK_LABEL(dangerous_label_), FALSE);
+
+ GtkRequisition req;
+ gtk_widget_size_request(dangerous_label_, &req);
+
+ gint label_width = req.width * 6 / 10;
+ gtk_label_set_line_wrap(GTK_LABEL(dangerous_label_), TRUE);
+ gtk_widget_set_size_request(dangerous_label_, label_width, -1);
+
+ // The width will depend on the text. We must do this each time we possibly
+ // change the label above.
+ gtk_widget_size_request(dangerous_hbox_, &req);
+ dangerous_hbox_full_width_ = req.width;
+ dangerous_hbox_start_width_ = dangerous_hbox_full_width_ - label_width;
+ }
+}
+
+// static
+void DownloadItemGtk::InitNineBoxes() {
+ if (body_nine_box_normal_)
+ return;
+
+ body_nine_box_normal_ = new NineBox(
+ IDR_DOWNLOAD_BUTTON_LEFT_TOP,
+ IDR_DOWNLOAD_BUTTON_CENTER_TOP,
+ IDR_DOWNLOAD_BUTTON_RIGHT_TOP,
+ IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE,
+ IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE,
+ IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE,
+ IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM,
+ IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM,
+ IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM);
+
+ body_nine_box_prelight_ = new NineBox(
+ IDR_DOWNLOAD_BUTTON_LEFT_TOP_H,
+ IDR_DOWNLOAD_BUTTON_CENTER_TOP_H,
+ IDR_DOWNLOAD_BUTTON_RIGHT_TOP_H,
+ IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_H,
+ IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_H,
+ IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_H,
+ IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_H,
+ IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_H,
+ IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_H);
+
+ body_nine_box_active_ = new NineBox(
+ IDR_DOWNLOAD_BUTTON_LEFT_TOP_P,
+ IDR_DOWNLOAD_BUTTON_CENTER_TOP_P,
+ IDR_DOWNLOAD_BUTTON_RIGHT_TOP_P,
+ IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_P,
+ IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_P,
+ IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_P,
+ IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_P,
+ IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_P,
+ IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_P);
+
+ menu_nine_box_normal_ = new NineBox(
+ IDR_DOWNLOAD_BUTTON_MENU_TOP, 0, 0,
+ IDR_DOWNLOAD_BUTTON_MENU_MIDDLE, 0, 0,
+ IDR_DOWNLOAD_BUTTON_MENU_BOTTOM, 0, 0);
+
+ menu_nine_box_prelight_ = new NineBox(
+ IDR_DOWNLOAD_BUTTON_MENU_TOP_H, 0, 0,
+ IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_H, 0, 0,
+ IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_H, 0, 0);
+
+ menu_nine_box_active_ = new NineBox(
+ IDR_DOWNLOAD_BUTTON_MENU_TOP_P, 0, 0,
+ IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_P, 0, 0,
+ IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_P, 0, 0);
+
+ dangerous_nine_box_ = new NineBox(
+ IDR_DOWNLOAD_BUTTON_LEFT_TOP,
+ IDR_DOWNLOAD_BUTTON_CENTER_TOP,
+ IDR_DOWNLOAD_BUTTON_RIGHT_TOP_NO_DD,
+ IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE,
+ IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE,
+ IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_NO_DD,
+ IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM,
+ IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM,
+ IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_NO_DD);
+}
+
+gboolean DownloadItemGtk::OnHboxExpose(GtkWidget* widget, GdkEventExpose* e) {
+ if (theme_provider_->UseGtkTheme()) {
+ int border_width = GTK_CONTAINER(widget)->border_width;
+ int x = widget->allocation.x + border_width;
+ int y = widget->allocation.y + border_width;
+ int width = widget->allocation.width - border_width * 2;
+ int height = widget->allocation.height - border_width * 2;
+
+ if (IsDangerous()) {
+ // Draw a simple frame around the area when we're displaying the warning.
+ gtk_paint_shadow(widget->style, widget->window,
+ static_cast<GtkStateType>(widget->state),
+ static_cast<GtkShadowType>(GTK_SHADOW_OUT),
+ &e->area, widget, "frame",
+ x, y, width, height);
+ } else {
+ // Manually draw the GTK button border around the download item. We draw
+ // the left part of the button (the file), a divider, and then the right
+ // part of the button (the menu). We can't draw a button on top of each
+ // other (*cough*Clearlooks*cough*) so instead, to draw the left part of
+ // the button, we instruct GTK to draw the entire button...with a
+ // doctored clip rectangle to the left part of the button sans
+ // separator. We then repeat this for the right button.
+ GtkStyle* style = body_.get()->style;
+
+ GtkAllocation left_allocation = body_.get()->allocation;
+ GdkRectangle left_clip = {
+ left_allocation.x, left_allocation.y,
+ left_allocation.width, left_allocation.height
+ };
+
+ GtkAllocation right_allocation = menu_button_->allocation;
+ GdkRectangle right_clip = {
+ right_allocation.x, right_allocation.y,
+ right_allocation.width, right_allocation.height
+ };
+
+ GtkShadowType body_shadow =
+ GTK_BUTTON(body_.get())->depressed ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
+ gtk_paint_box(style, widget->window,
+ static_cast<GtkStateType>(GTK_WIDGET_STATE(body_.get())),
+ body_shadow,
+ &left_clip, widget, "button",
+ x, y, width, height);
+
+ GtkShadowType menu_shadow =
+ GTK_BUTTON(menu_button_)->depressed ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
+ gtk_paint_box(style, widget->window,
+ static_cast<GtkStateType>(GTK_WIDGET_STATE(menu_button_)),
+ menu_shadow,
+ &right_clip, widget, "button",
+ x, y, width, height);
+
+ // Doing the math to reverse engineer where we should be drawing our line
+ // is hard and relies on copying GTK internals, so instead steal the
+ // allocation of the gtk arrow which is close enough (and will error on
+ // the conservative side).
+ GtkAllocation arrow_allocation = arrow_->allocation;
+ gtk_paint_vline(style, widget->window,
+ static_cast<GtkStateType>(GTK_WIDGET_STATE(widget)),
+ &e->area, widget, "button",
+ arrow_allocation.y,
+ arrow_allocation.y + arrow_allocation.height,
+ left_allocation.x + left_allocation.width);
+ }
+ }
+ return FALSE;
+}
+
+gboolean DownloadItemGtk::OnExpose(GtkWidget* widget, GdkEventExpose* e) {
+ if (!theme_provider_->UseGtkTheme()) {
+ bool is_body = widget == body_.get();
+
+ NineBox* nine_box = NULL;
+ // If true, this widget is |body_|, otherwise it is |menu_button_|.
+ if (GTK_WIDGET_STATE(widget) == GTK_STATE_PRELIGHT)
+ nine_box = is_body ? body_nine_box_prelight_ : menu_nine_box_prelight_;
+ else if (GTK_WIDGET_STATE(widget) == GTK_STATE_ACTIVE)
+ nine_box = is_body ? body_nine_box_active_ : menu_nine_box_active_;
+ else
+ nine_box = is_body ? body_nine_box_normal_ : menu_nine_box_normal_;
+
+ // When the button is showing, we want to draw it as active. We have to do
+ // this explicitly because the button's state will be NORMAL while the menu
+ // has focus.
+ if (!is_body && menu_showing_)
+ nine_box = menu_nine_box_active_;
+
+ nine_box->RenderToWidget(widget);
+ }
+
+ GtkWidget* child = gtk_bin_get_child(GTK_BIN(widget));
+ if (child)
+ gtk_container_propagate_expose(GTK_CONTAINER(widget), child, e);
+
+ return TRUE;
+}
+
+void DownloadItemGtk::OnClick(GtkWidget* widget) {
+ UMA_HISTOGRAM_LONG_TIMES("clickjacking.open_download",
+ base::Time::Now() - creation_time_);
+
+ DownloadItem* download = get_download();
+
+ if (download->state() == DownloadItem::IN_PROGRESS) {
+ download->set_open_when_complete(
+ !download->open_when_complete());
+ } else if (download->state() == DownloadItem::COMPLETE) {
+ download_util::OpenDownload(download);
+ }
+}
+
+gboolean DownloadItemGtk::OnProgressAreaExpose(GtkWidget* widget,
+ GdkEventExpose* event) {
+ // Create a transparent canvas.
+ gfx::CanvasSkiaPaint canvas(event, false);
+ if (complete_animation_.get()) {
+ if (complete_animation_->is_animating()) {
+ download_util::PaintDownloadComplete(&canvas,
+ widget->allocation.x, widget->allocation.y,
+ complete_animation_->GetCurrentValue(),
+ download_util::SMALL);
+ }
+ } else if (get_download()->state() !=
+ DownloadItem::CANCELLED) {
+ download_util::PaintDownloadProgress(&canvas,
+ widget->allocation.x, widget->allocation.y,
+ progress_angle_,
+ get_download()->PercentComplete(),
+ download_util::SMALL);
+ }
+
+ // |icon_small_| may be NULL if it is still loading. If the file is an
+ // unrecognized type then we will get back a generic system icon. Hence
+ // there is no need to use the chromium-specific default download item icon.
+ if (icon_small_) {
+ const int offset = download_util::kSmallProgressIconOffset;
+ canvas.DrawBitmapInt(*icon_small_,
+ widget->allocation.x + offset, widget->allocation.y + offset);
+ }
+
+ return TRUE;
+}
+
+gboolean DownloadItemGtk::OnMenuButtonPressEvent(GtkWidget* button,
+ GdkEvent* event) {
+ // Stop any completion animation.
+ if (complete_animation_.get() && complete_animation_->is_animating())
+ complete_animation_->End();
+
+ if (event->type == GDK_BUTTON_PRESS) {
+ GdkEventButton* event_button = reinterpret_cast<GdkEventButton*>(event);
+ if (event_button->button == 1) {
+ if (menu_.get() == NULL) {
+ menu_.reset(new DownloadShelfContextMenuGtk(
+ download_model_.get(), this));
+ }
+ menu_->Popup(button, event);
+ menu_showing_ = true;
+ gtk_widget_queue_draw(button);
+ }
+ }
+
+ return FALSE;
+}
+
+gboolean DownloadItemGtk::OnDangerousPromptExpose(GtkWidget* widget,
+ GdkEventExpose* event) {
+ if (!theme_provider_->UseGtkTheme()) {
+ // The hbox renderer will take care of the border when in GTK mode.
+ dangerous_nine_box_->RenderToWidget(widget);
+ }
+ return FALSE; // Continue propagation.
+}
+
+void DownloadItemGtk::OnDangerousAccept(GtkWidget* button) {
+ UMA_HISTOGRAM_LONG_TIMES("clickjacking.save_download",
+ base::Time::Now() - creation_time_);
+ get_download()->manager()->DangerousDownloadValidated(get_download());
+}
+
+void DownloadItemGtk::OnDangerousDecline(GtkWidget* button) {
+ UMA_HISTOGRAM_LONG_TIMES("clickjacking.discard_download",
+ base::Time::Now() - creation_time_);
+ if (get_download()->state() == DownloadItem::IN_PROGRESS)
+ get_download()->Cancel(true);
+ get_download()->Remove(true);
+}