From 3ac113857071fc1f225b2e1b42547269e568c6b7 Mon Sep 17 00:00:00 2001 From: Thomas Preud'homme Date: Tue, 11 Aug 2020 22:35:12 +0100 Subject: New upstream version 2.2.4.4 --- plugins/Themes/Oxygen2/radialMap/labels.cpp | 338 +++++++++++++++++ plugins/Themes/Oxygen2/radialMap/map.cpp | 421 ++++++++++++++++++++++ plugins/Themes/Oxygen2/radialMap/map.h | 85 +++++ plugins/Themes/Oxygen2/radialMap/radialMap.h | 109 ++++++ plugins/Themes/Oxygen2/radialMap/widget.cpp | 211 +++++++++++ plugins/Themes/Oxygen2/radialMap/widget.h | 118 ++++++ plugins/Themes/Oxygen2/radialMap/widgetEvents.cpp | 254 +++++++++++++ 7 files changed, 1536 insertions(+) create mode 100755 plugins/Themes/Oxygen2/radialMap/labels.cpp create mode 100755 plugins/Themes/Oxygen2/radialMap/map.cpp create mode 100755 plugins/Themes/Oxygen2/radialMap/map.h create mode 100755 plugins/Themes/Oxygen2/radialMap/radialMap.h create mode 100755 plugins/Themes/Oxygen2/radialMap/widget.cpp create mode 100755 plugins/Themes/Oxygen2/radialMap/widget.h create mode 100755 plugins/Themes/Oxygen2/radialMap/widgetEvents.cpp (limited to 'plugins/Themes/Oxygen2/radialMap') 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 +* 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 +#include +#include +#include + +#include "../fileTree.h" +#include "radialMap.h" +#include "widget.h" +#include +#include + +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 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::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 +* 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 //make() +#include //make() & paint() +#include //ctor +#include //ctor +#include +#include + +#include "radialMap.h" // defines +#include "../interface.h" +#include "../fileTree.h" +#include "widget.h" +#include +#include + +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[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(cosra * radius); + pos.ry() = cpos.y() - static_cast(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 +* 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 RadialMapMAP_H +#define RadialMapMAP_H + +#include "../fileTree.h" + +#include +#include +#include + +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 *m_signature; + + const Folder *m_root; + uint m_minSize; + std::vector 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 +* 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 RadialMapRADIALMAP_H +#define RadialMapRADIALMAP_H + +#include + +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 +* 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 "widget.h" + +#include "../fileTree.h" +#include "radialMap.h" //constants +#include "map.h" + +#include + +#include //sendEvent +#include //ctor - finding cursor size +#include //slotPostMouseEvent() +#include //member +#include + + +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 +* 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 RadialMapWIDGET_H +#define RadialMapWIDGET_H + +#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(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 +* 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 "radialMap.h" //class Segment +#include "widget.h" + +#include //::mousePressEvent() + +#include +#include //::resizeEvent() +#include +#include +#include +#include + +#include //::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= 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(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(); +} -- cgit v1.2.3