summaryrefslogtreecommitdiff
path: root/plugins/Themes/Oxygen2/radialMap
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/Themes/Oxygen2/radialMap')
-rwxr-xr-xplugins/Themes/Oxygen2/radialMap/labels.cpp338
-rwxr-xr-xplugins/Themes/Oxygen2/radialMap/map.cpp421
-rwxr-xr-xplugins/Themes/Oxygen2/radialMap/map.h85
-rwxr-xr-xplugins/Themes/Oxygen2/radialMap/radialMap.h109
-rwxr-xr-xplugins/Themes/Oxygen2/radialMap/widget.cpp211
-rwxr-xr-xplugins/Themes/Oxygen2/radialMap/widget.h118
-rwxr-xr-xplugins/Themes/Oxygen2/radialMap/widgetEvents.cpp254
7 files changed, 1536 insertions, 0 deletions
diff --git a/plugins/Themes/Oxygen2/radialMap/labels.cpp b/plugins/Themes/Oxygen2/radialMap/labels.cpp
new file mode 100755
index 0000000..c5ff770
--- /dev/null
+++ b/plugins/Themes/Oxygen2/radialMap/labels.cpp
@@ -0,0 +1,338 @@
+/***********************************************************************
+* Copyright 2003-2004 Max Howell <max.howell@methylblue.com>
+* Copyright 2008-2009 Martin Sandsmark <martin.sandsmark@kde.org>
+*
+* 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 <http://www.gnu.org/licenses/>.
+***********************************************************************/
+
+#include <QFont>
+#include <QFontMetrics>
+#include <QPainter>
+#include <QVector>
+
+#include "../fileTree.h"
+#include "radialMap.h"
+#include "widget.h"
+#include <math.h>
+#include <qmath.h>
+
+namespace RadialMap
+{
+class Label
+{
+public:
+ Label(const RadialMap::Segment *s, int l) : segment(s), level(l), angle(segment->start() + (segment->length() / 2)) { }
+
+ bool tooClose(const int otherAngle) const {
+ return (angle > otherAngle - LABEL_ANGLE_MARGIN && angle < otherAngle + LABEL_ANGLE_MARGIN);
+ }
+
+ const RadialMap::Segment *segment;
+ const unsigned int level;
+ const int angle;
+
+ int targetX, targetY, middleX, startY, startX;
+ int textX, textY, tw, th;
+
+ QString qs;
+};
+
+void RadialMap::Widget::paintExplodedLabels(QPainter &paint) const
+{
+ //we are a friend of RadialMap::Map
+
+ QVector<Label*> list;
+ unsigned int startLevel = 0;
+
+
+ //1. Create list of labels sorted in the order they will be rendered
+
+ if (m_focus && m_focus->file() != m_tree) { //separate behavior for selected vs unselected segments
+ //don't bother with files
+ if (m_focus && m_focus->file() && !m_focus->file()->isFolder()) {
+ return;
+ }
+
+ //find the range of levels we will be potentially drawing labels for
+ //startLevel is the level above whatever m_focus is in
+ for (const Folder *p = (const Folder*)m_focus->file(); p != m_tree; ++startLevel) {
+ p = p->parent();
+ }
+
+ //range=2 means 2 levels to draw labels for
+
+ const uint start = m_focus->start();
+ const uint end = m_focus->end(); //boundary angles
+ const uint minAngle = int(m_focus->length() * LABEL_MIN_ANGLE_FACTOR);
+
+
+ //**** Levels should be on a scale starting with 0
+ //**** range is a useless parameter
+ //**** keep a topblock var which is the lowestLevel OR startLevel for indentation purposes
+ for (unsigned int i = startLevel; i <= m_map.m_visibleDepth; ++i) {
+ for (const Segment *segment : m_map.m_signature[i]) {
+ if (segment->start() >= start && segment->end() <= end) {
+ if (segment->length() > minAngle) {
+ list.append(new Label(segment, i));
+ }
+ }
+ }
+ }
+ } else {
+ for (Segment *segment : *m_map.m_signature) {
+ if (segment->length() > 288) {
+ list.append(new Label(segment, 0));
+
+ }
+ }
+ }
+
+ std::sort(list.begin(), list.end(), [](Label *item1, Label *item2) {
+ //you add 1440 to work round the fact that later you want the circle split vertically
+ //and as it is you start at 3 o' clock. It's to do with rightPrevY, stops annoying bug
+
+ int angle1 = (item1)->angle + 1440;
+ int angle2 = (item2)->angle + 1440;
+
+ // Also sort by level
+ if (angle1 == angle2) {
+ return (item1->level > item2->level);
+ }
+
+ if (angle1 > 5760) angle1 -= 5760;
+ if (angle2 > 5760) angle2 -= 5760;
+
+ return (angle1 < angle2);
+
+ });
+
+ //2. Check to see if any adjacent labels are too close together
+ // if so, remove it (the least significant labels, since we sort by level too).
+
+ int pos = 0;
+ while (pos < list.size() - 1) {
+ if (list[pos]->tooClose(list[pos+1]->angle)) {
+ delete list.takeAt(pos+1);
+ } else {
+ ++pos;
+ }
+ }
+
+ //used in next two steps
+ bool varySizes;
+ //**** should perhaps use doubles
+ int *sizes = new int [ m_map.m_visibleDepth + 1 ]; //**** make sizes an array of floats I think instead (or doubles)
+
+ // If the minimum is larger than the default it fucks up further down
+ if (paint.font().pointSize() < 0 ||
+ paint.font().pointSize() < minFontPitch) {
+ QFont font = paint.font();
+ font.setPointSize(minFontPitch);
+ paint.setFont(font);
+ }
+
+ QVector<Label*>::iterator it;
+
+ do {
+ //3. Calculate font sizes
+
+ {
+ //determine current range of levels to draw for
+ uint range = 0;
+
+ for (Label *label : list) {
+ range = qMax(range, label->level);
+
+ //**** better way would just be to assign if nothing is range
+ }
+
+ range -= startLevel; //range 0 means 1 level of labels
+
+ varySizes = range != 0;
+
+ if (varySizes) {
+ //create an array of font sizes for various levels
+ //will exceed normal font pitch automatically if necessary, but not minPitch
+ //**** this needs to be checked lots
+
+ //**** what if this is negative (min size gtr than default size)
+ uint step = (paint.font().pointSize() - minFontPitch) / range;
+ if (step == 0) {
+ step = 1;
+ }
+
+ for (uint x = range + startLevel, y = minFontPitch; x >= startLevel; y += step, --x) {
+ sizes[x] = y;
+ }
+ }
+ }
+
+ //4. determine label co-ordinates
+
+
+ const int preSpacer = int(m_map.m_ringBreadth * 0.5) + m_map.m_innerRadius;
+ const int fullStrutLength = (m_map.width() - m_map.MAP_2MARGIN) / 2 + LABEL_MAP_SPACER; //full length of a strut from map center
+
+ int prevLeftY = 0;
+ int prevRightY = height();
+
+ QFont font;
+
+ for (it = list.begin(); it != list.end(); ++it) {
+ Label *label = *it;
+ //** bear in mind that text is drawn with QPoint param as BOTTOM left corner of text box
+ QString string = label->segment->file()->displayName();
+ if (varySizes) {
+ font.setPointSize(sizes[label->level]);
+ }
+ QFontMetrics fontMetrics(font);
+ #if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
+ const int minTextWidth = fontMetrics.width(QStringLiteral("M...")) + LABEL_TEXT_HMARGIN; // Fully elided string
+ #else
+ const int minTextWidth = fontMetrics.horizontalAdvance(QStringLiteral("M...")) + LABEL_TEXT_HMARGIN; // Fully elided string
+ #endif
+
+ const int fontHeight = fontMetrics.height() + LABEL_TEXT_VMARGIN; //used to ensure label texts don't overlap
+ const int lineSpacing = fontHeight / 4;
+
+ const bool rightSide = (label->angle < 1440 || label->angle > 4320);
+
+ double sinra, cosra;
+ const double ra = M_PI/2880 * label->angle; //convert to radians
+ sinra = qSin(ra);
+ cosra = qCos(ra);
+
+ const int spacer = preSpacer + m_map.m_ringBreadth * label->level;
+
+ const int centerX = m_map.width() / 2 + m_offset.x(); //centre relative to canvas
+ const int centerY = m_map.height() / 2 + m_offset.y();
+ int targetX = centerX + cosra * spacer;
+ int targetY = centerY - sinra * spacer;
+ int startX = targetX + cosra * (fullStrutLength - spacer + m_map.m_ringBreadth / 2);
+ int startY = targetY - sinra * (fullStrutLength - spacer);
+
+ if (rightSide) { //righthand side, going upwards
+ if (startY > prevRightY /*- fmh*/) { //then it is too low, needs to be drawn higher
+ startY = prevRightY /*- fmh*/;
+ }
+ } else {//lefthand side, going downwards
+ if (startY < prevLeftY/* + fmh*/) { //then we're too high, need to be drawn lower
+ startY = prevLeftY /*+ fmh*/;
+ }
+ }
+
+ int middleX = targetX - (startY - targetY) / tan(ra);
+ int textY = startY + lineSpacing;
+
+ int textX;
+ #if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
+ const int textWidth = fontMetrics.width(string) + LABEL_TEXT_HMARGIN;
+ #else
+ const int textWidth = fontMetrics.horizontalAdvance(string) + LABEL_TEXT_HMARGIN;
+ #endif
+ if (rightSide) {
+ if (startX + minTextWidth > width() || textY < fontHeight || middleX < targetX) {
+ //skip this strut
+ //**** don't duplicate this code
+ list.erase(it); //will delete the label and set it to list.current() which _should_ be the next ptr
+ break;
+ }
+
+ prevRightY = textY - fontHeight - lineSpacing; //must be after above's "continue"
+
+ if (m_offset.x() + m_map.width() + textWidth < width()) {
+ startX = m_offset.x() + m_map.width();
+ } else {
+ startX = qMax(width() - textWidth, startX);
+ string = fontMetrics.elidedText(string, Qt::ElideMiddle, width() - startX);
+ }
+
+ textX = startX + LABEL_TEXT_HMARGIN;
+ } else { // left side
+ if (startX - minTextWidth < 0 || textY > height() || middleX > targetX) {
+ //skip this strut
+ list.erase(it); //will delete the label and set it to list.current() which _should_ be the next ptr
+ break;
+ }
+
+ prevLeftY = textY + fontHeight - lineSpacing;
+
+ if (m_offset.x() - textWidth > 0) {
+ startX = m_offset.x();
+ textX = startX - textWidth - LABEL_TEXT_HMARGIN;
+ } else {
+ textX = 0;
+ string = fontMetrics.elidedText(string, Qt::ElideMiddle, startX);
+ #if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
+ startX = fontMetrics.width(string) + LABEL_TEXT_HMARGIN;
+ #else
+ startX = fontMetrics.horizontalAdvance(string) + LABEL_TEXT_HMARGIN;
+ #endif
+ }
+ }
+
+ label->targetX = targetX;
+ label->targetY = targetY;
+ label->middleX = middleX;
+ label->startY = startY;
+ label->startX = startX;
+ label->textX = textX;
+ label->textY = textY;
+ label->qs = string;
+ }
+
+ //if an element is deleted at this stage, we need to do this whole
+ //iteration again, thus the following loop
+ //**** in rare case that deleted label was last label in top level
+ // and last in labelList too, this will not work as expected (not critical)
+
+ } while (it != list.end());
+
+
+ //5. Render labels
+
+ QFont font;
+ for (Label *label : list) {
+ if (varySizes) {
+ //**** how much overhead in making new QFont each time?
+ // (implicate sharing remember)
+ font.setPointSize(sizes[label->level]);
+ paint.setFont(font);
+ }
+
+ paint.setPen(QPen(QColor(0,0,0),2));
+ paint.drawLine(label->targetX, label->targetY, label->middleX, label->startY);
+ paint.drawLine(label->middleX, label->startY, label->startX, label->startY);
+
+ paint.setPen(QPen(QColor(255,255,255),1));
+ paint.drawLine(label->targetX, label->targetY, label->middleX, label->startY);
+ paint.drawLine(label->middleX, label->startY, label->startX, label->startY);
+
+ paint.setPen(QPen(QColor(0,0,0),1));
+ paint.drawText(label->textX-1, label->textY-1, label->qs);
+ paint.drawText(label->textX+1, label->textY-1, label->qs);
+ paint.drawText(label->textX+1, label->textY+1, label->qs);
+ paint.drawText(label->textX-1, label->textY+1, label->qs);
+ paint.setPen(QPen(QColor(255,255,255),1));
+ paint.drawText(label->textX, label->textY, label->qs);
+ }
+
+ qDeleteAll(list);
+ delete [] sizes;
+}
+}
+
diff --git a/plugins/Themes/Oxygen2/radialMap/map.cpp b/plugins/Themes/Oxygen2/radialMap/map.cpp
new file mode 100755
index 0000000..a7306ac
--- /dev/null
+++ b/plugins/Themes/Oxygen2/radialMap/map.cpp
@@ -0,0 +1,421 @@
+/***********************************************************************
+* Copyright 2003-2004 Max Howell <max.howell@methylblue.com>
+* Copyright 2008-2009 Martin Sandsmark <martin.sandsmark@kde.org>
+*
+* 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 <http://www.gnu.org/licenses/>.
+***********************************************************************/
+
+#include <QApplication> //make()
+#include <QImage> //make() & paint()
+#include <QFont> //ctor
+#include <QFontMetrics> //ctor
+#include <QPainter>
+#include <QBrush>
+
+#include "radialMap.h" // defines
+#include "../interface.h"
+#include "../fileTree.h"
+#include "widget.h"
+#include <cmath>
+#include <qmath.h>
+
+RadialMap::Map::Map()
+ : m_signature(nullptr)
+ , m_visibleDepth(DEFAULT_RING_DEPTH)
+ , m_ringBreadth(MIN_RING_BREADTH)
+ , m_innerRadius(0)
+ , defaultRingDepth(4)
+{
+
+ //FIXME this is all broken. No longer is a maximum depth!
+ const int fmh = QFontMetrics(QFont()).height();
+ const int fmhD4 = fmh / 4;
+ MAP_2MARGIN = 2 * (fmh - (fmhD4 - LABEL_MAP_SPACER)); //margin is dependent on fitting in labels at top and bottom
+
+ m_minSize=27300;
+}
+
+RadialMap::Map::~Map()
+{
+ delete [] m_signature;
+}
+
+void RadialMap::Map::invalidate()
+{
+ delete [] m_signature;
+ m_signature = nullptr;
+
+ m_visibleDepth = defaultRingDepth;
+}
+
+void RadialMap::Map::make(const Folder *tree, bool refresh)
+{
+ if(height()<1)
+ return;
+ //slow operation so set the wait cursor
+ QApplication::setOverrideCursor(Qt::WaitCursor);
+
+ //build a signature of visible components
+ {
+ //**** REMOVE NEED FOR the +1 with MAX_RING_DEPTH uses
+ //**** add some angle bounds checking (possibly in Segment ctor? can I delete in a ctor?)
+ //**** this is a mess
+
+ delete [] m_signature;
+ m_signature = new std::vector<Segment*>[m_visibleDepth + 1];
+
+ m_root = tree;
+
+ if (!refresh) {
+ quint64 varSize=tree->size();
+ quint64 varHeight=height();
+ quint64 varA=(varSize * 3);
+ quint64 varB=(PI * varHeight - MAP_2MARGIN);
+ m_minSize = varA / varB;
+ findVisibleDepth(tree);
+ }
+
+ setRingBreadth();
+
+ // Calculate ring size limits
+ m_limits.resize(m_visibleDepth + 1);
+ const double size = m_root->size();
+ const double pi2B = M_PI * 4 * m_ringBreadth;
+ for (uint depth = 0; depth <= m_visibleDepth; ++depth) {
+ m_limits[depth] = uint(size / double(pi2B * (depth + 1))); //min is angle that gives 3px outer diameter for that depth
+ }
+
+ build(tree);
+ }
+
+ //colour the segments
+ colorise();
+
+ m_centerText = tree->humanReadableSize()+"\n"+QObject::tr("%1 files").arg(Themes::simplifiedBigNum(tree->children()));
+
+ //paint the pixmap
+ paint();
+
+ QApplication::restoreOverrideCursor();
+}
+
+void RadialMap::Map::setRingBreadth()
+{
+ //FIXME called too many times on creation
+
+ m_ringBreadth = (height() - MAP_2MARGIN) / (2 * m_visibleDepth + 4);
+ m_ringBreadth = qBound(MIN_RING_BREADTH, m_ringBreadth, MAX_RING_BREADTH);
+}
+
+void RadialMap::Map::findVisibleDepth(const Folder *dir, uint currentDepth)
+{
+
+ //**** because I don't use the same minimumSize criteria as in the visual function
+ // this can lead to incorrect visual representation
+ //**** BUT, you can't set those limits until you know m_depth!
+
+ //**** also this function doesn't check to see if anything is actually visible
+ // it just assumes that when it reaches a new level everything in it is visible
+ // automatically. This isn't right especially as there might be no files in the
+ // dir provided to this function!
+
+ static uint stopDepth = 0;
+
+ if (dir == m_root) {
+ stopDepth = m_visibleDepth;
+ m_visibleDepth = 0;
+ }
+
+ if (m_visibleDepth < currentDepth) m_visibleDepth = currentDepth;
+ if (m_visibleDepth >= stopDepth) return;
+
+ for(const auto& n : dir->folders)
+ {
+ Folder * folder=n.second;
+ if (folder->size() > m_minSize) {
+ findVisibleDepth(folder, currentDepth + 1); //if no files greater than min size the depth is still recorded
+ }
+ }
+}
+
+//**** segments currently overlap at edges (i.e. end of first is start of next)
+bool RadialMap::Map::build(const Folder * const dir, const uint depth, uint a_start, const uint a_end)
+{
+ //first iteration: dir == m_root
+
+ if (dir->children() == 0) //we do fileCount rather than size to avoid chance of divide by zero later
+ return false;
+
+ uint64_t hiddenSize = 0;
+ uint hiddenFileCount = 0;
+
+ for(const auto& n : dir->folders)
+ {
+ Folder * folder=n.second;
+ if (folder->size() < m_limits[depth] * 6) { // limit is half a degree? we want at least 3 degrees
+ hiddenSize += folder->size();
+ hiddenFileCount += folder->children(); //need to add one to count the dir as well
+ ++hiddenFileCount;
+ continue;
+ }
+ unsigned int a_len = (unsigned int)(5760 * ((double)folder->size() / (double)m_root->size()));
+ Segment *s = new Segment(folder, a_start, a_len);
+ m_signature[depth].push_back(s);
+ if (depth != m_visibleDepth) {
+ //recurse
+ s->m_hasHiddenChildren = build(folder, depth + 1, a_start, a_start + a_len);
+ } else {
+ s->m_hasHiddenChildren = true;
+ }
+ a_start += a_len; //**** should we add 1?
+ }
+ for (File *file : dir->onlyFiles) {
+ if (file->size() < m_limits[depth] * 6) { // limit is half a degree? we want at least 3 degrees
+ hiddenSize += file->size();
+ ++hiddenFileCount;
+ continue;
+ }
+ unsigned int a_len = (unsigned int)(5760 * ((double)file->size() / (double)m_root->size()));
+ Segment *s = new Segment(file, a_start, a_len);
+ m_signature[depth].push_back(s);
+ a_start += a_len; //**** should we add 1?
+ }
+
+ if (hiddenFileCount == dir->children()) {
+ return true;
+ }
+
+ if (depth == 0 && hiddenSize >= m_limits[depth] && hiddenFileCount > 0) {
+ //append a segment for unrepresented space - a "fake" segment
+ const QString s = QObject::tr("%1 file, with an average size of %2")
+ .arg(hiddenFileCount)
+ .arg(QString::fromStdString(File::facilityEngine->sizeToString(hiddenSize/hiddenFileCount)));
+
+
+ (m_signature + depth)->push_back(new Segment(new File(s.toUtf8().constData(), hiddenSize), a_start, a_end - a_start, true));
+ }
+
+ return false;
+}
+
+bool RadialMap::Map::resize(const QRect &rect)
+{
+ //there's a MAP_2MARGIN border
+
+ const int mw=width();
+ const int mh=height();
+ const int cw=rect.width();
+ const int ch=rect.height();
+
+ if (cw < mw || ch < mh || (cw > mw && ch > mh))
+ {
+ uint size = ((cw < ch) ? cw : ch) - MAP_2MARGIN;
+
+ //this also causes uneven sizes to always resize when resizing but map is small in that dimension
+ //size -= size % 2; //even sizes mean less staggered non-antialiased resizing
+
+ {
+ const uint minSize = MIN_RING_BREADTH * 2 * (m_visibleDepth + 2);
+
+ if (size < minSize)
+ size = minSize;
+
+ //this QRect is used by paint()
+ m_rect.setRect(0,0,size,size);
+ }
+ m_pixmap = QPixmap(m_rect.size());
+
+ //resize the pixmap
+ size += MAP_2MARGIN;
+
+ if (m_signature != nullptr)
+ {
+ setRingBreadth();
+ paint();
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+void RadialMap::Map::colorise()
+{
+ if (!m_signature || m_signature->empty()) {
+ //std::cerr << "no signature yet" << std::endl;
+ return;
+ }
+
+ QColor cp, cb;
+ double darkness = 1;
+ double contrast = (double)94 / (double)100;
+ int h, s1, s2, v1, v2;
+
+ for (uint i = 0; i <= m_visibleDepth; ++i, darkness += 0.04) {
+ for (Segment *segment : m_signature[i]) {
+ h = int(segment->start() / 16);
+ s1 = 160;
+ v1 = (int)(255.0 / darkness); //doing this more often than once seems daft!
+
+ v2 = v1 - int(contrast * v1);
+ s2 = s1 + int(contrast * (255 - s1));
+
+ if (s1 < 80) s1 = 80; //can fall too low and makes contrast between the files hard to discern
+
+ if (segment->isFake()) { //multi-file
+ cb.setHsv(h, s2, (v2 < 90) ? 90 : v2); //too dark if < 100
+ cp.setHsv(h, 17, v1);
+ } else if (!segment->file()->isFolder()) { //file
+ cb.setHsv(h, 17, v1);
+ cp.setHsv(h, 17, v2);
+ } else { //folder
+ cb.setHsv(h, s1, v1); //v was 225
+ cp.setHsv(h, s2, v2); //v was 225 - delta
+ }
+
+ segment->setPalette(cp, cb);
+ }
+ }
+}
+
+void RadialMap::Map::paint(bool antialias)
+{
+ QPainter paint;
+ QRect rect = m_rect;
+
+ rect.adjust(5, 5, -5, -5);
+ m_pixmap.fill(Qt::transparent);
+
+ //m_rect.moveRight(1); // Uncommenting this breaks repainting when recreating map from cache
+
+
+ //**** best option you can think of is to make the circles slightly less perfect,
+ // ** i.e. slightly eliptic when resizing inbetween
+
+ if (m_pixmap.isNull())
+ return;
+
+ if (!paint.begin(&m_pixmap)) {
+ //qWarning() << "Filelight::RadialMap Failed to initialize painting, returning...";
+ return;
+ }
+
+ if (antialias) {
+ paint.translate(0.7, 0.7);
+ paint.setRenderHint(QPainter::Antialiasing);
+ }
+
+ int step = m_ringBreadth;
+ int excess = -1;
+
+ //do intelligent distribution of excess to prevent nasty resizing
+ if (m_ringBreadth != MAX_RING_BREADTH && m_ringBreadth != MIN_RING_BREADTH) {
+ excess = rect.width() % m_ringBreadth;
+ ++step;
+ }
+
+ for (int x = m_visibleDepth; x >= 0; --x)
+ {
+ int width = rect.width() / 2;
+ //clever geometric trick to find largest angle that will give biggest arrow head
+ uint a_max = int(acos((double)width / double((width + 5))) * (180*16 / M_PI));
+
+ for (Segment *segment : m_signature[x]) {
+ //draw the pie segments, most of this code is concerned with drawing the little
+ //arrows on the ends of segments when they have hidden files
+
+ paint.setPen(segment->pen());
+
+ if (segment->hasHiddenChildren())
+ {
+ //draw arrow head to indicate undisplayed files/directories
+ QPolygon pts(3);
+ QPoint pos, cpos = rect.center();
+ uint a[3] = { segment->start(), segment->length(), 0 };
+
+ a[2] = a[0] + (a[1] / 2); //assign to halfway between
+ if (a[1] > a_max)
+ {
+ a[1] = a_max;
+ a[0] = a[2] - a_max / 2;
+ }
+
+ a[1] += a[0];
+
+ for (int i = 0, radius = width; i < 3; ++i)
+ {
+ double ra = M_PI/(180*16) * a[i], sinra, cosra;
+
+ if (i == 2)
+ radius += 5;
+ sinra = qSin(ra);
+ cosra = qCos(ra);
+ pos.rx() = cpos.x() + static_cast<int>(cosra * radius);
+ pos.ry() = cpos.y() - static_cast<int>(sinra * radius);
+ pts.setPoint(i, pos);
+ }
+
+ paint.setBrush(segment->pen());
+ paint.drawPolygon(pts);
+ }
+
+ paint.setPen(QColor(120,120,120));
+ paint.setBrush(segment->brush());
+ paint.drawPie(rect, segment->start(), segment->length());
+
+ if (segment->hasHiddenChildren())
+ {
+ //**** code is bloated!
+ paint.save();
+ QPen pen = paint.pen();
+ int width = 2;
+ pen.setWidth(width);
+ paint.setPen(pen);
+ QRect rect2 = rect;
+ width /= 2;
+ rect2.adjust(width, width, -width, -width);
+ paint.drawArc(rect2, segment->start(), segment->length());
+ paint.restore();
+ }
+ }
+
+ if (excess >= 0) { //excess allows us to resize more smoothly (still crud tho)
+ if (excess < 2) //only decrease rect by more if even number of excesses left
+ --step;
+ excess -= 2;
+ }
+
+ rect.adjust(step, step, -step, -step);
+ }
+
+ // if(excess > 0) rect.addCoords(excess, excess, 0, 0); //ugly
+
+ paint.setPen(QColor(120,120,120));
+ paint.setBrush(QColor(255,255,255));
+ paint.drawEllipse(rect);
+ if(width()>200)
+ {
+ paint.setPen(QColor(0,0,0));
+ paint.drawText(rect, Qt::AlignCenter, m_centerText);
+ }
+
+ m_innerRadius = rect.width() / 2; //rect.width should be multiple of 2
+
+ paint.end();
+}
+
diff --git a/plugins/Themes/Oxygen2/radialMap/map.h b/plugins/Themes/Oxygen2/radialMap/map.h
new file mode 100755
index 0000000..a78e56d
--- /dev/null
+++ b/plugins/Themes/Oxygen2/radialMap/map.h
@@ -0,0 +1,85 @@
+/***********************************************************************
+* Copyright 2003-2004 Max Howell <max.howell@methylblue.com>
+* Copyright 2008-2009 Martin Sandsmark <martin.sandsmark@kde.org>
+*
+* 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 <http://www.gnu.org/licenses/>.
+***********************************************************************/
+
+#ifndef RadialMapMAP_H
+#define RadialMapMAP_H
+
+#include "../fileTree.h"
+
+#include <QPixmap>
+#include <QRect>
+#include <QString>
+
+namespace RadialMap {
+class Segment;
+
+class Map
+{
+public:
+ explicit Map();
+ ~Map();
+
+ void make(const Folder *, bool = false);
+ bool resize(const QRect&);
+
+ bool isNull() const {
+ return (m_signature == nullptr);
+ }
+ void invalidate();
+
+ int height() const {
+ return m_rect.height();
+ }
+ int width() const {
+ return m_rect.width();
+ }
+ QPixmap pixmap() const {
+ return m_pixmap;
+ }
+
+
+ friend class Widget;
+
+private:
+ void paint(bool antialias = true);
+ void colorise();
+ void setRingBreadth();
+ void findVisibleDepth(const Folder *dir, uint currentDepth = 0);
+ bool build(const Folder* const dir, const uint depth =0, uint a_start =0, const uint a_end =5760);
+
+ std::vector<Segment*> *m_signature;
+
+ const Folder *m_root;
+ uint m_minSize;
+ std::vector<uint64_t> m_limits;
+ QRect m_rect;
+ uint m_visibleDepth; ///visible level depth of system
+ QPixmap m_pixmap;
+ int m_ringBreadth;
+ uint m_innerRadius; ///radius of inner circle
+ QString m_centerText;
+
+ uint MAP_2MARGIN;
+ int defaultRingDepth;
+};
+}
+
+#endif
diff --git a/plugins/Themes/Oxygen2/radialMap/radialMap.h b/plugins/Themes/Oxygen2/radialMap/radialMap.h
new file mode 100755
index 0000000..7935010
--- /dev/null
+++ b/plugins/Themes/Oxygen2/radialMap/radialMap.h
@@ -0,0 +1,109 @@
+/***********************************************************************
+* Copyright 2003-2004 Max Howell <max.howell@methylblue.com>
+* Copyright 2008-2009 Martin Sandsmark <martin.sandsmark@kde.org>
+*
+* 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 <http://www.gnu.org/licenses/>.
+***********************************************************************/
+
+#ifndef RadialMapRADIALMAP_H
+#define RadialMapRADIALMAP_H
+
+#include <QColor>
+
+class File;
+
+namespace RadialMap
+{
+class Segment //all angles are in 16ths of degrees
+{
+public:
+ Segment(const File *f, uint s, uint l, bool isFake = false)
+ : m_angleStart(s)
+ , m_angleSegment(l)
+ , m_file(f)
+ , m_hasHiddenChildren(false)
+ , m_fake(isFake) {}
+ ~Segment();
+
+ uint start() const {
+ return m_angleStart;
+ }
+ uint length() const {
+ return m_angleSegment;
+ }
+ uint end() const {
+ return m_angleStart + m_angleSegment;
+ }
+ const File *file() const {
+ return m_file;
+ }
+ const QColor& pen() const {
+ return m_pen;
+ }
+ const QColor& brush() const {
+ return m_brush;
+ }
+
+ bool isFake() const {
+ return m_fake;
+ }
+ bool hasHiddenChildren() const {
+ return m_hasHiddenChildren;
+ }
+
+ bool intersects(uint a) const {
+ return ((a >= start()) && (a < end()));
+ }
+
+ friend class Map;
+ friend class Builder;
+
+private:
+ void setPalette(const QColor &p, const QColor &b) {
+ m_pen = p;
+ m_brush = b;
+ }
+
+ const uint m_angleStart, m_angleSegment;
+ const File* const m_file;
+ QColor m_pen, m_brush;
+ bool m_hasHiddenChildren;
+ const bool m_fake;
+};
+}
+
+
+#ifndef PI
+#define PI 3.141592653589793
+#endif
+#ifndef M_PI
+#define M_PI 3.14159265358979323846264338327
+#endif
+
+#define MIN_RING_BREADTH 7
+#define MAX_RING_BREADTH 60
+#define DEFAULT_RING_DEPTH 4 //first level = 0
+#define MIN_RING_DEPTH 0
+
+#define LABEL_MAP_SPACER 7
+#define LABEL_TEXT_HMARGIN 5
+#define LABEL_TEXT_VMARGIN 0
+#define LABEL_ANGLE_MARGIN 32
+#define LABEL_MIN_ANGLE_FACTOR 0.05
+#define LABEL_MAX_CHARS 30
+
+#endif
diff --git a/plugins/Themes/Oxygen2/radialMap/widget.cpp b/plugins/Themes/Oxygen2/radialMap/widget.cpp
new file mode 100755
index 0000000..9baf6db
--- /dev/null
+++ b/plugins/Themes/Oxygen2/radialMap/widget.cpp
@@ -0,0 +1,211 @@
+/***********************************************************************
+* Copyright 2003-2004 Max Howell <max.howell@methylblue.com>
+* Copyright 2008-2009 Martin Sandsmark <martin.sandsmark@kde.org>
+*
+* 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 <http://www.gnu.org/licenses/>.
+***********************************************************************/
+
+#include "widget.h"
+
+#include "../fileTree.h"
+#include "radialMap.h" //constants
+#include "map.h"
+
+#include <QUrl>
+
+#include <QApplication> //sendEvent
+#include <QBitmap> //ctor - finding cursor size
+#include <QCursor> //slotPostMouseEvent()
+#include <QTimer> //member
+#include <QWidget>
+
+
+RadialMap::Widget::Widget(const bool dark, QWidget *parent)
+ : QWidget(parent)
+ , m_tree(nullptr)
+ , m_focus(nullptr)
+ , m_map()
+ , m_rootSegment(nullptr) //TODO we don't delete it, *shrug*
+ , m_toBeDeleted(nullptr)
+ , minFontPitch(QFont().pointSize() - 3)
+{
+ setMaximumSize(16777215, 16777215);
+ setMinimumSize(150, 100);
+
+ connect(this, &Widget::folderCreated, this, &Widget::sendFakeMouseEvent);
+ connect(&m_timer, &QTimer::timeout, this, &Widget::resizeTimeout);
+ m_updateCache.start(100);
+ connect(&m_updateCache, &QTimer::timeout, this, &Widget::updateCache);
+ m_tooltip.setFrameShape(QFrame::StyledPanel);
+ m_tooltip.setWindowFlags(Qt::ToolTip | Qt::WindowTransparentForInput);
+ this->dark=dark;
+ newData=false;
+}
+
+RadialMap::Widget::~Widget()
+{
+ if(m_rootSegment!=nullptr)
+ delete m_rootSegment;
+}
+
+
+QString RadialMap::Widget::path() const
+{
+ return m_tree->displayPath();
+}
+
+QUrl RadialMap::Widget::url(File const * const file) const
+{
+ return file ? file->url() : m_tree->url();
+}
+
+void RadialMap::Widget::invalidate()
+{
+ newData=true;
+ if (isValid())
+ {
+ //**** have to check that only way to invalidate is this function frankly
+ //**** otherwise you may get bugs..
+
+ //disable mouse tracking
+ setMouseTracking(false);
+
+ // Get this before reseting m_tree below
+ QUrl invalidatedUrl(url());
+
+ //ensure this class won't think we have a map still
+ m_tree = nullptr;
+ m_focus = nullptr;
+
+ delete m_rootSegment;
+ m_rootSegment = nullptr;
+
+ //FIXME move this disablement thing no?
+ // it is confusing in other areas, like the whole createFromCache() thing
+ m_map.invalidate();
+ update();
+
+ //tell rest of Filelight
+ emit invalidated(invalidatedUrl);
+ }
+}
+
+void
+RadialMap::Widget::create(const Folder *tree)
+{
+ newData=true;
+ //it is not the responsibility of create() to invalidate first
+ //skip invalidation at your own risk
+
+ //FIXME make it the responsibility of create to invalidate first
+
+ if (tree)
+ {
+ m_focus = nullptr;
+ //generate the filemap image
+ m_map.make(tree);
+
+ //this is the inner circle in the center
+ if(m_rootSegment!=nullptr)
+ delete m_rootSegment;
+ m_rootSegment = new Segment(tree, 0, 16*360);
+
+ setMouseTracking(true);
+ }
+
+ m_tree = tree;
+
+ //tell rest of Filelight
+ emit folderCreated(tree);
+}
+
+void
+RadialMap::Widget::createFromCache(const Folder *tree)
+{
+ //no scan was necessary, use cached tree, however we MUST still emit invalidate
+ invalidate();
+ create(tree);
+}
+
+void
+RadialMap::Widget::sendFakeMouseEvent() //slot
+{
+ QMouseEvent me(QEvent::MouseMove, mapFromGlobal(QCursor::pos()), Qt::NoButton, Qt::NoButton, Qt::NoModifier);
+ QApplication::sendEvent(this, &me);
+ update();
+}
+
+void
+RadialMap::Widget::resizeTimeout() //slot
+{
+ // the segments are about to erased!
+ // this was a horrid bug, and proves the OO programming should be obeyed always!
+ m_focus = nullptr;
+ if (m_tree)
+ m_map.make(m_tree, true);
+ updateCache();
+}
+
+void
+RadialMap::Widget::updateCache()
+{
+ if(newData)
+ {
+ newData=false;
+ cache=QPixmap();
+ update();
+ }
+}
+
+void
+RadialMap::Widget::refresh(int filth)
+{
+ //TODO consider a more direct connection
+
+ if (!m_map.isNull())
+ {
+ switch (filth)
+ {
+ case 1:
+ m_focus=nullptr;
+ m_map.make(m_tree, true); //true means refresh only
+ break;
+
+ case 2:
+ m_map.paint(true); //antialiased painting
+ break;
+
+ case 3:
+ m_map.colorise(); //FALL THROUGH!
+ case 4:
+ m_map.paint();
+
+ default:
+ break;
+ }
+
+ update();
+ }
+}
+
+RadialMap::Segment::~Segment()
+{
+ if (isFake())
+ delete m_file; //created by us in Builder::build()
+}
+
+
diff --git a/plugins/Themes/Oxygen2/radialMap/widget.h b/plugins/Themes/Oxygen2/radialMap/widget.h
new file mode 100755
index 0000000..50dfea9
--- /dev/null
+++ b/plugins/Themes/Oxygen2/radialMap/widget.h
@@ -0,0 +1,118 @@
+/***********************************************************************
+* Copyright 2003-2004 Max Howell <max.howell@methylblue.com>
+* Copyright 2008-2009 Martin Sandsmark <martin.sandsmark@kde.org>
+*
+* 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 <http://www.gnu.org/licenses/>.
+***********************************************************************/
+
+#ifndef RadialMapWIDGET_H
+#define RadialMapWIDGET_H
+
+#include <QUrl>
+
+#include <QLabel>
+#include <QDragEnterEvent>
+#include <QDropEvent>
+#include <QMouseEvent>
+#include <QPaintEvent>
+#include <QResizeEvent>
+#include <QWidget>
+#include <QTimer>
+
+#include "map.h"
+
+class Folder;
+class File;
+namespace KIO {
+class Job;
+}
+
+namespace RadialMap
+{
+class Segment;
+
+class Widget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit Widget(const bool dark,QWidget* = nullptr);
+ ~Widget() override;
+ QString path() const;
+ QUrl url(File const * const = nullptr) const;
+
+ bool isValid() const {
+ return m_tree != nullptr;
+ }
+
+ friend class Label; //FIXME badness
+
+public Q_SLOTS:
+ void create(const Folder*);
+ void invalidate();
+ void resizeTimeout();
+ void refresh(int);
+
+private Q_SLOTS:
+ void sendFakeMouseEvent();
+ 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 mouseMoveEvent(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 *rootSegment() const {
+ return m_rootSegment; ///never == 0
+ }
+ const Segment *focusSegment() const {
+ return m_focus; ///0 == nothing in focus
+ }
+
+private:
+ void paintExplodedLabels(QPainter&) const;
+ void updateCache();
+
+ const Folder *m_tree;
+ const Segment *m_focus;
+ QPoint m_offset;
+ QTimer m_timer;
+ QTimer m_updateCache;
+ Map m_map;
+ Segment *m_rootSegment;
+ const Segment *m_toBeDeleted;
+ QLabel m_tooltip;
+ bool dark;
+ int minFontPitch;
+ QPixmap cache;
+ bool newData;
+};
+}
+
+#endif
diff --git a/plugins/Themes/Oxygen2/radialMap/widgetEvents.cpp b/plugins/Themes/Oxygen2/radialMap/widgetEvents.cpp
new file mode 100755
index 0000000..d9c23a0
--- /dev/null
+++ b/plugins/Themes/Oxygen2/radialMap/widgetEvents.cpp
@@ -0,0 +1,254 @@
+/***********************************************************************
+* Copyright 2003-2004 Max Howell <max.howell@methylblue.com>
+* Copyright 2008-2009 Martin Sandsmark <martin.sandsmark@kde.org>
+*
+* 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 <http://www.gnu.org/licenses/>.
+***********************************************************************/
+
+#include "../fileTree.h"
+#include "radialMap.h" //class Segment
+#include "widget.h"
+
+#include <QMenu> //::mousePressEvent()
+
+#include <QPainter>
+#include <QTimer> //::resizeEvent()
+#include <QDropEvent>
+#include <QPaintEvent>
+#include <QResizeEvent>
+#include <QMouseEvent>
+
+#include <cmath> //::segmentAt()
+
+void RadialMap::Widget::resizeEvent(QResizeEvent*)
+{
+ QRect rectTemp(rect());
+ if (m_map.resize(rectTemp))
+ m_timer.setSingleShot(true);
+ m_timer.start(100); //will cause signature to rebuild for new size
+
+ //always do these as they need to be initialised on creation
+ const unsigned int w=width();
+ const unsigned int h=height();
+ m_offset.rx() = (w - m_map.width()) / 2;
+ m_offset.ry() = (h - m_map.height()) / 2;
+}
+
+void RadialMap::Widget::paintEvent(QPaintEvent*)
+{
+ if(cache.isNull() || cache.width()!=width() || cache.height()!=height())
+ {
+ QImage temp(width(),height(),QImage::Format_ARGB32);
+ temp.fill(Qt::transparent);
+ QPainter paint;
+ paint.begin(&temp);
+
+ if (!m_map.isNull())
+ {
+ QPixmap p(m_map.pixmap());
+
+ int margin=((p.width() < p.height()) ? p.width() : p.height())/50;
+ if(margin<1)
+ margin=1;
+ paint.setRenderHint(QPainter::Antialiasing);
+ QRect rect = p.rect();
+ rect.moveTo(m_offset);
+ rect.adjust(-margin, -margin, margin, margin);
+ paint.setPen(QColor(200,200,200));
+ paint.setBrush(QColor(255,255,255));
+ paint.drawEllipse(rect);
+ paint.setPen(QColor(0,0,0));
+
+ paint.drawPixmap(m_offset,p);
+ }
+ else
+ {
+ const unsigned int w=width();
+ const unsigned int h=height();
+ unsigned int min=w;
+ unsigned int x=0;
+ unsigned int y=0;
+ if(h<w)
+ {
+ min=h;
+ x=(width()-min)/2;
+ }
+ else
+ y=(height()-min)/2;
+
+ paint.setRenderHint(QPainter::Antialiasing);
+ QRect rect(x,y,min,min);
+ //rect.moveTo(m_offset);
+ paint.setPen(QColor(200,200,200));
+ paint.setBrush(QColor(255,255,255));
+ paint.drawEllipse(rect);
+ paint.setPen(QColor(0,0,0));
+
+ paint.drawText(rect, Qt::AlignHCenter | Qt::AlignVCenter, "...");
+ return;
+ }
+
+ //exploded labels
+ if (!m_map.isNull() && !m_timer.isActive())
+ {
+ if (true) {
+ paint.setRenderHint(QPainter::Antialiasing);
+ //make lines appear on pixel boundaries
+ paint.translate(0.5, 0.5);
+ }
+ paintExplodedLabels(paint);
+ }
+
+ cache=QPixmap::fromImage(temp);
+ }
+ QPainter paint;
+ paint.begin(this);
+ paint.drawPixmap(0,0,cache.width(), cache.height(), cache);
+}
+
+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;
+
+ const int m_map_width=m_map.width();
+ const int m_map_height=m_map.height();
+ 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(p); //NOTE p is passed by non-const reference
+
+ 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;
+
+
+ const QString &path=m_focus->file()->displayPath();
+ if (m_focus->file()->isFolder())
+ {
+ const Folder* folder=static_cast<const Folder*>(m_focus->file());
+ if(path.isEmpty())
+ string += m_focus->file()->humanReadableSize()+tr(" into %1 files").arg(folder->children());
+ else
+ string += path+"\n"+m_focus->file()->humanReadableSize()+tr(" into %1 files").arg(folder->children());
+ }
+ else
+ string += path+" "+m_focus->file()->humanReadableSize();
+
+ // 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();
+ #if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
+ tooltipWidth = qMax(tooltipWidth, fontMetrics.width(part));
+ #else
+ tooltipWidth = qMax(tooltipWidth, fontMetrics.horizontalAdvance(part));
+ #endif
+ }
+ // 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();
+ }
+ }
+ else if (oldFocus && oldFocus->file() != m_tree)
+ {
+ m_tooltip.hide();
+ unsetCursor();
+ update();
+
+ emit mouseHover(QString());
+ }
+}
+
+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::changeEvent(QEvent *e)
+{
+ if (e->type() == QEvent::ApplicationPaletteChange ||
+ e->type() == QEvent::PaletteChange)
+ m_map.paint();
+}