Warning, file /utilities/krusader/app/DiskUsage/radialMap/widgetEvents.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2003-2004 Max Howell <max.howell@methylblue.com>
0003     SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "../../icon.h"
0009 #include "fileTree.h"
0010 #include "radialMap.h" //class Segment
0011 #include "widget.h"
0012 
0013 #include <cmath>
0014 
0015 // QtCore
0016 #include <QTimer> //::resizeEvent()
0017 // QtGui
0018 #include <QPaintEvent>
0019 #include <QPainter>
0020 // QtWidgets
0021 #include <QApplication> //QApplication::setOverrideCursor()
0022 #include <QMenu>
0023 
0024 #include <KI18n/KLocalizedString>
0025 #include <KIO/DeleteJob>
0026 #include <KIO/JobUiDelegate>
0027 #include <KIOWidgets/KRun>
0028 #include <KWidgetsAddons/KMessageBox>
0029 #include <kio_version.h>
0030 #include <kservice_version.h>
0031 
0032 #if KIO_VERSION >= QT_VERSION_CHECK(5, 71, 0)
0033 #include <KIO/OpenUrlJob>
0034 #endif
0035 
0036 #if KSERVICE_VERSION >= QT_VERSION_CHECK(5, 83, 0)
0037 #include <KTerminalLauncherJob>
0038 #else
0039 #include <KToolInvocation>
0040 #endif
0041 
0042 void RadialMap::Widget::resizeEvent(QResizeEvent *)
0043 {
0044     if (m_map.resize(rect())) {
0045         m_timer.setSingleShot(true);
0046         m_timer.start(500); // will cause signature to rebuild for new size
0047     }
0048 
0049     // always do these as they need to be initialised on creation
0050     m_offset.rx() = (width() - m_map.width()) / 2;
0051     m_offset.ry() = (height() - m_map.height()) / 2;
0052 }
0053 
0054 void RadialMap::Widget::paintEvent(QPaintEvent *)
0055 {
0056     // bltBit for some Qt setups will bitBlt _after_ the labels are painted. Which buggers things up!
0057     // shame as bitBlt is faster, possibly Qt bug? Should report the bug? - seems to be race condition
0058     // bitBlt( this, m_offset, &m_map );
0059 
0060     QPainter paint(this);
0061 
0062     paint.drawPixmap(m_offset, m_map);
0063 
0064     // vertical strips
0065     if (m_map.width() < width()) {
0066         paint.eraseRect(0, 0, m_offset.x(), height());
0067         paint.eraseRect(m_map.width() + m_offset.x(), 0, m_offset.x() + 1, height());
0068     }
0069     // horizontal strips
0070     if (m_map.height() < height()) {
0071         paint.eraseRect(0, 0, width(), m_offset.y());
0072         paint.eraseRect(0, m_map.height() + m_offset.y(), width(), m_offset.y() + 1);
0073     }
0074 
0075     // exploded labels
0076     if (!m_map.isNull() && !m_timer.isActive())
0077         paintExplodedLabels(paint);
0078 }
0079 
0080 const RadialMap::Segment *RadialMap::Widget::segmentAt(QPoint &e) const
0081 {
0082     // determine which segment QPoint e is above
0083 
0084     e -= m_offset;
0085 
0086     if (e.x() <= m_map.width() && e.y() <= m_map.height()) {
0087         // transform to cartesian coords
0088         e.rx() -= m_map.width() / 2; // should be an int
0089         e.ry() = m_map.height() / 2 - e.y();
0090 
0091         double length = std::hypot(e.x(), e.y());
0092 
0093         if (length >= m_map.m_innerRadius) { // not hovering over inner circle
0094             uint depth = ((int)length - m_map.m_innerRadius) / m_map.m_ringBreadth;
0095 
0096             if (depth <= m_map.m_visibleDepth) { //**** do earlier since you can //** check not outside of range
0097                 // vector calculation, reduces to simple trigonometry
0098                 // cos angle = (aibi + ajbj) / albl
0099                 // ai = x, bi=1, aj=y, bj=0
0100                 // cos angle = x / (length)
0101 
0102                 auto a = (uint)(acos((double)e.x() / length) * 916.736); // 916.7324722 = #radians in circle * 16
0103 
0104                 // acos only understands 0-180 degrees
0105                 if (e.y() < 0)
0106                     a = 5760 - a;
0107 
0108 #define ring (m_map.m_signature + depth)
0109                 for (ConstIterator<Segment> it = ring->constIterator(); it != ring->end(); ++it)
0110                     if ((*it)->intersects(a))
0111                         return *it;
0112 #undef ring
0113             }
0114         } else
0115             return m_rootSegment; // hovering over inner circle
0116     }
0117 
0118     return nullptr;
0119 }
0120 
0121 void RadialMap::Widget::mouseMoveEvent(QMouseEvent *e)
0122 {
0123     // set m_focus to what we hover over, update UI if it's a new segment
0124 
0125     Segment const *const oldFocus = m_focus;
0126     QPoint p = e->pos();
0127 
0128     m_focus = segmentAt(p); // NOTE p is passed by non-const reference
0129 
0130     if (m_focus && m_focus->file() != m_tree) {
0131         if (m_focus != oldFocus) { // if not same as last time
0132             setCursor(QCursor(Qt::PointingHandCursor));
0133             m_tip.updateTip(m_focus->file(), m_tree);
0134             emit mouseHover(m_focus->file()->fullPath());
0135 
0136             // repaint required to update labels now before transparency is generated
0137             repaint();
0138         }
0139 
0140         // updates tooltip pseudo-transparent background
0141         m_tip.moveto(e->globalPos(), *this, (p.y() < 0));
0142     } else if (oldFocus && oldFocus->file() != m_tree) {
0143         unsetCursor();
0144         m_tip.hide();
0145         update();
0146 
0147         emit mouseHover(QString());
0148     }
0149 }
0150 
0151 void RadialMap::Widget::mousePressEvent(QMouseEvent *e)
0152 {
0153     // m_tip is hidden already by event filter
0154     // m_focus is set correctly (I've been strict, I assure you it is correct!)
0155 
0156     if (m_focus && !m_focus->isFake()) {
0157         const QUrl url = Widget::url(m_focus->file());
0158         const bool isDir = m_focus->file()->isDir();
0159 
0160         if (e->button() == Qt::RightButton) {
0161             QMenu popup;
0162             popup.setTitle(m_focus->file()->fullPath(m_tree));
0163 
0164             QAction *actKonq = nullptr, *actKonsole = nullptr, *actViewMag = nullptr, *actFileOpen = nullptr, *actEditDel = nullptr;
0165 
0166             if (isDir) {
0167                 actKonq = popup.addAction(Icon("system-file-manager"), i18n("Open File Manager Here"));
0168                 if (url.scheme() == "file")
0169                     actKonsole = popup.addAction(Icon("utilities-terminal"), i18n("Open Terminal Here"));
0170 
0171                 if (m_focus->file() != m_tree) {
0172                     popup.addSeparator();
0173                     actViewMag = popup.addAction(Icon("zoom-original"), i18n("&Center Map Here"));
0174                 }
0175             } else
0176                 actFileOpen = popup.addAction(Icon("document-open"), i18n("&Open"));
0177 
0178             popup.addSeparator();
0179             actEditDel = popup.addAction(Icon("edit-delete"), i18n("&Delete"));
0180 
0181             QAction *result = popup.exec(e->globalPos());
0182             if (result == nullptr)
0183                 result = (QAction *)-1; // sanity
0184 
0185             if (result == actKonq) {
0186 #if KIO_VERSION >= QT_VERSION_CHECK(5, 71, 0)
0187                 // KJob jobs will delete themselves when they finish (see kjob.h for more info)
0188                 auto *job = new KIO::OpenUrlJob(url, this);
0189                 job->start();
0190 #else
0191                 // KRun::runCommand will show an error message if there was trouble
0192                 KRun::runCommand(QString("kfmclient openURL '%1'").arg(url.url()), this);
0193 #endif
0194             } else if (result == actKonsole) {
0195 #if KSERVICE_VERSION >= QT_VERSION_CHECK(5, 83, 0)
0196                 auto *job = new KTerminalLauncherJob(QString());
0197                 job->setWorkingDirectory(url.url());
0198                 job->start();
0199 #elif KSERVICE_VERSION >= QT_VERSION_CHECK(5, 79, 0)
0200                 KToolInvocation::invokeTerminal(QString(), QStringList(), url.url());
0201 #else
0202                 KToolInvocation::invokeTerminal(QString(), url.url());
0203 #endif
0204             } else if (result == actViewMag || result == actFileOpen) {
0205                 goto sectionTwo;
0206             } else if (result == actEditDel) {
0207                 const QUrl url = Widget::url(m_focus->file());
0208                 const QString message =
0209                     (m_focus->file()->isDir()
0210                          ? i18n("<qt>The folder at <i>'%1'</i> will be <b>recursively</b> and <b>permanently</b> deleted.</qt>", url.toDisplayString())
0211                          : i18n("<qt><i>'%1'</i> will be <b>permanently</b> deleted.</qt>", url.toDisplayString()));
0212                 const int userIntention = KMessageBox::warningContinueCancel(this, message, QString(), KStandardGuiItem::del());
0213 
0214                 if (userIntention == KMessageBox::Continue) {
0215                     KIO::Job *job = KIO::del(url);
0216                     auto *ui = dynamic_cast<KIO::JobUiDelegate *>(job->uiDelegate());
0217                     ui->setWindow(this);
0218                     connect(job, &KIO::Job::result, this, &Widget::deleteJobFinished);
0219                     QApplication::setOverrideCursor(Qt::BusyCursor);
0220                 }
0221             } else {
0222                 // ensure m_focus is set for new mouse position
0223                 sendFakeMouseEvent();
0224             }
0225 
0226         } else {
0227         sectionTwo:
0228 
0229             const QRect rect(e->x() - 20, e->y() - 20, 40, 40);
0230 
0231             m_tip.hide(); // user expects this
0232 
0233             if (!isDir || e->button() == Qt::MidButton) {
0234                 new KRun(url, this, true); // FIXME see above
0235             } else if (m_focus->file() != m_tree) { // is left mouse button
0236                 emit activated(url); // activate first, this will cause UI to prepare itself
0237                 if (m_focus)
0238                     createFromCache(dynamic_cast<const Directory *>(m_focus->file()));
0239             }
0240         }
0241     }
0242 }
0243 
0244 void RadialMap::Widget::deleteJobFinished(KJob *job)
0245 {
0246     QApplication::restoreOverrideCursor();
0247     if (!job->error())
0248         invalidate();
0249     else
0250         job->uiDelegate()->showErrorMessage();
0251 }