/* * Copyright (C) 2010 Stephan Aßmus * * All rights reserved. Distributed under the terms of the MIT License. */ #include "DownloadWindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "BrowserApp.h" #include "BrowserWindow.h" #include "DownloadProgressView.h" #include "SettingsKeys.h" #include "SettingsMessage.h" #include "WebDownload.h" #include "WebPage.h" #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "Download Window" enum { INIT = 'init', OPEN_DOWNLOADS_FOLDER = 'odnf', REMOVE_FINISHED_DOWNLOADS = 'rmfd', REMOVE_MISSING_DOWNLOADS = 'rmmd' }; class DownloadsContainerView : public BGroupView { public: DownloadsContainerView() : BGroupView(B_VERTICAL, 0.0) { SetFlags(Flags() | B_PULSE_NEEDED); SetViewColor(245, 245, 245); AddChild(BSpaceLayoutItem::CreateGlue()); } virtual BSize MinSize() { BSize minSize = BGroupView::MinSize(); return BSize(minSize.width, 80); } virtual void Pulse() { DownloadProgressView::SpeedVersusEstimatedFinishTogglePulse(); } protected: virtual void DoLayout() { BGroupView::DoLayout(); if (BScrollBar* scrollBar = ScrollBar(B_VERTICAL)) { BSize minSize = BGroupView::MinSize(); float height = Bounds().Height(); float max = minSize.height - height; scrollBar->SetRange(0, max); if (minSize.height > 0) scrollBar->SetProportion(height / minSize.height); else scrollBar->SetProportion(1); } } }; class DownloadContainerScrollView : public BScrollView { public: DownloadContainerScrollView(BView* target) : BScrollView("Downloads scroll view", target, 0, false, true, B_NO_BORDER) { } protected: virtual void DoLayout() { BScrollView::DoLayout(); // Tweak scroll bar layout to hide part of the frame for better looks. BScrollBar* scrollBar = ScrollBar(B_VERTICAL); scrollBar->MoveBy(1, -1); scrollBar->ResizeBy(0, 2); Target()->ResizeBy(1, 0); // Set the scroll steps if (BView* item = Target()->ChildAt(0)) { scrollBar->SetSteps(item->MinSize().height + 1, item->MinSize().height + 1); } } }; // #pragma mark - DownloadWindow::DownloadWindow(BRect frame, bool visible, SettingsMessage* settings) : BWindow(frame, B_TRANSLATE("Downloads"), B_TITLED_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, B_AUTO_UPDATE_SIZE_LIMITS | B_ASYNCHRONOUS_CONTROLS | B_NOT_ZOOMABLE), fMinimizeOnClose(false) { SetPulseRate(1000000); settings->AddListener(BMessenger(this)); BPath downloadPath; if (find_directory(B_DESKTOP_DIRECTORY, &downloadPath) != B_OK) downloadPath.SetTo("/boot/home/Desktop"); fDownloadPath = settings->GetValue(kSettingsKeyDownloadPath, downloadPath.Path()); settings->SetValue(kSettingsKeyDownloadPath, fDownloadPath); SetLayout(new BGroupLayout(B_VERTICAL, 0.0)); DownloadsContainerView* downloadsGroupView = new DownloadsContainerView(); fDownloadViewsLayout = downloadsGroupView->GroupLayout(); BMenuBar* menuBar = new BMenuBar("Menu bar"); BMenu* menu = new BMenu(B_TRANSLATE("Downloads")); menu->AddItem(new BMenuItem(B_TRANSLATE("Open downloads folder"), new BMessage(OPEN_DOWNLOADS_FOLDER))); BMessage* newWindowMessage = new BMessage(NEW_WINDOW); newWindowMessage->AddString("url", ""); BMenuItem* newWindowItem = new BMenuItem(B_TRANSLATE("New browser window"), newWindowMessage, 'N'); menu->AddItem(newWindowItem); newWindowItem->SetTarget(be_app); menu->AddSeparatorItem(); menu->AddItem(new BMenuItem(B_TRANSLATE("Hide"), new BMessage(B_QUIT_REQUESTED), 'D')); menuBar->AddItem(menu); fDownloadsScrollView = new DownloadContainerScrollView(downloadsGroupView); fRemoveFinishedButton = new BButton(B_TRANSLATE("Remove finished"), new BMessage(REMOVE_FINISHED_DOWNLOADS)); fRemoveFinishedButton->SetEnabled(false); fRemoveMissingButton = new BButton(B_TRANSLATE("Remove missing"), new BMessage(REMOVE_MISSING_DOWNLOADS)); fRemoveMissingButton->SetEnabled(false); const float spacing = be_control_look->DefaultItemSpacing(); AddChild(BGroupLayoutBuilder(B_VERTICAL, 0.0) .Add(menuBar) .Add(fDownloadsScrollView) .Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER)) .Add(BGroupLayoutBuilder(B_HORIZONTAL, spacing) .AddGlue() .Add(fRemoveMissingButton) .Add(fRemoveFinishedButton) .SetInsets(12, 5, 12, 5) ) ); PostMessage(INIT); if (!visible) Hide(); Show(); MoveOnScreen(B_MOVE_IF_PARTIALLY_OFFSCREEN); } DownloadWindow::~DownloadWindow() { // Only necessary to save the current progress of unfinished downloads: _SaveSettings(); } void DownloadWindow::DispatchMessage(BMessage* message, BHandler* target) { // We need to intercept mouse down events inside the area of download // progress views (regardless of whether they have children at the click), // so that they may display a context menu. BPoint where; int32 buttons; if (message->what == B_MOUSE_DOWN && message->FindPoint("screen_where", &where) == B_OK && message->FindInt32("buttons", &buttons) == B_OK && (buttons & B_SECONDARY_MOUSE_BUTTON) != 0) { for (int32 i = fDownloadViewsLayout->CountItems() - 1; BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i--) { DownloadProgressView* view = dynamic_cast( item->View()); if (!view) continue; BPoint viewWhere(where); view->ConvertFromScreen(&viewWhere); if (view->Bounds().Contains(viewWhere)) { view->ShowContextMenu(where); return; } } } BWindow::DispatchMessage(message, target); } void DownloadWindow::MessageReceived(BMessage* message) { switch (message->what) { case INIT: { _LoadSettings(); // Small trick to get the correct enabled status of the Remove // finished button _DownloadFinished(NULL); break; } case B_DOWNLOAD_ADDED: { BWebDownload* download; if (message->FindPointer("download", reinterpret_cast( &download)) == B_OK) { _DownloadStarted(download); } break; } case B_DOWNLOAD_REMOVED: { BWebDownload* download; if (message->FindPointer("download", reinterpret_cast( &download)) == B_OK) { _DownloadFinished(download); } break; } case OPEN_DOWNLOADS_FOLDER: { entry_ref ref; status_t status = get_ref_for_path(fDownloadPath.String(), &ref); if (status == B_OK) status = be_roster->Launch(&ref); if (status != B_OK && status != B_ALREADY_RUNNING) { BString errorString(B_TRANSLATE_COMMENT("The downloads folder could " "not be opened.\n\nError: %error", "Don't translate " "variable %error")); errorString.ReplaceFirst("%error", strerror(status)); BAlert* alert = new BAlert(B_TRANSLATE("Error opening downloads " "folder"), errorString.String(), B_TRANSLATE("OK")); alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); alert->Go(NULL); } break; } case REMOVE_FINISHED_DOWNLOADS: _RemoveFinishedDownloads(); break; case REMOVE_MISSING_DOWNLOADS: _RemoveMissingDownloads(); break; case SAVE_SETTINGS: _ValidateButtonStatus(); _SaveSettings(); break; case SETTINGS_VALUE_CHANGED: { BString string; if (message->FindString("name", &string) == B_OK && string == kSettingsKeyDownloadPath && message->FindString("value", &string) == B_OK) { fDownloadPath = string; } break; } default: BWindow::MessageReceived(message); break; } } bool DownloadWindow::QuitRequested() { if (fMinimizeOnClose) { if (!IsMinimized()) Minimize(true); } else { if (!IsHidden()) Hide(); } return false; } bool DownloadWindow::DownloadsInProgress() { bool downloadsInProgress = false; if (!Lock()) return downloadsInProgress; for (int32 i = fDownloadViewsLayout->CountItems() - 1; BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i--) { DownloadProgressView* view = dynamic_cast( item->View()); if (!view) continue; if (view->Download() != NULL) { downloadsInProgress = true; break; } } Unlock(); return downloadsInProgress; } void DownloadWindow::SetMinimizeOnClose(bool minimize) { if (Lock()) { fMinimizeOnClose = minimize; Unlock(); } } // #pragma mark - private void DownloadWindow::_DownloadStarted(BWebDownload* download) { download->Start(BPath(fDownloadPath.String())); int32 finishedCount = 0; int32 missingCount = 0; int32 index = 0; for (int32 i = fDownloadViewsLayout->CountItems() - 1; BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i--) { DownloadProgressView* view = dynamic_cast( item->View()); if (!view) continue; if (view->URL() == download->URL()) { index = i; view->RemoveSelf(); delete view; continue; } if (view->IsFinished()) finishedCount++; if (view->IsMissing()) missingCount++; } fRemoveFinishedButton->SetEnabled(finishedCount > 0); fRemoveMissingButton->SetEnabled(missingCount > 0); DownloadProgressView* view = new DownloadProgressView(download); if (!view->Init()) { delete view; return; } fDownloadViewsLayout->AddView(index, view); // Scroll new download into view if (BScrollBar* scrollBar = fDownloadsScrollView->ScrollBar(B_VERTICAL)) { float min; float max; scrollBar->GetRange(&min, &max); float viewHeight = view->MinSize().height + 1; float scrollOffset = min + index * viewHeight; float scrollBarHeight = scrollBar->Bounds().Height() - 1; float value = scrollBar->Value(); if (scrollOffset < value) scrollBar->SetValue(scrollOffset); else if (scrollOffset + viewHeight > value + scrollBarHeight) { float diff = scrollOffset + viewHeight - (value + scrollBarHeight); scrollBar->SetValue(value + diff); } } _SaveSettings(); SetWorkspaces(B_CURRENT_WORKSPACE); if (IsHidden()) Show(); Activate(true); } void DownloadWindow::_DownloadFinished(BWebDownload* download) { int32 finishedCount = 0; int32 missingCount = 0; for (int32 i = 0; BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i++) { DownloadProgressView* view = dynamic_cast( item->View()); if (!view) continue; if (download && view->Download() == download) { view->DownloadFinished(); finishedCount++; continue; } if (view->IsFinished()) finishedCount++; if (view->IsMissing()) missingCount++; } fRemoveFinishedButton->SetEnabled(finishedCount > 0); fRemoveMissingButton->SetEnabled(missingCount > 0); if (download) _SaveSettings(); } void DownloadWindow::_RemoveFinishedDownloads() { int32 missingCount = 0; for (int32 i = fDownloadViewsLayout->CountItems() - 1; BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i--) { DownloadProgressView* view = dynamic_cast( item->View()); if (!view) continue; if (view->IsFinished()) { view->RemoveSelf(); delete view; } else if (view->IsMissing()) missingCount++; } fRemoveFinishedButton->SetEnabled(false); fRemoveMissingButton->SetEnabled(missingCount > 0); _SaveSettings(); } void DownloadWindow::_RemoveMissingDownloads() { int32 finishedCount = 0; for (int32 i = fDownloadViewsLayout->CountItems() - 1; BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i--) { DownloadProgressView* view = dynamic_cast( item->View()); if (!view) continue; if (view->IsMissing()) { view->RemoveSelf(); delete view; } else if (view->IsFinished()) finishedCount++; } fRemoveMissingButton->SetEnabled(false); fRemoveFinishedButton->SetEnabled(finishedCount > 0); _SaveSettings(); } void DownloadWindow::_ValidateButtonStatus() { int32 finishedCount = 0; int32 missingCount = 0; for (int32 i = fDownloadViewsLayout->CountItems() - 1; BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i--) { DownloadProgressView* view = dynamic_cast( item->View()); if (!view) continue; if (view->IsFinished()) finishedCount++; if (view->IsMissing()) missingCount++; } fRemoveFinishedButton->SetEnabled(finishedCount > 0); fRemoveMissingButton->SetEnabled(missingCount > 0); } void DownloadWindow::_SaveSettings() { BFile file; if (!_OpenSettingsFile(file, B_ERASE_FILE | B_CREATE_FILE | B_WRITE_ONLY)) return; BMessage message; for (int32 i = fDownloadViewsLayout->CountItems() - 1; BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i--) { DownloadProgressView* view = dynamic_cast( item->View()); if (!view) continue; BMessage downloadArchive; if (view->SaveSettings(&downloadArchive) == B_OK) message.AddMessage("download", &downloadArchive); } message.Flatten(&file); } void DownloadWindow::_LoadSettings() { BFile file; if (!_OpenSettingsFile(file, B_READ_ONLY)) return; BMessage message; if (message.Unflatten(&file) != B_OK) return; BMessage downloadArchive; for (int32 i = 0; message.FindMessage("download", i, &downloadArchive) == B_OK; i++) { DownloadProgressView* view = new DownloadProgressView( &downloadArchive); if (!view->Init(&downloadArchive)) continue; fDownloadViewsLayout->AddView(0, view); } } bool DownloadWindow::_OpenSettingsFile(BFile& file, uint32 mode) { BPath path; if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK || path.Append(kApplicationName) != B_OK || path.Append("Downloads") != B_OK) { return false; } return file.SetTo(path.Path(), mode) == B_OK; }