1 /*****************************************************************************
2 * Copyright (C) 2003-2004 Max Howell <max.howell@methylblue.com> *
3 * Copyright (C) 2004-2019 Krusader Krew [https://krusader.org] *
4 * *
5 * This file is part of Krusader [https://krusader.org]. *
6 * *
7 * Krusader is free software: you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation, either version 2 of the License, or *
10 * (at your option) any later version. *
11 * *
12 * Krusader is distributed in the hope that it will be useful, *
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 * GNU General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU General Public License *
18 * along with Krusader. If not, see [http://www.gnu.org/licenses/]. *
19 *****************************************************************************/
20
21 // QtCore
22 #include <QList>
23 // QtGui
24 #include <QFont>
25 #include <QFontMetrics>
26 #include <QPainter>
27
28 #include <KCoreAddons/KStringHandler>
29
30 #include "Config.h"
31 #include "fileTree.h"
32 #include "radialMap.h"
33 #include "sincos.h"
34 #include "widget.h"
35
36 namespace RadialMap
37 {
38 struct Label {
LabelRadialMap::Label39 Label(const RadialMap::Segment *s, int l) : segment(s), lvl(l), a(segment->start() + (segment->length() / 2)) { }
40
tooCloseRadialMap::Label41 bool tooClose(const int &aa) const {
42 return (a > aa - LABEL_ANGLE_MARGIN && a < aa + LABEL_ANGLE_MARGIN);
43 }
44
45 const RadialMap::Segment *segment;
46 const unsigned int lvl;
47 const int a;
48
49 int x1, y1, x2, y2, x3;
50 int tx, ty;
51
52 QString qs;
53 };
54
55 class LabelList : public QList<Label *>
56 {
57
58 public:
~LabelList()59 ~LabelList() {
60 QListIterator<Label *> it(*this);
61 while (it.hasNext())
62 delete it.next();
63 }
64
inSort(Label * label1)65 void inSort(Label * label1) {
66 for (QList<Label *>::iterator it(begin()); it != end(); ++it) {
67 Label * label2 = *it;
68 bool ins = false;
69
70 //you add 1440 to work round the fact that later you want the circle split vertically
71 //and as it is you start at 3 o' clock. It's to do with rightPrevY, stops annoying bug
72
73 int a1 = label1->a + 1440;
74 int a2 = label2->a + 1440;
75
76 if (a1 == a2)
77 ins = true;
78
79 if (a1 > 5760) a1 -= 5760;
80 if (a2 > 5760) a2 -= 5760;
81
82 if (a1 < a2)
83 ins = true;
84
85 if (ins) {
86 insert(it, label1);
87 return;
88 }
89 }
90
91 append(label1);
92 }
93 };
94 }
95
96
97 void
paintExplodedLabels(QPainter & paint) const98 RadialMap::Widget::paintExplodedLabels(QPainter &paint) const
99 {
100 //we are a friend of RadialMap::Map
101
102 LabelList list;
103 unsigned int startLevel = 0;
104
105
106 //1. Create list of labels sorted in the order they will be rendered
107
108 if (m_focus != NULL && m_focus->file() != m_tree) { //separate behavior for selected vs unselected segments
109 //don't bother with files
110 if (m_focus->file() == 0 || !m_focus->file()->isDir()) return;
111
112 //find the range of levels we will be potentially drawing labels for
113 for (const Directory *p = (const Directory *)m_focus->file();
114 p != m_tree;
115 ++startLevel) { //startLevel is the level above whatever m_focus is in
116 p = p->parent();
117 }
118
119 //range=2 means 2 levels to draw labels for
120
121 unsigned int a1, a2, minAngle;
122
123 a1 = m_focus->start();
124 a2 = m_focus->end(); // boundary angles
125 minAngle = int(m_focus->length() * LABEL_MIN_ANGLE_FACTOR);
126
127
128 #define segment (*it)
129 #define ring (m_map.m_signature + i)
130
131 //**** Levels should be on a scale starting with 0
132 //**** range is a useless parameter
133 //**** keep a topblock var which is the lowestLevel OR startLevel for identation purposes
134 for (unsigned int i = startLevel; i <= m_map.m_visibleDepth; ++i) {
135 for (Iterator<Segment> it = ring->iterator(); it != ring->end(); ++it)
136 if (segment->start() >= a1 && segment->end() <= a2)
137 if (segment->length() > minAngle)
138 list.inSort(new Label(segment, i));
139 }
140
141 #undef ring
142 #undef segment
143
144 } else {
145
146 #define ring m_map.m_signature
147
148 for (Iterator<Segment> it = ring->iterator(); it != ring->end(); ++it)
149 if ((*it)->length() > 288)
150 list.inSort(new Label((*it), 0));
151
152 #undef ring
153
154 }
155
156 //2. Check to see if any adjacent labels are too close together
157 // if so, remove the least significant labels
158
159 int itn = 0;
160 int jtn = 1;
161
162 while (jtn < list.count()) {
163 //this method is fairly efficient
164
165 if (list[ itn ]->tooClose(list[ jtn ]->a)) {
166 if (list[ itn ]->lvl > list[ jtn ]->lvl) {
167 delete list[ itn ];
168 list.removeAt(itn);
169 jtn--;
170 itn = jtn;
171 } else {
172 delete list[ jtn ];
173 list.removeAt(jtn);
174 }
175 } else
176 ++itn;
177
178 jtn = itn;
179 ++jtn;
180 }
181
182 LabelList::iterator it = list.begin();
183
184 //used in next two steps
185 bool varySizes;
186 //**** should perhaps use doubles
187 int *sizes = new int [ m_map.m_visibleDepth + 1 ]; //**** make sizes an array of floats I think instead (or doubles)
188
189 do {
190 //3. Calculate font sizes
191
192 {
193 //determine current range of levels to draw for
194 unsigned int range = 0;
195
196 for (it = list.begin(); it != list.end(); ++it) {
197 unsigned int lvl = (*it)->lvl;
198 if (lvl > range)
199 range = lvl;
200
201 //**** better way would just be to assign if nothing is range
202 }
203
204 range -= startLevel; //range 0 means 1 level of labels
205
206 varySizes = Config::varyLabelFontSizes && (range != 0);
207
208 if (varySizes) {
209 //create an array of font sizes for various levels
210 //will exceed normal font pitch automatically if necessary, but not minPitch
211 //**** this needs to be checked lots
212
213 //**** what if this is negative (min size gtr than default size)
214 unsigned int step = (paint.font().pointSize() - Config::minFontPitch) / range;
215 if (step == 0) step = 1;
216
217 for (unsigned int x = range + startLevel, y = Config::minFontPitch;
218 x >= startLevel;
219 y += step, --x) {
220 sizes[x] = y;
221 }
222 }
223 }
224
225 //4. determine label co-ordinates
226
227 int x1, y1, x2, y2, x3, tx, ty; //coords
228 double sinra, cosra, ra; //angles
229
230 int cx = m_map.width() / 2 + m_offset.x(); //centre relative to canvas
231 int cy = m_map.height() / 2 + m_offset.y();
232
233 int spacer, preSpacer = int(m_map.m_ringBreadth * 0.5) + m_map.m_innerRadius;
234 int fullStrutLength = (m_map.width() - m_map.MAP_2MARGIN) / 2 + LABEL_MAP_SPACER; //full length of a strut from map center
235
236 int prevLeftY = 0;
237 int prevRightY = height();
238
239 bool rightSide;
240
241 QFont font;
242
243 for (it = list.begin(); it != list.end(); ++it) {
244 //** bear in mind that text is drawn with QPoint param as BOTTOM left corner of text box
245 if (varySizes) font.setPointSize(sizes[(*it)->lvl]);
246 QFontMetrics fm(font);
247 int fmh = fm.height(); //used to ensure label texts don't overlap
248 int fmhD4 = fmh / 4;
249
250 fmh += LABEL_TEXT_VMARGIN;
251
252 rightSide = ((*it)->a < 1440 || (*it)->a > 4320);
253
254 ra = M_PI / 2880 * (*it)->a; //convert to radians
255 #if 0
256 sincos(ra, &sinra, &cosra);
257 #endif
258 sinra = sin(ra); cosra = cos(ra);
259
260 spacer = preSpacer + m_map.m_ringBreadth * (*it)->lvl;
261
262 x1 = cx + (int)(cosra * spacer);
263 y1 = cy - (int)(sinra * spacer);
264 y2 = y1 - (int)(sinra * (fullStrutLength - spacer));
265
266 if (rightSide) { //righthand side, going upwards
267
268 if (y2 > prevRightY /*- fmh*/) //then it is too low, needs to be drawn higher
269 y2 = prevRightY /*- fmh*/;
270
271 } else { //lefthand side, going downwards
272
273 if (y2 < prevLeftY/* + fmh*/) //then we're too high, need to be drawn lower
274 y2 = prevLeftY /*+ fmh*/;
275 }
276
277 x2 = x1 - int(double(y2 - y1) / tan(ra));
278 ty = y2 + fmhD4;
279
280 QString qs;
281 if (rightSide) {
282
283 if (x2 > width() || ty < fmh || x2 < x1) {
284 //skip this strut
285 //**** don't duplicate this code
286 delete *it;
287 it = list.erase(it); //will delete the label and set it to list.current() which _should_ be the next ptr
288 break;
289 }
290
291 prevRightY = ty - fmh - fmhD4; //must be after above's "continue"
292
293 qs = fm.elidedText((*it)->segment->file()->name(), Qt::ElideMiddle, width() - x2);
294
295 x3 = width() - fm.width(qs)
296 - LABEL_HMARGIN //outer margin
297 - LABEL_TEXT_HMARGIN //margin between strut and text
298 //- ((*it)->lvl - startLevel) * LABEL_HMARGIN //indentation
299 ;
300 if (x3 < x2) x3 = x2;
301 tx = x3 + LABEL_TEXT_HMARGIN;
302
303 } else {
304
305 if (x2 < 0 || ty > height() || x2 > x1) {
306 //skip this strut
307 delete *it;
308 it = list.erase(it); //will delete the label and set it to list.current() which _should_ be the next ptr
309 break;
310 }
311
312 prevLeftY = ty + fmh - fmhD4;
313
314 qs = fm.elidedText((*it)->segment->file()->name(), Qt::ElideMiddle, x2);
315
316 //**** needs a little tweaking:
317
318 tx = fm.width(qs) + LABEL_HMARGIN/* + ((*it)->lvl - startLevel) * LABEL_HMARGIN*/;
319 if (tx > x2) { //text is too long
320 tx = LABEL_HMARGIN + x2 - tx; //some text will be lost from sight
321 x3 = x2; //no text margin (right side of text here)
322 } else {
323 x3 = tx + LABEL_TEXT_HMARGIN;
324 tx = LABEL_HMARGIN /* + ((*it)->lvl - startLevel) * LABEL_HMARGIN*/;
325 }
326 }
327
328 (*it)->x1 = x1;
329 (*it)->y1 = y1;
330 (*it)->x2 = x2;
331 (*it)->y2 = y2;
332 (*it)->x3 = x3;
333 (*it)->tx = tx;
334 (*it)->ty = ty;
335 (*it)->qs = qs;
336 }
337
338 //if an element is deleted at this stage, we need to do this whole
339 //iteration again, thus the following loop
340 //**** in rare case that deleted label was last label in top level
341 // and last in labelList too, this will not work as expected (not critical)
342
343 } while (it != list.end());
344
345
346 //5. Render labels
347
348 paint.setPen(QPen(Qt::black, 1));
349
350 for (it = list.begin(); it != list.end(); ++it) {
351 if (varySizes) {
352 //**** how much overhead in making new QFont each time?
353 // (implicate sharing remember)
354 QFont font = paint.font();
355 font.setPointSize(sizes[(*it)->lvl]);
356 paint.setFont(font);
357 }
358
359 paint.drawEllipse((*it)->x1 - 3, (*it)->y1 - 3, 7, 7); //**** CPU intensive! better to use a pixmap
360 paint.drawLine((*it)->x1, (*it)->y1, (*it)->x2, (*it)->y2);
361 paint.drawLine((*it)->x2, (*it)->y2, (*it)->x3, (*it)->y2);
362 paint.drawText((*it)->tx, (*it)->ty, (*it)->qs);
363 }
364
365 delete [] sizes;
366 }
367