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 }