OLD | NEW |
| (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_shelf_gtk.h" | |
6 | |
7 #include <string> | |
8 | |
9 #include "base/bind.h" | |
10 #include "chrome/browser/chrome_notification_types.h" | |
11 #include "chrome/browser/download/download_item_model.h" | |
12 #include "chrome/browser/download/download_stats.h" | |
13 #include "chrome/browser/themes/theme_properties.h" | |
14 #include "chrome/browser/ui/browser.h" | |
15 #include "chrome/browser/ui/chrome_pages.h" | |
16 #include "chrome/browser/ui/gtk/browser_window_gtk.h" | |
17 #include "chrome/browser/ui/gtk/custom_button.h" | |
18 #include "chrome/browser/ui/gtk/download/download_item_gtk.h" | |
19 #include "chrome/browser/ui/gtk/gtk_chrome_link_button.h" | |
20 #include "chrome/browser/ui/gtk/gtk_chrome_shrinkable_hbox.h" | |
21 #include "chrome/browser/ui/gtk/gtk_theme_service.h" | |
22 #include "chrome/browser/ui/gtk/gtk_util.h" | |
23 #include "content/public/browser/download_item.h" | |
24 #include "content/public/browser/notification_source.h" | |
25 #include "content/public/browser/page_navigator.h" | |
26 #include "grit/generated_resources.h" | |
27 #include "grit/theme_resources.h" | |
28 #include "grit/ui_resources.h" | |
29 #include "third_party/skia/include/core/SkBitmap.h" | |
30 #include "ui/base/gtk/gtk_screen_util.h" | |
31 #include "ui/base/l10n/l10n_util.h" | |
32 #include "ui/base/resource/resource_bundle.h" | |
33 #include "ui/gfx/gtk_util.h" | |
34 #include "ui/gfx/image/image.h" | |
35 #include "ui/gfx/insets.h" | |
36 #include "ui/gfx/point.h" | |
37 #include "ui/gfx/rect.h" | |
38 | |
39 namespace { | |
40 | |
41 // The height of the download items. | |
42 const int kDownloadItemHeight = DownloadShelf::kSmallProgressIconSize; | |
43 | |
44 // Padding between the download widgets. | |
45 const int kDownloadItemPadding = 10; | |
46 | |
47 // Padding between the top/bottom of the download widgets and the edge of the | |
48 // shelf. | |
49 const int kTopBottomPadding = 4; | |
50 | |
51 // Padding between the left side of the shelf and the first download item. | |
52 const int kLeftPadding = 2; | |
53 | |
54 // Padding between the right side of the shelf and the close button. | |
55 const int kRightPadding = 10; | |
56 | |
57 // Speed of the shelf show/hide animation. | |
58 const int kShelfAnimationDurationMs = 120; | |
59 | |
60 // The time between when the user mouses out of the download shelf zone and | |
61 // when the shelf closes (when auto-close is enabled). | |
62 const int kAutoCloseDelayMs = 300; | |
63 | |
64 // The area to the top of the shelf that is considered part of its "zone". | |
65 const int kShelfAuraSize = 40; | |
66 | |
67 } // namespace | |
68 | |
69 using content::DownloadItem; | |
70 | |
71 DownloadShelfGtk::DownloadShelfGtk(Browser* browser, GtkWidget* parent) | |
72 : browser_(browser), | |
73 is_showing_(false), | |
74 theme_service_(GtkThemeService::GetFrom(browser->profile())), | |
75 close_on_mouse_out_(false), | |
76 mouse_in_shelf_(false), | |
77 weak_factory_(this) { | |
78 // Logically, the shelf is a vbox that contains two children: a one pixel | |
79 // tall event box, which serves as the top border, and an hbox, which holds | |
80 // the download items and other shelf widgets (close button, show-all- | |
81 // downloads link). | |
82 // To make things pretty, we have to add a few more widgets. To get padding | |
83 // right, we stick the hbox in an alignment. We put that alignment in an | |
84 // event box so we can color the background. | |
85 | |
86 // Create the top border. | |
87 top_border_ = gtk_event_box_new(); | |
88 gtk_widget_set_size_request(GTK_WIDGET(top_border_), 0, 1); | |
89 | |
90 // Create |items_hbox_|. We use GtkChromeShrinkableHBox, so that download | |
91 // items can be hid automatically when there is no enough space to show them. | |
92 items_hbox_.Own(gtk_chrome_shrinkable_hbox_new( | |
93 TRUE, FALSE, kDownloadItemPadding)); | |
94 // We want the download shelf to be horizontally shrinkable, so that the | |
95 // Chrome window can be resized freely even with many download items. | |
96 gtk_widget_set_size_request(items_hbox_.get(), 0, kDownloadItemHeight); | |
97 | |
98 // Create a hbox that holds |items_hbox_| and other shelf widgets. | |
99 GtkWidget* outer_hbox = gtk_hbox_new(FALSE, kDownloadItemPadding); | |
100 | |
101 // Pack the |items_hbox_| in the outer hbox. | |
102 gtk_box_pack_start(GTK_BOX(outer_hbox), items_hbox_.get(), TRUE, TRUE, 0); | |
103 | |
104 // Get the padding and background color for |outer_hbox| right. | |
105 GtkWidget* padding = gtk_alignment_new(0, 0, 1, 1); | |
106 // Subtract 1 from top spacing to account for top border. | |
107 gtk_alignment_set_padding(GTK_ALIGNMENT(padding), | |
108 kTopBottomPadding - 1, kTopBottomPadding, kLeftPadding, kRightPadding); | |
109 padding_bg_ = gtk_event_box_new(); | |
110 gtk_container_add(GTK_CONTAINER(padding_bg_), padding); | |
111 gtk_container_add(GTK_CONTAINER(padding), outer_hbox); | |
112 | |
113 GtkWidget* vbox = gtk_vbox_new(FALSE, 0); | |
114 gtk_box_pack_start(GTK_BOX(vbox), top_border_, FALSE, FALSE, 0); | |
115 gtk_box_pack_start(GTK_BOX(vbox), padding_bg_, FALSE, FALSE, 0); | |
116 | |
117 // Put the shelf in an event box so it gets its own window, which makes it | |
118 // easier to get z-ordering right. | |
119 shelf_.Own(gtk_event_box_new()); | |
120 gtk_container_add(GTK_CONTAINER(shelf_.get()), vbox); | |
121 | |
122 // Create and pack the close button. | |
123 close_button_.reset(CustomDrawButton::CloseButtonBar(theme_service_)); | |
124 gtk_util::CenterWidgetInHBox(outer_hbox, close_button_->widget(), true, 0); | |
125 g_signal_connect(close_button_->widget(), "clicked", | |
126 G_CALLBACK(OnButtonClickThunk), this); | |
127 | |
128 // Create the "Show all downloads..." link and connect to the click event. | |
129 link_button_ = theme_service_->BuildChromeLinkButton( | |
130 l10n_util::GetStringUTF8(IDS_SHOW_ALL_DOWNLOADS)); | |
131 g_signal_connect(link_button_, "clicked", | |
132 G_CALLBACK(OnButtonClickThunk), this); | |
133 gtk_util::SetButtonTriggersNavigation(link_button_); | |
134 // Until we switch to vector graphics, force the font size. | |
135 // 13.4px == 10pt @ 96dpi | |
136 gtk_util::ForceFontSizePixels(GTK_CHROME_LINK_BUTTON(link_button_)->label, | |
137 13.4); | |
138 | |
139 // Make the download arrow icon. | |
140 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
141 GtkWidget* download_image = gtk_image_new_from_pixbuf( | |
142 rb.GetNativeImageNamed(IDR_DOWNLOADS_FAVICON).ToGdkPixbuf()); | |
143 | |
144 // Pack the link and the icon in outer hbox. | |
145 gtk_util::CenterWidgetInHBox(outer_hbox, link_button_, true, 0); | |
146 gtk_util::CenterWidgetInHBox(outer_hbox, download_image, true, 0); | |
147 | |
148 slide_widget_.reset(new SlideAnimatorGtk(shelf_.get(), | |
149 SlideAnimatorGtk::UP, | |
150 kShelfAnimationDurationMs, | |
151 false, true, this)); | |
152 | |
153 theme_service_->InitThemesFor(this); | |
154 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED, | |
155 content::Source<ThemeService>(theme_service_)); | |
156 | |
157 gtk_widget_show_all(shelf_.get()); | |
158 | |
159 // Stick ourselves at the bottom of the parent browser. | |
160 gtk_box_pack_end(GTK_BOX(parent), slide_widget_->widget(), | |
161 FALSE, FALSE, 0); | |
162 // Make sure we are at the very end. | |
163 gtk_box_reorder_child(GTK_BOX(parent), slide_widget_->widget(), 0); | |
164 } | |
165 | |
166 DownloadShelfGtk::~DownloadShelfGtk() { | |
167 for (std::vector<DownloadItemGtk*>::iterator iter = download_items_.begin(); | |
168 iter != download_items_.end(); ++iter) { | |
169 delete *iter; | |
170 } | |
171 | |
172 shelf_.Destroy(); | |
173 items_hbox_.Destroy(); | |
174 | |
175 // Make sure we're no longer an observer of the message loop. | |
176 SetCloseOnMouseOut(false); | |
177 } | |
178 | |
179 content::PageNavigator* DownloadShelfGtk::GetNavigator() { | |
180 return browser_; | |
181 } | |
182 | |
183 void DownloadShelfGtk::DoAddDownload(DownloadItem* download) { | |
184 download_items_.push_back(new DownloadItemGtk(this, download)); | |
185 } | |
186 | |
187 bool DownloadShelfGtk::IsShowing() const { | |
188 return slide_widget_->IsShowing(); | |
189 } | |
190 | |
191 bool DownloadShelfGtk::IsClosing() const { | |
192 return slide_widget_->IsClosing(); | |
193 } | |
194 | |
195 void DownloadShelfGtk::DoShow() { | |
196 slide_widget_->Open(); | |
197 browser_->UpdateDownloadShelfVisibility(true); | |
198 CancelAutoClose(); | |
199 } | |
200 | |
201 void DownloadShelfGtk::DoClose(CloseReason reason) { | |
202 // When we are closing, we can vertically overlap the render view. Make sure | |
203 // we are on top. | |
204 gdk_window_raise(gtk_widget_get_window(shelf_.get())); | |
205 slide_widget_->Close(); | |
206 browser_->UpdateDownloadShelfVisibility(false); | |
207 int num_in_progress = 0; | |
208 for (size_t i = 0; i < download_items_.size(); ++i) { | |
209 if (download_items_[i]->download()->GetState() == DownloadItem::IN_PROGRESS) | |
210 ++num_in_progress; | |
211 } | |
212 RecordDownloadShelfClose( | |
213 download_items_.size(), num_in_progress, reason == AUTOMATIC); | |
214 SetCloseOnMouseOut(false); | |
215 } | |
216 | |
217 Browser* DownloadShelfGtk::browser() const { | |
218 return browser_; | |
219 } | |
220 | |
221 void DownloadShelfGtk::Closed() { | |
222 // Don't remove completed downloads if the shelf is just being auto-hidden | |
223 // rather than explicitly closed by the user. | |
224 if (is_hidden()) | |
225 return; | |
226 // When the close animation is complete, remove all completed downloads. | |
227 size_t i = 0; | |
228 while (i < download_items_.size()) { | |
229 DownloadItem* download = download_items_[i]->download(); | |
230 DownloadItem::DownloadState state = download->GetState(); | |
231 bool is_transfer_done = state == DownloadItem::COMPLETE || | |
232 state == DownloadItem::CANCELLED || | |
233 state == DownloadItem::INTERRUPTED; | |
234 if (is_transfer_done && !download->IsDangerous()) { | |
235 RemoveDownloadItem(download_items_[i]); | |
236 } else { | |
237 // We set all remaining items as "opened", so that the shelf will auto- | |
238 // close in the future without the user clicking on them. | |
239 download->SetOpened(true); | |
240 ++i; | |
241 } | |
242 } | |
243 } | |
244 | |
245 void DownloadShelfGtk::Observe(int type, | |
246 const content::NotificationSource& source, | |
247 const content::NotificationDetails& details) { | |
248 if (type == chrome::NOTIFICATION_BROWSER_THEME_CHANGED) { | |
249 GdkColor color = theme_service_->GetGdkColor( | |
250 ThemeProperties::COLOR_TOOLBAR); | |
251 gtk_widget_modify_bg(padding_bg_, GTK_STATE_NORMAL, &color); | |
252 | |
253 color = theme_service_->GetBorderColor(); | |
254 gtk_widget_modify_bg(top_border_, GTK_STATE_NORMAL, &color); | |
255 | |
256 // When using a non-standard, non-gtk theme, we make the link color match | |
257 // the bookmark text color. Otherwise, standard link blue can look very | |
258 // bad for some dark themes. | |
259 bool use_default_color = theme_service_->GetColor( | |
260 ThemeProperties::COLOR_BOOKMARK_TEXT) == | |
261 ThemeProperties::GetDefaultColor( | |
262 ThemeProperties::COLOR_BOOKMARK_TEXT); | |
263 GdkColor bookmark_color = theme_service_->GetGdkColor( | |
264 ThemeProperties::COLOR_BOOKMARK_TEXT); | |
265 gtk_chrome_link_button_set_normal_color( | |
266 GTK_CHROME_LINK_BUTTON(link_button_), | |
267 use_default_color ? NULL : &bookmark_color); | |
268 | |
269 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
270 close_button_->SetBackground( | |
271 theme_service_->GetColor(ThemeProperties::COLOR_TAB_TEXT), | |
272 rb.GetImageNamed(IDR_CLOSE_1).AsBitmap(), | |
273 rb.GetImageNamed(IDR_CLOSE_1_MASK).AsBitmap()); | |
274 } | |
275 } | |
276 | |
277 int DownloadShelfGtk::GetHeight() const { | |
278 GtkAllocation allocation; | |
279 gtk_widget_get_allocation(slide_widget_->widget(), &allocation); | |
280 return allocation.height; | |
281 } | |
282 | |
283 void DownloadShelfGtk::RemoveDownloadItem(DownloadItemGtk* download_item) { | |
284 DCHECK(download_item); | |
285 std::vector<DownloadItemGtk*>::iterator i = | |
286 find(download_items_.begin(), download_items_.end(), download_item); | |
287 DCHECK(i != download_items_.end()); | |
288 download_items_.erase(i); | |
289 delete download_item; | |
290 if (download_items_.empty()) { | |
291 slide_widget_->CloseWithoutAnimation(); | |
292 browser_->UpdateDownloadShelfVisibility(false); | |
293 } else { | |
294 AutoCloseIfPossible(); | |
295 } | |
296 } | |
297 | |
298 GtkWidget* DownloadShelfGtk::GetHBox() const { | |
299 return items_hbox_.get(); | |
300 } | |
301 | |
302 void DownloadShelfGtk::MaybeShowMoreDownloadItems() { | |
303 // Show all existing download items. It'll trigger "size-allocate" signal, | |
304 // which will hide download items that don't have enough space to show. | |
305 gtk_widget_show_all(items_hbox_.get()); | |
306 } | |
307 | |
308 void DownloadShelfGtk::OnButtonClick(GtkWidget* button) { | |
309 if (button == close_button_->widget()) { | |
310 Close(USER_ACTION); | |
311 } else { | |
312 // The link button was clicked. | |
313 chrome::ShowDownloads(browser_); | |
314 } | |
315 } | |
316 | |
317 void DownloadShelfGtk::AutoCloseIfPossible() { | |
318 for (std::vector<DownloadItemGtk*>::iterator iter = download_items_.begin(); | |
319 iter != download_items_.end(); ++iter) { | |
320 if (!(*iter)->download()->GetOpened()) | |
321 return; | |
322 } | |
323 | |
324 SetCloseOnMouseOut(true); | |
325 } | |
326 | |
327 void DownloadShelfGtk::CancelAutoClose() { | |
328 SetCloseOnMouseOut(false); | |
329 weak_factory_.InvalidateWeakPtrs(); | |
330 } | |
331 | |
332 void DownloadShelfGtk::ItemOpened() { | |
333 AutoCloseIfPossible(); | |
334 } | |
335 | |
336 void DownloadShelfGtk::SetCloseOnMouseOut(bool close) { | |
337 if (close_on_mouse_out_ == close) | |
338 return; | |
339 | |
340 close_on_mouse_out_ = close; | |
341 mouse_in_shelf_ = close; | |
342 if (close) | |
343 base::MessageLoopForUI::current()->AddObserver(this); | |
344 else | |
345 base::MessageLoopForUI::current()->RemoveObserver(this); | |
346 } | |
347 | |
348 void DownloadShelfGtk::WillProcessEvent(GdkEvent* event) { | |
349 } | |
350 | |
351 void DownloadShelfGtk::DidProcessEvent(GdkEvent* event) { | |
352 gfx::Point cursor_screen_coords; | |
353 | |
354 switch (event->type) { | |
355 case GDK_MOTION_NOTIFY: | |
356 cursor_screen_coords = | |
357 gfx::Point(event->motion.x_root, event->motion.y_root); | |
358 break; | |
359 case GDK_LEAVE_NOTIFY: | |
360 cursor_screen_coords = | |
361 gfx::Point(event->crossing.x_root, event->crossing.y_root); | |
362 break; | |
363 default: | |
364 return; | |
365 } | |
366 | |
367 bool mouse_in_shelf = IsCursorInShelfZone(cursor_screen_coords); | |
368 if (mouse_in_shelf == mouse_in_shelf_) | |
369 return; | |
370 mouse_in_shelf_ = mouse_in_shelf; | |
371 | |
372 if (mouse_in_shelf) | |
373 MouseEnteredShelf(); | |
374 else | |
375 MouseLeftShelf(); | |
376 } | |
377 | |
378 bool DownloadShelfGtk::IsCursorInShelfZone( | |
379 const gfx::Point& cursor_screen_coords) { | |
380 bool realized = (shelf_.get() && | |
381 gtk_widget_get_window(shelf_.get())); | |
382 // Do nothing if we've been unrealized in order to avoid a NOTREACHED in | |
383 // GetWidgetScreenPosition. | |
384 if (!realized) | |
385 return false; | |
386 | |
387 gfx::Rect bounds = ui::GetWidgetScreenBounds(shelf_.get()); | |
388 | |
389 // Negative insets expand the rectangle. We only expand the top. | |
390 bounds.Inset(gfx::Insets(-kShelfAuraSize, 0, 0, 0)); | |
391 | |
392 return bounds.Contains(cursor_screen_coords); | |
393 } | |
394 | |
395 void DownloadShelfGtk::MouseLeftShelf() { | |
396 DCHECK(close_on_mouse_out_); | |
397 | |
398 base::MessageLoop::current()->PostDelayedTask( | |
399 FROM_HERE, | |
400 base::Bind( | |
401 &DownloadShelfGtk::Close, weak_factory_.GetWeakPtr(), AUTOMATIC), | |
402 base::TimeDelta::FromMilliseconds(kAutoCloseDelayMs)); | |
403 } | |
404 | |
405 void DownloadShelfGtk::MouseEnteredShelf() { | |
406 weak_factory_.InvalidateWeakPtrs(); | |
407 } | |
OLD | NEW |