File indexing completed on 2024-12-01 07:38:56

0001 /* This file is part of the KDE project
0002 
0003    Copyright (C) 2002 Patrick Charbonnier <pch@freeshell.org>
0004    Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss
0005    Copyright (C) 2008 Urs Wolfer <uwolfer @ kde.org>
0006 
0007    This program is free software; you can redistribute it and/or
0008    modify it under the terms of the GNU General Public
0009    License as published by the Free Software Foundation; either
0010    version 2 of the License, or (at your option) any later version.
0011 */
0012 
0013 #include "ui/droptarget.h"
0014 
0015 #include "core/kget.h"
0016 #include "core/transfergrouphandler.h"
0017 #include "core/transferhandler.h"
0018 #include "core/transfertreemodel.h"
0019 #include "mainwindow.h"
0020 #include "settings.h"
0021 #include "ui/newtransferdialog.h"
0022 
0023 #include <KMessageBox>
0024 #include <KNotification>
0025 #include <KWindowSystem>
0026 
0027 #include <QBitmap>
0028 #include <QClipboard>
0029 #include <QGuiApplication>
0030 #include <QMenu>
0031 #include <QPainter>
0032 #include <QStringList>
0033 #include <QTimer>
0034 #include <QToolTip>
0035 
0036 #ifndef Q_OS_WIN
0037 #include <KX11Extras>
0038 #endif
0039 
0040 #include <cmath>
0041 
0042 #define TARGET_SIZE 64
0043 #define TARGET_ANI_MS 20
0044 #define TARGET_TOOLTIP_MS 1000
0045 
0046 DropTarget::DropTarget(MainWindow *mw)
0047     : QWidget(nullptr, Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint)
0048     , parentWidget(mw)
0049     , animTimer(nullptr)
0050     , showInformation(false)
0051 {
0052 #ifndef Q_OS_WIN
0053     KX11Extras::setState(winId(), NET::SkipTaskbar | NET::KeepAbove);
0054 #endif
0055 
0056     auto screen = qApp->screenAt(Settings::dropPosition());
0057     if (screen) {
0058         QRect screenGeo = screen->availableGeometry();
0059         if ((screenGeo.x() + screenGeo.width() >= Settings::dropPosition().x() && screenGeo.y() + screenGeo.height() >= Settings::dropPosition().y())
0060             && Settings::dropPosition().y() >= 0 && Settings::dropPosition().x() >= 0)
0061             position = QPoint(Settings::dropPosition());
0062         else
0063             position = QPoint(screenGeo.x() + screenGeo.width() / 2, screenGeo.y() + screenGeo.height() / 2);
0064     }
0065     setFixedSize(TARGET_SIZE, TARGET_SIZE);
0066 
0067     cachedPixmap = QIcon::fromTheme("kget").pixmap(TARGET_SIZE);
0068     if (!cachedPixmap.mask().isNull()) {
0069         QBitmap mask(size());
0070         mask.fill(Qt::color0);
0071         QBitmap pixMask = cachedPixmap.mask();
0072         QPainter p(&mask);
0073         p.drawPixmap((mask.width() - pixMask.width()) / 2, (mask.height() - pixMask.height()) / 2, pixMask);
0074         setMask(mask);
0075     } else
0076         setMask(QBitmap());
0077 
0078     // popup menu for right mouse button
0079     popupMenu = new QMenu(this);
0080     popupMenu->addSection(mw->windowTitle());
0081 
0082     QAction *downloadAction = mw->actionCollection()->action("start_all_download");
0083     popupMenu->addAction(downloadAction);
0084     connect(downloadAction, &QAction::toggled, this, &DropTarget::slotStartStopToggled);
0085     popupMenu->addSeparator();
0086     pop_show = popupMenu->addAction(QString(), this, &DropTarget::toggleMinimizeRestore);
0087     popupMenu->addAction(parentWidget->actionCollection()->action("show_drop_target"));
0088     pop_sticky = popupMenu->addAction(i18nc("fix position for droptarget", "Sticky"), this, &DropTarget::toggleSticky);
0089     pop_sticky->setCheckable(true);
0090     pop_sticky->setChecked(Settings::dropSticky());
0091     popupMenu->addSeparator();
0092     popupMenu->addAction(mw->actionCollection()->action("preferences"));
0093 
0094     auto *quitAction = new QAction(this);
0095     quitAction->setText(i18n("Quit KGet"));
0096     quitAction->setIcon(QIcon::fromTheme("system-shutdown"));
0097     connect(quitAction, &QAction::triggered, mw, &MainWindow::slotQuit);
0098     popupMenu->addAction(quitAction);
0099 
0100     isdragging = false;
0101 
0102     // Enable dropping
0103     setAcceptDrops(true);
0104 
0105     if (Settings::showDropTarget() && Settings::firstRun()) {
0106         showInformation = true;
0107     }
0108 
0109     animTimer = new QTimer(this);
0110     popupTimer = new QTimer(this);
0111 
0112     setMouseTracking(true);
0113 
0114     connect(KGet::model(), &TransferTreeModel::transfersChangedEvent, this, &DropTarget::slotToolTipUpdate);
0115 
0116     connect(popupTimer, &QTimer::timeout, this, &DropTarget::slotToolTipTimer);
0117 }
0118 
0119 DropTarget::~DropTarget()
0120 {
0121     Settings::setDropPosition(pos());
0122     Settings::setShowDropTarget(!isHidden());
0123     Settings::self()->save();
0124     //    unsigned long state = KWindowSystem::windowInfo(kdrop->winId()).state();
0125     //    // state will be 0L if droptarget is hidden. Sigh.
0126     //    config->writeEntry("State", state ? state : DEFAULT_DOCK_STATE );
0127 }
0128 
0129 void DropTarget::setDropTargetVisible(bool shown, bool internal)
0130 {
0131     if (shown == !isHidden())
0132         return;
0133 
0134     if (internal)
0135         Settings::setShowDropTarget(shown);
0136 
0137     if (!shown) {
0138         Settings::setDropPosition(pos());
0139         position = pos();
0140         if (Settings::animateDropTarget())
0141             playAnimationHide();
0142         else
0143             hide();
0144     } else {
0145         if (Settings::animateDropTarget()) {
0146             playAnimationShow();
0147         } else {
0148             move(position);
0149             show();
0150         }
0151         slotToolTipUpdate();
0152     }
0153 }
0154 
0155 void DropTarget::playAnimationShow()
0156 {
0157     if (animTimer->isActive())
0158         animTimer->stop();
0159     animTimer->disconnect();
0160     connect(animTimer, &QTimer::timeout, this, &DropTarget::slotAnimateShow);
0161 
0162     move(position.x(), -TARGET_SIZE);
0163 
0164     ani_y = -1;
0165     ani_vy = 0;
0166     show();
0167     animTimer->start(TARGET_ANI_MS);
0168 }
0169 
0170 void DropTarget::playAnimationHide()
0171 {
0172     if (animTimer->isActive())
0173         animTimer->stop();
0174 
0175     animTimer->disconnect();
0176     connect(animTimer, &QTimer::timeout, this, &DropTarget::slotAnimateHide);
0177     ani_y = (float)y();
0178     ani_vy = 0;
0179     animTimer->start(TARGET_ANI_MS);
0180 }
0181 
0182 void DropTarget::playAnimationSync()
0183 {
0184     if (animTimer->isActive())
0185         animTimer->stop();
0186 
0187     animTimer->disconnect();
0188     connect(animTimer, &QTimer::timeout, this, &DropTarget::slotAnimateSync);
0189     ani_y = (float)y();
0190     ani_vy = -1;
0191     animTimer->start(TARGET_ANI_MS);
0192 }
0193 
0194 void DropTarget::slotStartStopToggled(bool started)
0195 {
0196     if (started && Settings::animateDropTarget())
0197         playAnimationSync();
0198 }
0199 
0200 /** widget events */
0201 
0202 void DropTarget::dragEnterEvent(QDragEnterEvent *event)
0203 {
0204     event->setAccepted(event->mimeData()->hasUrls() || event->mimeData()->hasText());
0205 }
0206 
0207 void DropTarget::dropEvent(QDropEvent *event)
0208 {
0209     QList<QUrl> list = event->mimeData()->urls();
0210     QString str;
0211 
0212     if (!list.isEmpty()) {
0213         if (list.count() == 1 && list.first().url().endsWith(QLatin1String(".kgt"))) {
0214             const int msgBoxResult = KMessageBox::questionTwoActionsCancel(this,
0215                                                                            i18n("The dropped file is a KGet Transfer List"),
0216                                                                            "KGet",
0217                                                                            KGuiItem(i18n("&Download"), QIcon::fromTheme("document-save")),
0218                                                                            KGuiItem(i18n("&Load transfer list"), QIcon::fromTheme("list-add")),
0219                                                                            KStandardGuiItem::cancel());
0220 
0221             if (msgBoxResult == KMessageBox::PrimaryAction) // Download
0222                 NewTransferDialogHandler::showNewTransferDialog(list.first());
0223             else if (msgBoxResult == KMessageBox::SecondaryAction) // Load
0224                 KGet::load(list.first().url());
0225         } else {
0226             if (list.count() == 1) {
0227                 str = event->mimeData()->text();
0228                 NewTransferDialogHandler::showNewTransferDialog(QUrl(str));
0229             } else
0230                 NewTransferDialogHandler::showNewTransferDialog(list);
0231         }
0232     } else {
0233         NewTransferDialogHandler::showNewTransferDialog();
0234     }
0235 
0236     if (Settings::animateDropTarget())
0237         playAnimationSync();
0238 }
0239 
0240 void DropTarget::closeEvent(QCloseEvent *e)
0241 {
0242     if (qApp->isSavingSession())
0243         e->ignore();
0244     else {
0245         setVisible(false);
0246         e->accept();
0247     }
0248 }
0249 
0250 void DropTarget::mousePressEvent(QMouseEvent *e)
0251 {
0252     // If the user click on the droptarget, stop any animation that is going on
0253     if (animTimer) {
0254         animTimer->stop();
0255     }
0256 
0257     if (e->button() == Qt::LeftButton) {
0258         isdragging = true;
0259         dx = e->globalPosition().x() - pos().x();
0260         dy = e->globalPosition().y() - pos().y();
0261     } else if (e->button() == Qt::RightButton) {
0262         pop_show->setText(parentWidget->isHidden() ? i18n("Show Main Window") : i18n("Hide Main Window"));
0263         popupMenu->popup(e->globalPosition().toPoint());
0264     } else if (e->button() == Qt::MiddleButton) {
0265         // Here we paste the transfer
0266         QString newtransfer = QApplication::clipboard()->text();
0267         newtransfer = newtransfer.trimmed();
0268 
0269         if (!newtransfer.isEmpty())
0270             KGet::addTransfer(QUrl(newtransfer), QString(), QString(), QString(), true);
0271     }
0272 }
0273 
0274 void DropTarget::mouseReleaseEvent(QMouseEvent *)
0275 {
0276     isdragging = false;
0277 }
0278 
0279 void DropTarget::mouseDoubleClickEvent(QMouseEvent *e)
0280 {
0281     if (e->button() == Qt::LeftButton)
0282         toggleMinimizeRestore();
0283 }
0284 
0285 void DropTarget::mouseMoveEvent(QMouseEvent *e)
0286 {
0287     Q_UNUSED(e)
0288     if (isdragging && !Settings::dropSticky()) {
0289         move(QCursor::pos().x() - dx, QCursor::pos().y() - dy);
0290         e->accept();
0291     }
0292 }
0293 
0294 void DropTarget::enterEvent(QEnterEvent *event)
0295 {
0296     Q_UNUSED(event)
0297     popupTimer->start(2000);
0298 }
0299 
0300 void DropTarget::leaveEvent(QEvent *event)
0301 {
0302     Q_UNUSED(event)
0303     popupTimer->stop();
0304 }
0305 
0306 void DropTarget::paintEvent(QPaintEvent *)
0307 {
0308     QPainter p(this);
0309     p.drawPixmap(0, 0, cachedPixmap);
0310 }
0311 
0312 void DropTarget::toggleSticky()
0313 {
0314     Settings::setDropSticky(!Settings::dropSticky());
0315     pop_sticky->setChecked(Settings::dropSticky());
0316 }
0317 
0318 void DropTarget::toggleMinimizeRestore()
0319 {
0320     bool nextState = parentWidget->isHidden();
0321     Settings::setShowMain(nextState);
0322     parentWidget->setVisible(nextState);
0323     if (nextState) {
0324         KWindowSystem::activateWindow(parentWidget->windowHandle());
0325     }
0326 }
0327 
0328 /** widget animations */
0329 void DropTarget::slotAnimateShow()
0330 {
0331     static float dT = TARGET_ANI_MS / 1000.0;
0332 
0333     ani_vy -= ani_y * 30 * dT;
0334     ani_vy *= 0.95;
0335     ani_y += ani_vy * dT;
0336 
0337     move(x(), qRound(position.y() * (1 + ani_y)));
0338 
0339     if (fabs(ani_y) < 0.01 && fabs(ani_vy) < 0.01 && animTimer->isActive()) {
0340         animTimer->stop();
0341 
0342         if (showInformation) {
0343             QToolTip::showText({}, i18n("Drop Target") + '\n' + i18n("You can drag download links into the drop target."), this);
0344         }
0345     }
0346 }
0347 
0348 void DropTarget::slotAnimateHide()
0349 {
0350     static float dT = TARGET_ANI_MS / 1000.0;
0351 
0352     ani_vy += -2000 * dT;
0353     float new_y = y() + ani_vy * dT;
0354 
0355     if (new_y < -height()) {
0356         animTimer->stop();
0357         hide();
0358         move(x(), qRound(ani_y));
0359     } else
0360         move(x(), qRound(new_y));
0361 }
0362 
0363 void DropTarget::slotAnimateSync()
0364 {
0365     static float dT = TARGET_ANI_MS / 1000.0;
0366 
0367     ani_vy += 4 * dT; // from -1 to 1 in 0.5 seconds
0368     float i = 2 * M_PI * ani_vy; // from -2PI to 2PI
0369     float j = (i == 0.0) ? 1 : (sin(i) / i) * (1 + fabs(ani_vy));
0370 
0371     if (ani_vy >= 1) {
0372         animTimer->stop();
0373         move(x(), qRound(ani_y));
0374     } else
0375         move(x(), qRound(ani_y + 6 * j));
0376 }
0377 
0378 void DropTarget::slotToolTipUpdate()
0379 {
0380     QStringList dataList;
0381     QString data;
0382 
0383     foreach (TransferHandler *transfer, KGet::allTransfers()) {
0384         data.clear();
0385         switch (transfer->status()) {
0386         case Job::Finished:
0387             data = i18nc("%1 filename, %2 total size, %3 status",
0388                          "%1(%2) %3",
0389                          transfer->source().fileName(),
0390                          KIO::convertSize(transfer->totalSize()),
0391                          transfer->statusText());
0392             break;
0393         case Job::Running:
0394             data = i18nc("%1 filename, %2 percent complete, %3 downloaded out of %4 total size",
0395                          "%1(%2% %3/%4) Speed:%5/s",
0396                          transfer->source().fileName(),
0397                          transfer->percent(),
0398                          KIO::convertSize(transfer->downloadedSize()),
0399                          KIO::convertSize(transfer->totalSize()),
0400                          KIO::convertSize(transfer->downloadSpeed()));
0401             break;
0402         default:
0403             data = i18nc("%1 filename, %2 percent complete, %3 downloaded out of %4 total size, %5 status",
0404                          "%1(%2% %3/%4) %5",
0405                          transfer->source().fileName(),
0406                          transfer->percent(),
0407                          KIO::convertSize(transfer->downloadedSize()),
0408                          KIO::convertSize(transfer->totalSize()),
0409                          transfer->statusText());
0410             break;
0411         }
0412         dataList << data;
0413     }
0414 
0415     if (!dataList.empty())
0416         tooltipText = dataList.join("\n");
0417     else
0418         tooltipText = i18n("Ready");
0419 }
0420 
0421 void DropTarget::slotToolTipTimer()
0422 {
0423     if (!popupMenu->isVisible() && isVisible() && mask().contains(mapFromGlobal(QCursor::pos())))
0424         QToolTip::showText(QCursor::pos(), tooltipText, this, rect());
0425 }
0426 
0427 void DropTarget::slotClose()
0428 {
0429     setVisible(false);
0430 }
0431 
0432 #include "moc_droptarget.cpp"