1 /* 2 * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> 3 * 4 * All rights reserved. Distributed under the terms of the MIT License. 5 */ 6 7 #include "DownloadWindow.h" 8 9 #include <stdio.h> 10 11 #include <Alert.h> 12 #include <Button.h> 13 #include <Catalog.h> 14 #include <ControlLook.h> 15 #include <Entry.h> 16 #include <File.h> 17 #include <FindDirectory.h> 18 #include <GroupLayout.h> 19 #include <GroupLayoutBuilder.h> 20 #include <Locale.h> 21 #include <MenuBar.h> 22 #include <MenuItem.h> 23 #include <Path.h> 24 #include <Roster.h> 25 #include <ScrollView.h> 26 #include <SeparatorView.h> 27 #include <SpaceLayoutItem.h> 28 29 #include "BrowserApp.h" 30 #include "BrowserWindow.h" 31 #include "DownloadProgressView.h" 32 #include "SettingsKeys.h" 33 #include "SettingsMessage.h" 34 #include "WebDownload.h" 35 #include "WebPage.h" 36 37 38 #undef B_TRANSLATION_CONTEXT 39 #define B_TRANSLATION_CONTEXT "Download Window" 40 41 enum { 42 INIT = 'init', 43 OPEN_DOWNLOADS_FOLDER = 'odnf', 44 REMOVE_FINISHED_DOWNLOADS = 'rmfd', 45 REMOVE_MISSING_DOWNLOADS = 'rmmd' 46 }; 47 48 49 class DownloadsContainerView : public BGroupView { 50 public: 51 DownloadsContainerView() 52 : 53 BGroupView(B_VERTICAL, 0.0) 54 { 55 SetFlags(Flags() | B_PULSE_NEEDED); 56 SetViewColor(245, 245, 245); 57 AddChild(BSpaceLayoutItem::CreateGlue()); 58 } 59 60 virtual BSize MinSize() 61 { 62 BSize minSize = BGroupView::MinSize(); 63 return BSize(minSize.width, 80); 64 } 65 66 virtual void Pulse() 67 { 68 DownloadProgressView::SpeedVersusEstimatedFinishTogglePulse(); 69 } 70 71 protected: 72 virtual void DoLayout() 73 { 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); 82 else 83 scrollBar->SetProportion(1); 84 } 85 } 86 }; 87 88 89 class DownloadContainerScrollView : public BScrollView { 90 public: 91 DownloadContainerScrollView(BView* target) 92 : 93 BScrollView("Downloads scroll view", target, 0, false, true, 94 B_NO_BORDER) 95 { 96 } 97 98 protected: 99 virtual void DoLayout() 100 { 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); 111 } 112 } 113 }; 114 115 116 // #pragma mark - 117 118 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) 125 { 126 SetPulseRate(1000000); 127 128 settings->AddListener(BMessenger(this)); 129 BPath downloadPath; 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); 135 136 SetLayout(new BGroupLayout(B_VERTICAL, 0.0)); 137 138 DownloadsContainerView* downloadsGroupView = new DownloadsContainerView(); 139 fDownloadViewsLayout = downloadsGroupView->GroupLayout(); 140 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("Hide"), 153 new BMessage(B_QUIT_REQUESTED), 'D')); 154 menuBar->AddItem(menu); 155 156 fDownloadsScrollView = new DownloadContainerScrollView(downloadsGroupView); 157 158 fRemoveFinishedButton = new BButton(B_TRANSLATE("Remove finished"), 159 new BMessage(REMOVE_FINISHED_DOWNLOADS)); 160 fRemoveFinishedButton->SetEnabled(false); 161 162 fRemoveMissingButton = new BButton(B_TRANSLATE("Remove missing"), 163 new BMessage(REMOVE_MISSING_DOWNLOADS)); 164 fRemoveMissingButton->SetEnabled(false); 165 166 const float spacing = be_control_look->DefaultItemSpacing(); 167 168 AddChild(BGroupLayoutBuilder(B_VERTICAL, 0.0) 169 .Add(menuBar) 170 .Add(fDownloadsScrollView) 171 .Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER)) 172 .Add(BGroupLayoutBuilder(B_HORIZONTAL, spacing) 173 .AddGlue() 174 .Add(fRemoveMissingButton) 175 .Add(fRemoveFinishedButton) 176 .SetInsets(12, 5, 12, 5) 177 ) 178 ); 179 180 PostMessage(INIT); 181 182 if (!visible) 183 Hide(); 184 Show(); 185 } 186 187 188 DownloadWindow::~DownloadWindow() 189 { 190 // Only necessary to save the current progress of unfinished downloads: 191 _SaveSettings(); 192 } 193 194 195 void 196 DownloadWindow::DispatchMessage(BMessage* message, BHandler* target) 197 { 198 // We need to intercept mouse down events inside the area of download 199 // progress views (regardless of whether they have children at the click), 200 // so that they may display a context menu. 201 BPoint where; 202 int32 buttons; 203 if (message->what == B_MOUSE_DOWN 204 && message->FindPoint("screen_where", &where) == B_OK 205 && message->FindInt32("buttons", &buttons) == B_OK 206 && (buttons & B_SECONDARY_MOUSE_BUTTON) != 0) { 207 for (int32 i = fDownloadViewsLayout->CountItems() - 1; 208 BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i--) { 209 DownloadProgressView* view = dynamic_cast<DownloadProgressView*>( 210 item->View()); 211 if (!view) 212 continue; 213 BPoint viewWhere(where); 214 view->ConvertFromScreen(&viewWhere); 215 if (view->Bounds().Contains(viewWhere)) { 216 view->ShowContextMenu(where); 217 return; 218 } 219 } 220 } 221 BWindow::DispatchMessage(message, target); 222 } 223 224 225 void 226 DownloadWindow::MessageReceived(BMessage* message) 227 { 228 switch (message->what) { 229 case INIT: 230 { 231 _LoadSettings(); 232 // Small trick to get the correct enabled status of the Remove 233 // finished button 234 _DownloadFinished(NULL); 235 break; 236 } 237 case B_DOWNLOAD_ADDED: 238 { 239 BWebDownload* download; 240 if (message->FindPointer("download", reinterpret_cast<void**>( 241 &download)) == B_OK) { 242 _DownloadStarted(download); 243 } 244 break; 245 } 246 case B_DOWNLOAD_REMOVED: 247 { 248 BWebDownload* download; 249 if (message->FindPointer("download", reinterpret_cast<void**>( 250 &download)) == B_OK) { 251 _DownloadFinished(download); 252 } 253 break; 254 } 255 case OPEN_DOWNLOADS_FOLDER: 256 { 257 entry_ref ref; 258 status_t status = get_ref_for_path(fDownloadPath.String(), &ref); 259 if (status == B_OK) 260 status = be_roster->Launch(&ref); 261 if (status != B_OK && status != B_ALREADY_RUNNING) { 262 BString errorString(B_TRANSLATE_COMMENT("The downloads folder could " 263 "not be opened.\n\nError: %error", "Don't translate " 264 "variable %error")); 265 errorString.ReplaceFirst("%error", strerror(status)); 266 BAlert* alert = new BAlert(B_TRANSLATE("Error opening downloads " 267 "folder"), errorString.String(), B_TRANSLATE("OK")); 268 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 269 alert->Go(NULL); 270 } 271 break; 272 } 273 case REMOVE_FINISHED_DOWNLOADS: 274 _RemoveFinishedDownloads(); 275 break; 276 case REMOVE_MISSING_DOWNLOADS: 277 _RemoveMissingDownloads(); 278 break; 279 case SAVE_SETTINGS: 280 _ValidateButtonStatus(); 281 _SaveSettings(); 282 break; 283 284 case SETTINGS_VALUE_CHANGED: 285 { 286 BString string; 287 if (message->FindString("name", &string) == B_OK 288 && string == kSettingsKeyDownloadPath 289 && message->FindString("value", &string) == B_OK) { 290 fDownloadPath = string; 291 } 292 break; 293 } 294 default: 295 BWindow::MessageReceived(message); 296 break; 297 } 298 } 299 300 301 bool 302 DownloadWindow::QuitRequested() 303 { 304 if (fMinimizeOnClose) { 305 if (!IsMinimized()) 306 Minimize(true); 307 } else { 308 if (!IsHidden()) 309 Hide(); 310 } 311 return false; 312 } 313 314 315 bool 316 DownloadWindow::DownloadsInProgress() 317 { 318 bool downloadsInProgress = false; 319 if (!Lock()) 320 return downloadsInProgress; 321 322 for (int32 i = fDownloadViewsLayout->CountItems() - 1; 323 BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i--) { 324 DownloadProgressView* view = dynamic_cast<DownloadProgressView*>( 325 item->View()); 326 if (!view) 327 continue; 328 if (view->Download() != NULL) { 329 downloadsInProgress = true; 330 break; 331 } 332 } 333 334 Unlock(); 335 336 return downloadsInProgress; 337 } 338 339 340 void 341 DownloadWindow::SetMinimizeOnClose(bool minimize) 342 { 343 if (Lock()) { 344 fMinimizeOnClose = minimize; 345 Unlock(); 346 } 347 } 348 349 350 // #pragma mark - private 351 352 353 void 354 DownloadWindow::_DownloadStarted(BWebDownload* download) 355 { 356 download->Start(BPath(fDownloadPath.String())); 357 358 int32 finishedCount = 0; 359 int32 missingCount = 0; 360 int32 index = 0; 361 for (int32 i = fDownloadViewsLayout->CountItems() - 1; 362 BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i--) { 363 DownloadProgressView* view = dynamic_cast<DownloadProgressView*>( 364 item->View()); 365 if (!view) 366 continue; 367 if (view->URL() == download->URL()) { 368 index = i; 369 view->RemoveSelf(); 370 delete view; 371 continue; 372 } 373 if (view->IsFinished()) 374 finishedCount++; 375 if (view->IsMissing()) 376 missingCount++; 377 } 378 fRemoveFinishedButton->SetEnabled(finishedCount > 0); 379 fRemoveMissingButton->SetEnabled(missingCount > 0); 380 DownloadProgressView* view = new DownloadProgressView(download); 381 if (!view->Init()) { 382 delete view; 383 return; 384 } 385 fDownloadViewsLayout->AddView(index, view); 386 387 // Scroll new download into view 388 if (BScrollBar* scrollBar = fDownloadsScrollView->ScrollBar(B_VERTICAL)) { 389 float min; 390 float max; 391 scrollBar->GetRange(&min, &max); 392 float viewHeight = view->MinSize().height + 1; 393 float scrollOffset = min + index * viewHeight; 394 float scrollBarHeight = scrollBar->Bounds().Height() - 1; 395 float value = scrollBar->Value(); 396 if (scrollOffset < value) 397 scrollBar->SetValue(scrollOffset); 398 else if (scrollOffset + viewHeight > value + scrollBarHeight) { 399 float diff = scrollOffset + viewHeight - (value + scrollBarHeight); 400 scrollBar->SetValue(value + diff); 401 } 402 } 403 404 _SaveSettings(); 405 406 SetWorkspaces(B_CURRENT_WORKSPACE); 407 if (IsHidden()) 408 Show(); 409 } 410 411 412 void 413 DownloadWindow::_DownloadFinished(BWebDownload* download) 414 { 415 int32 finishedCount = 0; 416 int32 missingCount = 0; 417 for (int32 i = 0; 418 BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i++) { 419 DownloadProgressView* view = dynamic_cast<DownloadProgressView*>( 420 item->View()); 421 if (!view) 422 continue; 423 if (download && view->Download() == download) { 424 view->DownloadFinished(); 425 finishedCount++; 426 continue; 427 } 428 if (view->IsFinished()) 429 finishedCount++; 430 if (view->IsMissing()) 431 missingCount++; 432 } 433 fRemoveFinishedButton->SetEnabled(finishedCount > 0); 434 fRemoveMissingButton->SetEnabled(missingCount > 0); 435 if (download) 436 _SaveSettings(); 437 } 438 439 440 void 441 DownloadWindow::_RemoveFinishedDownloads() 442 { 443 int32 missingCount = 0; 444 for (int32 i = fDownloadViewsLayout->CountItems() - 1; 445 BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i--) { 446 DownloadProgressView* view = dynamic_cast<DownloadProgressView*>( 447 item->View()); 448 if (!view) 449 continue; 450 if (view->IsFinished()) { 451 view->RemoveSelf(); 452 delete view; 453 } else if (view->IsMissing()) 454 missingCount++; 455 } 456 fRemoveFinishedButton->SetEnabled(false); 457 fRemoveMissingButton->SetEnabled(missingCount > 0); 458 _SaveSettings(); 459 } 460 461 462 void 463 DownloadWindow::_RemoveMissingDownloads() 464 { 465 int32 finishedCount = 0; 466 for (int32 i = fDownloadViewsLayout->CountItems() - 1; 467 BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i--) { 468 DownloadProgressView* view = dynamic_cast<DownloadProgressView*>( 469 item->View()); 470 if (!view) 471 continue; 472 if (view->IsMissing()) { 473 view->RemoveSelf(); 474 delete view; 475 } else if (view->IsFinished()) 476 finishedCount++; 477 } 478 fRemoveMissingButton->SetEnabled(false); 479 fRemoveFinishedButton->SetEnabled(finishedCount > 0); 480 _SaveSettings(); 481 } 482 483 484 void 485 DownloadWindow::_ValidateButtonStatus() 486 { 487 int32 finishedCount = 0; 488 int32 missingCount = 0; 489 for (int32 i = fDownloadViewsLayout->CountItems() - 1; 490 BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i--) { 491 DownloadProgressView* view = dynamic_cast<DownloadProgressView*>( 492 item->View()); 493 if (!view) 494 continue; 495 if (view->IsFinished()) 496 finishedCount++; 497 if (view->IsMissing()) 498 missingCount++; 499 } 500 fRemoveFinishedButton->SetEnabled(finishedCount > 0); 501 fRemoveMissingButton->SetEnabled(missingCount > 0); 502 } 503 504 505 void 506 DownloadWindow::_SaveSettings() 507 { 508 BFile file; 509 if (!_OpenSettingsFile(file, B_ERASE_FILE | B_CREATE_FILE | B_WRITE_ONLY)) 510 return; 511 BMessage message; 512 for (int32 i = fDownloadViewsLayout->CountItems() - 1; 513 BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i--) { 514 DownloadProgressView* view = dynamic_cast<DownloadProgressView*>( 515 item->View()); 516 if (!view) 517 continue; 518 519 BMessage downloadArchive; 520 if (view->SaveSettings(&downloadArchive) == B_OK) 521 message.AddMessage("download", &downloadArchive); 522 } 523 message.Flatten(&file); 524 } 525 526 527 void 528 DownloadWindow::_LoadSettings() 529 { 530 BFile file; 531 if (!_OpenSettingsFile(file, B_READ_ONLY)) 532 return; 533 BMessage message; 534 if (message.Unflatten(&file) != B_OK) 535 return; 536 BMessage downloadArchive; 537 for (int32 i = 0; 538 message.FindMessage("download", i, &downloadArchive) == B_OK; 539 i++) { 540 DownloadProgressView* view = new DownloadProgressView( 541 &downloadArchive); 542 if (!view->Init(&downloadArchive)) 543 continue; 544 fDownloadViewsLayout->AddView(0, view); 545 } 546 } 547 548 549 bool 550 DownloadWindow::_OpenSettingsFile(BFile& file, uint32 mode) 551 { 552 BPath path; 553 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK 554 || path.Append(kApplicationName) != B_OK 555 || path.Append("Downloads") != B_OK) { 556 return false; 557 } 558 return file.SetTo(path.Path(), mode) == B_OK; 559 } 560 561 562