Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1367)

Side by Side Diff: chrome/browser/ui/gtk/download/download_item_gtk.cc

Issue 231733005: Delete the GTK+ port of Chrome. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Remerge to ToT Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 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/download/download_item_gtk.h"
6
7 #include "base/basictypes.h"
8 #include "base/callback.h"
9 #include "base/debug/trace_event.h"
10 #include "base/metrics/histogram.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/time/time.h"
14 #include "chrome/browser/browser_process.h"
15 #include "chrome/browser/chrome_notification_types.h"
16 #include "chrome/browser/download/chrome_download_manager_delegate.h"
17 #include "chrome/browser/download/download_item_model.h"
18 #include "chrome/browser/download/download_stats.h"
19 #include "chrome/browser/themes/theme_properties.h"
20 #include "chrome/browser/ui/browser.h"
21 #include "chrome/browser/ui/gtk/download/download_item_drag.h"
22 #include "chrome/browser/ui/gtk/download/download_shelf_context_menu_gtk.h"
23 #include "chrome/browser/ui/gtk/download/download_shelf_gtk.h"
24 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
25 #include "chrome/browser/ui/gtk/gtk_util.h"
26 #include "chrome/browser/ui/gtk/nine_box.h"
27 #include "content/public/browser/download_manager.h"
28 #include "content/public/browser/notification_source.h"
29 #include "grit/generated_resources.h"
30 #include "grit/theme_resources.h"
31 #include "third_party/skia/include/core/SkBitmap.h"
32 #include "ui/base/l10n/l10n_util.h"
33 #include "ui/base/resource/resource_bundle.h"
34 #include "ui/gfx/animation/slide_animation.h"
35 #include "ui/gfx/canvas_skia_paint.h"
36 #include "ui/gfx/color_utils.h"
37 #include "ui/gfx/image/image.h"
38 #include "ui/gfx/skia_utils_gtk.h"
39 #include "ui/gfx/text_elider.h"
40 #include "ui/gfx/text_utils.h"
41
42 namespace {
43
44 // The width of the |menu_button_| widget. It has to be at least as wide as the
45 // bitmap that we use to draw it, i.e. 16, but can be more.
46 const int kMenuButtonWidth = 16;
47
48 // Padding on left and right of items in dangerous download prompt.
49 const int kDangerousElementPadding = 3;
50
51 // Minimum width of the dangerous download message at which we will start
52 // wrapping.
53 const int kDangerousTextWidth = 200;
54
55 // Amount of space we allot to showing the filename. If the filename is too wide
56 // it will be elided.
57 const int kTextWidth = 140;
58
59 // We only cap the size of the tooltip so we don't crash.
60 const int kTooltipMaxWidth = 1000;
61
62 // The minimum width we will ever draw the download item. Used as a lower bound
63 // during animation. This number comes from the width of the images used to
64 // make the download item.
65 const int kMinDownloadItemWidth = DownloadShelf::kSmallProgressIconSize;
66
67 // New download item animation speed in milliseconds.
68 const int kNewItemAnimationDurationMs = 800;
69
70 // How long the 'download complete/interrupted' animation should last for.
71 const int kCompleteAnimationDurationMs = 2500;
72
73 // Height of the body.
74 const int kBodyHeight = DownloadShelf::kSmallProgressIconSize;
75
76 // Width of the body area of the download item.
77 // TODO(estade): get rid of the fudge factor. http://crbug.com/18692
78 const int kBodyWidth = kTextWidth + 50 + DownloadShelf::kSmallProgressIconSize;
79
80 // The font size of the text, and that size rounded down to the nearest integer
81 // for the size of the arrow in GTK theme mode.
82 const double kTextSize = 13.4; // 13.4px == 10pt @ 96dpi
83
84 // Darken light-on-dark download status text by 20% before drawing, thus
85 // creating a "muted" version of title text for both dark-on-light and
86 // light-on-dark themes.
87 static const double kDownloadItemLuminanceMod = 0.8;
88
89 // How long we keep the item disabled after the user clicked it to open the
90 // downloaded item.
91 static const int kDisabledOnOpenDurationMs = 3000;
92
93 } // namespace
94
95 NineBox* DownloadItemGtk::body_nine_box_normal_ = NULL;
96 NineBox* DownloadItemGtk::body_nine_box_prelight_ = NULL;
97 NineBox* DownloadItemGtk::body_nine_box_active_ = NULL;
98
99 NineBox* DownloadItemGtk::menu_nine_box_normal_ = NULL;
100 NineBox* DownloadItemGtk::menu_nine_box_prelight_ = NULL;
101 NineBox* DownloadItemGtk::menu_nine_box_active_ = NULL;
102
103 NineBox* DownloadItemGtk::dangerous_nine_box_ = NULL;
104
105 using content::DownloadItem;
106
107 DownloadItemGtk::DownloadItemGtk(DownloadShelfGtk* parent_shelf,
108 DownloadItem* download_item)
109 : parent_shelf_(parent_shelf),
110 arrow_(NULL),
111 menu_showing_(false),
112 theme_service_(
113 GtkThemeService::GetFrom(parent_shelf->browser()->profile())),
114 progress_angle_(DownloadShelf::kStartAngleDegrees),
115 download_model_(download_item),
116 dangerous_prompt_(NULL),
117 dangerous_label_(NULL),
118 complete_animation_(this),
119 icon_small_(NULL),
120 icon_large_(NULL),
121 creation_time_(base::Time::Now()),
122 download_complete_(false),
123 disabled_while_opening_(false),
124 weak_ptr_factory_(this) {
125 LoadIcon();
126
127 body_.Own(gtk_button_new());
128 gtk_widget_set_app_paintable(body_.get(), TRUE);
129 UpdateTooltip();
130
131 g_signal_connect(body_.get(), "expose-event",
132 G_CALLBACK(OnExposeThunk), this);
133 g_signal_connect(body_.get(), "clicked",
134 G_CALLBACK(OnClickThunk), this);
135 g_signal_connect(body_.get(), "button-press-event",
136 G_CALLBACK(OnButtonPressThunk), this);
137 gtk_widget_set_can_focus(body_.get(), FALSE);
138 // Remove internal padding on the button.
139 GtkRcStyle* no_padding_style = gtk_rc_style_new();
140 no_padding_style->xthickness = 0;
141 no_padding_style->ythickness = 0;
142 gtk_widget_modify_style(body_.get(), no_padding_style);
143 g_object_unref(no_padding_style);
144
145 name_label_ = gtk_label_new(NULL);
146 // Left align and vertically center the labels.
147 gtk_misc_set_alignment(GTK_MISC(name_label_), 0, 0.5);
148 // Until we switch to vector graphics, force the font size.
149 gtk_util::ForceFontSizePixels(name_label_, kTextSize);
150
151 UpdateNameLabel();
152
153 status_label_ = NULL;
154
155 // Stack the labels on top of one another.
156 text_stack_ = gtk_vbox_new(FALSE, 0);
157 g_signal_connect(text_stack_, "destroy",
158 G_CALLBACK(gtk_widget_destroyed), &text_stack_);
159 gtk_box_pack_start(GTK_BOX(text_stack_), name_label_, TRUE, TRUE, 0);
160
161 // We use a GtkFixed because we don't want it to have its own window.
162 // This choice of widget is not critically important though.
163 progress_area_.Own(gtk_fixed_new());
164 gtk_widget_set_size_request(progress_area_.get(),
165 DownloadShelf::kSmallProgressIconSize,
166 DownloadShelf::kSmallProgressIconSize);
167 gtk_widget_set_app_paintable(progress_area_.get(), TRUE);
168 g_signal_connect(progress_area_.get(), "expose-event",
169 G_CALLBACK(OnProgressAreaExposeThunk), this);
170
171 // Put the download progress icon on the left of the labels.
172 GtkWidget* body_hbox = gtk_hbox_new(FALSE, 0);
173 gtk_container_add(GTK_CONTAINER(body_.get()), body_hbox);
174 gtk_box_pack_start(GTK_BOX(body_hbox), progress_area_.get(), FALSE, FALSE, 0);
175 gtk_box_pack_start(GTK_BOX(body_hbox), text_stack_, TRUE, TRUE, 0);
176
177 menu_button_ = gtk_button_new();
178 gtk_widget_set_app_paintable(menu_button_, TRUE);
179 gtk_widget_set_can_focus(menu_button_, FALSE);
180 g_signal_connect(menu_button_, "expose-event",
181 G_CALLBACK(OnExposeThunk), this);
182 g_signal_connect(menu_button_, "button-press-event",
183 G_CALLBACK(OnMenuButtonPressEventThunk), this);
184 g_object_set_data(G_OBJECT(menu_button_), "left-align-popup",
185 reinterpret_cast<void*>(true));
186
187 GtkWidget* shelf_hbox = parent_shelf->GetHBox();
188 hbox_.Own(gtk_hbox_new(FALSE, 0));
189 g_signal_connect(hbox_.get(), "expose-event",
190 G_CALLBACK(OnHboxExposeThunk), this);
191 gtk_box_pack_start(GTK_BOX(hbox_.get()), body_.get(), FALSE, FALSE, 0);
192 gtk_box_pack_start(GTK_BOX(hbox_.get()), menu_button_, FALSE, FALSE, 0);
193 gtk_box_pack_start(GTK_BOX(shelf_hbox), hbox_.get(), FALSE, FALSE, 0);
194 // Insert as the leftmost item.
195 gtk_box_reorder_child(GTK_BOX(shelf_hbox), hbox_.get(), 0);
196
197 download()->AddObserver(this);
198
199 new_item_animation_.reset(new gfx::SlideAnimation(this));
200 new_item_animation_->SetSlideDuration(kNewItemAnimationDurationMs);
201 gtk_widget_show_all(hbox_.get());
202
203 if (download_model_.IsDangerous()) {
204 RecordDangerousDownloadWarningShown(download()->GetDangerType());
205
206 // Hide the download item components for now.
207 gtk_widget_set_no_show_all(body_.get(), TRUE);
208 gtk_widget_set_no_show_all(menu_button_, TRUE);
209 gtk_widget_hide(body_.get());
210 gtk_widget_hide(menu_button_);
211
212 // Create an hbox to hold it all.
213 dangerous_hbox_.Own(gtk_hbox_new(FALSE, kDangerousElementPadding));
214
215 // Add padding at the beginning and end. The hbox will add padding between
216 // the empty labels and the other elements.
217 GtkWidget* empty_label_a = gtk_label_new(NULL);
218 GtkWidget* empty_label_b = gtk_label_new(NULL);
219 gtk_box_pack_start(GTK_BOX(dangerous_hbox_.get()), empty_label_a,
220 FALSE, FALSE, 0);
221 gtk_box_pack_end(GTK_BOX(dangerous_hbox_.get()), empty_label_b,
222 FALSE, FALSE, 0);
223
224 // Create the warning icon.
225 dangerous_image_ = gtk_image_new();
226 gtk_box_pack_start(GTK_BOX(dangerous_hbox_.get()), dangerous_image_,
227 FALSE, FALSE, 0);
228
229 dangerous_label_ = gtk_label_new(NULL);
230 // We pass TRUE, TRUE so that the label will condense to less than its
231 // request when the animation is going on.
232 gtk_box_pack_start(GTK_BOX(dangerous_hbox_.get()), dangerous_label_,
233 TRUE, TRUE, 0);
234
235 // Create the nevermind button.
236 GtkWidget* dangerous_decline = gtk_button_new_with_label(
237 l10n_util::GetStringUTF8(IDS_DISCARD_DOWNLOAD).c_str());
238 g_signal_connect(dangerous_decline, "clicked",
239 G_CALLBACK(OnDangerousDeclineThunk), this);
240 gtk_util::CenterWidgetInHBox(dangerous_hbox_.get(), dangerous_decline,
241 false, 0);
242
243 // Create the ok button, if this is the kind that can be bypassed.
244 if (!download_model_.IsMalicious()) {
245 GtkWidget* dangerous_accept = gtk_button_new_with_label(
246 base::UTF16ToUTF8(
247 download_model_.GetWarningConfirmButtonText()).c_str());
248 g_signal_connect(dangerous_accept, "clicked",
249 G_CALLBACK(OnDangerousAcceptThunk), this);
250 gtk_util::CenterWidgetInHBox(
251 dangerous_hbox_.get(), dangerous_accept, false, 0);
252 }
253
254 // Put it in an alignment so that padding will be added on the left and
255 // right.
256 dangerous_prompt_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
257 gtk_alignment_set_padding(GTK_ALIGNMENT(dangerous_prompt_),
258 0, 0, kDangerousElementPadding, kDangerousElementPadding);
259 gtk_container_add(GTK_CONTAINER(dangerous_prompt_), dangerous_hbox_.get());
260 gtk_box_pack_start(GTK_BOX(hbox_.get()), dangerous_prompt_, FALSE, FALSE,
261 0);
262 gtk_widget_set_app_paintable(dangerous_prompt_, TRUE);
263 gtk_widget_set_redraw_on_allocate(dangerous_prompt_, TRUE);
264 g_signal_connect(dangerous_prompt_, "expose-event",
265 G_CALLBACK(OnDangerousPromptExposeThunk), this);
266 gtk_widget_show_all(dangerous_prompt_);
267 }
268
269 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
270 content::Source<ThemeService>(theme_service_));
271 theme_service_->InitThemesFor(this);
272
273 // Set the initial width of the widget to be animated.
274 if (download_model_.IsDangerous()) {
275 gtk_widget_set_size_request(dangerous_hbox_.get(),
276 dangerous_hbox_start_width_, -1);
277 } else {
278 gtk_widget_set_size_request(body_.get(), kMinDownloadItemWidth, -1);
279 }
280
281 new_item_animation_->Show();
282
283 complete_animation_.SetTweenType(gfx::Tween::LINEAR);
284 complete_animation_.SetSlideDuration(kCompleteAnimationDurationMs);
285
286 // Update the status text and animation state.
287 OnDownloadUpdated(download());
288 }
289
290 DownloadItemGtk::~DownloadItemGtk() {
291 // First close the menu and then destroy the GtkWidgets. Bug#97724
292 if (menu_.get())
293 menu_.reset();
294
295 StopDownloadProgress();
296 download()->RemoveObserver(this);
297
298 // We may free some shelf space for showing more download items.
299 parent_shelf_->MaybeShowMoreDownloadItems();
300
301 hbox_.Destroy();
302 progress_area_.Destroy();
303 body_.Destroy();
304 dangerous_hbox_.Destroy();
305
306 // Make sure this widget has been destroyed and the pointer we hold to it
307 // NULLed.
308 DCHECK(!status_label_);
309 }
310
311 void DownloadItemGtk::OnDownloadUpdated(DownloadItem* download_item) {
312 DCHECK_EQ(download(), download_item);
313
314 if (dangerous_prompt_ != NULL && !download_model_.IsDangerous()) {
315 // We have been approved.
316 gtk_widget_set_no_show_all(body_.get(), FALSE);
317 gtk_widget_set_no_show_all(menu_button_, FALSE);
318 gtk_widget_show_all(hbox_.get());
319 gtk_widget_destroy(dangerous_prompt_);
320 gtk_widget_set_size_request(body_.get(), kBodyWidth, -1);
321 dangerous_prompt_ = NULL;
322
323 // We may free some shelf space for showing more download items.
324 parent_shelf_->MaybeShowMoreDownloadItems();
325 }
326
327 if (download()->GetTargetFilePath() != icon_filepath_) {
328 LoadIcon();
329 UpdateTooltip();
330 }
331
332 switch (download()->GetState()) {
333 case DownloadItem::CANCELLED:
334 StopDownloadProgress();
335 gtk_widget_queue_draw(progress_area_.get());
336 break;
337 case DownloadItem::INTERRUPTED:
338 StopDownloadProgress();
339 UpdateTooltip();
340
341 complete_animation_.Show();
342 break;
343 case DownloadItem::COMPLETE:
344 // ShouldRemoveFromShelfWhenComplete() may change after the download's
345 // initial transition to COMPLETE, so we check it before the idemopotency
346 // shield below.
347 if (download_model_.ShouldRemoveFromShelfWhenComplete()) {
348 parent_shelf_->RemoveDownloadItem(this); // This will delete us!
349 return;
350 }
351
352 // We've already handled the completion specific actions; skip
353 // doing the non-idempotent ones again.
354 if (download_complete_)
355 break;
356
357 StopDownloadProgress();
358
359 // Set up the widget as a drag source.
360 DownloadItemDrag::SetSource(body_.get(), download(), icon_large_);
361
362 complete_animation_.Show();
363 download_complete_ = true;
364 break;
365 case DownloadItem::IN_PROGRESS:
366 download()->IsPaused() ?
367 StopDownloadProgress() : StartDownloadProgress();
368 break;
369 default:
370 NOTREACHED();
371 }
372
373 status_text_ = base::UTF16ToUTF8(download_model_.GetStatusText());
374 UpdateStatusLabel(status_text_);
375 }
376
377 void DownloadItemGtk::OnDownloadDestroyed(DownloadItem* download_item) {
378 DCHECK_EQ(download(), download_item);
379 parent_shelf_->RemoveDownloadItem(this);
380 // This will delete us!
381 }
382
383 void DownloadItemGtk::AnimationProgressed(const gfx::Animation* animation) {
384 if (animation == &complete_animation_) {
385 gtk_widget_queue_draw(progress_area_.get());
386 } else {
387 DCHECK(animation == new_item_animation_.get());
388 if (download_model_.IsDangerous()) {
389 int progress = static_cast<int>((dangerous_hbox_full_width_ -
390 dangerous_hbox_start_width_) *
391 animation->GetCurrentValue());
392 int showing_width = dangerous_hbox_start_width_ + progress;
393 gtk_widget_set_size_request(dangerous_hbox_.get(), showing_width, -1);
394 } else {
395 int showing_width = std::max(kMinDownloadItemWidth,
396 static_cast<int>(kBodyWidth * animation->GetCurrentValue()));
397 gtk_widget_set_size_request(body_.get(), showing_width, -1);
398 }
399 }
400 }
401
402 void DownloadItemGtk::Observe(int type,
403 const content::NotificationSource& source,
404 const content::NotificationDetails& details) {
405 if (type == chrome::NOTIFICATION_BROWSER_THEME_CHANGED) {
406 // Our GtkArrow is only visible in gtk mode. Otherwise, we let the custom
407 // rendering code do whatever it wants.
408 if (theme_service_->UsingNativeTheme()) {
409 if (!arrow_) {
410 arrow_ = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
411 gtk_widget_set_size_request(arrow_,
412 static_cast<int>(kTextSize),
413 static_cast<int>(kTextSize));
414 gtk_container_add(GTK_CONTAINER(menu_button_), arrow_);
415 }
416
417 gtk_widget_set_size_request(menu_button_, -1, -1);
418 gtk_widget_show(arrow_);
419 } else {
420 InitNineBoxes();
421
422 gtk_widget_set_size_request(menu_button_, kMenuButtonWidth, 0);
423
424 if (arrow_)
425 gtk_widget_hide(arrow_);
426 }
427
428 UpdateNameLabel();
429 UpdateStatusLabel(status_text_);
430 UpdateDangerWarning();
431 }
432 }
433
434 // Download progress animation functions.
435
436 void DownloadItemGtk::UpdateDownloadProgress() {
437 progress_angle_ =
438 (progress_angle_ + DownloadShelf::kUnknownIncrementDegrees) %
439 DownloadShelf::kMaxDegrees;
440 gtk_widget_queue_draw(progress_area_.get());
441 }
442
443 void DownloadItemGtk::StartDownloadProgress() {
444 if (progress_timer_.IsRunning())
445 return;
446 progress_timer_.Start(FROM_HERE,
447 base::TimeDelta::FromMilliseconds(DownloadShelf::kProgressRateMs), this,
448 &DownloadItemGtk::UpdateDownloadProgress);
449 }
450
451 void DownloadItemGtk::StopDownloadProgress() {
452 progress_timer_.Stop();
453 }
454
455 // Icon loading functions.
456
457 void DownloadItemGtk::OnLoadSmallIconComplete(gfx::Image* image) {
458 icon_small_ = image;
459 gtk_widget_queue_draw(progress_area_.get());
460 }
461
462 void DownloadItemGtk::OnLoadLargeIconComplete(gfx::Image* image) {
463 icon_large_ = image;
464 if (download()->GetState() == DownloadItem::COMPLETE)
465 DownloadItemDrag::SetSource(body_.get(), download(), icon_large_);
466 // Else, the download will be made draggable once an OnDownloadUpdated()
467 // notification is received with a download in COMPLETE state.
468 }
469
470 void DownloadItemGtk::LoadIcon() {
471 cancelable_task_tracker_.TryCancelAll();
472 IconManager* im = g_browser_process->icon_manager();
473 icon_filepath_ = download()->GetTargetFilePath();
474 im->LoadIcon(icon_filepath_,
475 IconLoader::SMALL,
476 base::Bind(&DownloadItemGtk::OnLoadSmallIconComplete,
477 base::Unretained(this)),
478 &cancelable_task_tracker_);
479 im->LoadIcon(icon_filepath_,
480 IconLoader::LARGE,
481 base::Bind(&DownloadItemGtk::OnLoadLargeIconComplete,
482 base::Unretained(this)),
483 &cancelable_task_tracker_);
484 }
485
486 void DownloadItemGtk::UpdateTooltip() {
487 const gfx::FontList& font_list =
488 ui::ResourceBundle::GetSharedInstance().GetFontList(
489 ui::ResourceBundle::BaseFont);
490 base::string16 tooltip_text =
491 download_model_.GetTooltipText(font_list, kTooltipMaxWidth);
492 gtk_widget_set_tooltip_text(body_.get(),
493 base::UTF16ToUTF8(tooltip_text).c_str());
494 }
495
496 void DownloadItemGtk::UpdateNameLabel() {
497 const gfx::FontList& font_list =
498 ui::ResourceBundle::GetSharedInstance().GetFontList(
499 ui::ResourceBundle::BaseFont);
500 base::string16 filename;
501 if (!disabled_while_opening_) {
502 filename = gfx::ElideFilename(
503 download()->GetFileNameToReportUser(), font_list, kTextWidth);
504 } else {
505 // First, Calculate the download status opening string width.
506 base::string16 status_string =
507 l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING,
508 base::string16());
509 int status_string_width = gfx::GetStringWidth(status_string, font_list);
510 // Then, elide the file name.
511 base::string16 filename_string =
512 gfx::ElideFilename(download()->GetFileNameToReportUser(), font_list,
513 kTextWidth - status_string_width);
514 // Last, concat the whole string.
515 filename = l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING,
516 filename_string);
517 }
518
519 GdkColor color = theme_service_->GetGdkColor(
520 ThemeProperties::COLOR_BOOKMARK_TEXT);
521 gtk_util::SetLabelColor(
522 name_label_,
523 theme_service_->UsingNativeTheme() ? NULL : &color);
524 gtk_label_set_text(GTK_LABEL(name_label_),
525 base::UTF16ToUTF8(filename).c_str());
526 }
527
528 void DownloadItemGtk::UpdateStatusLabel(const std::string& status_text) {
529 if (!text_stack_) {
530 // At least our container has been destroyed, which means that
531 // this item is on the way to being destroyed; don't do anything.
532 return;
533 }
534
535 // If |status_text| is empty, only |name_label_| is displayed at the
536 // vertical center of |text_stack_|. Otherwise, |name_label_| is displayed
537 // on the upper half of |text_stack_| and |status_label_| is displayed
538 // on the lower half of |text_stack_|.
539 if (status_text.empty()) {
540 if (status_label_)
541 gtk_widget_destroy(status_label_);
542 return;
543 }
544 if (!status_label_) {
545 status_label_ = gtk_label_new(NULL);
546 g_signal_connect(status_label_, "destroy",
547 G_CALLBACK(gtk_widget_destroyed), &status_label_);
548 // Left align and vertically center the labels.
549 gtk_misc_set_alignment(GTK_MISC(status_label_), 0, 0.5);
550 // Until we switch to vector graphics, force the font size.
551 gtk_util::ForceFontSizePixels(status_label_, kTextSize);
552
553 gtk_box_pack_start(GTK_BOX(text_stack_), status_label_, FALSE, FALSE, 0);
554 gtk_widget_show_all(status_label_);
555 }
556
557 GdkColor text_color;
558 if (!theme_service_->UsingNativeTheme()) {
559 SkColor color = theme_service_->GetColor(
560 ThemeProperties::COLOR_BOOKMARK_TEXT);
561 if (color_utils::RelativeLuminance(color) > 0.5) {
562 color = SkColorSetRGB(
563 static_cast<int>(kDownloadItemLuminanceMod *
564 SkColorGetR(color)),
565 static_cast<int>(kDownloadItemLuminanceMod *
566 SkColorGetG(color)),
567 static_cast<int>(kDownloadItemLuminanceMod *
568 SkColorGetB(color)));
569 }
570
571 // Lighten the color by blending it with the download item body color. These
572 // values are taken from IDR_DOWNLOAD_BUTTON.
573 SkColor blend_color = SkColorSetRGB(241, 245, 250);
574 text_color = gfx::SkColorToGdkColor(
575 color_utils::AlphaBlend(blend_color, color, 77));
576 }
577
578 gtk_util::SetLabelColor(
579 status_label_,
580 theme_service_->UsingNativeTheme() ? NULL : &text_color);
581 gtk_label_set_text(GTK_LABEL(status_label_), status_text.c_str());
582 }
583
584 void DownloadItemGtk::UpdateDangerWarning() {
585 if (dangerous_prompt_) {
586 UpdateDangerIcon();
587
588 // We create |dangerous_warning| as a wide string so we can more easily
589 // calculate its length in characters.
590 const gfx::FontList& font_list =
591 ui::ResourceBundle::GetSharedInstance().GetFontList(
592 ui::ResourceBundle::BaseFont);
593 base::string16 dangerous_warning =
594 download_model_.GetWarningText(font_list, kTextWidth);
595 if (theme_service_->UsingNativeTheme()) {
596 gtk_util::SetLabelColor(dangerous_label_, NULL);
597 } else {
598 GdkColor color = theme_service_->GetGdkColor(
599 ThemeProperties::COLOR_BOOKMARK_TEXT);
600 gtk_util::SetLabelColor(dangerous_label_, &color);
601 }
602
603 gtk_label_set_text(GTK_LABEL(dangerous_label_),
604 base::UTF16ToUTF8(dangerous_warning).c_str());
605
606 // Until we switch to vector graphics, force the font size.
607 gtk_util::ForceFontSizePixels(dangerous_label_, kTextSize);
608
609 gtk_widget_set_size_request(dangerous_label_, -1, -1);
610 gtk_label_set_line_wrap(GTK_LABEL(dangerous_label_), FALSE);
611
612 GtkRequisition req;
613 gtk_widget_size_request(dangerous_label_, &req);
614
615 gint label_width = req.width;
616 if (req.width > kDangerousTextWidth) {
617 // If the label width exceeds kDangerousTextWidth, we try line wrapping
618 // starting at 60% and increasing in 10% intervals of the full width until
619 // we have a label that fits within the height constraints of the shelf.
620 gtk_label_set_line_wrap(GTK_LABEL(dangerous_label_), TRUE);
621 int full_width = req.width;
622 int tenths = 6;
623 do {
624 label_width = full_width * tenths / 10;
625 gtk_widget_set_size_request(dangerous_label_, label_width, -1);
626 gtk_widget_size_request(dangerous_label_, &req);
627 } while (req.height > kBodyHeight && ++tenths <= 10);
628 DCHECK(req.height <= kBodyHeight);
629 }
630
631 // The width will depend on the text. We must do this each time we possibly
632 // change the label above.
633 gtk_widget_size_request(dangerous_hbox_.get(), &req);
634 dangerous_hbox_full_width_ = req.width;
635 dangerous_hbox_start_width_ = dangerous_hbox_full_width_ - label_width;
636 }
637 }
638
639 void DownloadItemGtk::UpdateDangerIcon() {
640 if (theme_service_->UsingNativeTheme()) {
641 const char* stock = download_model_.MightBeMalicious() ?
642 GTK_STOCK_DIALOG_ERROR : GTK_STOCK_DIALOG_WARNING;
643 gtk_image_set_from_stock(
644 GTK_IMAGE(dangerous_image_), stock, GTK_ICON_SIZE_SMALL_TOOLBAR);
645 } else {
646 // Set the warning icon.
647 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
648 int pixbuf_id =
649 download_model_.MightBeMalicious() ? IDR_SAFEBROWSING_WARNING
650 : IDR_WARNING;
651 gtk_image_set_from_pixbuf(GTK_IMAGE(dangerous_image_),
652 rb.GetNativeImageNamed(pixbuf_id).ToGdkPixbuf());
653 }
654 }
655
656 // static
657 void DownloadItemGtk::InitNineBoxes() {
658 if (body_nine_box_normal_)
659 return;
660
661 body_nine_box_normal_ = new NineBox(
662 IDR_DOWNLOAD_BUTTON_LEFT_TOP,
663 IDR_DOWNLOAD_BUTTON_CENTER_TOP,
664 IDR_DOWNLOAD_BUTTON_RIGHT_TOP,
665 IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE,
666 IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE,
667 IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE,
668 IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM,
669 IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM,
670 IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM);
671
672 body_nine_box_prelight_ = new NineBox(
673 IDR_DOWNLOAD_BUTTON_LEFT_TOP_H,
674 IDR_DOWNLOAD_BUTTON_CENTER_TOP_H,
675 IDR_DOWNLOAD_BUTTON_RIGHT_TOP_H,
676 IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_H,
677 IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_H,
678 IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_H,
679 IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_H,
680 IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_H,
681 IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_H);
682
683 body_nine_box_active_ = new NineBox(
684 IDR_DOWNLOAD_BUTTON_LEFT_TOP_P,
685 IDR_DOWNLOAD_BUTTON_CENTER_TOP_P,
686 IDR_DOWNLOAD_BUTTON_RIGHT_TOP_P,
687 IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_P,
688 IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_P,
689 IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_P,
690 IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_P,
691 IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_P,
692 IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_P);
693
694 menu_nine_box_normal_ = new NineBox(
695 IDR_DOWNLOAD_BUTTON_MENU_TOP, 0, 0,
696 IDR_DOWNLOAD_BUTTON_MENU_MIDDLE, 0, 0,
697 IDR_DOWNLOAD_BUTTON_MENU_BOTTOM, 0, 0);
698
699 menu_nine_box_prelight_ = new NineBox(
700 IDR_DOWNLOAD_BUTTON_MENU_TOP_H, 0, 0,
701 IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_H, 0, 0,
702 IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_H, 0, 0);
703
704 menu_nine_box_active_ = new NineBox(
705 IDR_DOWNLOAD_BUTTON_MENU_TOP_P, 0, 0,
706 IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_P, 0, 0,
707 IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_P, 0, 0);
708
709 dangerous_nine_box_ = new NineBox(
710 IDR_DOWNLOAD_BUTTON_LEFT_TOP,
711 IDR_DOWNLOAD_BUTTON_CENTER_TOP,
712 IDR_DOWNLOAD_BUTTON_RIGHT_TOP_NO_DD,
713 IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE,
714 IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE,
715 IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_NO_DD,
716 IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM,
717 IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM,
718 IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_NO_DD);
719 }
720
721 gboolean DownloadItemGtk::OnHboxExpose(GtkWidget* widget, GdkEventExpose* e) {
722 TRACE_EVENT0("ui::gtk", "DownloadItemGtk::OnHboxExpose");
723 if (theme_service_->UsingNativeTheme()) {
724 GtkAllocation allocation;
725 gtk_widget_get_allocation(widget, &allocation);
726 int border_width = gtk_container_get_border_width(GTK_CONTAINER(widget));
727 int x = allocation.x + border_width;
728 int y = allocation.y + border_width;
729 int width = allocation.width - border_width * 2;
730 int height = allocation.height - border_width * 2;
731
732 if (download_model_.IsDangerous()) {
733 // Draw a simple frame around the area when we're displaying the warning.
734 gtk_paint_shadow(gtk_widget_get_style(widget),
735 gtk_widget_get_window(widget),
736 gtk_widget_get_state(widget),
737 static_cast<GtkShadowType>(GTK_SHADOW_OUT),
738 &e->area, widget, "frame",
739 x, y, width, height);
740 } else {
741 // Manually draw the GTK button border around the download item. We draw
742 // the left part of the button (the file), a divider, and then the right
743 // part of the button (the menu). We can't draw a button on top of each
744 // other (*cough*Clearlooks*cough*) so instead, to draw the left part of
745 // the button, we instruct GTK to draw the entire button...with a
746 // doctored clip rectangle to the left part of the button sans
747 // separator. We then repeat this for the right button.
748 GtkStyle* style = gtk_widget_get_style(body_.get());
749
750 GtkAllocation left_clip;
751 gtk_widget_get_allocation(body_.get(), &left_clip);
752
753 GtkAllocation right_clip;
754 gtk_widget_get_allocation(menu_button_, &right_clip);
755
756 GtkShadowType body_shadow =
757 GTK_BUTTON(body_.get())->depressed ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
758 gtk_paint_box(style,
759 gtk_widget_get_window(widget),
760 gtk_widget_get_state(body_.get()),
761 body_shadow,
762 &left_clip, widget, "button",
763 x, y, width, height);
764
765 GtkShadowType menu_shadow =
766 GTK_BUTTON(menu_button_)->depressed ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
767 gtk_paint_box(style,
768 gtk_widget_get_window(widget),
769 gtk_widget_get_state(menu_button_),
770 menu_shadow,
771 &right_clip, widget, "button",
772 x, y, width, height);
773
774 // Doing the math to reverse engineer where we should be drawing our line
775 // is hard and relies on copying GTK internals, so instead steal the
776 // allocation of the gtk arrow which is close enough (and will error on
777 // the conservative side).
778 GtkAllocation arrow_allocation;
779 gtk_widget_get_allocation(arrow_, &arrow_allocation);
780 gtk_paint_vline(style,
781 gtk_widget_get_window(widget),
782 gtk_widget_get_state(widget),
783 &e->area, widget, "button",
784 arrow_allocation.y,
785 arrow_allocation.y + arrow_allocation.height,
786 left_clip.x + left_clip.width);
787 }
788 }
789 return FALSE;
790 }
791
792 gboolean DownloadItemGtk::OnExpose(GtkWidget* widget, GdkEventExpose* e) {
793 TRACE_EVENT0("ui::gtk", "DownloadItemGtk::OnExpose");
794 if (!theme_service_->UsingNativeTheme()) {
795 bool is_body = widget == body_.get();
796
797 NineBox* nine_box = NULL;
798 // If true, this widget is |body_|, otherwise it is |menu_button_|.
799 if (gtk_widget_get_state(widget) == GTK_STATE_PRELIGHT)
800 nine_box = is_body ? body_nine_box_prelight_ : menu_nine_box_prelight_;
801 else if (gtk_widget_get_state(widget) == GTK_STATE_ACTIVE)
802 nine_box = is_body ? body_nine_box_active_ : menu_nine_box_active_;
803 else
804 nine_box = is_body ? body_nine_box_normal_ : menu_nine_box_normal_;
805
806 // When the button is showing, we want to draw it as active. We have to do
807 // this explicitly because the button's state will be NORMAL while the menu
808 // has focus.
809 if (!is_body && menu_showing_)
810 nine_box = menu_nine_box_active_;
811
812 nine_box->RenderToWidget(widget);
813 }
814
815 GtkWidget* child = gtk_bin_get_child(GTK_BIN(widget));
816 if (child)
817 gtk_container_propagate_expose(GTK_CONTAINER(widget), child, e);
818
819 return TRUE;
820 }
821
822 void DownloadItemGtk::ReenableHbox() {
823 gtk_widget_set_sensitive(hbox_.get(), true);
824 disabled_while_opening_ = false;
825 UpdateNameLabel();
826 }
827
828 void DownloadItemGtk::OnDownloadOpened(DownloadItem* download) {
829 disabled_while_opening_ = true;
830 gtk_widget_set_sensitive(hbox_.get(), false);
831 base::MessageLoop::current()->PostDelayedTask(
832 FROM_HERE,
833 base::Bind(&DownloadItemGtk::ReenableHbox,
834 weak_ptr_factory_.GetWeakPtr()),
835 base::TimeDelta::FromMilliseconds(kDisabledOnOpenDurationMs));
836 UpdateNameLabel();
837 parent_shelf_->ItemOpened();
838 }
839
840 void DownloadItemGtk::OnClick(GtkWidget* widget) {
841 UMA_HISTOGRAM_LONG_TIMES("clickjacking.open_download",
842 base::Time::Now() - creation_time_);
843 download()->OpenDownload();
844 }
845
846 gboolean DownloadItemGtk::OnButtonPress(GtkWidget* button,
847 GdkEventButton* event) {
848 if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
849 ShowPopupMenu(NULL, event);
850 return TRUE;
851 }
852 return FALSE;
853 }
854
855 gboolean DownloadItemGtk::OnProgressAreaExpose(GtkWidget* widget,
856 GdkEventExpose* event) {
857 TRACE_EVENT0("ui::gtk", "DownloadItemGtk::OnProgressAreaExpose");
858
859 GtkAllocation allocation;
860 gtk_widget_get_allocation(widget, &allocation);
861
862 // Create a transparent canvas.
863 gfx::CanvasSkiaPaint canvas(event, false);
864 DownloadItem::DownloadState state = download()->GetState();
865 if (complete_animation_.is_animating()) {
866 if (state == DownloadItem::INTERRUPTED) {
867 DownloadShelf::PaintDownloadInterrupted(
868 &canvas,
869 allocation.x,
870 allocation.y,
871 complete_animation_.GetCurrentValue(),
872 DownloadShelf::SMALL);
873 } else {
874 DownloadShelf::PaintDownloadComplete(
875 &canvas,
876 allocation.x,
877 allocation.y,
878 complete_animation_.GetCurrentValue(),
879 DownloadShelf::SMALL);
880 }
881 } else if (state == DownloadItem::IN_PROGRESS) {
882 DownloadShelf::PaintDownloadProgress(&canvas,
883 allocation.x,
884 allocation.y,
885 progress_angle_,
886 download_model_.PercentComplete(),
887 DownloadShelf::SMALL);
888 }
889
890 // |icon_small_| may be NULL if it is still loading. If the file is an
891 // unrecognized type then we will get back a generic system icon. Hence
892 // there is no need to use the chromium-specific default download item icon.
893 if (icon_small_) {
894 const int offset = DownloadShelf::kSmallProgressIconOffset;
895 canvas.DrawImageInt(icon_small_->AsImageSkia(),
896 allocation.x + offset, allocation.y + offset);
897 }
898
899 return TRUE;
900 }
901
902 gboolean DownloadItemGtk::OnMenuButtonPressEvent(GtkWidget* button,
903 GdkEventButton* event) {
904 if (event->type == GDK_BUTTON_PRESS && event->button == 1) {
905 ShowPopupMenu(button, event);
906 menu_showing_ = true;
907 gtk_widget_queue_draw(button);
908 return TRUE;
909 }
910 return FALSE;
911 }
912
913 void DownloadItemGtk::ShowPopupMenu(GtkWidget* button,
914 GdkEventButton* event) {
915 // Stop any completion animation.
916 if (complete_animation_.is_animating())
917 complete_animation_.End();
918
919 if (!menu_.get()) {
920 menu_.reset(new DownloadShelfContextMenuGtk(this,
921 parent_shelf_->GetNavigator()));
922 }
923 menu_->Popup(button, event);
924 }
925
926 gboolean DownloadItemGtk::OnDangerousPromptExpose(GtkWidget* widget,
927 GdkEventExpose* event) {
928 TRACE_EVENT0("ui::gtk", "DownloadItemGtk::OnDangerousPromptExpose");
929 if (!theme_service_->UsingNativeTheme()) {
930 // The hbox renderer will take care of the border when in GTK mode.
931 dangerous_nine_box_->RenderToWidget(widget);
932 }
933 return FALSE; // Continue propagation.
934 }
935
936 void DownloadItemGtk::OnDangerousAccept(GtkWidget* button) {
937 UMA_HISTOGRAM_LONG_TIMES("clickjacking.save_download",
938 base::Time::Now() - creation_time_);
939 download()->ValidateDangerousDownload();
940 }
941
942 void DownloadItemGtk::OnDangerousDecline(GtkWidget* button) {
943 UMA_HISTOGRAM_LONG_TIMES("clickjacking.discard_download",
944 base::Time::Now() - creation_time_);
945 download()->Remove();
946 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698