// Copyright (c) 2009 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 "base/basictypes.h"
#include "base/string_util.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/gtk/menu_gtk.h"
#include "chrome/browser/gtk/nine_box.h"
#include "chrome/common/gfx/chrome_font.h"
#include "chrome/common/gfx/text_elider.h"
#include "chrome/common/resource_bundle.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.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;
// Amount of space we allot to showing the filename. If the filename is too wide
// it will be elided.
const int kTextWidth = 140;
const char* kLabelColorMarkup = "%s";
const char* kFilenameColor = "576C95"; // 87, 108, 149
const char* kStatusColor = "7B8DAE"; // 123, 141, 174
} // 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)
: DownloadShelfContextMenu(model),
menu_is_for_complete_download_(false) {
}
~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.
bool download_is_complete = download_->state() == DownloadItem::COMPLETE;
if (menu_.get() == NULL ||
(download_is_complete && !menu_is_for_complete_download_)) {
menu_.reset(new MenuGtk(this, download_is_complete ?
finished_download_menu : in_progress_download_menu, NULL));
menu_is_for_complete_download_ = download_is_complete;
}
menu_->Popup(widget, event);
}
// MenuGtk::Delegate implementation ------------------------------------------
virtual bool IsCommandEnabled(int id) const {
return IsItemCommandEnabled(id);
}
virtual bool IsItemChecked(int id) const {
return ItemIsChecked(id);
}
virtual void ExecuteCommand(int id) {
return ExecuteItemCommand(id);
}
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 menu_;
// If true, the MenuGtk in |menu_| refers to a finished download menu.
bool menu_is_for_complete_download_;
// We show slightly different menus if the download is in progress vs. if the
// download has finished.
static MenuCreateMaterial in_progress_download_menu[];
static MenuCreateMaterial finished_download_menu[];
};
MenuCreateMaterial DownloadShelfContextMenuGtk::finished_download_menu[] = {
{ MENU_NORMAL, OPEN_WHEN_COMPLETE, IDS_DOWNLOAD_MENU_OPEN, 0, NULL },
{ MENU_CHECKBOX, ALWAYS_OPEN_TYPE, IDS_DOWNLOAD_MENU_ALWAYS_OPEN_TYPE,
0, NULL},
{ MENU_SEPARATOR, 0, 0, 0, NULL },
{ MENU_NORMAL, SHOW_IN_FOLDER, IDS_DOWNLOAD_LINK_SHOW, 0, NULL},
{ MENU_SEPARATOR, 0, 0, 0, NULL },
{ MENU_NORMAL, CANCEL, IDS_DOWNLOAD_MENU_CANCEL, 0, NULL},
{ MENU_END, 0, 0, 0, NULL },
};
MenuCreateMaterial DownloadShelfContextMenuGtk::in_progress_download_menu[] = {
{ MENU_CHECKBOX, OPEN_WHEN_COMPLETE, IDS_DOWNLOAD_MENU_OPEN_WHEN_COMPLETE,
0, NULL },
{ MENU_CHECKBOX, ALWAYS_OPEN_TYPE, IDS_DOWNLOAD_MENU_ALWAYS_OPEN_TYPE,
0, NULL},
{ MENU_SEPARATOR, 0, 0, 0, NULL },
{ MENU_NORMAL, SHOW_IN_FOLDER, IDS_DOWNLOAD_LINK_SHOW, 0, NULL},
{ MENU_SEPARATOR, 0, 0, 0, NULL },
{ MENU_NORMAL, CANCEL, IDS_DOWNLOAD_MENU_CANCEL, 0, NULL},
{ MENU_END, 0, 0, 0, NULL },
};
// 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;
DownloadItemGtk::DownloadItemGtk(BaseDownloadItemModel* download_model,
GtkWidget* parent_shelf,
GtkWidget* bounding_widget)
: download_model_(download_model),
parent_shelf_(parent_shelf),
bounding_widget_(bounding_widget) {
InitNineBoxes();
body_ = gtk_button_new();
// Eventually we will show an icon and graphical download progress, but for
// now the only contents of body_ is text, so to make its size request the
// same as the width of the text (plus a little padding: see below).
gtk_widget_set_size_request(body_, kTextWidth + 50, -1);
gtk_widget_set_app_paintable(body_, TRUE);
g_signal_connect(G_OBJECT(body_), "expose-event",
G_CALLBACK(OnExpose), this);
GTK_WIDGET_UNSET_FLAGS(body_, GTK_CAN_FOCUS);
GtkWidget* name_label = gtk_label_new(NULL);
// TODO(estade): This is at best an educated guess, since we don't actually
// use ChromeFont() 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 ChromeFont
// or somehow extend TextElider.
std::wstring elided_filename = gfx::ElideFilename(
download_model->download()->GetFileName().ToWStringHack(),
ChromeFont(), kTextWidth);
std::string label_markup =
StringPrintf(kLabelColorMarkup, kFilenameColor,
WideToUTF8(elided_filename).c_str());
gtk_label_set_markup(GTK_LABEL(name_label), label_markup.c_str());
status_label_ = gtk_label_new(NULL);
// 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);
// 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);
gtk_container_add(GTK_CONTAINER(body_), text_stack);
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(G_OBJECT(menu_button_), "expose-event",
G_CALLBACK(OnExpose), this);
g_signal_connect(G_OBJECT(menu_button_), "button-press-event",
G_CALLBACK(OnMenuButtonPressEvent), this);
g_object_set_data(G_OBJECT(menu_button_), "left-align-popup",
reinterpret_cast(true));
gtk_widget_set_size_request(menu_button_, kMenuButtonWidth, 0);
hbox_ = gtk_hbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(hbox_), body_, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(hbox_), menu_button_, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(parent_shelf), hbox_, FALSE, FALSE, 0);
// Insert as the leftmost item.
gtk_box_reorder_child(GTK_BOX(parent_shelf), hbox_, 1);
gtk_widget_show_all(hbox_);
g_signal_connect(G_OBJECT(parent_shelf_), "size-allocate",
G_CALLBACK(OnShelfResized), this);
download_model_->download()->AddObserver(this);
}
DownloadItemGtk::~DownloadItemGtk() {
download_model_->download()->RemoveObserver(this);
}
void DownloadItemGtk::OnDownloadUpdated(DownloadItem* download) {
DCHECK_EQ(download, download_model_->download());
if (!status_label_) {
return;
}
std::wstring status_text = download_model_->GetStatusText();
// Remove the status text label.
if (status_text.empty()) {
gtk_widget_destroy(status_label_);
status_label_ = NULL;
return;
}
std::string label_markup =
StringPrintf(kLabelColorMarkup, kStatusColor,
WideToUTF8(status_text).c_str());
gtk_label_set_markup(GTK_LABEL(status_label_), label_markup.c_str());
}
// static
void DownloadItemGtk::InitNineBoxes() {
if (body_nine_box_normal_)
return;
GdkPixbuf* images[9];
ResourceBundle &rb = ResourceBundle::GetSharedInstance();
int i = 0;
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_LEFT_TOP);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_CENTER_TOP);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_RIGHT_TOP);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM);
body_nine_box_normal_ = new NineBox(images);
i = 0;
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_LEFT_TOP_H);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_CENTER_TOP_H);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_H);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_H);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_H);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_H);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_H);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_H);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_H);
body_nine_box_prelight_ = new NineBox(images);
i = 0;
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_LEFT_TOP_P);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_CENTER_TOP_P);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_P);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_P);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_P);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_P);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_P);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_P);
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_P);
body_nine_box_active_ = new NineBox(images);
i = 0;
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_MENU_TOP);
images[i++] = NULL;
images[i++] = NULL;
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE);
images[i++] = NULL;
images[i++] = NULL;
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM);
images[i++] = NULL;
images[i++] = NULL;
menu_nine_box_normal_ = new NineBox(images);
i = 0;
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_MENU_TOP_H);
images[i++] = NULL;
images[i++] = NULL;
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_H);
images[i++] = NULL;
images[i++] = NULL;
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_H);
images[i++] = NULL;
images[i++] = NULL;
menu_nine_box_prelight_ = new NineBox(images);
i = 0;
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_MENU_TOP_P);
images[i++] = NULL;
images[i++] = NULL;
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_P);
images[i++] = NULL;
images[i++] = NULL;
images[i++] = rb.LoadPixbuf(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_P);
images[i++] = NULL;
images[i++] = NULL;
menu_nine_box_active_ = new NineBox(images);
}
// static
gboolean DownloadItemGtk::OnExpose(GtkWidget* widget, GdkEventExpose* e,
DownloadItemGtk* download_item) {
NineBox* nine_box = NULL;
// If true, this widget is |body_|, otherwise it is |menu_button_|.
bool is_body = widget == download_item->body_;
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_;
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;
}
gboolean DownloadItemGtk::OnMenuButtonPressEvent(GtkWidget* button,
GdkEvent* event,
DownloadItemGtk* item) {
// TODO(port): this never puts the button into the "active" state,
// so this may need to be changed. See note in BrowserToolbarGtk.
if (event->type == GDK_BUTTON_PRESS) {
GdkEventButton* event_button = reinterpret_cast(event);
if (event_button->button == 1) {
if (item->menu_.get() == NULL) {
item->menu_.reset(new DownloadShelfContextMenuGtk(
item->download_model_.get()));
}
item->menu_->Popup(button, event);
}
}
return FALSE;
}
void DownloadItemGtk::OnShelfResized(GtkWidget *widget,
GtkAllocation *allocation,
DownloadItemGtk* item) {
if (item->hbox_->allocation.x + item->hbox_->allocation.width >
item->bounding_widget_->allocation.x)
gtk_widget_hide(item->hbox_);
else
gtk_widget_show(item->hbox_);
}