2 * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de>
4 * All rights reserved. Distributed under the terms of the MIT License.
7 #include "DownloadWindow.h"
14 #include <ControlLook.h>
17 #include <FindDirectory.h>
18 #include <GroupLayout.h>
19 #include <GroupLayoutBuilder.h>
25 #include <ScrollView.h>
26 #include <SeparatorView.h>
27 #include <SpaceLayoutItem.h>
29 #include "BrowserApp.h"
30 #include "BrowserWindow.h"
31 #include "DownloadProgressView.h"
32 #include "SettingsKeys.h"
33 #include "SettingsMessage.h"
34 #include "WebDownload.h"
38 #undef B_TRANSLATION_CONTEXT
39 #define B_TRANSLATION_CONTEXT "Download Window"
43 OPEN_DOWNLOADS_FOLDER
= 'odnf',
44 REMOVE_FINISHED_DOWNLOADS
= 'rmfd',
45 REMOVE_MISSING_DOWNLOADS
= 'rmmd'
49 class DownloadsContainerView
: public BGroupView
{
51 DownloadsContainerView()
53 BGroupView(B_VERTICAL
, 0.0)
55 SetFlags(Flags() | B_PULSE_NEEDED
);
56 SetViewColor(245, 245, 245);
57 AddChild(BSpaceLayoutItem::CreateGlue());
60 virtual BSize
MinSize()
62 BSize minSize
= BGroupView::MinSize();
63 return BSize(minSize
.width
, 80);
68 DownloadProgressView::SpeedVersusEstimatedFinishTogglePulse();
72 virtual void DoLayout()
74 BGroupView::DoLayout();
75 if (BScrollBar
* scrollBar
= ScrollBar(B_VERTICAL
)) {
76 BSize minSize
= BGroupView::MinSize();
77 float height
= Bounds().Height();
78 float max
= minSize
.height
- height
;
79 scrollBar
->SetRange(0, max
);
80 if (minSize
.height
> 0)
81 scrollBar
->SetProportion(height
/ minSize
.height
);
83 scrollBar
->SetProportion(1);
89 class DownloadContainerScrollView
: public BScrollView
{
91 DownloadContainerScrollView(BView
* target
)
93 BScrollView("Downloads scroll view", target
, 0, false, true,
99 virtual void DoLayout()
101 BScrollView::DoLayout();
102 // Tweak scroll bar layout to hide part of the frame for better looks.
103 BScrollBar
* scrollBar
= ScrollBar(B_VERTICAL
);
104 scrollBar
->MoveBy(1, -1);
105 scrollBar
->ResizeBy(0, 2);
106 Target()->ResizeBy(1, 0);
107 // Set the scroll steps
108 if (BView
* item
= Target()->ChildAt(0)) {
109 scrollBar
->SetSteps(item
->MinSize().height
+ 1,
110 item
->MinSize().height
+ 1);
119 DownloadWindow::DownloadWindow(BRect frame
, bool visible
,
120 SettingsMessage
* settings
)
121 : BWindow(frame
, B_TRANSLATE("Downloads"),
122 B_TITLED_WINDOW_LOOK
, B_NORMAL_WINDOW_FEEL
,
123 B_AUTO_UPDATE_SIZE_LIMITS
| B_ASYNCHRONOUS_CONTROLS
| B_NOT_ZOOMABLE
),
124 fMinimizeOnClose(false)
126 SetPulseRate(1000000);
128 settings
->AddListener(BMessenger(this));
130 if (find_directory(B_DESKTOP_DIRECTORY
, &downloadPath
) != B_OK
)
131 downloadPath
.SetTo("/boot/home/Desktop");
132 fDownloadPath
= settings
->GetValue(kSettingsKeyDownloadPath
,
133 downloadPath
.Path());
134 settings
->SetValue(kSettingsKeyDownloadPath
, fDownloadPath
);
136 SetLayout(new BGroupLayout(B_VERTICAL
, 0.0));
138 DownloadsContainerView
* downloadsGroupView
= new DownloadsContainerView();
139 fDownloadViewsLayout
= downloadsGroupView
->GroupLayout();
141 BMenuBar
* menuBar
= new BMenuBar("Menu bar");
142 BMenu
* menu
= new BMenu(B_TRANSLATE("Downloads"));
143 menu
->AddItem(new BMenuItem(B_TRANSLATE("Open downloads folder"),
144 new BMessage(OPEN_DOWNLOADS_FOLDER
)));
145 BMessage
* newWindowMessage
= new BMessage(NEW_WINDOW
);
146 newWindowMessage
->AddString("url", "");
147 BMenuItem
* newWindowItem
= new BMenuItem(B_TRANSLATE("New browser window"),
148 newWindowMessage
, 'N');
149 menu
->AddItem(newWindowItem
);
150 newWindowItem
->SetTarget(be_app
);
151 menu
->AddSeparatorItem();
152 menu
->AddItem(new BMenuItem(B_TRANSLATE("Close"),
153 new BMessage(B_QUIT_REQUESTED
), 'D'));
154 menuBar
->AddItem(menu
);
156 fDownloadsScrollView
= new DownloadContainerScrollView(downloadsGroupView
);
158 fRemoveFinishedButton
= new BButton(B_TRANSLATE("Remove finished"),
159 new BMessage(REMOVE_FINISHED_DOWNLOADS
));
160 fRemoveFinishedButton
->SetEnabled(false);
162 fRemoveMissingButton
= new BButton(B_TRANSLATE("Remove missing"),
163 new BMessage(REMOVE_MISSING_DOWNLOADS
));
164 fRemoveMissingButton
->SetEnabled(false);
166 const float spacing
= be_control_look
->DefaultItemSpacing();
168 AddChild(BGroupLayoutBuilder(B_VERTICAL
, 0.0)
170 .Add(fDownloadsScrollView
)
171 .Add(new BSeparatorView(B_HORIZONTAL
, B_PLAIN_BORDER
))
172 .Add(BGroupLayoutBuilder(B_HORIZONTAL
, spacing
)
174 .Add(fRemoveMissingButton
)
175 .Add(fRemoveFinishedButton
)
176 .SetInsets(12, 5, 12, 5)
185 MoveOnScreen(B_MOVE_IF_PARTIALLY_OFFSCREEN
);
189 DownloadWindow::~DownloadWindow()
191 // Only necessary to save the current progress of unfinished downloads:
197 DownloadWindow::DispatchMessage(BMessage
* message
, BHandler
* target
)
199 // We need to intercept mouse down events inside the area of download
200 // progress views (regardless of whether they have children at the click),
201 // so that they may display a context menu.
204 if (message
->what
== B_MOUSE_DOWN
205 && message
->FindPoint("screen_where", &where
) == B_OK
206 && message
->FindInt32("buttons", &buttons
) == B_OK
207 && (buttons
& B_SECONDARY_MOUSE_BUTTON
) != 0) {
208 for (int32 i
= fDownloadViewsLayout
->CountItems() - 1;
209 BLayoutItem
* item
= fDownloadViewsLayout
->ItemAt(i
); i
--) {
210 DownloadProgressView
* view
= dynamic_cast<DownloadProgressView
*>(
214 BPoint
viewWhere(where
);
215 view
->ConvertFromScreen(&viewWhere
);
216 if (view
->Bounds().Contains(viewWhere
)) {
217 view
->ShowContextMenu(where
);
222 BWindow::DispatchMessage(message
, target
);
227 DownloadWindow::MessageReceived(BMessage
* message
)
229 switch (message
->what
) {
233 // Small trick to get the correct enabled status of the Remove
235 _DownloadFinished(NULL
);
238 case B_DOWNLOAD_ADDED
:
240 BWebDownload
* download
;
241 if (message
->FindPointer("download", reinterpret_cast<void**>(
242 &download
)) == B_OK
) {
243 _DownloadStarted(download
);
247 case B_DOWNLOAD_REMOVED
:
249 BWebDownload
* download
;
250 if (message
->FindPointer("download", reinterpret_cast<void**>(
251 &download
)) == B_OK
) {
252 _DownloadFinished(download
);
256 case OPEN_DOWNLOADS_FOLDER
:
259 status_t status
= get_ref_for_path(fDownloadPath
.String(), &ref
);
261 status
= be_roster
->Launch(&ref
);
262 if (status
!= B_OK
&& status
!= B_ALREADY_RUNNING
) {
263 BString
errorString(B_TRANSLATE_COMMENT("The downloads folder could "
264 "not be opened.\n\nError: %error", "Don't translate "
266 errorString
.ReplaceFirst("%error", strerror(status
));
267 BAlert
* alert
= new BAlert(B_TRANSLATE("Error opening downloads "
268 "folder"), errorString
.String(), B_TRANSLATE("OK"));
269 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
274 case REMOVE_FINISHED_DOWNLOADS
:
275 _RemoveFinishedDownloads();
277 case REMOVE_MISSING_DOWNLOADS
:
278 _RemoveMissingDownloads();
281 _ValidateButtonStatus();
285 case SETTINGS_VALUE_CHANGED
:
288 if (message
->FindString("name", &string
) == B_OK
289 && string
== kSettingsKeyDownloadPath
290 && message
->FindString("value", &string
) == B_OK
) {
291 fDownloadPath
= string
;
296 BWindow::MessageReceived(message
);
303 DownloadWindow::QuitRequested()
305 if (fMinimizeOnClose
) {
317 DownloadWindow::DownloadsInProgress()
319 bool downloadsInProgress
= false;
321 return downloadsInProgress
;
323 for (int32 i
= fDownloadViewsLayout
->CountItems() - 1;
324 BLayoutItem
* item
= fDownloadViewsLayout
->ItemAt(i
); i
--) {
325 DownloadProgressView
* view
= dynamic_cast<DownloadProgressView
*>(
329 if (view
->Download() != NULL
) {
330 downloadsInProgress
= true;
337 return downloadsInProgress
;
342 DownloadWindow::SetMinimizeOnClose(bool minimize
)
345 fMinimizeOnClose
= minimize
;
351 // #pragma mark - private
355 DownloadWindow::_DownloadStarted(BWebDownload
* download
)
357 download
->Start(BPath(fDownloadPath
.String()));
359 int32 finishedCount
= 0;
360 int32 missingCount
= 0;
362 for (int32 i
= fDownloadViewsLayout
->CountItems() - 1;
363 BLayoutItem
* item
= fDownloadViewsLayout
->ItemAt(i
); i
--) {
364 DownloadProgressView
* view
= dynamic_cast<DownloadProgressView
*>(
368 if (view
->URL() == download
->URL()) {
374 if (view
->IsFinished())
376 if (view
->IsMissing())
379 fRemoveFinishedButton
->SetEnabled(finishedCount
> 0);
380 fRemoveMissingButton
->SetEnabled(missingCount
> 0);
381 DownloadProgressView
* view
= new DownloadProgressView(download
);
386 fDownloadViewsLayout
->AddView(index
, view
);
388 // Scroll new download into view
389 if (BScrollBar
* scrollBar
= fDownloadsScrollView
->ScrollBar(B_VERTICAL
)) {
392 scrollBar
->GetRange(&min
, &max
);
393 float viewHeight
= view
->MinSize().height
+ 1;
394 float scrollOffset
= min
+ index
* viewHeight
;
395 float scrollBarHeight
= scrollBar
->Bounds().Height() - 1;
396 float value
= scrollBar
->Value();
397 if (scrollOffset
< value
)
398 scrollBar
->SetValue(scrollOffset
);
399 else if (scrollOffset
+ viewHeight
> value
+ scrollBarHeight
) {
400 float diff
= scrollOffset
+ viewHeight
- (value
+ scrollBarHeight
);
401 scrollBar
->SetValue(value
+ diff
);
407 SetWorkspaces(B_CURRENT_WORKSPACE
);
416 DownloadWindow::_DownloadFinished(BWebDownload
* download
)
418 int32 finishedCount
= 0;
419 int32 missingCount
= 0;
421 BLayoutItem
* item
= fDownloadViewsLayout
->ItemAt(i
); i
++) {
422 DownloadProgressView
* view
= dynamic_cast<DownloadProgressView
*>(
426 if (download
&& view
->Download() == download
) {
427 view
->DownloadFinished();
431 if (view
->IsFinished())
433 if (view
->IsMissing())
436 fRemoveFinishedButton
->SetEnabled(finishedCount
> 0);
437 fRemoveMissingButton
->SetEnabled(missingCount
> 0);
444 DownloadWindow::_RemoveFinishedDownloads()
446 int32 missingCount
= 0;
447 for (int32 i
= fDownloadViewsLayout
->CountItems() - 1;
448 BLayoutItem
* item
= fDownloadViewsLayout
->ItemAt(i
); i
--) {
449 DownloadProgressView
* view
= dynamic_cast<DownloadProgressView
*>(
453 if (view
->IsFinished()) {
456 } else if (view
->IsMissing())
459 fRemoveFinishedButton
->SetEnabled(false);
460 fRemoveMissingButton
->SetEnabled(missingCount
> 0);
466 DownloadWindow::_RemoveMissingDownloads()
468 int32 finishedCount
= 0;
469 for (int32 i
= fDownloadViewsLayout
->CountItems() - 1;
470 BLayoutItem
* item
= fDownloadViewsLayout
->ItemAt(i
); i
--) {
471 DownloadProgressView
* view
= dynamic_cast<DownloadProgressView
*>(
475 if (view
->IsMissing()) {
478 } else if (view
->IsFinished())
481 fRemoveMissingButton
->SetEnabled(false);
482 fRemoveFinishedButton
->SetEnabled(finishedCount
> 0);
488 DownloadWindow::_ValidateButtonStatus()
490 int32 finishedCount
= 0;
491 int32 missingCount
= 0;
492 for (int32 i
= fDownloadViewsLayout
->CountItems() - 1;
493 BLayoutItem
* item
= fDownloadViewsLayout
->ItemAt(i
); i
--) {
494 DownloadProgressView
* view
= dynamic_cast<DownloadProgressView
*>(
498 if (view
->IsFinished())
500 if (view
->IsMissing())
503 fRemoveFinishedButton
->SetEnabled(finishedCount
> 0);
504 fRemoveMissingButton
->SetEnabled(missingCount
> 0);
509 DownloadWindow::_SaveSettings()
512 if (!_OpenSettingsFile(file
, B_ERASE_FILE
| B_CREATE_FILE
| B_WRITE_ONLY
))
515 for (int32 i
= fDownloadViewsLayout
->CountItems() - 1;
516 BLayoutItem
* item
= fDownloadViewsLayout
->ItemAt(i
); i
--) {
517 DownloadProgressView
* view
= dynamic_cast<DownloadProgressView
*>(
522 BMessage downloadArchive
;
523 if (view
->SaveSettings(&downloadArchive
) == B_OK
)
524 message
.AddMessage("download", &downloadArchive
);
526 message
.Flatten(&file
);
531 DownloadWindow::_LoadSettings()
534 if (!_OpenSettingsFile(file
, B_READ_ONLY
))
537 if (message
.Unflatten(&file
) != B_OK
)
539 BMessage downloadArchive
;
541 message
.FindMessage("download", i
, &downloadArchive
) == B_OK
;
543 DownloadProgressView
* view
= new DownloadProgressView(
545 if (!view
->Init(&downloadArchive
))
547 fDownloadViewsLayout
->AddView(0, view
);
553 DownloadWindow::_OpenSettingsFile(BFile
& file
, uint32 mode
)
556 if (find_directory(B_USER_SETTINGS_DIRECTORY
, &path
) != B_OK
557 || path
.Append(kApplicationName
) != B_OK
558 || path
.Append("Downloads") != B_OK
) {
561 return file
.SetTo(path
.Path(), mode
) == B_OK
;