summaryrefslogtreecommitdiff
path: root/test/radialmap/radialMap/map.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'test/radialmap/radialMap/map.cpp')
-rw-r--r--test/radialmap/radialMap/map.cpp413
1 files changed, 413 insertions, 0 deletions
diff --git a/test/radialmap/radialMap/map.cpp b/test/radialmap/radialMap/map.cpp
new file mode 100644
index 0000000..16d492b
--- /dev/null
+++ b/test/radialmap/radialMap/map.cpp
@@ -0,0 +1,413 @@
+/***********************************************************************
+* 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 "Config.h"
+#include "fileTree.h"
+#define SINCOS_H_IMPLEMENTATION (1)
+#include "sincos.h"
+#include "widget.h"
+
+RadialMap::Map::Map()
+ : m_signature(nullptr)
+ , m_visibleDepth(DEFAULT_RING_DEPTH)
+ , m_ringBreadth(MIN_RING_BREADTH)
+ , m_innerRadius(0)
+{
+
+ //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 = Config::defaultRingDepth;
+}
+
+void RadialMap::Map::make(const Folder *tree, bool refresh)
+{
+ if(height()<1)
+ abort();
+ //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 QList<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();
+
+ //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 (File *file : dir->files) {
+ if (file->isFolder() && file->size() > m_minSize) {
+ findVisibleDepth((Folder *)file, 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;
+
+ FileSize hiddenSize = 0;
+ uint hiddenFileCount = 0;
+
+ for (File *file : dir->files) {
+ if (file->size() < m_limits[depth] * 6) { // limit is half a degree? we want at least 3 degrees
+ hiddenSize += file->size();
+ if (file->isFolder()) { //**** considered virtual, but dir wouldn't count itself!
+ hiddenFileCount += static_cast<const Folder*>(file)->children(); //need to add one to count the dir as well
+ }
+ ++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].append(s);
+
+ if (file->isFolder()) {
+ if (depth != m_visibleDepth) {
+ //recurse
+ s->m_hasHiddenChildren = build((Folder*)file, depth + 1, a_start, a_start + a_len);
+ } else {
+ s->m_hasHiddenChildren = true;
+ }
+ }
+
+ a_start += a_len; //**** should we add 1?
+ }
+
+ if (hiddenFileCount == dir->children() && !Config::showSmallFiles) {
+ return true;
+ }
+
+ if ((depth == 0 || Config::showSmallFiles) && 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",
+ "%1 files, with an average size of %2").arg(hiddenFileCount)
+ .arg(QString::number(hiddenSize/hiddenFileCount));
+
+
+ (m_signature + depth)->append(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
+
+#define mw width()
+#define mh height()
+#define cw rect.width()
+#define 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;
+ }
+
+#undef mw
+#undef mh
+#undef cw
+#undef ch
+
+ return false;
+}
+
+void RadialMap::Map::colorise()
+{
+ if (!m_signature || m_signature->isEmpty()) {
+ qDebug() << "no signature yet";
+ return;
+ }
+
+ QColor cp, cb;
+ double darkness = 1;
+ double contrast = (double)Config::contrast / (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(QColor(255,255,255));
+
+ //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 && Config::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;
+ sincos(ra, &sinra, &cosra);
+ 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.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(0,0,0));
+ paint.setBrush(QColor(255,255,255));
+ paint.drawEllipse(rect);
+ paint.drawText(rect, Qt::AlignCenter, m_centerText);
+
+ m_innerRadius = rect.width() / 2; //rect.width should be multiple of 2
+
+ paint.end();
+}