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 }