/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 2002 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Conrad Carlen * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "UDownloadDisplay.h" #include "ApplIDs.h" #include "CIconServicesIcon.h" // Gecko #include "nsString.h" #include "nsILocalFileMac.h" // Std #include using namespace std; // PowerPlant #include #include //***************************************************************************** // CMultiDownloadProgress //***************************************************************************** #pragma mark [CMultiDownloadProgress] bool CMultiDownloadProgress::sRegisteredViewClasses = false; CMultiDownloadProgress::CMultiDownloadProgress() : mWindow(nil) { if (!sRegisteredViewClasses) { RegisterClass_(CMultiDownloadProgressWindow); RegisterClass_(CDownloadProgressView); RegisterClass_(CIconServicesIcon); sRegisteredViewClasses = true; } } CMultiDownloadProgress::~CMultiDownloadProgress() { } void CMultiDownloadProgress::AddDownloadItem(CDownload *aDownloadItem) { if (!mWindow) { mWindow = static_cast (LWindow::CreateWindow(wind_DownloadProgress, this)); ThrowIfNil_(mWindow); // Add this as a listener to the window so we can know when it's destroyed. mWindow->AddListener(this); } // Create the view... LView::SetDefaultView(mWindow); LCommander::SetDefaultCommander(mWindow); LAttachable::SetDefaultAttachable(nil); CDownloadProgressView *itemView = static_cast (UReanimator::ReadObjects(ResType_PPob, view_DownloadProgressItem)); ThrowIfNil_(itemView); // and add it to the window. mWindow->AddDownloadView(itemView); itemView->SetDownload(aDownloadItem); } // This happens in response to the window being closed. Check for active // downloads and confirm stopping them if there are any. Returning false // will prevent the window from being closed. Boolean CMultiDownloadProgress::AllowSubRemoval(LCommander* inSub) { return (!mWindow || (inSub == mWindow && mWindow->ConfirmClose())); } // This happens in response to the app being quit. Check for active // downloads and confirm stopping them if there are any. Returning // false will prevent the app from quitting. Boolean CMultiDownloadProgress::AttemptQuitSelf(SInt32 inSaveOption) { return (!mWindow || mWindow->ConfirmClose()); } void CMultiDownloadProgress::ListenToMessage(MessageT inMessage, void* ioParam) { if (inMessage == msg_BroadcasterDied && (CMultiDownloadProgressWindow*)((LBroadcaster*)ioParam) == mWindow) mWindow = nil; } //***************************************************************************** // CMultiDownloadProgressWindow //***************************************************************************** #pragma mark - #pragma mark [CMultiDownloadProgressWindow] CMultiDownloadProgressWindow::CMultiDownloadProgressWindow() : mDownloadViewCount(0) { StartBroadcasting(); } CMultiDownloadProgressWindow::CMultiDownloadProgressWindow(LStream* inStream) : LWindow(inStream), mDownloadViewCount(0) { StartBroadcasting(); } CMultiDownloadProgressWindow::~CMultiDownloadProgressWindow() { } void CMultiDownloadProgressWindow::AddDownloadView(CDownloadProgressView *aView) { const SInt16 kSeparatorHeight = 3; SDimension16 currSize; GetFrameSize(currSize); SDimension16 viewSize; aView->GetFrameSize(viewSize); ResizeWindowTo(currSize.width, (viewSize.height * (mDownloadViewCount + 1)) - kSeparatorHeight); aView->PlaceInSuperFrameAt(0, viewSize.height * mDownloadViewCount++, false); aView->FinishCreate(); } void CMultiDownloadProgressWindow::RemoveDownloadView(CDownloadProgressView *aView) { // We can't remove the last view, leaving an empty window frame if (mDownloadViewCount <= 1) return; SDimension16 removedPaneSize; aView->GetFrameSize(removedPaneSize); SPoint32 removedPaneLoc; aView->GetFrameLocation(removedPaneLoc); delete aView; RemoveSubPane(aView); mDownloadViewCount--; TArrayIterator iterator(GetSubPanes()); LPane *subPane; while (iterator.Next(subPane)) { SPoint32 subPaneLoc; subPane->GetFrameLocation(subPaneLoc); if (subPaneLoc.v >= removedPaneLoc.v + removedPaneSize.height) { subPane->MoveBy(0, -removedPaneSize.height, true); } } ResizeWindowBy(0, -removedPaneSize.height); } Boolean CMultiDownloadProgressWindow::ConfirmClose() { Boolean canClose = true; SInt32 numActiveDownloads = 0; TArrayIterator iterator(GetSubPanes()); LPane *subPane; while (iterator.Next(subPane)) { CDownloadProgressView *downloadView = dynamic_cast(subPane); if (downloadView && downloadView->IsActive()) numActiveDownloads++; } if (numActiveDownloads != 0) { short itemHit; AlertStdAlertParamRec pb; pb.movable = false; pb.helpButton = false; pb.filterProc = nil; pb.defaultText = (StringPtr) kAlertDefaultOKText; pb.cancelText = (StringPtr) kAlertDefaultCancelText; pb.otherText = nil; pb.defaultButton = kStdOkItemIndex; pb.cancelButton = kStdCancelItemIndex; pb.position = kWindowAlertPositionParentWindowScreen; LStr255 msgString(STRx_StdAlertStrings, str_ConfirmCloseDownloads); LStr255 explainString(STRx_StdAlertStrings, str_ConfirmCloseDownloadsExp); ::StandardAlert(kAlertStopAlert, msgString, explainString, &pb, &itemHit); if (itemHit != kAlertStdAlertOKButton) canClose = false; } return canClose; } //***************************************************************************** // CDownloadProgressView //***************************************************************************** #pragma mark - #pragma mark [CDownloadProgressView] enum { paneID_ProgressBar = 'Prog', paneID_CancelButton = 'Cncl', paneID_OpenButton = 'Open', paneID_RevealButton = 'Rvel', paneID_CloseButton = 'Clos', paneID_StatusText = 'Stat', paneID_StatusLabel = 'StLa', paneID_TimeRemText = 'Time', paneID_TimeRemLabel = 'TiLa', paneID_SrcURIText = 'SURI', paneID_SrcURILabel = 'SULa', paneID_DestFileText = 'Dest', paneID_DestFileLabel = 'DFLa' }; CDownloadProgressView::CDownloadProgressView() : mDownloadActive(false) { } CDownloadProgressView::CDownloadProgressView(LStream* inStream) : LView(inStream), mDownloadActive(false) { } CDownloadProgressView::~CDownloadProgressView() { if (mDownloadActive) CancelDownload(); mDownload = nsnull; } void CDownloadProgressView::FinishCreateSelf() { mProgressBar = dynamic_cast(FindPaneByID(paneID_ProgressBar)); mCancelButton = dynamic_cast(FindPaneByID(paneID_CancelButton)); mOpenButton = dynamic_cast(FindPaneByID(paneID_OpenButton)); mRevealButton = dynamic_cast(FindPaneByID(paneID_RevealButton)); mCloseButton = dynamic_cast(FindPaneByID(paneID_CloseButton)); mStatusText = dynamic_cast(FindPaneByID(paneID_StatusText)); mTimeRemainingText = dynamic_cast(FindPaneByID(paneID_TimeRemText)); mSrcURIText = dynamic_cast(FindPaneByID(paneID_SrcURIText)); mDestFileText = dynamic_cast(FindPaneByID(paneID_DestFileText)); // The control value is set in terms of percent if (mProgressBar) mProgressBar->SetMaxValue(100); ControlFontStyleRec styleRec; if (CreateStyleRecFromThemeFont(kThemeSmallSystemFont, styleRec) == noErr) { if (mStatusText) mStatusText->SetFontStyle(styleRec); if (mTimeRemainingText) mTimeRemainingText->SetFontStyle(styleRec); if (mSrcURIText) mSrcURIText->SetFontStyle(styleRec); if (mDestFileText) mDestFileText->SetFontStyle(styleRec); } if (CreateStyleRecFromThemeFont(kThemeSmallEmphasizedSystemFont, styleRec) == noErr) { ResIDT labelIDs [] = { paneID_StatusLabel, paneID_TimeRemLabel, paneID_SrcURILabel, paneID_DestFileLabel }; for (size_t i = 0; i < sizeof(labelIDs) / sizeof(labelIDs[0]); i++) { LStaticText *staticText = dynamic_cast(FindPaneByID(labelIDs[i])); if (staticText) staticText->SetFontStyle(styleRec); } } UReanimator::LinkListenerToControls(this, this, view_DownloadProgressItem); StartListening(); } Boolean CDownloadProgressView::ObeyCommand(CommandT inCommand, void *ioParam) { #pragma unused(ioParam) Boolean cmdHandled = false; nsCOMPtr targetFile; switch (inCommand) { case paneID_CancelButton: { CancelDownload(); cmdHandled = true; } break; case paneID_OpenButton: { mDownload->GetTarget(getter_AddRefs(targetFile)); if (targetFile) targetFile->Launch(); } break; case paneID_RevealButton: { mDownload->GetTarget(getter_AddRefs(targetFile)); if (targetFile) targetFile->Reveal(); } break; case paneID_CloseButton: { LView *view = this, *superView; while ((superView = view->GetSuperView()) != nil) view = superView; CMultiDownloadProgressWindow *multiWindow = dynamic_cast(view); if (multiWindow) multiWindow->RemoveDownloadView(this); } break; } return cmdHandled; } void CDownloadProgressView::ListenToMessage(MessageT inMessage, void* ioParam) { switch (inMessage) { case CDownload::msg_OnDLStart: { mDownloadActive = true; if (mCancelButton) mCancelButton->Enable(); } break; case CDownload::msg_OnDLComplete: { mDownloadActive = false; if (mCancelButton) mCancelButton->Disable(); if (mCloseButton) mCloseButton->Enable(); CDownload *download = reinterpret_cast(ioParam); nsresult downloadStatus; download->GetStatus(downloadStatus); // When saving documents as plain text, we might not get any // progress change notifications in which to set the progress bar. // Now that the download is done, put the bar in a reasonable state. if (mProgressBar) { mProgressBar->SetIndeterminateFlag(false, false); if (NS_SUCCEEDED(downloadStatus)) mProgressBar->SetValue(mProgressBar->GetMaxValue()); else mProgressBar->SetValue(0); } if (NS_SUCCEEDED(downloadStatus)) { if (mOpenButton) mOpenButton->Enable(); } if (mRevealButton) mRevealButton->Enable(); if (mTimeRemainingText) mTimeRemainingText->SetText(LStr255("\p")); } break; case CDownload::msg_OnDLProgressChange: { CDownload::MsgOnDLProgressChangeInfo *info = static_cast(ioParam); if (mProgressBar) { PRInt32 percentComplete; info->mBroadcaster->GetPercentComplete(&percentComplete); if (percentComplete != -1 && mProgressBar->IsIndeterminate()) mProgressBar->SetIndeterminateFlag(false, false); else if (percentComplete == -1 && !mProgressBar->IsIndeterminate()) mProgressBar->SetIndeterminateFlag(true, true); if (!mProgressBar->IsIndeterminate()) { PRInt32 controlVal = min(100, max(0, percentComplete)); mProgressBar->SetValue(controlVal); } } // Set the progress bar as often as we're called. Smooth movement is nice. // Limit the frequency at which the textual status is updated though. if (info->mCurProgress == info->mMaxProgress || ::TickCount() - mLastStatusUpdateTicks >= kStatusUpdateIntervalTicks) { UpdateStatus(info); mLastStatusUpdateTicks = ::TickCount(); } } break; default: ProcessCommand(inMessage, ioParam); break; } } void CDownloadProgressView::SetDownload(CDownload *aDownload) { mDownload = aDownload; if (!mDownload) return; mDownloadActive = true; mLastStatusUpdateTicks = ::TickCount(); aDownload->AddListener(this); nsresult rv; nsCAutoString tempStr; if (mSrcURIText) { nsCOMPtr srcURI; aDownload->GetSource(getter_AddRefs(srcURI)); if (srcURI) { rv = srcURI->GetSpec(tempStr); if (NS_SUCCEEDED(rv)) mSrcURIText->SetText(const_cast(PromiseFlatCString(tempStr).get()), tempStr.Length()); } } if (mDestFileText) { nsCOMPtr destFile; aDownload->GetTarget(getter_AddRefs(destFile)); if (destFile) { rv = destFile->GetNativePath(tempStr); if (NS_SUCCEEDED(rv)) mDestFileText->SetText(const_cast(PromiseFlatCString(tempStr).get()), tempStr.Length()); } } // At this point, make sure our window is showing. LWindow::FetchWindowObject(GetMacWindow())->Show(); } void CDownloadProgressView::CancelDownload() { CDownload *download = dynamic_cast(mDownload.get()); download->Cancel(); } Boolean CDownloadProgressView::IsActive() { return mDownloadActive; } void CDownloadProgressView::UpdateStatus(CDownload::MsgOnDLProgressChangeInfo *info) { PRInt64 startTime; mDownload->GetStartTime(&startTime); PRInt32 elapsedSecs = (PR_Now() - startTime) / PR_USEC_PER_SEC; float bytesPerSec = info->mCurProgress / elapsedSecs; UInt8 startPos; LStr255 valueStr; if (mStatusText) { // "@1 of @2 (at @3/sec)" LStr255 formatStr(STRx_DownloadStatus, str_ProgressFormat); // Insert each item into the string individually in order to // allow certain elements to be omitted from the format. if ((startPos = formatStr.Find("\p@1")) != 0) formatStr.Replace(startPos, 2, FormatBytes(info->mCurProgress, valueStr)); if ((startPos = formatStr.Find("\p@2")) != 0) formatStr.Replace(startPos, 2, FormatBytes(info->mMaxProgress, valueStr)); if ((startPos = formatStr.Find("\p@3")) != 0) formatStr.Replace(startPos, 2, FormatBytes(bytesPerSec, valueStr)); mStatusText->SetText(formatStr); } if (mTimeRemainingText) { PRInt32 secsRemaining = (PRInt32)(float(info->mMaxProgress - info->mCurProgress) / bytesPerSec + 0.5); mTimeRemainingText->SetText(FormatFuzzyTime(secsRemaining, valueStr)); } } LStr255& CDownloadProgressView::FormatBytes(float inBytes, LStr255& ioString) { const float kOneThousand24 = 1024.0; char buf[256]; if (inBytes < 0) { return (ioString = "???"); } if (inBytes < kOneThousand24) { sprintf(buf, "%.1f Bytes", inBytes); return (ioString = buf); } inBytes /= kOneThousand24; if (inBytes < 1024) { sprintf(buf, "%.1f KB", inBytes); return (ioString = buf); } inBytes /= kOneThousand24; if (inBytes < 1024) { sprintf(buf, "%.1f MB", inBytes); return (ioString = buf); } inBytes /= kOneThousand24; sprintf(buf, "%.2f GB", inBytes); return (ioString = buf); } LStr255& CDownloadProgressView::FormatFuzzyTime(PRInt32 inSecs, LStr255& ioString) { char valueBuf[32]; if (inSecs < 90) { if (inSecs < 7) ioString.Assign(STRx_DownloadStatus, str_About5Seconds); else if (inSecs < 13) ioString.Assign(STRx_DownloadStatus, str_About10Seconds); else if (inSecs < 60) ioString.Assign(STRx_DownloadStatus, str_LessThan1Minute); else ioString.Assign(STRx_DownloadStatus, str_About1Minute); return ioString; } inSecs = (inSecs + 30) / 60; // Round up so we don't say "About 1 minutes" if (inSecs < 60) { sprintf(valueBuf, "%d", inSecs); ioString.Assign(STRx_DownloadStatus, str_AboutNMinutes); ioString.Replace(ioString.Find("\p@1"), 2, LStr255(valueBuf)); return ioString; } inSecs /= 60; if (inSecs == 1) ioString.Assign(STRx_DownloadStatus, str_About1Hour); else { sprintf(valueBuf, "%d", inSecs); ioString.Assign(STRx_DownloadStatus, str_AboutNHours); ioString.Replace(ioString.Find("\p@1"), 2, LStr255(valueBuf)); } return ioString; } OSErr CDownloadProgressView::CreateStyleRecFromThemeFont(ThemeFontID inThemeID, ControlFontStyleRec& outStyle) { Str255 themeFontName; SInt16 themeFontSize; Style themeStyle; SInt16 themeFontNum; OSErr err = ::GetThemeFont(kThemeSmallSystemFont, smSystemScript, themeFontName, &themeFontSize, &themeStyle); if (err != noErr) return err; outStyle.flags = kControlUseFontMask + kControlUseFaceMask + kControlUseSizeMask; ::GetFNum(themeFontName, &themeFontNum); outStyle.font = themeFontNum; outStyle.size = themeFontSize; outStyle.style = themeStyle; return noErr; }