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

0001 /*
0002     SPDX-FileCopyrightText: 2003-2004 Max Howell <max.howell@methylblue.com>
0003     SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 // QtGui
0009 #include <QFont> //ctor
0010 #include <QFontMetrics> //ctor
0011 #include <QImage> //make() & paint()
0012 #include <QPainter>
0013 #include <QPolygon>
0014 // QtWidgets
0015 #include <QApplication> //make()
0016 
0017 #include <KConfigWidgets/KColorScheme>
0018 #include <KIconThemes/KIconEffect> //desaturate()
0019 #include <KWidgetsAddons/KCursor> //make()
0020 
0021 #include "Config.h"
0022 #include "builder.h"
0023 #include "fileTree.h"
0024 #include "widget.h"
0025 #include <cmath>
0026 
0027 #define COLOR_GREY QColor::fromHsv(0, 0, 140)
0028 
0029 RadialMap::Map::Map()
0030     : m_signature(nullptr)
0031     , m_ringBreadth(MIN_RING_BREADTH)
0032     , m_innerRadius(0)
0033     , m_visibleDepth(DEFAULT_RING_DEPTH)
0034 {
0035     // FIXME this is all broken. No longer is a maximum depth!
0036     const int fmh = QFontMetrics(QFont()).height();
0037     const int fmhD4 = fmh / 4;
0038     MAP_2MARGIN = 2 * (fmh - (fmhD4 - LABEL_MAP_SPACER)); // margin is dependent on fitting in labels at top and bottom
0039 }
0040 
0041 RadialMap::Map::~Map()
0042 {
0043     delete[] m_signature;
0044 }
0045 
0046 void RadialMap::Map::invalidate(const bool desaturateTheImage)
0047 {
0048     delete[] m_signature;
0049     m_signature = nullptr;
0050 
0051     if (desaturateTheImage) {
0052         QImage img = this->toImage();
0053 
0054         KIconEffect::deSaturate(img, 0.7f);
0055         KIconEffect::toGray(img, true);
0056 
0057         this->QPixmap::operator=(fromImage(img, Qt::AutoColor));
0058     }
0059 
0060     m_visibleDepth = Config::defaultRingDepth;
0061 }
0062 
0063 void RadialMap::Map::make(const Directory *tree, bool refresh)
0064 {
0065     //**** determineText seems pointless optimization
0066     //   but is it good to keep the text consistent?
0067     //   even if it makes it a lie?
0068 
0069     // slow operation so set the wait cursor
0070     QApplication::setOverrideCursor(Qt::WaitCursor);
0071 
0072     {
0073         // build a signature of visible components
0074         delete[] m_signature;
0075         Builder builder(this, tree, refresh);
0076     }
0077 
0078     // colour the segments
0079     colorise();
0080 
0081     // determine centerText
0082     if (!refresh) {
0083         int i;
0084 
0085         for (i = 3; i > 0; --i)
0086             if (tree->size() > File::DENOMINATOR[i])
0087                 break;
0088 
0089         m_centerText = tree->humanReadableSize((File::UnitPrefix)i);
0090     }
0091 
0092     // paint the pixmap
0093     aaPaint();
0094 
0095     QApplication::restoreOverrideCursor();
0096 }
0097 
0098 void RadialMap::Map::setRingBreadth()
0099 {
0100     m_ringBreadth = (height() - MAP_2MARGIN) / (2 * m_visibleDepth + 4);
0101 
0102     if (m_ringBreadth < MIN_RING_BREADTH)
0103         m_ringBreadth = MIN_RING_BREADTH;
0104     else if (m_ringBreadth > MAX_RING_BREADTH)
0105         m_ringBreadth = MAX_RING_BREADTH;
0106 }
0107 
0108 bool RadialMap::Map::resize(const QRect &rect)
0109 {
0110     // there's a MAP_2MARGIN border
0111 
0112 #define mw width()
0113 #define mh height()
0114 #define cw rect.width()
0115 #define ch rect.height()
0116 
0117     if (cw < mw || ch < mh || (cw > mw && ch > mh)) {
0118         uint size = ((cw < ch) ? cw : ch) - MAP_2MARGIN;
0119 
0120         // this also causes uneven sizes to always resize when resizing but map is small in that dimension
0121         // size -= size % 2; //even sizes mean less staggered non-antialiased resizing
0122 
0123         {
0124             const uint minSize = MIN_RING_BREADTH * 2 * (m_visibleDepth + 2);
0125             const uint mD2 = MAP_2MARGIN / 2;
0126 
0127             if (size < minSize)
0128                 size = minSize;
0129 
0130             // this QRect is used by paint()
0131             m_rect.setRect(mD2, mD2, size, size);
0132         }
0133 
0134         // resize the pixmap
0135         size += MAP_2MARGIN;
0136         this->QPixmap::operator=(QPixmap(size, size));
0137 
0138         if (m_signature != nullptr) {
0139             setRingBreadth();
0140             paint();
0141         } else
0142             fill(); // FIXME I don't like having to do this..
0143 
0144         return true;
0145     }
0146 
0147 #undef mw
0148 #undef mh
0149 #undef cw
0150 #undef ch
0151 
0152     return false;
0153 }
0154 
0155 void RadialMap::Map::colorise()
0156 {
0157     QColor cp, cb;
0158     double darkness = 1;
0159     double contrast = (double)Config::contrast / (double)100;
0160     int h, s1, s2, v1, v2;
0161 
0162     QColor kdeColour[2] = {KColorScheme(QPalette::Inactive, KColorScheme::Window).background().color(),
0163                            KColorScheme(QPalette::Active, KColorScheme::Window).background(KColorScheme::ActiveBackground).color()};
0164 
0165     double deltaRed = (double)(kdeColour[0].red() - kdeColour[1].red()) / 2880; // 2880 for semicircle
0166     double deltaGreen = (double)(kdeColour[0].green() - kdeColour[1].green()) / 2880;
0167     double deltaBlue = (double)(kdeColour[0].blue() - kdeColour[1].blue()) / 2880;
0168 
0169     for (uint i = 0; i <= m_visibleDepth; ++i, darkness += 0.04) {
0170         for (Iterator<Segment> it = m_signature[i].iterator(); it != m_signature[i].end(); ++it) {
0171             switch (Config::scheme) {
0172             case Filelight::KDE: {
0173                 // gradient will work by figuring out rgb delta values for 360 degrees
0174                 // then each component is angle*delta
0175 
0176                 int a = (*it)->start();
0177 
0178                 if (a > 2880)
0179                     a = 2880 - (a - 2880);
0180 
0181                 h = (int)(deltaRed * a) + kdeColour[1].red();
0182                 s1 = (int)(deltaGreen * a) + kdeColour[1].green();
0183                 v1 = (int)(deltaBlue * a) + kdeColour[1].blue();
0184 
0185                 cb.setRgb(h, s1, v1);
0186                 cb.getHsv(&h, &s1, &v1);
0187 
0188                 break;
0189             }
0190 
0191             case Filelight::HighContrast:
0192 
0193                 cp.setHsv(0, 0, 0); // values of h, s and v are irrelevant
0194                 cb.setHsv(180, 0, int(255.0 * contrast));
0195                 (*it)->setPalette(cp, cb);
0196                 continue;
0197 
0198             default:
0199                 h = int((*it)->start() / 16);
0200                 s1 = 160;
0201                 v1 = (int)(255.0 / darkness); //****doing this more often than once seems daft!
0202             }
0203 
0204             v2 = v1 - int(contrast * v1);
0205             s2 = s1 + int(contrast * (255 - s1));
0206 
0207             if (s1 < 80)
0208                 s1 = 80; // can fall too low and makes contrast between the files hard to discern
0209 
0210             if ((*it)->isFake()) { // multi-file
0211                 cb.setHsv(h, s2, (v2 < 90) ? 90 : v2); // too dark if < 100
0212                 cp.setHsv(h, 17, v1);
0213             } else if (!(*it)->file()->isDir()) { // file
0214                 cb.setHsv(h, 17, v1);
0215                 cp.setHsv(h, 17, v2);
0216             } else { // directory
0217                 cb.setHsv(h, s1, v1); // v was 225
0218                 cp.setHsv(h, s2, v2); // v was 225 - delta
0219             }
0220 
0221             (*it)->setPalette(cp, cb);
0222 
0223             //**** may be better to store KDE colours as H and S and vary V as others
0224             //**** perhaps make saturation difference for s2 dependent on contrast too
0225             //**** fake segments don't work with highContrast
0226             //**** may work better with cp = cb rather than Qt::white
0227             //**** you have to ensure the grey of files is sufficient, currently it works only with rainbow (perhaps use contrast there too)
0228             //**** change v1,v2 to vp, vb etc.
0229             //**** using percentages is not strictly correct as the eye doesn't work like that
0230             //**** darkness factor is not done for kde_colour scheme, and also value for files is incorrect really for files in this scheme as it is not set
0231             // like rainbow one is
0232         }
0233     }
0234 }
0235 
0236 void RadialMap::Map::aaPaint()
0237 {
0238     // paint() is called during continuous processes
0239     // aaPaint() is not and is slower so set overidecursor (make sets it too)
0240     QApplication::setOverrideCursor(Qt::WaitCursor);
0241     paint(Config::antiAliasFactor);
0242     QApplication::restoreOverrideCursor();
0243 }
0244 
0245 void RadialMap::Map::paint(unsigned int scaleFactor)
0246 {
0247     if (scaleFactor == 0) // just in case
0248         scaleFactor = 1;
0249 
0250     QPainter paint;
0251     QRect rect = m_rect;
0252     int step = m_ringBreadth;
0253     int excess = -1;
0254 
0255     // scale the pixmap, or do intelligent distribution of excess to prevent nasty resizing
0256     if (scaleFactor > 1) {
0257         int x1, y1, x2, y2;
0258         rect.getCoords(&x1, &y1, &x2, &y2);
0259         x1 *= scaleFactor;
0260         y1 *= scaleFactor;
0261         x2 *= scaleFactor;
0262         y2 *= scaleFactor;
0263         rect.setCoords(x1, y1, x2, y2);
0264 
0265         step *= scaleFactor;
0266         QPixmap::operator=(QPixmap(this->size() * (int)scaleFactor));
0267     } else if (m_ringBreadth != MAX_RING_BREADTH && m_ringBreadth != MIN_RING_BREADTH) {
0268         excess = rect.width() % m_ringBreadth;
0269         ++step;
0270     }
0271 
0272     //**** best option you can think of is to make the circles slightly less perfect,
0273     //  ** i.e. slightly elliptic when resizing inbetween
0274 
0275     paint.begin(this);
0276 
0277     fill(); // erase background
0278 
0279     for (int x = m_visibleDepth; x >= 0; --x) {
0280         int width = rect.width() / 2;
0281         // clever geometric trick to find largest angle that will give biggest arrow head
0282         auto a_max = int(acos((double)width / double((width + 5) * scaleFactor)) * (180 * 16 / M_PI));
0283 
0284         for (ConstIterator<Segment> it = m_signature[x].constIterator(); it != m_signature[x].end(); ++it) {
0285             // draw the pie segments, most of this code is concerned with drawing the little
0286             // arrows on the ends of segments when they have hidden files
0287 
0288             paint.setPen((*it)->pen());
0289 
0290             if ((*it)->hasHiddenChildren()) {
0291                 // draw arrow head to indicate undisplayed files/directories
0292                 QPolygon pts(3);
0293                 QPoint pos, cpos = rect.center();
0294                 int a[3] = {static_cast<int>((*it)->start()), static_cast<int>((*it)->length()), 0};
0295 
0296                 a[2] = a[0] + (a[1] / 2); // assign to halfway between
0297                 if (a[1] > a_max) {
0298                     a[1] = a_max;
0299                     a[0] = a[2] - a_max / 2;
0300                 }
0301 
0302                 a[1] += a[0];
0303 
0304                 for (int i = 0, radius = width; i < 3; ++i) {
0305                     double ra = M_PI / (180 * 16) * a[i], sinra, cosra;
0306 
0307                     if (i == 2)
0308                         radius += 5 * scaleFactor;
0309                     sinra = sin(ra);
0310                     cosra = cos(ra);
0311                     pos.rx() = cpos.x() + static_cast<int>(cosra * radius);
0312                     pos.ry() = cpos.y() - static_cast<int>(sinra * radius);
0313                     pts.setPoint(i, pos);
0314                 }
0315 
0316                 paint.setBrush((*it)->pen());
0317                 paint.drawPolygon(pts);
0318             }
0319 
0320             paint.setBrush((*it)->brush());
0321             paint.drawPie(rect, (*it)->start(), (*it)->length());
0322 
0323             if ((*it)->hasHiddenChildren()) {
0324                 //**** code is bloated!
0325                 paint.save();
0326                 QPen pen = paint.pen();
0327                 int width = 2 * scaleFactor;
0328                 pen.setWidth(width);
0329                 paint.setPen(pen);
0330                 QRect rect2 = rect;
0331                 width /= 2;
0332                 rect2.adjust(width, width, -width, -width);
0333                 paint.drawArc(rect2, (*it)->start(), (*it)->length());
0334                 paint.restore();
0335             }
0336         }
0337 
0338         if (excess >= 0) { // excess allows us to resize more smoothly (still crud tho)
0339             if (excess < 2) // only decrease rect by more if even number of excesses left
0340                 --step;
0341             excess -= 2;
0342         }
0343 
0344         rect.adjust(step, step, -step, -step);
0345     }
0346 
0347     //  if( excess > 0 ) rect.adjust( excess, excess, 0, 0 ); //ugly
0348 
0349     paint.setPen(COLOR_GREY);
0350     paint.setBrush(Qt::white);
0351     paint.drawEllipse(rect);
0352 
0353     if (scaleFactor > 1) {
0354         // have to end in order to smoothscale()
0355         paint.end();
0356 
0357         int x1, y1, x2, y2;
0358         rect.getCoords(&x1, &y1, &x2, &y2);
0359         x1 /= scaleFactor;
0360         y1 /= scaleFactor;
0361         x2 /= scaleFactor;
0362         y2 /= scaleFactor;
0363         rect.setCoords(x1, y1, x2, y2);
0364 
0365         QImage img = this->toImage();
0366         img = img.scaled(this->size() / (int)scaleFactor, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
0367         this->QPixmap::operator=(fromImage(img, Qt::AutoColor));
0368 
0369         paint.begin(this);
0370         paint.setPen(COLOR_GREY);
0371         paint.setBrush(Qt::white);
0372     }
0373 
0374     paint.drawText(rect, Qt::AlignCenter, m_centerText);
0375 
0376     m_innerRadius = rect.width() / 2; // rect.width should be multiple of 2
0377 
0378     paint.end();
0379 }