Warning, file /utilities/krusader/app/DiskUsage/radialMap/labels.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 // QtCore
0009 #include <QList>
0010 // QtGui
0011 #include <QFont>
0012 #include <QFontMetrics>
0013 #include <QPainter>
0014 
0015 #include <KCoreAddons/KStringHandler>
0016 
0017 #include "../compat.h"
0018 #include "Config.h"
0019 #include "fileTree.h"
0020 #include "radialMap.h"
0021 #include "widget.h"
0022 #include <cmath>
0023 
0024 namespace RadialMap
0025 {
0026 struct Label {
0027     Label(const RadialMap::Segment *s, int l)
0028         : segment(s)
0029         , lvl(l)
0030         , a(segment->start() + (segment->length() / 2))
0031     {
0032     }
0033 
0034     bool tooClose(const int &aa) const
0035     {
0036         return (a > aa - LABEL_ANGLE_MARGIN && a < aa + LABEL_ANGLE_MARGIN);
0037     }
0038 
0039     const RadialMap::Segment *segment;
0040     const unsigned int lvl;
0041     const int a;
0042 
0043     int x1, y1, x2, y2, x3;
0044     int tx, ty;
0045 
0046     QString qs;
0047 };
0048 
0049 class LabelList : public QList<Label *>
0050 {
0051 public:
0052     ~LabelList()
0053     {
0054         QListIterator<Label *> it(*this);
0055         while (it.hasNext())
0056             delete it.next();
0057     }
0058 
0059     void inSort(Label *label1)
0060     {
0061         for (QList<Label *>::iterator it(begin()); it != end(); ++it) {
0062             Label *label2 = *it;
0063             bool ins = false;
0064 
0065             // you add 1440 to work round the fact that later you want the circle split vertically
0066             // and as it is you start at 3 o' clock. It's to do with rightPrevY, stops annoying bug
0067 
0068             int a1 = label1->a + 1440;
0069             int a2 = label2->a + 1440;
0070 
0071             if (a1 == a2)
0072                 ins = true;
0073 
0074             if (a1 > 5760)
0075                 a1 -= 5760;
0076             if (a2 > 5760)
0077                 a2 -= 5760;
0078 
0079             if (a1 < a2)
0080                 ins = true;
0081 
0082             if (ins) {
0083                 insert(it, label1);
0084                 return;
0085             }
0086         }
0087 
0088         append(label1);
0089     }
0090 };
0091 }
0092 
0093 void RadialMap::Widget::paintExplodedLabels(QPainter &paint) const
0094 {
0095     // we are a friend of RadialMap::Map
0096 
0097     LabelList list;
0098     unsigned int startLevel = 0;
0099 
0100     // 1. Create list of labels  sorted in the order they will be rendered
0101 
0102     if (m_focus != nullptr && m_focus->file() != m_tree) { // separate behavior for selected vs unselected segments
0103         // don't bother with files
0104         if (m_focus->file() == nullptr || !m_focus->file()->isDir())
0105             return;
0106 
0107         // find the range of levels we will be potentially drawing labels for
0108         for (const auto *p = dynamic_cast<const Directory *>(m_focus->file()); p != m_tree;
0109              ++startLevel) { // startLevel is the level above whatever m_focus is in
0110             p = p->parent();
0111         }
0112 
0113         // range=2 means 2 levels to draw labels for
0114 
0115         unsigned int a1, a2, minAngle;
0116 
0117         a1 = m_focus->start();
0118         a2 = m_focus->end(); // boundary angles
0119         minAngle = int(m_focus->length() * LABEL_MIN_ANGLE_FACTOR);
0120 
0121 #define segment (*it)
0122 #define ring (m_map.m_signature + i)
0123 
0124         //**** Levels should be on a scale starting with 0
0125         //**** range is a useless parameter
0126         //**** keep a topblock var which is the lowestLevel OR startLevel for indentation purposes
0127         for (unsigned int i = startLevel; i <= m_map.m_visibleDepth; ++i) {
0128             for (Iterator<Segment> it = ring->iterator(); it != ring->end(); ++it)
0129                 if (segment->start() >= a1 && segment->end() <= a2)
0130                     if (segment->length() > minAngle)
0131                         list.inSort(new Label(segment, i));
0132         }
0133 
0134 #undef ring
0135 #undef segment
0136 
0137     } else {
0138 #define ring m_map.m_signature
0139 
0140         for (Iterator<Segment> it = ring->iterator(); it != ring->end(); ++it)
0141             if ((*it)->length() > 288)
0142                 list.inSort(new Label((*it), 0));
0143 
0144 #undef ring
0145     }
0146 
0147     // 2. Check to see if any adjacent labels are too close together
0148     //   if so, remove the least significant labels
0149 
0150     int itn = 0;
0151     int jtn = 1;
0152 
0153     while (jtn < list.count()) {
0154         // this method is fairly efficient
0155 
0156         if (list[itn]->tooClose(list[jtn]->a)) {
0157             if (list[itn]->lvl > list[jtn]->lvl) {
0158                 delete list[itn];
0159                 list.removeAt(itn);
0160                 jtn--;
0161                 itn = jtn;
0162             } else {
0163                 delete list[jtn];
0164                 list.removeAt(jtn);
0165             }
0166         } else
0167             ++itn;
0168 
0169         jtn = itn;
0170         ++jtn;
0171     }
0172 
0173     LabelList::iterator it = list.begin();
0174 
0175     // used in next two steps
0176     bool varySizes;
0177     //**** should perhaps use doubles
0178     auto *sizes = new int[m_map.m_visibleDepth + 1]; //**** make sizes an array of floats I think instead (or doubles)
0179 
0180     do {
0181         // 3. Calculate font sizes
0182 
0183         {
0184             // determine current range of levels to draw for
0185             unsigned int range = 0;
0186 
0187             for (it = list.begin(); it != list.end(); ++it) {
0188                 unsigned int lvl = (*it)->lvl;
0189                 if (lvl > range)
0190                     range = lvl;
0191 
0192                 //**** better way would just be to assign if nothing is range
0193             }
0194 
0195             range -= startLevel; // range 0 means 1 level of labels
0196 
0197             varySizes = Config::varyLabelFontSizes && (range != 0);
0198 
0199             if (varySizes) {
0200                 // create an array of font sizes for various levels
0201                 // will exceed normal font pitch automatically if necessary, but not minPitch
0202                 //**** this needs to be checked lots
0203 
0204                 //**** what if this is negative (min size gtr than default size)
0205                 unsigned int step = (paint.font().pointSize() - Config::minFontPitch) / range;
0206                 if (step == 0)
0207                     step = 1;
0208 
0209                 for (unsigned int x = range + startLevel, y = Config::minFontPitch; x >= startLevel; y += step, --x) {
0210                     sizes[x] = y;
0211                 }
0212             }
0213         }
0214 
0215         // 4. determine label co-ordinates
0216 
0217         int x1, y1, x2, y2, x3, tx, ty; // coords
0218         double sinra, cosra, ra; // angles
0219 
0220         int cx = m_map.width() / 2 + m_offset.x(); // centre relative to canvas
0221         int cy = m_map.height() / 2 + m_offset.y();
0222 
0223         int spacer, preSpacer = int(m_map.m_ringBreadth * 0.5) + m_map.m_innerRadius;
0224         int fullStrutLength = (m_map.width() - m_map.MAP_2MARGIN) / 2 + LABEL_MAP_SPACER; // full length of a strut from map center
0225 
0226         int prevLeftY = 0;
0227         int prevRightY = height();
0228 
0229         bool rightSide;
0230 
0231         QFont font;
0232 
0233         for (it = list.begin(); it != list.end(); ++it) {
0234             //** bear in mind that text is drawn with QPoint param as BOTTOM left corner of text box
0235             if (varySizes)
0236                 font.setPointSize(sizes[(*it)->lvl]);
0237             QFontMetrics fm(font);
0238             int fmh = fm.height(); // used to ensure label texts don't overlap
0239             int fmhD4 = fmh / 4;
0240 
0241             fmh += LABEL_TEXT_VMARGIN;
0242 
0243             rightSide = ((*it)->a < 1440 || (*it)->a > 4320);
0244 
0245             ra = M_PI / 2880 * (*it)->a; // convert to radians
0246             sinra = sin(ra);
0247             cosra = cos(ra);
0248 
0249             spacer = preSpacer + m_map.m_ringBreadth * (*it)->lvl;
0250 
0251             x1 = cx + (int)(cosra * spacer);
0252             y1 = cy - (int)(sinra * spacer);
0253             y2 = y1 - (int)(sinra * (fullStrutLength - spacer));
0254 
0255             if (rightSide) { // righthand side, going upwards
0256 
0257                 if (y2 > prevRightY /*- fmh*/) // then it is too low, needs to be drawn higher
0258                     y2 = prevRightY /*- fmh*/;
0259 
0260             } else { // lefthand side, going downwards
0261 
0262                 if (y2 < prevLeftY /* + fmh*/) // then we're too high, need to be drawn lower
0263                     y2 = prevLeftY /*+ fmh*/;
0264             }
0265 
0266             x2 = x1 - int(double(y2 - y1) / tan(ra));
0267             ty = y2 + fmhD4;
0268 
0269             QString qs;
0270             if (rightSide) {
0271                 if (x2 > width() || ty < fmh || x2 < x1) {
0272                     // skip this strut
0273                     //**** don't duplicate this code
0274                     delete *it;
0275                     it = list.erase(it); // will delete the label and set it to list.current() which _should_ be the next ptr
0276                     break;
0277                 }
0278 
0279                 prevRightY = ty - fmh - fmhD4; // must be after above's "continue"
0280 
0281                 qs = fm.elidedText((*it)->segment->file()->name(), Qt::ElideMiddle, width() - x2);
0282 
0283                 x3 = width() - fm.horizontalAdvance(qs) - LABEL_HMARGIN // outer margin
0284                     - LABEL_TEXT_HMARGIN // margin between strut and text
0285                     //- ((*it)->lvl - startLevel) * LABEL_HMARGIN //indentation
0286                     ;
0287                 if (x3 < x2)
0288                     x3 = x2;
0289                 tx = x3 + LABEL_TEXT_HMARGIN;
0290 
0291             } else {
0292                 if (x2 < 0 || ty > height() || x2 > x1) {
0293                     // skip this strut
0294                     delete *it;
0295                     it = list.erase(it); // will delete the label and set it to list.current() which _should_ be the next ptr
0296                     break;
0297                 }
0298 
0299                 prevLeftY = ty + fmh - fmhD4;
0300 
0301                 qs = fm.elidedText((*it)->segment->file()->name(), Qt::ElideMiddle, x2);
0302 
0303                 //**** needs a little tweaking:
0304 
0305                 tx = fm.horizontalAdvance(qs) + LABEL_HMARGIN /* + ((*it)->lvl - startLevel) * LABEL_HMARGIN*/;
0306                 if (tx > x2) { // text is too long
0307                     tx = LABEL_HMARGIN + x2 - tx; // some text will be lost from sight
0308                     x3 = x2; // no text margin (right side of text here)
0309                 } else {
0310                     x3 = tx + LABEL_TEXT_HMARGIN;
0311                     tx = LABEL_HMARGIN /* + ((*it)->lvl - startLevel) * LABEL_HMARGIN*/;
0312                 }
0313             }
0314 
0315             (*it)->x1 = x1;
0316             (*it)->y1 = y1;
0317             (*it)->x2 = x2;
0318             (*it)->y2 = y2;
0319             (*it)->x3 = x3;
0320             (*it)->tx = tx;
0321             (*it)->ty = ty;
0322             (*it)->qs = qs;
0323         }
0324 
0325         // if an element is deleted at this stage, we need to do this whole
0326         // iteration again, thus the following loop
0327         //**** in rare case that deleted label was last label in top level
0328         //      and last in labelList too, this will not work as expected (not critical)
0329 
0330     } while (it != list.end());
0331 
0332     // 5. Render labels
0333 
0334     paint.setPen(QPen(Qt::black, 1));
0335 
0336     for (it = list.begin(); it != list.end(); ++it) {
0337         if (varySizes) {
0338             //**** how much overhead in making new QFont each time?
0339             //     (implicate sharing remember)
0340             QFont font = paint.font();
0341             font.setPointSize(sizes[(*it)->lvl]);
0342             paint.setFont(font);
0343         }
0344 
0345         paint.drawEllipse((*it)->x1 - 3, (*it)->y1 - 3, 7, 7); //**** CPU intensive! better to use a pixmap
0346         paint.drawLine((*it)->x1, (*it)->y1, (*it)->x2, (*it)->y2);
0347         paint.drawLine((*it)->x2, (*it)->y2, (*it)->x3, (*it)->y2);
0348         paint.drawText((*it)->tx, (*it)->ty, (*it)->qs);
0349     }
0350 
0351     delete[] sizes;
0352 }