Marble

GeoPainter.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2006-2009 Torsten Rahn <tackat@kde.org>
4
5#include "GeoPainter.h"
6#include "GeoPainter_p.h"
7
8#include <QList>
9#include <QPainterPath>
10#include <QPixmapCache>
11#include <QRegion>
12#include <qmath.h>
13
14#include "MarbleDebug.h"
15
16#include "GeoDataCoordinates.h"
17#include "GeoDataLatLonAltBox.h"
18#include "GeoDataLineString.h"
19#include "GeoDataLinearRing.h"
20#include "GeoDataPoint.h"
21#include "GeoDataPolygon.h"
22
23#include "AbstractProjection.h"
24#include "ViewportParams.h"
25
26// #define MARBLE_DEBUG
27
28using namespace Marble;
29
30GeoPainterPrivate::GeoPainterPrivate(GeoPainter *q, const ViewportParams *viewport, MapQuality mapQuality)
31 : m_viewport(viewport)
32 , m_mapQuality(mapQuality)
33 , m_x(new qreal[100])
34 , m_parent(q)
35{
36}
37
38GeoPainterPrivate::~GeoPainterPrivate()
39{
40 delete[] m_x;
41}
42
43void GeoPainterPrivate::createAnnotationLayout(qreal x,
44 qreal y,
45 const QSizeF &bubbleSize,
46 qreal bubbleOffsetX,
47 qreal bubbleOffsetY,
48 qreal xRnd,
49 qreal yRnd,
50 QPainterPath &path,
51 QRectF &rect)
52{
53 // TODO: MOVE this into an own Annotation class
54 qreal arrowPosition = 0.3;
55 qreal arrowWidth = 12.0;
56
57 qreal width = bubbleSize.width();
58 qreal height = bubbleSize.height();
59
60 qreal dx = (bubbleOffsetX > 0) ? 1.0 : -1.0; // x-Mirror
61 qreal dy = (bubbleOffsetY < 0) ? 1.0 : -1.0; // y-Mirror
62
63 qreal x0 = (x + bubbleOffsetX) - dx * (1.0 - arrowPosition) * (width - 2.0 * xRnd) - xRnd * dx;
64 qreal x1 = (x + bubbleOffsetX) - dx * (1.0 - arrowPosition) * (width - 2.0 * xRnd);
65 qreal x2 = (x + bubbleOffsetX) - dx * (1.0 - arrowPosition) * (width - 2.0 * xRnd) + xRnd * dx;
66 qreal x3 = (x + bubbleOffsetX) - dx * arrowWidth / 2.0;
67 qreal x4 = (x + bubbleOffsetX) + dx * arrowWidth / 2.0;
68 qreal x5 = (x + bubbleOffsetX) + dx * arrowPosition * (width - 2.0 * xRnd) - xRnd * dx;
69 qreal x6 = (x + bubbleOffsetX) + dx * arrowPosition * (width - 2.0 * xRnd);
70 qreal x7 = (x + bubbleOffsetX) + dx * arrowPosition * (width - 2.0 * xRnd) + xRnd * dx;
71
72 qreal y0 = (y + bubbleOffsetY);
73 qreal y1 = (y + bubbleOffsetY) - dy * yRnd;
74 qreal y2 = (y + bubbleOffsetY) - dy * 2 * yRnd;
75 qreal y5 = (y + bubbleOffsetY) - dy * (height - 2 * yRnd);
76 qreal y6 = (y + bubbleOffsetY) - dy * (height - yRnd);
77 qreal y7 = (y + bubbleOffsetY) - dy * height;
78
79 QPointF p1(x, y); // pointing point
80 QPointF p2(x4, y0);
81 QPointF p3(x6, y0);
82 QPointF p4(x7, y1);
83 QPointF p5(x7, y6);
84 QPointF p6(x6, y7);
85 QPointF p7(x1, y7);
86 QPointF p8(x0, y6);
87 QPointF p9(x0, y1);
88 QPointF p10(x1, y0);
89 QPointF p11(x3, y0);
90
91 QRectF bubbleBoundingBox(QPointF(x0, y7), QPointF(x7, y0));
92
93 path.moveTo(p1);
94 path.lineTo(p2);
95
96 path.lineTo(p3);
97 QRectF bottomRight(QPointF(x5, y2), QPointF(x7, y0));
98 path.arcTo(bottomRight, 270.0, 90.0);
99
100 path.lineTo(p5);
101 QRectF topRight(QPointF(x5, y7), QPointF(x7, y5));
102 path.arcTo(topRight, 0.0, 90.0);
103
104 path.lineTo(p7);
105 QRectF topLeft(QPointF(x0, y7), QPointF(x2, y5));
106 path.arcTo(topLeft, 90.0, 90.0);
107
108 path.lineTo(p9);
109 QRectF bottomLeft(QPointF(x0, y2), QPointF(x2, y0));
110 path.arcTo(bottomLeft, 180.0, 90.0);
111
112 path.lineTo(p10);
113 path.lineTo(p11);
114 path.lineTo(p1);
115
116 qreal left = (dx > 0) ? x1 : x6;
117 qreal right = (dx > 0) ? x6 : x1;
118 qreal top = (dy > 0) ? y6 : y1;
119 qreal bottom = (dy > 0) ? y1 : y6;
120
121 rect.setTopLeft(QPointF(left, top));
122 rect.setBottomRight(QPointF(right, bottom));
123}
124
125GeoDataLinearRing GeoPainterPrivate::createLinearRingFromGeoRect(const GeoDataCoordinates &centerCoordinates, qreal width, qreal height)
126{
127 qreal lon = 0.0;
128 qreal lat = 0.0;
129 qreal altitude = centerCoordinates.altitude();
130 centerCoordinates.geoCoordinates(lon, lat, GeoDataCoordinates::Degree);
131
132 lon = GeoDataCoordinates::normalizeLon(lon, GeoDataCoordinates::Degree);
133 lat = GeoDataCoordinates::normalizeLat(lat, GeoDataCoordinates::Degree);
134
135 qreal west = GeoDataCoordinates::normalizeLon(lon - width * 0.5, GeoDataCoordinates::Degree);
136 qreal east = GeoDataCoordinates::normalizeLon(lon + width * 0.5, GeoDataCoordinates::Degree);
137
138 qreal north = GeoDataCoordinates::normalizeLat(lat + height * 0.5, GeoDataCoordinates::Degree);
139 qreal south = GeoDataCoordinates::normalizeLat(lat - height * 0.5, GeoDataCoordinates::Degree);
140
141 GeoDataCoordinates southWest(west, south, altitude, GeoDataCoordinates::Degree);
142 GeoDataCoordinates southEast(east, south, altitude, GeoDataCoordinates::Degree);
143 GeoDataCoordinates northEast(east, north, altitude, GeoDataCoordinates::Degree);
144 GeoDataCoordinates northWest(west, north, altitude, GeoDataCoordinates::Degree);
145
146 GeoDataLinearRing rectangle(Tessellate | RespectLatitudeCircle);
147
148 // If the width of the rect is larger as 180 degree, we have to enforce the long way.
149 if (width >= 180) {
150 qreal center = lon;
151 GeoDataCoordinates southCenter(center, south, altitude, GeoDataCoordinates::Degree);
152 GeoDataCoordinates northCenter(center, north, altitude, GeoDataCoordinates::Degree);
153
154 rectangle << southWest << southCenter << southEast << northEast << northCenter << northWest;
155 } else {
156 rectangle << southWest << southEast << northEast << northWest;
157 }
158
159 return rectangle;
160}
161
162bool GeoPainterPrivate::doClip(const ViewportParams *viewport)
163{
164 if (!viewport->currentProjection()->isClippedToSphere())
165 return true;
166
167 const qint64 radius = viewport->radius() * viewport->currentProjection()->clippingRadius();
168
169 return (radius > viewport->width() / 2 || radius > viewport->height() / 2);
170}
171
172qreal GeoPainterPrivate::normalizeAngle(qreal angle)
173{
174 angle = fmodf(angle, 360);
175 return angle < 0 ? angle + 360 : angle;
176}
177
178void GeoPainterPrivate::drawTextRotated(const QPointF &startPoint, qreal angle, const QString &text)
179{
180 QRectF textRect(startPoint, m_parent->fontMetrics().size(0, text));
181 QTransform const oldTransform = m_parent->transform();
182 m_parent->translate(startPoint);
183 m_parent->rotate(angle);
184 m_parent->translate(-startPoint - QPointF(0.0, m_parent->fontMetrics().height() / 2.0));
185
186 m_parent->drawText(textRect, text);
187 m_parent->setTransform(oldTransform);
188}
189
190// -------------------------------------------------------------------------------------------------
191
193 : ClipPainter(pd, GeoPainterPrivate::doClip(viewport))
194 , d(new GeoPainterPrivate(this, viewport, mapQuality))
195{
196 const bool antialiased = mapQuality == HighQuality || mapQuality == PrintQuality;
198 ClipPainter::setScreenClip(false);
199}
200
202{
203 delete d;
204}
205
207{
208 return d->m_mapQuality;
209}
210
212 const QString &text,
213 QSizeF bubbleSize,
214 qreal bubbleOffsetX,
215 qreal bubbleOffsetY,
216 qreal xRnd,
217 qreal yRnd)
218{
219 int pointRepeatNum;
220 qreal y;
221 bool globeHidesPoint;
222
223 if (bubbleSize.height() <= 0) {
224 QRectF rect = QRectF(QPointF(0.0, 0.0), bubbleSize - QSizeF(2 * xRnd, 0.0));
225 qreal idealTextHeight = boundingRect(rect, Qt::TextWordWrap, text).height();
226 bubbleSize.setHeight(2 * yRnd + idealTextHeight);
227 }
228
229 bool visible = d->m_viewport->screenCoordinates(position, d->m_x, y, pointRepeatNum, QSizeF(), globeHidesPoint);
230
231 if (visible) {
232 // Draw all the x-repeat-instances of the point on the screen
233 for (int it = 0; it < pointRepeatNum; ++it) {
234 QPainterPath path;
235 QRectF rect;
236 d->createAnnotationLayout(d->m_x[it], y, bubbleSize, bubbleOffsetX, bubbleOffsetY, xRnd, yRnd, path, rect);
237 QPainter::drawPath(path);
238 QPainter::drawText(rect, Qt::TextWordWrap, text, &rect);
239 }
240 }
241}
242
244{
245 int pointRepeatNum;
246 qreal y;
247 bool globeHidesPoint;
248
249 bool visible = d->m_viewport->screenCoordinates(position, d->m_x, y, pointRepeatNum, QSizeF(), globeHidesPoint);
250
251 if (visible) {
252 // Draw all the x-repeat-instances of the point on the screen
253 for (int it = 0; it < pointRepeatNum; ++it) {
254 QPainter::drawPoint(QPointF(d->m_x[it], y));
255 }
256 }
257}
258
259QRegion GeoPainter::regionFromPoint(const GeoDataCoordinates &position, qreal width) const
260{
261 return regionFromRect(position, width, width, false, 3);
262}
263
265{
266 drawPoint(point.coordinates());
267}
268
269QRegion GeoPainter::regionFromPoint(const GeoDataPoint &point, qreal width) const
270{
271 return regionFromRect(point.coordinates(), width, width, false, 3);
272}
273
275 const QString &text,
276 qreal xOffset,
277 qreal yOffset,
278 qreal width,
279 qreal height,
280 const QTextOption &option)
281{
282 // Of course in theory we could have the "isGeoProjected" parameter used
283 // for drawText as well. However this would require us to convert all
284 // glyphs to PainterPaths / QPolygons. From QPolygons we could create
285 // GeoDataPolygons which could get painted on screen. Any patches appreciated ;-)
286
287 int pointRepeatNum;
288 qreal y;
289 bool globeHidesPoint;
290
291 QSizeF textSize(fontMetrics().horizontalAdvance(text), fontMetrics().height());
292
293 bool visible = d->m_viewport->screenCoordinates(position, d->m_x, y, pointRepeatNum, textSize, globeHidesPoint);
294
295 if (visible) {
296 // Draw all the x-repeat-instances of the point on the screen
297 const qreal posY = y - yOffset;
298 for (int it = 0; it < pointRepeatNum; ++it) {
299 const qreal posX = d->m_x[it] + xOffset;
300 if (width == 0.0 && height == 0.0) {
301 QPainter::drawText(QPointF(posX, posY), text);
302 } else {
303 const QRectF boundingRect(posX, posY, width, height);
304 QPainter::drawText(boundingRect, text, option);
305 }
306 }
307 }
308}
309
310void GeoPainter::drawEllipse(const GeoDataCoordinates &centerPosition, qreal width, qreal height, bool isGeoProjected)
311{
312 if (!isGeoProjected) {
313 int pointRepeatNum;
314 qreal y;
315 bool globeHidesPoint;
316
317 bool visible = d->m_viewport->screenCoordinates(centerPosition, d->m_x, y, pointRepeatNum, QSizeF(width, height), globeHidesPoint);
318
319 if (visible) {
320 // Draw all the x-repeat-instances of the point on the screen
321 const qreal rx = width / 2.0;
322 const qreal ry = height / 2.0;
323 for (int it = 0; it < pointRepeatNum; ++it) {
324 QPainter::drawEllipse(QPointF(d->m_x[it], y), rx, ry);
325 }
326 }
327 } else {
328 // Initialize variables
329 const qreal centerLon = centerPosition.longitude(GeoDataCoordinates::Degree);
330 const qreal centerLat = centerPosition.latitude(GeoDataCoordinates::Degree);
331 const qreal altitude = centerPosition.altitude();
332
333 // Ensure a valid latitude range:
334 if (centerLat + 0.5 * height > 90.0 || centerLat - 0.5 * height < -90.0) {
335 return;
336 }
337
338 // Don't show the ellipse if it's too small:
339 GeoDataLatLonBox ellipseBox(centerLat + 0.5 * height,
340 centerLat - 0.5 * height,
341 centerLon + 0.5 * width,
342 centerLon - 0.5 * width,
343 GeoDataCoordinates::Degree);
344 if (!d->m_viewport->viewLatLonAltBox().intersects(ellipseBox) || !d->m_viewport->resolves(ellipseBox))
345 return;
346
347 GeoDataLinearRing ellipse;
348
349 // Optimizing the precision by determining the size which the
350 // ellipse covers on the screen:
351 const qreal degreeResolution = d->m_viewport->angularResolution() * RAD2DEG;
352 // To create a circle shape even for very small precision we require uneven numbers:
353 const int precision = qMin<qreal>(width / degreeResolution / 8 + 1, 81);
354
355 // Calculate the shape of the upper half of the ellipse:
356 for (int i = 0; i <= precision; ++i) {
357 const qreal t = 1.0 - 2.0 * (qreal)(i) / (qreal)(precision);
358 const qreal lat = centerLat + 0.5 * height * sqrt(1.0 - t * t);
359 const qreal lon = centerLon + 0.5 * width * t;
360 ellipse << GeoDataCoordinates(lon, lat, altitude, GeoDataCoordinates::Degree);
361 }
362 // Calculate the shape of the lower half of the ellipse:
363 for (int i = 0; i <= precision; ++i) {
364 const qreal t = 2.0 * (qreal)(i) / (qreal)(precision)-1.0;
365 const qreal lat = centerLat - 0.5 * height * sqrt(1.0 - t * t);
366 const qreal lon = centerLon + 0.5 * width * t;
367 ellipse << GeoDataCoordinates(lon, lat, altitude, GeoDataCoordinates::Degree);
368 }
369
370 drawPolygon(ellipse);
371 }
372}
373
374QRegion GeoPainter::regionFromEllipse(const GeoDataCoordinates &centerPosition, qreal width, qreal height, bool isGeoProjected, qreal strokeWidth) const
375{
376 if (!isGeoProjected) {
377 int pointRepeatNum;
378 qreal y;
379 bool globeHidesPoint;
380
381 bool visible = d->m_viewport->screenCoordinates(centerPosition, d->m_x, y, pointRepeatNum, QSizeF(width, height), globeHidesPoint);
382
383 QRegion regions;
384
385 if (visible) {
386 // only a hint, a backend could still ignore it, but we cannot know more
387 const bool antialiased = testRenderHint(QPainter::Antialiasing);
388
389 const qreal halfStrokeWidth = strokeWidth / 2.0;
390 const int startY = antialiased ? (qFloor(y - halfStrokeWidth)) : (qFloor(y + 0.5 - halfStrokeWidth));
391 const int endY = antialiased ? (qCeil(y + height + halfStrokeWidth)) : (qFloor(y + 0.5 + height + halfStrokeWidth));
392 // Draw all the x-repeat-instances of the point on the screen
393 for (int it = 0; it < pointRepeatNum; ++it) {
394 const qreal x = d->m_x[it];
395 const int startX = antialiased ? (qFloor(x - halfStrokeWidth)) : (qFloor(x + 0.5 - halfStrokeWidth));
396 const int endX = antialiased ? (qCeil(x + width + halfStrokeWidth)) : (qFloor(x + 0.5 + width + halfStrokeWidth));
397
398 regions += QRegion(startX, startY, endX - startX, endY - startY, QRegion::Ellipse);
399 }
400 }
401 return regions;
402 } else {
403 // Initialize variables
404 const qreal centerLon = centerPosition.longitude(GeoDataCoordinates::Degree);
405 const qreal centerLat = centerPosition.latitude(GeoDataCoordinates::Degree);
406 const qreal altitude = centerPosition.altitude();
407
408 // Ensure a valid latitude range:
409 if (centerLat + 0.5 * height > 90.0 || centerLat - 0.5 * height < -90.0) {
410 return {};
411 }
412
413 // Don't show the ellipse if it's too small:
414 GeoDataLatLonBox ellipseBox(centerLat + 0.5 * height,
415 centerLat - 0.5 * height,
416 centerLon + 0.5 * width,
417 centerLon - 0.5 * width,
418 GeoDataCoordinates::Degree);
419 if (!d->m_viewport->viewLatLonAltBox().intersects(ellipseBox) || !d->m_viewport->resolves(ellipseBox))
420 return {};
421
422 GeoDataLinearRing ellipse;
423
424 // Optimizing the precision by determining the size which the
425 // ellipse covers on the screen:
426 const qreal degreeResolution = d->m_viewport->angularResolution() * RAD2DEG;
427 // To create a circle shape even for very small precision we require uneven numbers:
428 const int precision = qMin<qreal>(width / degreeResolution / 8 + 1, 81);
429
430 // Calculate the shape of the upper half of the ellipse:
431 for (int i = 0; i <= precision; ++i) {
432 const qreal t = 1.0 - 2.0 * (qreal)(i) / (qreal)(precision);
433 const qreal lat = centerLat + 0.5 * height * sqrt(1.0 - t * t);
434 const qreal lon = centerLon + 0.5 * width * t;
435 ellipse << GeoDataCoordinates(lon, lat, altitude, GeoDataCoordinates::Degree);
436 }
437 // Calculate the shape of the lower half of the ellipse:
438 for (int i = 0; i <= precision; ++i) {
439 const qreal t = 2.0 * (qreal)(i) / (qreal)(precision)-1.0;
440 const qreal lat = centerLat - 0.5 * height * sqrt(1.0 - t * t);
441 const qreal lon = centerLon + 0.5 * width * t;
442 ellipse << GeoDataCoordinates(lon, lat, altitude, GeoDataCoordinates::Degree);
443 }
444
445 return regionFromPolygon(ellipse, Qt::OddEvenFill, strokeWidth);
446 }
447}
448
449void GeoPainter::drawImage(const GeoDataCoordinates &centerPosition, const QImage &image /*, bool isGeoProjected */)
450{
451 // isGeoProjected = true would project the image/pixmap onto the globe. This
452 // requires to deal with the TextureMapping classes -> should get
453 // implemented later on
454
455 int pointRepeatNum;
456 qreal y;
457 bool globeHidesPoint;
458
459 // if ( !isGeoProjected ) {
460 bool visible = d->m_viewport->screenCoordinates(centerPosition, d->m_x, y, pointRepeatNum, image.size(), globeHidesPoint);
461
462 if (visible) {
463 // Draw all the x-repeat-instances of the point on the screen
464 const qreal posY = y - (image.height() / 2.0);
465 for (int it = 0; it < pointRepeatNum; ++it) {
466 const qreal posX = d->m_x[it] - (image.width() / 2.0);
467 QPainter::drawImage(QPointF(posX, posY), image);
468 }
469 }
470 // }
471}
472
473void GeoPainter::drawPixmap(const GeoDataCoordinates &centerPosition, const QPixmap &pixmap /* , bool isGeoProjected */)
474{
475 int pointRepeatNum;
476 qreal y;
477 bool globeHidesPoint;
478
479 // if ( !isGeoProjected ) {
480 // FIXME: Better visibility detection that takes the circle geometry into account
481 bool visible = d->m_viewport->screenCoordinates(centerPosition, d->m_x, y, pointRepeatNum, pixmap.size(), globeHidesPoint);
482
483 if (visible) {
484 // Draw all the x-repeat-instances of the point on the screen
485 const qreal posY = y - (pixmap.height() / 2.0);
486 for (int it = 0; it < pointRepeatNum; ++it) {
487 const qreal posX = d->m_x[it] - (pixmap.width() / 2.0);
488 QPainter::drawPixmap(QPointF(posX, posY), pixmap);
489 }
490 }
491 // }
492}
493
494QRegion GeoPainter::regionFromPixmapRect(const GeoDataCoordinates &centerCoordinates, int width, int height, int margin) const
495{
496 const int fullWidth = width + 2 * margin;
497 const int fullHeight = height + 2 * margin;
498 int pointRepeatNum;
499 qreal y;
500 bool globeHidesPoint;
501
502 const bool visible = d->m_viewport->screenCoordinates(centerCoordinates, d->m_x, y, pointRepeatNum, QSizeF(fullWidth, fullHeight), globeHidesPoint);
503
504 QRegion regions;
505
506 if (visible) {
507 // cmp. GeoPainter::drawPixmap() position calculation
508 // QPainter::drawPixmap seems to qRound the passed position
509 const int posY = qRound(y - (height / 2.0)) - margin;
510 for (int it = 0; it < pointRepeatNum; ++it) {
511 const int posX = qRound(d->m_x[it] - (width / 2.0)) - margin;
512 regions += QRegion(posX, posY, width, height);
513 }
514 }
515
516 return regions;
517}
518
520{
521 // Immediately leave this method now if:
522 // - the object is not visible in the viewport or if
523 // - the size of the object is below the resolution of the viewport
524 if (!d->m_viewport->viewLatLonAltBox().intersects(lineString.latLonAltBox()) || !d->m_viewport->resolves(lineString.latLonAltBox())) {
525 // mDebug() << "LineString doesn't get displayed on the viewport";
526 return;
527 }
528
529 d->m_viewport->screenCoordinates(lineString, polygons);
530}
531
532void GeoPainter::drawPolyline(const GeoDataLineString &lineString, const QString &labelText, LabelPositionFlags labelPositionFlags, const QColor &labelColor)
533{
534 // no labels to draw?
535 // TODO: !labelColor.isValid() || labelColor.alpha() == 0 does not work,
536 // something injects invalid labelColor for city streets
537 if (labelText.isEmpty() || labelPositionFlags.testFlag(NoLabel) || labelColor == Qt::transparent) {
538 drawPolyline(lineString);
539 return;
540 }
541
542 QList<QPolygonF *> polygons;
543 polygonsFromLineString(lineString, polygons);
544 if (polygons.empty())
545 return;
546
547 for (const QPolygonF *itPolygon : std::as_const(polygons)) {
548 ClipPainter::drawPolyline(*itPolygon);
549 }
550
551 drawLabelsForPolygons(polygons, labelText, labelPositionFlags, labelColor);
552
553 qDeleteAll(polygons);
554}
555
557 const QString &labelText,
558 LabelPositionFlags labelPositionFlags,
559 const QColor &labelColor)
560{
561 if (labelText.isEmpty()) {
562 return;
563 }
564 QPen const oldPen = pen();
565
566 if (labelPositionFlags.testFlag(FollowLine)) {
567 const qreal maximumLabelFontSize = 20;
568 qreal fontSize = pen().widthF() * 0.45;
569 fontSize = qMin(fontSize, maximumLabelFontSize);
570
571 if (fontSize < 6.0 || labelColor == "transparent") {
572 return;
573 }
574 QFont font = this->font();
575 font.setPointSizeF(fontSize);
576 setFont(font);
577 int labelWidth = fontMetrics().horizontalAdvance(labelText);
578 if (labelText.size() < 20) {
579 labelWidth *= (20.0 / labelText.size());
580 }
581 setPen(labelColor);
582
583 QList<QPointF> labelNodes;
584 QRectF viewportRect = QRectF(QPointF(0, 0), d->m_viewport->size());
585 for (QPolygonF *itPolygon : polygons) {
586 if (!itPolygon->boundingRect().intersects(viewportRect)) {
587 continue;
588 }
589
590 labelNodes.clear();
591
592 QPainterPath path;
593 path.addPolygon(*itPolygon);
594 qreal pathLength = path.length();
595 if (pathLength == 0)
596 continue;
597
598 int maxNumLabels = static_cast<int>(pathLength / labelWidth);
599
600 if (maxNumLabels > 0) {
601 qreal textRelativeLength = labelWidth / pathLength;
602 int numLabels = 1;
603 if (maxNumLabels > 1) {
604 numLabels = maxNumLabels / 2;
605 }
606 qreal offset = (1.0 - numLabels * textRelativeLength) / numLabels;
607 qreal startPercent = offset / 2.0;
608
609 for (int k = 0; k < numLabels; ++k, startPercent += textRelativeLength + offset) {
610 QPointF point = path.pointAtPercent(startPercent);
611 QPointF endPoint = path.pointAtPercent(startPercent + textRelativeLength);
612
613 if (viewport().contains(point.toPoint()) || viewport().contains(endPoint.toPoint())) {
614 qreal angle = -path.angleAtPercent(startPercent);
615 qreal angle2 = -path.angleAtPercent(startPercent + textRelativeLength);
616 angle = GeoPainterPrivate::normalizeAngle(angle);
617 angle2 = GeoPainterPrivate::normalizeAngle(angle2);
618 bool upsideDown = angle > 90.0 && angle < 270.0;
619
620 if (qAbs(angle - angle2) < 3.0) {
621 if (upsideDown) {
622 angle += 180.0;
623 point = path.pointAtPercent(startPercent + textRelativeLength);
624 }
625
626 d->drawTextRotated(point, angle, labelText);
627 } else {
628 for (int i = 0; i < labelText.length(); ++i) {
629 qreal currentGlyphTextLength = fontMetrics().horizontalAdvance(labelText.left(i)) / pathLength;
630
631 if (!upsideDown) {
632 angle = -path.angleAtPercent(startPercent + currentGlyphTextLength);
633 point = path.pointAtPercent(startPercent + currentGlyphTextLength);
634 } else {
635 angle = -path.angleAtPercent(startPercent + textRelativeLength - currentGlyphTextLength) + 180;
636 point = path.pointAtPercent(startPercent + textRelativeLength - currentGlyphTextLength);
637 }
638
639 d->drawTextRotated(point, angle, labelText.at(i));
640 }
641 }
642 }
643 }
644 }
645 }
646 } else {
647 setPen(labelColor);
648
649 int labelWidth = fontMetrics().horizontalAdvance(labelText);
650 int labelAscent = fontMetrics().ascent();
651
652 QList<QPointF> labelNodes;
653 for (QPolygonF *itPolygon : polygons) {
654 labelNodes.clear();
655 ClipPainter::labelPosition(*itPolygon, labelNodes, labelPositionFlags);
656 if (!labelNodes.isEmpty()) {
657 for (const QPointF &labelNode : std::as_const(labelNodes)) {
658 QPointF labelPosition = labelNode + QPointF(3.0, -2.0);
659
660 // FIXME: This is a Q&D fix.
661 qreal xmax = viewport().width() - 10.0 - labelWidth;
662 if (labelPosition.x() > xmax)
663 labelPosition.setX(xmax);
664 qreal ymin = 10.0 + labelAscent;
665 if (labelPosition.y() < ymin)
666 labelPosition.setY(ymin);
667 qreal ymax = viewport().height() - 10.0 - labelAscent;
668 if (labelPosition.y() > ymax)
669 labelPosition.setY(ymax);
670
671 drawText(QRectF(labelPosition, fontMetrics().size(0, labelText)), labelText);
672 }
673 }
674 }
675 }
676 setPen(oldPen);
677}
678
680{
681 QList<QPolygonF *> polygons;
682 polygonsFromLineString(lineString, polygons);
683 if (polygons.empty())
684 return;
685
686 for (const QPolygonF *itPolygon : std::as_const(polygons)) {
687 ClipPainter::drawPolyline(*itPolygon);
688 }
689
690 qDeleteAll(polygons);
691}
692
693QRegion GeoPainter::regionFromPolyline(const GeoDataLineString &lineString, qreal strokeWidth) const
694{
695 // Immediately leave this method now if:
696 // - the object is not visible in the viewport or if
697 // - the size of the object is below the resolution of the viewport
698 if (!d->m_viewport->viewLatLonAltBox().intersects(lineString.latLonAltBox()) || !d->m_viewport->resolves(lineString.latLonAltBox())) {
699 // mDebug() << "LineString doesn't get displayed on the viewport";
700 return {};
701 }
702
703 QPainterPath painterPath;
704
705 QList<QPolygonF *> polygons;
706 d->m_viewport->screenCoordinates(lineString, polygons);
707
708 for (QPolygonF *itPolygon : std::as_const(polygons)) {
709 painterPath.addPolygon(*itPolygon);
710 }
711
712 qDeleteAll(polygons);
713
714 QPainterPathStroker stroker;
715 stroker.setWidth(strokeWidth);
716 QPainterPath strokePath = stroker.createStroke(painterPath);
717
718 return QRegion(strokePath.toFillPolygon().toPolygon(), Qt::WindingFill);
719}
720
722{
723 // Immediately leave this method now if:
724 // - the object is not visible in the viewport or if
725 // - the size of the object is below the resolution of the viewport
726 if (!d->m_viewport->viewLatLonAltBox().intersects(linearRing.latLonAltBox()) || !d->m_viewport->resolves(linearRing.latLonAltBox())) {
727 // mDebug() << "Polygon doesn't get displayed on the viewport";
728 return;
729 }
730
731 QList<QPolygonF *> polygons;
732 d->m_viewport->screenCoordinates(linearRing, polygons);
733
734 for (QPolygonF *itPolygon : std::as_const(polygons)) {
735 ClipPainter::drawPolygon(*itPolygon, fillRule);
736 }
737
738 qDeleteAll(polygons);
739}
740
741QRegion GeoPainter::regionFromPolygon(const GeoDataLinearRing &linearRing, Qt::FillRule fillRule, qreal strokeWidth) const
742{
743 // Immediately leave this method now if:
744 // - the object is not visible in the viewport or if
745 // - the size of the object is below the resolution of the viewport
746 if (!d->m_viewport->viewLatLonAltBox().intersects(linearRing.latLonAltBox()) || !d->m_viewport->resolves(linearRing.latLonAltBox())) {
747 return {};
748 }
749
750 QRegion regions;
751
752 QList<QPolygonF *> polygons;
753 d->m_viewport->screenCoordinates(linearRing, polygons);
754
755 if (strokeWidth == 0) {
756 // This is the faster way
757 for (QPolygonF *itPolygon : std::as_const(polygons)) {
758 regions += QRegion((*itPolygon).toPolygon(), fillRule);
759 }
760 } else {
761 QPainterPath painterPath;
762 for (QPolygonF *itPolygon : std::as_const(polygons)) {
763 painterPath.addPolygon(*itPolygon);
764 }
765
766 QPainterPathStroker stroker;
767 stroker.setWidth(strokeWidth);
768 QPainterPath strokePath = stroker.createStroke(painterPath);
769 painterPath = painterPath.united(strokePath);
770 regions = QRegion(painterPath.toFillPolygon().toPolygon());
771 }
772
773 qDeleteAll(polygons);
774
775 return regions;
776}
777
779{
780 // If the object is not visible in the viewport return
781 if (!d->m_viewport->viewLatLonAltBox().intersects(polygon.outerBoundary().latLonAltBox()) ||
782 // If the size of the object is below the resolution of the viewport then return
783 !d->m_viewport->resolves(polygon.outerBoundary().latLonAltBox())) {
784 // mDebug() << "Polygon doesn't get displayed on the viewport";
785 return;
786 }
787 // mDebug() << "Drawing Polygon";
788
789 QList<QPolygonF *> outerPolygons;
790 QList<QPolygonF *> innerPolygons;
791 d->m_viewport->screenCoordinates(polygon.outerBoundary(), outerPolygons);
792
793 QPen const currentPen = pen();
794
795 bool const hasInnerBoundaries = !polygon.innerBoundaries().isEmpty();
796 bool innerBoundariesOnScreen = false;
797
798 if (hasInnerBoundaries) {
799 QList<GeoDataLinearRing> const &innerBoundaries = polygon.innerBoundaries();
800
801 const GeoDataLatLonAltBox &viewLatLonAltBox = d->m_viewport->viewLatLonAltBox();
802 for (const GeoDataLinearRing &itInnerBoundary : innerBoundaries) {
803 if (viewLatLonAltBox.intersects(itInnerBoundary.latLonAltBox()) && d->m_viewport->resolves(itInnerBoundary.latLonAltBox()), 4) {
804 innerBoundariesOnScreen = true;
805 break;
806 }
807 }
808
809 if (innerBoundariesOnScreen) {
810 // Create the inner screen polygons
811 for (const GeoDataLinearRing &itInnerBoundary : innerBoundaries) {
812 QList<QPolygonF *> innerPolygonsPerBoundary;
813
814 d->m_viewport->screenCoordinates(itInnerBoundary, innerPolygonsPerBoundary);
815
816 for (QPolygonF *innerPolygonPerBoundary : std::as_const(innerPolygonsPerBoundary)) {
817 innerPolygons << innerPolygonPerBoundary;
818 }
819 }
820
821 setPen(Qt::NoPen);
822 QList<QPolygonF *> fillPolygons = createFillPolygons(outerPolygons, innerPolygons);
823
824 for (const QPolygonF *fillPolygon : std::as_const(fillPolygons)) {
825 ClipPainter::drawPolygon(*fillPolygon, fillRule);
826 }
827
828 setPen(currentPen);
829
830 for (const QPolygonF *outerPolygon : std::as_const(outerPolygons)) {
831 ClipPainter::drawPolyline(*outerPolygon);
832 }
833 for (const QPolygonF *innerPolygon : std::as_const(innerPolygons)) {
834 ClipPainter::drawPolyline(*innerPolygon);
835 }
836
837 qDeleteAll(fillPolygons);
838 }
839 }
840
841 if (!hasInnerBoundaries || !innerBoundariesOnScreen) {
842 drawPolygon(polygon.outerBoundary(), fillRule);
843 }
844
845 qDeleteAll(outerPolygons);
846 qDeleteAll(innerPolygons);
847}
848
849QList<QPolygonF *> GeoPainter::createFillPolygons(const QList<QPolygonF *> &outerPolygons, const QList<QPolygonF *> &innerPolygons) const
850{
851 QList<QPolygonF *> fillPolygons;
852 fillPolygons.reserve(outerPolygons.size());
853
854 for (const QPolygonF *outerPolygon : outerPolygons) {
855 auto fillPolygon = new QPolygonF;
856 *fillPolygon << *outerPolygon;
857 *fillPolygon << outerPolygon->first();
858
859 for (const QPolygonF *innerPolygon : innerPolygons) {
860 *fillPolygon << *innerPolygon;
861 *fillPolygon << innerPolygon->first();
862 *fillPolygon << outerPolygon->first();
863 }
864
865 fillPolygons << fillPolygon;
866 }
867
868 return fillPolygons;
869}
870
871void GeoPainter::drawRect(const GeoDataCoordinates &centerCoordinates, qreal width, qreal height, bool isGeoProjected)
872{
873 if (!isGeoProjected) {
874 int pointRepeatNum;
875 qreal y;
876 bool globeHidesPoint;
877
878 bool visible = d->m_viewport->screenCoordinates(centerCoordinates, d->m_x, y, pointRepeatNum, QSizeF(width, height), globeHidesPoint);
879
880 if (visible) {
881 // Draw all the x-repeat-instances of the point on the screen
882 const qreal posY = y - height / 2.0;
883 for (int it = 0; it < pointRepeatNum; ++it) {
884 const qreal posX = d->m_x[it] - width / 2.0;
885 QPainter::drawRect(QRectF(posX, posY, width, height));
886 }
887 }
888 } else {
889 drawPolygon(d->createLinearRingFromGeoRect(centerCoordinates, width, height), Qt::OddEvenFill);
890 }
891}
892
893QRegion GeoPainter::regionFromRect(const GeoDataCoordinates &centerCoordinates, qreal width, qreal height, bool isGeoProjected, qreal strokeWidth) const
894{
895 if (!isGeoProjected) {
896 int pointRepeatNum;
897 qreal centerY;
898 bool globeHidesPoint;
899
900 bool visible = d->m_viewport->screenCoordinates(centerCoordinates, d->m_x, centerY, pointRepeatNum, QSizeF(width, height), globeHidesPoint);
901
902 QRegion regions;
903
904 if (visible) {
905 // only a hint, a backend could still ignore it, but we cannot know more
906 const bool antialiased = testRenderHint(QPainter::Antialiasing);
907
908 const qreal halfStrokeWidth = strokeWidth / 2.0;
909 const int topY = centerY - height / 2.0;
910 const int startY = antialiased ? (qFloor(topY - halfStrokeWidth)) : (qFloor(topY + 0.5 - halfStrokeWidth));
911 const int endY = antialiased ? (qCeil(topY + height + halfStrokeWidth)) : (qFloor(centerY + 0.5 + height + halfStrokeWidth));
912 // Draw all the x-repeat-instances of the point on the screen
913 for (int it = 0; it < pointRepeatNum; ++it) {
914 const qreal leftX = d->m_x[it] - width / 2.0;
915 const int startX = antialiased ? (qFloor(leftX - halfStrokeWidth)) : (qFloor(leftX + 0.5 - halfStrokeWidth));
916 const int endX = antialiased ? (qCeil(leftX + width + halfStrokeWidth)) : (qFloor(leftX + 0.5 + width + halfStrokeWidth));
917 regions += QRegion(startX, startY, endX - startX, endY - startY);
918 }
919 }
920 return regions;
921 } else {
922 return regionFromPolygon(d->createLinearRingFromGeoRect(centerCoordinates, width, height), Qt::OddEvenFill, strokeWidth);
923 }
924}
925
926void GeoPainter::drawRoundedRect(const GeoDataCoordinates &centerPosition, qreal width, qreal height, qreal xRnd, qreal yRnd)
927{
928 int pointRepeatNum;
929 qreal y;
930 bool globeHidesPoint;
931
932 // FIXME: Better visibility detection that takes the circle geometry into account
933 bool visible = d->m_viewport->screenCoordinates(centerPosition, d->m_x, y, pointRepeatNum, QSizeF(width, height), globeHidesPoint);
934
935 if (visible) {
936 // Draw all the x-repeat-instances of the point on the screen
937 const qreal posY = y - height / 2.0;
938 for (int it = 0; it < pointRepeatNum; ++it) {
939 const qreal posX = d->m_x[it] - width / 2.0;
940 QPainter::drawRoundedRect(QRectF(posX, posY, width, height), xRnd, yRnd);
941 }
942 }
943}
944
945void GeoPainter::drawTextFragment(const QPoint &position, const QString &text, const qreal fontSize, const QColor &color, const Frames &flags)
946{
947 const QString key = text + QString::fromLatin1(":") + QString::number(static_cast<int>(flags));
948
949 QPixmap pixmap;
950
951 if (!QPixmapCache::find(key, &pixmap)) {
952 const bool hasRoundFrame = flags.testFlag(RoundFrame);
953
954 QPixmap pixmap(10, 10);
955 QPainter textPainter;
956
957 textPainter.begin(&pixmap);
958 const QFontMetrics metrics = textPainter.fontMetrics();
959 textPainter.end();
960
961 const int width = metrics.horizontalAdvance(text);
962 const int height = metrics.height();
963 const QSize size = hasRoundFrame ? QSize(qMax(1.2 * width, 1.1 * height), 1.2 * height) : QSize(width, height);
964 pixmap = QPixmap(size);
965 pixmap.fill(Qt::transparent);
966 const QRect labelRect(QPoint(), size);
967 textPainter.begin(&pixmap);
968 QFont textFont = textPainter.font();
969 textFont.setPointSize(fontSize);
970 textPainter.setFont(textFont);
971 textPainter.setRenderHint(QPainter::Antialiasing, true);
972
973 const QColor brushColor = color;
974 if (hasRoundFrame) {
975 QColor lighterColor = brushColor.lighter(110);
976 lighterColor.setAlphaF(0.9);
977 textPainter.setBrush(lighterColor);
978 textPainter.drawRoundedRect(labelRect, 3, 3);
979 }
980
981 textPainter.setBrush(brushColor);
982 textPainter.drawText(labelRect, Qt::AlignHCenter, text);
983
984 if (hasRoundFrame) {
985 textPainter.setBrush(brushColor);
986 }
987
988 textPainter.end();
989 QPixmapCache::insert(key, pixmap);
990 }
991
992 QPainter::drawPixmap(position.x() - pixmap.width() / 2, position.y() - pixmap.height() / 2, pixmap);
993}
This file contains the headers for AbstractProjection.
This file contains the headers for ViewportParams.
virtual bool isClippedToSphere() const
Defines whether a projection is supposed to be clipped to a certain radius.
A 3d point representation.
static qreal normalizeLon(qreal lon, GeoDataCoordinates::Unit=GeoDataCoordinates::Radian)
normalize the longitude to always be -M_PI <= lon <= +M_PI (Radian).
qreal altitude() const
return the altitude of the Point in meters
static qreal normalizeLat(qreal lat, GeoDataCoordinates::Unit=GeoDataCoordinates::Radian)
normalize latitude to always be in -M_PI / 2.
void geoCoordinates(qreal &lon, qreal &lat, GeoDataCoordinates::Unit unit) const
use this function to get the longitude and latitude with one call - use the unit parameter to switch ...
A class that defines a 3D bounding box for geographic data.
virtual bool intersects(const GeoDataLatLonAltBox &) const
Check if this GeoDataLatLonAltBox intersects with the given one.
A class that defines a 2D bounding box for geographic data.
A LineString that allows to store a contiguous set of line segments.
const GeoDataLatLonAltBox & latLonAltBox() const override
Returns the smallest latLonAltBox that contains the LineString.
A LinearRing that allows to store a closed, contiguous set of line segments.
A Geometry object representing a 3d point.
A polygon that can have "holes".
GeoDataLinearRing & outerBoundary()
Returns the outer boundary that is represented as a LinearRing.
QList< GeoDataLinearRing > & innerBoundaries()
Returns a set of inner boundaries which are represented as LinearRings.
A painter that allows to draw geometric primitives on the map.
Definition GeoPainter.h:86
GeoPainter(QPaintDevice *paintDevice, const ViewportParams *viewportParams, MapQuality mapQuality=NormalQuality)
Creates a new geo painter.
MapQuality mapQuality() const
Returns the map quality.
void drawRoundedRect(const GeoDataCoordinates &centerPosition, qreal width, qreal height, qreal xRnd=25.0, qreal yRnd=25.0)
Draws a rectangle with rounded corners at the given position. The rectangle is placed with its center...
void drawImage(const GeoDataCoordinates &centerPosition, const QImage &image)
Draws an image at the given position. The image is placed with its center located at the given center...
void drawText(const GeoDataCoordinates &position, const QString &text, qreal xOffset=0.0, qreal yOffset=0.0, qreal width=0.0, qreal height=0.0, const QTextOption &option=QTextOption())
Draws the given text at a given geographic position. The text is drawn starting at the given position...
QRegion regionFromEllipse(const GeoDataCoordinates &centerPosition, qreal width, qreal height, bool isGeoProjected=false, qreal strokeWidth=3) const
Creates a region for an ellipse at a given position.
void drawPolygon(const GeoDataLinearRing &linearRing, Qt::FillRule fillRule=Qt::OddEvenFill)
Draws a given linear ring (a "polygon without holes").
void drawAnnotation(const GeoDataCoordinates &position, const QString &text, QSizeF bubbleSize=QSizeF(130, 100), qreal bubbleOffsetX=-10, qreal bubbleOffsetY=-30, qreal xRnd=5, qreal yRnd=5)
Draws a text annotation that points to a geodesic position.
QRegion regionFromPoint(const GeoDataCoordinates &position, qreal strokeWidth=3) const
Creates a region for a given geographic position.
~GeoPainter()
Destroys the geo painter.
void drawEllipse(const GeoDataCoordinates &centerPosition, qreal width, qreal height, bool isGeoProjected=false)
Draws an ellipse at the given position. The ellipse is placed with its center located at the given ce...
void drawPoint(const GeoDataCoordinates &position)
Draws a single point at a given geographic position. The point is drawn using the painter's pen color...
QRegion regionFromPolygon(const GeoDataLinearRing &linearRing, Qt::FillRule fillRule, qreal strokeWidth=3) const
Creates a region for a given linear ring (a "polygon without holes").
QRegion regionFromPixmapRect(const GeoDataCoordinates &centerCoordinates, int width, int height, int margin=0) const
Creates a region for a rectangle for a pixmap at a given position.
void drawLabelsForPolygons(const QList< QPolygonF * > &polygons, const QString &labelText, LabelPositionFlags labelPositionFlags, const QColor &labelColor)
Draws Labels for a given set of screen polygons.
void polygonsFromLineString(const GeoDataLineString &lineString, QList< QPolygonF * > &polygons) const
Helper method for safe and quick linestring conversion.
void drawPixmap(const GeoDataCoordinates &centerPosition, const QPixmap &pixmap)
Draws a pixmap at the given position. The pixmap is placed with its center located at the given cente...
void drawRect(const GeoDataCoordinates &centerPosition, qreal width, qreal height, bool isGeoProjected=false)
Draws a rectangle at the given position. The rectangle is placed with its center located at the given...
void drawPolyline(const GeoDataLineString &lineString, const QString &labelText, LabelPositionFlags labelPositionFlags=LineCenter, const QColor &labelcolor=Qt::black)
Draws a given line string (a "polyline") with a label.
QRegion regionFromPolyline(const GeoDataLineString &lineString, qreal strokeWidth=3) const
Creates a region for a given line string (a "polyline").
QRegion regionFromRect(const GeoDataCoordinates &centerPosition, qreal width, qreal height, bool isGeoProjected=false, qreal strokeWidth=3) const
Creates a region for a rectangle at a given position.
A public class that controls what is visible in the viewport of a Marble map.
QString path(const QString &relativePath)
Binds a QML item to a specific geodetic location in screen coordinates.
MapQuality
This enum is used to choose the map quality shown in the view.
@ HighQuality
High quality (e.g. antialiasing for lines)
@ PrintQuality
Print quality.
QColor lighter(int factor) const const
void setAlphaF(float alpha)
bool testFlag(Enum flag) const const
void setPointSize(int pointSize)
void setPointSizeF(qreal pointSize)
int ascent() const const
int height() const const
int horizontalAdvance(QChar ch) const const
int height() const const
QSize size() const const
int width() const const
void clear()
bool empty() const const
T & first()
bool isEmpty() const const
void reserve(qsizetype size)
qsizetype size() const const
bool begin(QPaintDevice *device)
QRect boundingRect(const QRect &rectangle, int flags, const QString &text)
void drawEllipse(const QPoint &center, int rx, int ry)
void drawImage(const QPoint &point, const QImage &image)
void drawPath(const QPainterPath &path)
void drawPixmap(const QPoint &point, const QPixmap &pixmap)
void drawPoint(const QPoint &position)
void drawRect(const QRect &rectangle)
void drawRoundedRect(const QRect &rect, qreal xRadius, qreal yRadius, Qt::SizeMode mode)
void drawText(const QPoint &position, const QString &text)
bool end()
const QFont & font() const const
QFontMetrics fontMetrics() const const
const QPen & pen() const const
void setBrush(Qt::BrushStyle style)
void setFont(const QFont &font)
void setRenderHint(RenderHint hint, bool on)
void strokePath(const QPainterPath &path, const QPen &pen)
bool testRenderHint(RenderHint hint) const const
QRect viewport() const const
void addPolygon(const QPolygonF &polygon)
QPolygonF toFillPolygon(const QTransform &matrix) const const
QPainterPath united(const QPainterPath &p) const const
QPainterPath createStroke(const QPainterPath &path) const const
void setWidth(qreal width)
qreal widthF() const const
void fill(const QColor &color)
int height() const const
QSize size() const const
int width() const const
bool find(const Key &key, QPixmap *pixmap)
Key insert(const QPixmap &pixmap)
int x() const const
int y() const const
void setX(qreal x)
void setY(qreal y)
QPoint toPoint() const const
qreal x() const const
qreal y() const const
QPolygon toPolygon() const const
int height() const const
int width() const const
void setBottomRight(const QPointF &position)
void setTopLeft(const QPointF &position)
qreal height() const const
void setHeight(qreal height)
qreal width() const const
const QChar at(qsizetype position) const const
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QString left(qsizetype n) const const
qsizetype length() const const
QString number(double n, char format, int precision)
qsizetype size() const const
AlignHCenter
OddEvenFill
transparent
TextWordWrap
QTextStream & center(QTextStream &stream)
QTextStream & left(QTextStream &stream)
QTextStream & right(QTextStream &stream)
QTransform & rotate(qreal a, Qt::Axis axis)
QTransform & translate(qreal dx, qreal dy)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 8 2024 12:02:44 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.