diff --git a/src/radialMap/widget.h b/src/radialMap/widget.h index 33d5fdd..2d9c6c5 100644 --- a/src/radialMap/widget.h +++ b/src/radialMap/widget.h @@ -1,124 +1,124 @@ /*********************************************************************** * Copyright 2003-2004 Max Howell * Copyright 2008-2009 Martin Sandsmark * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . ***********************************************************************/ #ifndef WIDGET_H #define WIDGET_H #include #include #include #include #include #include #include #include #include #include #include "map.h" class Folder; class File; namespace KIO { class Job; } namespace RadialMap { class Segment; class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget* = nullptr, bool = false); ~Widget() override; QString path() const; QUrl url(File const * const = nullptr) const; bool isValid() const { return m_tree != nullptr; } bool isSummary() const { return m_isSummary; } friend class Label; //FIXME badness public Q_SLOTS: void zoomIn(); void zoomOut(); void create(const Folder*); void invalidate(); void refresh(int); private Q_SLOTS: void resizeTimeout(); void sendFakeMouseEvent(); void deleteJobFinished(KJob*); void createFromCache(const Folder*); Q_SIGNALS: void activated(const QUrl&); void invalidated(const QUrl&); void folderCreated(const Folder*); void mouseHover(const QString&); void giveMeTreeFor(const QUrl&); protected: void changeEvent(QEvent*) override; void dragEnterEvent(QDragEnterEvent*) override; void dropEvent(QDropEvent*) override; void mouseMoveEvent(QMouseEvent*) override; void mousePressEvent(QMouseEvent*) override; void paintEvent(QPaintEvent*) override; void resizeEvent(QResizeEvent*) override; void enterEvent(QEvent*) override; void leaveEvent(QEvent*) override; protected: - const Segment *segmentAt(QPoint&) const; //FIXME const reference for a library others can use + const Segment *segmentAt(QPoint position) const; //FIXME const reference for a library others can use const Segment *rootSegment() const { return m_rootSegment; ///never == 0 } const Segment *focusSegment() const { return m_focus; ///0 == nothing in focus } private: void paintExplodedLabels(QPainter&) const; const Folder *m_tree; const Segment *m_focus; QPoint m_offset; QTimer m_timer; Map m_map; Segment *m_rootSegment; const bool m_isSummary; const Segment *m_toBeDeleted; QLabel m_tooltip; }; } #endif diff --git a/src/radialMap/widgetEvents.cpp b/src/radialMap/widgetEvents.cpp index 2e7479f..a5965f0 100644 --- a/src/radialMap/widgetEvents.cpp +++ b/src/radialMap/widgetEvents.cpp @@ -1,388 +1,412 @@ /*********************************************************************** * Copyright 2003-2004 Max Howell * Copyright 2008-2009 Martin Sandsmark * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . ***********************************************************************/ #include "fileTree.h" #include "Config.h" #include "radialMap.h" //class Segment #include "widget.h" #include //::mouseMoveEvent() #include #include //::mousePressEvent() #include #include #include //::mousePressEvent() #include //::mousePressEvent() #include //::mousePressEvent() #include #include #include #include #include #include //QApplication::setOverrideCursor() #include #include #include //::resizeEvent() #include #include #include #include #include #include #include +#include +#include #include #include //::segmentAt() void RadialMap::Widget::resizeEvent(QResizeEvent*) { if (m_map.resize(rect())) m_timer.setSingleShot(true); m_timer.start(500); //will cause signature to rebuild for new size //always do these as they need to be initialised on creation m_offset.rx() = (width() - m_map.width()) / 2; m_offset.ry() = (height() - m_map.height()) / 2; } void RadialMap::Widget::paintEvent(QPaintEvent*) { QPainter paint; paint.begin(this); if (!m_map.isNull()) paint.drawPixmap(m_offset, m_map.pixmap()); else { paint.drawText(rect(), 0, i18nc("We messed up, the user needs to initiate a rescan.", "Internal representation is invalid,\nplease rescan.")); return; } //exploded labels if (!m_map.isNull() && !m_timer.isActive()) { if (Config::antialias) { paint.setRenderHint(QPainter::Antialiasing); //make lines appear on pixel boundaries paint.translate(0.5, 0.5); } paintExplodedLabels(paint); } } -const RadialMap::Segment* RadialMap::Widget::segmentAt(QPoint &e) const +const RadialMap::Segment* RadialMap::Widget::segmentAt(QPoint e) const { //determine which segment QPoint e is above e -= m_offset; if (!m_map.m_signature) return nullptr; if (e.x() <= m_map.width() && e.y() <= m_map.height()) { //transform to cartesian coords e.rx() -= m_map.width() / 2; //should be an int e.ry() = m_map.height() / 2 - e.y(); double length = hypot(e.x(), e.y()); if (length >= m_map.m_innerRadius) //not hovering over inner circle { uint depth = ((int)length - m_map.m_innerRadius) / m_map.m_ringBreadth; if (depth <= m_map.m_visibleDepth) //**** do earlier since you can //** check not outside of range { //vector calculation, reduces to simple trigonometry //cos angle = (aibi + ajbj) / albl //ai = x, bi=1, aj=y, bj=0 //cos angle = x / (length) uint a = (uint)(acos((double)e.x() / length) * 916.736); //916.7324722 = #radians in circle * 16 //acos only understands 0-180 degrees if (e.y() < 0) a = 5760 - a; for (Segment *segment : m_map.m_signature[depth]) { if (segment->intersects(a)) return segment; } } } else return m_rootSegment; //hovering over inner circle } return nullptr; } void RadialMap::Widget::mouseMoveEvent(QMouseEvent *e) { //set m_focus to what we hover over, update UI if it's a new segment Segment const * const oldFocus = m_focus; - QPoint p = e->pos(); + m_focus = segmentAt(e->pos()); - m_focus = segmentAt(p); //NOTE p is passed by non-const reference + if (!m_focus) { + if (oldFocus && oldFocus->file() != m_tree) { + m_tooltip.hide(); + unsetCursor(); + update(); - if (m_focus) - { - m_tooltip.move(e->globalX() + 20, e->globalY() + 20); - if (m_focus != oldFocus) //if not same as last time - { - setCursor(Qt::PointingHandCursor); - - QString string; - - if (isSummary()) { - if (strcmp("used", m_focus->file()->name8Bit()) == 0) { - string = i18nc("Tooltip of used space on the partition, %1 is path, %2 is size", - "%1\nUsed: %2", - m_focus->file()->parent()->displayPath(), - m_focus->file()->humanReadableSize()); - } else if (strcmp("free", m_focus->file()->name8Bit()) == 0) { - string = i18nc("Tooltip of free space on the partition, %1 is path, %2 is size", - "%1\nFree: %2", - m_focus->file()->parent()->displayPath(), - m_focus->file()->humanReadableSize()); - } else { - string = i18nc("Tooltip of file/folder, %1 is path, %2 is size", - "%1\n%2", - m_focus->file()->displayPath(), - m_focus->file()->humanReadableSize()); - } - } else { - string = i18nc("Tooltip of file/folder, %1 is path, %2 is size", - "%1\n%2", - m_focus->file()->displayPath(), - m_focus->file()->humanReadableSize()); - - if (m_focus->file()->isFolder()) { - int files = static_cast(m_focus->file())->children(); - const uint percent = uint((100 * files) / (double)m_tree->children()); - - string += QLatin1Char('\n'); - if (percent > 0) { - string += i18ncp("Tooltip of folder, %1 is number of files", - "%1 File (%2%)", "%1 Files (%2%)", - files, percent); - } else { - string += i18ncp("Tooltip of folder, %1 is number of files", - "%1 File", "%1 Files", - files); - } - } + emit mouseHover(QString()); + } - const QUrl url = Widget::url(m_focus->file()); - if (m_focus == m_rootSegment && url != KIO::upUrl(url)) { - string += i18n("\nClick to go up to parent directory"); - } - } + return; + } + + const QRect screenRect = window()->windowHandle()->screen()->availableGeometry(); + + QPoint tooltipPosition = e->globalPos() + QPoint(20, 20); + QRect tooltipRect(tooltipPosition, m_tooltip.size()); + + // Same content as before + if (m_focus == oldFocus) { + if (tooltipRect.right() > screenRect.right()) { + tooltipPosition.setX(screenRect.width() - m_tooltip.width()); + } + if (tooltipRect.bottom() > screenRect.bottom()) { + tooltipPosition.setY(screenRect.height() - m_tooltip.height()); + } + m_tooltip.move(tooltipPosition); + return; + } - // Calculate a semi-sane size for the tooltip - QFontMetrics fontMetrics(font()); - int tooltipWidth = 0; - int tooltipHeight = 0; - for (const QString &part : string.split(QLatin1Char('\n'))) { - tooltipHeight += fontMetrics.height(); - tooltipWidth = qMax(tooltipWidth, fontMetrics.width(part)); + setCursor(Qt::PointingHandCursor); + + QString string; + + if (isSummary()) { + if (strcmp("used", m_focus->file()->name8Bit()) == 0) { + string = i18nc("Tooltip of used space on the partition, %1 is path, %2 is size", + "%1\nUsed: %2", + m_focus->file()->parent()->displayPath(), + m_focus->file()->humanReadableSize()); + } else if (strcmp("free", m_focus->file()->name8Bit()) == 0) { + string = i18nc("Tooltip of free space on the partition, %1 is path, %2 is size", + "%1\nFree: %2", + m_focus->file()->parent()->displayPath(), + m_focus->file()->humanReadableSize()); + } else { + string = i18nc("Tooltip of file/folder, %1 is path, %2 is size", + "%1\n%2", + m_focus->file()->displayPath(), + m_focus->file()->humanReadableSize()); + } + } else { + string = i18nc("Tooltip of file/folder, %1 is path, %2 is size", + "%1\n%2", + m_focus->file()->displayPath(), + m_focus->file()->humanReadableSize()); + + if (m_focus->file()->isFolder()) { + int files = static_cast(m_focus->file())->children(); + const uint percent = uint((100 * files) / (double)m_tree->children()); + + string += QLatin1Char('\n'); + if (percent > 0) { + string += i18ncp("Tooltip of folder, %1 is number of files", + "%1 File (%2%)", "%1 Files (%2%)", + files, percent); + } else { + string += i18ncp("Tooltip of folder, %1 is number of files", + "%1 File", "%1 Files", + files); } - // Limit it to the window size, probably should find something better - tooltipWidth = qMin(tooltipWidth, window()->width()); - tooltipWidth += 10; - tooltipHeight += 10; - m_tooltip.resize(tooltipWidth, tooltipHeight); - m_tooltip.setText(string); - m_tooltip.show(); - - emit mouseHover(m_focus->file()->displayPath()); - update(); + } + + const QUrl url = Widget::url(m_focus->file()); + if (m_focus == m_rootSegment && url != KIO::upUrl(url)) { + string += i18n("\nClick to go up to parent directory"); } } - else if (oldFocus && oldFocus->file() != m_tree) - { - m_tooltip.hide(); - unsetCursor(); - update(); - emit mouseHover(QString()); + // Calculate a semi-sane size for the tooltip + QFontMetrics fontMetrics(font()); + int tooltipWidth = 0; + int tooltipHeight = 0; + for (const QString &part : string.split(QLatin1Char('\n'))) { + tooltipHeight += fontMetrics.height(); + tooltipWidth = qMax(tooltipWidth, fontMetrics.width(part)); + } + tooltipWidth += 10; + tooltipHeight += 10; + + m_tooltip.resize(tooltipWidth, tooltipHeight); + m_tooltip.setText(string); + + // Make sure we're visible on screen + tooltipRect.setSize(QSize(tooltipWidth, tooltipHeight)); + if (tooltipRect.right() > screenRect.right()) { + tooltipPosition.setX(screenRect.width() - m_tooltip.width()); + } + if (tooltipRect.bottom() > screenRect.bottom()) { + tooltipPosition.setY(screenRect.height() - m_tooltip.height()); } + m_tooltip.move(tooltipPosition); + + m_tooltip.show(); + + emit mouseHover(m_focus->file()->displayPath()); + update(); } void RadialMap::Widget::enterEvent(QEvent *) { if (!m_focus) return; setCursor(Qt::PointingHandCursor); emit mouseHover(m_focus->file()->displayPath()); update(); } void RadialMap::Widget::leaveEvent(QEvent *) { m_tooltip.hide(); } void RadialMap::Widget::mousePressEvent(QMouseEvent *e) { if (!isEnabled()) return; //m_focus is set correctly (I've been strict, I assure you it is correct!) if (!m_focus || m_focus->isFake()) { return; } const QUrl url = Widget::url(m_focus->file()); const bool isDir = m_focus->file()->isFolder(); // Open file if (e->button() == Qt::MidButton || (e->button() == Qt::LeftButton && !isDir)) { new KRun(url, this, true); return; } if (e->button() == Qt::LeftButton) { if (m_focus->file() != m_tree) { emit activated(url); //activate first, this will cause UI to prepare itself createFromCache((Folder *)m_focus->file()); } else if (KIO::upUrl(url) != url) { emit giveMeTreeFor(KIO::upUrl(url)); } return; } if (e->button() != Qt::RightButton) { // Ignore other mouse buttons return; } // Actions in the right click menu QAction* openFileManager = nullptr; QAction* openTerminal = nullptr; QAction* centerMap = nullptr; QAction* openFile = nullptr; QAction* copyClipboard = nullptr; QAction* deleteItem = nullptr; QMenu popup; popup.setTitle(m_focus->file()->displayPath(m_tree)); if (isDir) { openFileManager = popup.addAction(QIcon::fromTheme(QStringLiteral("system-file-manager")), i18n("Open &File Manager Here")); if (url.scheme() == QLatin1String("file")) { openTerminal = popup.addAction(QIcon::fromTheme(QStringLiteral( "utilities-terminal" )), i18n("Open &Terminal Here")); } if (m_focus->file() != m_tree) { popup.addSeparator(); centerMap = popup.addAction(QIcon::fromTheme(QStringLiteral( "zoom-in" )), i18n("&Center Map Here")); } } else { openFile = popup.addAction(QIcon::fromTheme(QStringLiteral("document-open")), i18nc("Scan/open the path of the selected element", "&Open")); } popup.addSeparator(); copyClipboard = popup.addAction(QIcon::fromTheme(QStringLiteral( "edit-copy" )), i18n("&Copy to clipboard")); if (m_focus->file() != m_tree) { popup.addSeparator(); deleteItem = popup.addAction(QIcon::fromTheme(QStringLiteral( "edit-delete" )), i18n("&Delete")); } QAction* clicked = popup.exec(e->globalPos(), nullptr); if (openFileManager && clicked == openFileManager) { KRun::runUrl(url, QStringLiteral("inode/directory"), this #if KIO_VERSION >= QT_VERSION_CHECK(5, 31, 0) , KRun::RunFlags() #endif ); } else if (openTerminal && clicked == openTerminal) { KToolInvocation::invokeTerminal(QString(),url.path()); } else if (centerMap && clicked == centerMap) { emit activated(url); //activate first, this will cause UI to prepare itself createFromCache((Folder *)m_focus->file()); } else if (openFile && clicked == openFile) { new KRun(url, this, true); } else if (clicked == copyClipboard) { QMimeData* mimedata = new QMimeData(); mimedata->setUrls(QList() << url); QApplication::clipboard()->setMimeData(mimedata , QClipboard::Clipboard); } else if (clicked == deleteItem && m_focus->file() != m_tree) { m_toBeDeleted = m_focus; const QUrl url = Widget::url(m_toBeDeleted->file()); const QString message = m_toBeDeleted->file()->isFolder() ? i18n("The folder at '%1' will be recursively and permanently deleted.", url.toString()) : i18n("'%1' will be permanently deleted.", url.toString()); const int userIntention = KMessageBox::warningContinueCancel( this, message, QString(), KGuiItem(i18n("&Delete"), QStringLiteral("edit-delete"))); if (userIntention == KMessageBox::Continue) { KIO::Job *job = KIO::del(url); connect(job, &KJob::finished, this, &RadialMap::Widget::deleteJobFinished); QApplication::setOverrideCursor(Qt::BusyCursor); setEnabled(false); } } else { //ensure m_focus is set for new mouse position sendFakeMouseEvent(); } } void RadialMap::Widget::deleteJobFinished(KJob *job) { QApplication::restoreOverrideCursor(); setEnabled(true); if (!job->error() && m_toBeDeleted) { m_toBeDeleted->file()->parent()->remove(m_toBeDeleted->file()); delete m_toBeDeleted->file(); m_toBeDeleted = nullptr; m_focus = nullptr; m_map.make(m_tree, true); update(); } else KMessageBox::error(this, job->errorString(), i18n("Error while deleting")); } void RadialMap::Widget::dropEvent(QDropEvent *e) { QList uriList = KUrlMimeData::urlsFromMimeData(e->mimeData()); if (!uriList.isEmpty()) emit giveMeTreeFor(uriList.first()); } void RadialMap::Widget::dragEnterEvent(QDragEnterEvent *e) { QList uriList = KUrlMimeData::urlsFromMimeData(e->mimeData()); e->setAccepted(!uriList.isEmpty()); } void RadialMap::Widget::changeEvent(QEvent *e) { if (e->type() == QEvent::ApplicationPaletteChange || e->type() == QEvent::PaletteChange) m_map.paint(); }