1 /**************************************************************************
2 * Otter Browser: Web browser controlled by the user, not vice-versa.
3 * Copyright (C) 2018 Michal Dutkiewicz aka Emdek <michal@emdek.pl>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 **************************************************************************/
19 
20 #include "TransfersWidget.h"
21 #include "../../../core/Application.h"
22 #include "../../../core/ThemesManager.h"
23 #include "../../../core/TransfersManager.h"
24 #include "../../../core/Utils.h"
25 #include "../../../ui/Action.h"
26 #include "../../../ui/ProgressBarWidget.h"
27 
28 #include <QtCore/QDir>
29 #include <QtCore/QFileInfo>
30 #include <QtCore/QtMath>
31 #include <QtGui/QMouseEvent>
32 #include <QtWidgets/QBoxLayout>
33 #include <QtWidgets/QFileIconProvider>
34 #include <QtWidgets/QFrame>
35 #include <QtWidgets/QMenu>
36 #include <QtWidgets/QToolTip>
37 #include <QtWidgets/QWidgetAction>
38 
39 namespace Otter
40 {
41 
TransfersWidget(const ToolBarsManager::ToolBarDefinition::Entry & definition,QWidget * parent)42 TransfersWidget::TransfersWidget(const ToolBarsManager::ToolBarDefinition::Entry &definition, QWidget *parent) : ToolButtonWidget(definition, parent),
43 	m_icon(ThemesManager::createIcon(QLatin1String("transfers")))
44 {
45 	setMenu(new QMenu(this));
46 	setPopupMode(QToolButton::InstantPopup);
47 	setToolTip(tr("Downloads"));
48 	updateState();
49 
50 	connect(TransfersManager::getInstance(), &TransfersManager::transferChanged, this, &TransfersWidget::updateState);
51 	connect(TransfersManager::getInstance(), &TransfersManager::transferStarted, this, [&](Transfer *transfer)
52 	{
53 		if ((!transfer->isArchived() || transfer->getState() == Transfer::RunningState) && menu()->isVisible())
54 		{
55 			QAction *firstAction(menu()->actions().value(0));
56 			QWidgetAction *widgetAction(new QWidgetAction(menu()));
57 			widgetAction->setDefaultWidget(new TransferActionWidget(transfer, menu()));
58 
59 			menu()->insertAction(firstAction, widgetAction);
60 			menu()->insertSeparator(firstAction);
61 		}
62 
63 		updateState();
64 	});
65 	connect(TransfersManager::getInstance(), &TransfersManager::transferFinished, this, [&](Transfer *transfer)
66 	{
67 		const QList<QAction*> actions(menu()->actions());
68 
69 		for (int i = 0; i < actions.count(); ++i)
70 		{
71 			const QWidgetAction *widgetAction(qobject_cast<QWidgetAction*>(actions.at(i)));
72 
73 			if (widgetAction && widgetAction->defaultWidget())
74 			{
75 				const TransferActionWidget *transferActionWidget(qobject_cast<TransferActionWidget*>(widgetAction->defaultWidget()));
76 
77 				if (transferActionWidget && transferActionWidget->getTransfer() == transfer)
78 				{
79 					menu()->removeAction(actions.at(i));
80 					menu()->removeAction(actions.value(i + 1));
81 
82 					break;
83 				}
84 			}
85 		}
86 
87 		updateState();
88 	});
89 	connect(TransfersManager::getInstance(), &TransfersManager::transferRemoved, this, &TransfersWidget::updateState);
90 	connect(TransfersManager::getInstance(), &TransfersManager::transferStopped, this, &TransfersWidget::updateState);
91 	connect(menu(), &QMenu::aboutToShow, this, &TransfersWidget::populateMenu);
92 	connect(menu(), &QMenu::aboutToHide, menu(), &QMenu::clear);
93 }
94 
changeEvent(QEvent * event)95 void TransfersWidget::changeEvent(QEvent *event)
96 {
97 	ToolButtonWidget::changeEvent(event);
98 
99 	if (event->type() == QEvent::LanguageChange)
100 	{
101 		setToolTip(tr("Downloads"));
102 	}
103 }
104 
populateMenu()105 void TransfersWidget::populateMenu()
106 {
107 	const QVector<Transfer*> transfers(TransfersManager::getInstance()->getTransfers());
108 
109 	for (int i = 0; i < transfers.count(); ++i)
110 	{
111 		Transfer *transfer(transfers.at(i));
112 
113 		if (!transfer->isArchived() || transfer->getState() == Transfer::RunningState)
114 		{
115 			QWidgetAction *widgetAction(new QWidgetAction(menu()));
116 			widgetAction->setDefaultWidget(new TransferActionWidget(transfer, menu()));
117 
118 			menu()->addAction(widgetAction);
119 			menu()->addSeparator();
120 		}
121 	}
122 
123 	menu()->addAction(new Action(ActionsManager::TransfersAction, {}, {{QLatin1String("text"), tr("Show all Downloads")}}, ActionExecutor::Object(Application::getInstance(), Application::getInstance()), this));
124 }
125 
updateState()126 void TransfersWidget::updateState()
127 {
128 	const QVector<Transfer*> transfers(TransfersManager::getInstance()->getTransfers());
129 	qint64 bytesTotal(0);
130 	qint64 bytesReceived(0);
131 	qint64 transferAmount(0);
132 
133 	for (int i = 0; i < transfers.count(); ++i)
134 	{
135 		Transfer *transfer(transfers.at(i));
136 
137 		if (transfer->getState() == Transfer::RunningState && transfer->getBytesTotal() > 0)
138 		{
139 			++transferAmount;
140 
141 			bytesTotal += transfer->getBytesTotal();
142 			bytesReceived += transfer->getBytesReceived();
143 		}
144 	}
145 
146 	setIcon(getIcon());
147 }
148 
getIcon() const149 QIcon TransfersWidget::getIcon() const
150 {
151 	return m_icon;
152 }
153 
TransferActionWidget(Transfer * transfer,QWidget * parent)154 TransferActionWidget::TransferActionWidget(Transfer *transfer, QWidget *parent) : QWidget(parent),
155 	m_transfer(transfer),
156 	m_detailsLabel(new QLabel(this)),
157 	m_fileNameLabel(new QLabel(this)),
158 	m_iconLabel(new QLabel(this)),
159 	m_progressBar(new ProgressBarWidget(this)),
160 	m_toolButton(new QToolButton(this)),
161 	m_centralWidget(new QWidget(this))
162 {
163 	QVBoxLayout *centralLayout(new QVBoxLayout(m_centralWidget));
164 	centralLayout->setContentsMargins(0, 0, 0, 0);
165 	centralLayout->addWidget(m_fileNameLabel);
166 	centralLayout->addWidget(m_progressBar);
167 	centralLayout->addWidget(m_detailsLabel);
168 
169 	QFrame *leftSeparatorFrame(new QFrame(this));
170 	leftSeparatorFrame->setFrameShape(QFrame::VLine);
171 
172 	QFrame *rightSeparatorFrame(new QFrame(this));
173 	rightSeparatorFrame->setFrameShape(QFrame::VLine);
174 
175 	QHBoxLayout *mainLayout(new QHBoxLayout(this));
176 	mainLayout->addWidget(m_iconLabel);
177 	mainLayout->addWidget(leftSeparatorFrame);
178 	mainLayout->addWidget(m_centralWidget);
179 	mainLayout->addWidget(rightSeparatorFrame);
180 	mainLayout->addWidget(m_toolButton);
181 
182 	setLayout(mainLayout);
183 	updateState();
184 
185 	m_iconLabel->setFixedSize(32, 32);
186 	m_progressBar->setMode(ProgressBarWidget::ThinMode);
187 	m_toolButton->setIconSize({16, 16});
188 	m_toolButton->setAutoRaise(true);
189 
190 	connect(transfer, &Transfer::changed, this, &TransferActionWidget::updateState);
191 	connect(transfer, &Transfer::finished, this, &TransferActionWidget::updateState);
192 	connect(transfer, &Transfer::stopped, this, &TransferActionWidget::updateState);
193 	connect(transfer, &Transfer::progressChanged, this, &TransferActionWidget::updateState);
194 	connect(m_toolButton, &QToolButton::clicked, [&]()
195 	{
196 		switch (m_transfer->getState())
197 		{
198 			case Transfer::CancelledState:
199 			case Transfer::ErrorState:
200 				m_transfer->restart();
201 
202 				break;
203 			case Transfer::FinishedState:
204 				Utils::runApplication({}, QUrl::fromLocalFile(QFileInfo(m_transfer->getTarget()).dir().canonicalPath()));
205 
206 				break;
207 			default:
208 				m_transfer->stop();
209 
210 				break;
211 		}
212 	});
213 }
214 
mousePressEvent(QMouseEvent * event)215 void TransferActionWidget::mousePressEvent(QMouseEvent *event)
216 {
217 	event->accept();
218 }
219 
mouseReleaseEvent(QMouseEvent * event)220 void TransferActionWidget::mouseReleaseEvent(QMouseEvent *event)
221 {
222 	event->accept();
223 
224 	if (event->button() == Qt::LeftButton)
225 	{
226 		m_transfer->openTarget();
227 	}
228 }
229 
updateState()230 void TransferActionWidget::updateState()
231 {
232 	const QString iconName(m_transfer->getMimeType().iconName());
233 	QString details;
234 	QVector<QPair<QString, QString> > detailsValues({{tr("From:"), Utils::extractHost(m_transfer->getSource())}});
235 	const bool isIndeterminate(m_transfer->getBytesTotal() <= 0);
236 	const bool hasError(m_transfer->getState() == Transfer::UnknownState || m_transfer->getState() == Transfer::ErrorState);
237 
238 	if (m_transfer->getState() == Transfer::FinishedState)
239 	{
240 		detailsValues.append({tr("Size:"), tr("%1 (download completed)").arg(Utils::formatUnit(m_transfer->getBytesTotal()))});
241 	}
242 	else
243 	{
244 		detailsValues.append({tr("Size:"), tr("%1 (%2% downloaded)").arg(Utils::formatUnit(m_transfer->getBytesTotal())).arg(Utils::calculatePercent(m_transfer->getBytesReceived(), m_transfer->getBytesTotal()), 0, 'f', 1)});
245 	}
246 
247 	for (int i = 0; i < detailsValues.count(); ++i)
248 	{
249 		details.append(detailsValues.at(i).first + QLatin1Char(' ') + detailsValues.at(i).second);
250 
251 		if (i < (detailsValues.count() - 1))
252 		{
253 			details.append(QLatin1String("<br>"));
254 		}
255 	}
256 
257 	m_fileNameLabel->setText(Utils::elideText(QFileInfo(m_transfer->getTarget()).fileName(), m_fileNameLabel->fontMetrics(), nullptr, 300));
258 	m_detailsLabel->setText(QLatin1String("<small>") + details + QLatin1String("</small>"));
259 	m_iconLabel->setPixmap(QIcon::fromTheme(iconName, QFileIconProvider().icon(iconName)).pixmap(32, 32));
260 	m_progressBar->setHasError(hasError);
261 	m_progressBar->setRange(0, ((isIndeterminate && !hasError) ? 0 : 100));
262 	m_progressBar->setValue(isIndeterminate ? (hasError ? 0 : -1) : ((m_transfer->getBytesTotal() > 0) ? qFloor(Utils::calculatePercent(m_transfer->getBytesReceived(), m_transfer->getBytesTotal())) : -1));
263 	m_progressBar->setFormat(isIndeterminate ? tr("Unknown") : QLatin1String("%p%"));
264 
265 	switch (m_transfer->getState())
266 	{
267 		case Transfer::CancelledState:
268 		case Transfer::ErrorState:
269 			m_toolButton->setIcon(ThemesManager::createIcon(QLatin1String("view-refresh")));
270 			m_toolButton->setToolTip(tr("Redownload"));
271 
272 			break;
273 		case Transfer::FinishedState:
274 			m_toolButton->setIcon(ThemesManager::createIcon(QLatin1String("document-open-folder")));
275 			m_toolButton->setToolTip(tr("Open Folder"));
276 
277 			break;
278 		default:
279 			m_toolButton->setIcon(ThemesManager::createIcon(QLatin1String("task-reject")));
280 			m_toolButton->setToolTip(tr("Stop"));
281 
282 			break;
283 	}
284 }
285 
getTransfer() const286 Transfer* TransferActionWidget::getTransfer() const
287 {
288 	return m_transfer;
289 }
290 
event(QEvent * event)291 bool TransferActionWidget::event(QEvent *event)
292 {
293 	if (event->type() == QEvent::ToolTip)
294 	{
295 		const bool isIndeterminate(m_transfer->getBytesTotal() <= 0);
296 
297 		QToolTip::showText(static_cast<QHelpEvent*>(event)->globalPos(), tr("<div style=\"white-space:pre;\">Source: %1\nTarget: %2\nSize: %3\nDownloaded: %4\nProgress: %5</div>").arg(m_transfer->getSource().toDisplayString().toHtmlEscaped()).arg(m_transfer->getTarget().toHtmlEscaped()).arg(isIndeterminate ? tr("Unknown") : Utils::formatUnit(m_transfer->getBytesTotal(), false, 1, true)).arg(Utils::formatUnit(m_transfer->getBytesReceived(), false, 1, true)).arg(isIndeterminate ? tr("Unknown") : QStringLiteral("%1%").arg(Utils::calculatePercent(m_transfer->getBytesReceived(), m_transfer->getBytesTotal()), 0, 'f', 1)));
298 
299 		return true;
300 	}
301 
302 	return QWidget::event(event);
303 }
304 
305 }
306