1 /* This file is part of the KDE project
2    Copyright (C) 1998, 1999 Reginald Stadlbauer <reggie@kde.org>
3    Copyright (C) 2006 Peter Simonsson <peter.simonsson@gmail.com>
4    Copyright (C) 2007 C. Boemann <cbo@boemann.dk>
5    Copyright (C) 2007-2008 Jan Hambrecht <jaham@gmx.net>
6    Copyright (C) 2007 Thomas Zander <zander@kde.org>
7 
8    This library is free software; you can redistribute it and/or
9    modify it under the terms of the GNU Library General Public
10    License as published by the Free Software Foundation; either
11    version 2 of the License, or (at your option) any later version.
12 
13    This library is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    Library General Public License for more details.
17 
18    You should have received a copy of the GNU Library General Public License
19    along with this library; see the file COPYING.LIB.  If not, write to
20    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21    Boston, MA 02110-1301, USA.
22 */
23 
24 #include "KReportRuler_p.h"
25 #include "KReportDesign_p.h"
26 #include "KReportZoomHandler_p.h"
27 
28 #include <QPainter>
29 #include <QMenu>
30 #include <QMouseEvent>
31 #include <QFontDatabase>
32 
33 // the distance in pixels of a mouse position considered outside the rule
34 static const int OutsideRulerThreshold = 20;
35 //
36 static const int fullStepMarkerLength = 6;
37 static const int halfStepMarkerLength = 6;
38 static const int quarterStepMarkerLength = 3;
39 static const int measurementTextAboveBelowMargin = 1;
40 
41 class RulerTabChooser : public QWidget
42 {
43 public:
RulerTabChooser(QWidget * parent)44     RulerTabChooser(QWidget *parent) : QWidget(parent), m_type(QTextOption::LeftTab), m_showTabs(false) {}
~RulerTabChooser()45     ~RulerTabChooser() override {}
46 
type()47     inline QTextOption::TabType type() {return m_type;}
setShowTabs(bool showTabs)48     void setShowTabs(bool showTabs) { if (m_showTabs == showTabs) return; m_showTabs = showTabs; update(); }
49     void mousePressEvent(QMouseEvent *) override;
50 
51     void paintEvent(QPaintEvent *) override;
52 
53 private:
54     QTextOption::TabType m_type;
55     bool m_showTabs :1;
56 };
57 
58 // ----
59 
60 class PaintingStrategy
61 {
62 public:
63     /// constructor
PaintingStrategy()64     PaintingStrategy() {}
65     /// destructor
~PaintingStrategy()66     virtual ~PaintingStrategy() {}
67 
68     /**
69      * Draw the background of the ruler.
70      * @param ruler the ruler to draw on.
71      * @param painter the painter we can paint with.
72      */
73     virtual QRectF drawBackground(const KReportRuler::Private *ruler, QPainter *painter) = 0;
74 
75     /**
76      * Draw the indicators for text-tabs.
77      * @param ruler the ruler to draw on.
78      * @param painter the painter we can paint with.
79      */
80     virtual void drawTabs(const KReportRuler::Private *ruler, QPainter *painter) = 0;
81 
82     /**
83      * Draw the indicators for the measurements which typically are drawn every [unit].
84      * @param ruler the ruler to draw on.
85      * @param painter the painter we can paint with.
86      * @param rectangle
87      */
88     virtual void drawMeasurements(const KReportRuler::Private *ruler, QPainter *painter, const QRectF &rectangle) = 0;
89 
90     /**
91      * Draw the indicators for the indents of a text paragraph
92      * @param ruler the ruler to draw on.
93      * @param painter the painter we can paint with.
94      */
95     virtual void drawIndents(const KReportRuler::Private *ruler, QPainter *painter) = 0;
96 
97     /**
98      *returns the size suggestion for a ruler with this strategy.
99      */
100     virtual QSize sizeHint() = 0;
101 };
102 
103 // ----
104 
105 class HorizontalPaintingStrategy : public PaintingStrategy
106 {
107 public:
HorizontalPaintingStrategy()108     HorizontalPaintingStrategy() : lengthInPixel(1) {}
109 
110     QRectF drawBackground(const KReportRuler::Private *ruler, QPainter *painter) override;
111     void drawTabs(const KReportRuler::Private *ruler, QPainter *painter) override;
112     void drawMeasurements(const KReportRuler::Private *ruler, QPainter *painter, const QRectF &rectangle) override;
113     void drawIndents(const KReportRuler::Private *ruler, QPainter *painter) override;
114     QSize sizeHint() override;
115 
116 private:
117     qreal lengthInPixel;
118 };
119 
120 // ----
121 
122 class VerticalPaintingStrategy : public PaintingStrategy
123 {
124 public:
VerticalPaintingStrategy()125     VerticalPaintingStrategy() : lengthInPixel(1) {}
126 
127     QRectF drawBackground(const KReportRuler::Private *ruler, QPainter *painter) override;
drawTabs(const KReportRuler::Private *,QPainter *)128     void drawTabs(const KReportRuler::Private *, QPainter *) override {}
129     void drawMeasurements(const KReportRuler::Private *ruler, QPainter *painter, const QRectF &rectangle) override;
drawIndents(const KReportRuler::Private *,QPainter *)130     void drawIndents(const KReportRuler::Private *, QPainter *) override {}
131     QSize sizeHint() override;
132 
133 private:
134     qreal lengthInPixel;
135 };
136 
137 class HorizontalDistancesPaintingStrategy : public HorizontalPaintingStrategy
138 {
139 public:
HorizontalDistancesPaintingStrategy()140     HorizontalDistancesPaintingStrategy() {}
141 
142     void drawMeasurements(const KReportRuler::Private *ruler, QPainter *painter, const QRectF &rectangle) override;
143 
144 private:
145     void drawDistanceLine(const KReportRuler::Private *d, QPainter *painter, qreal start, qreal end);
146 };
147 
148 // ----
149 
150 class KReportRuler::Private
151 {
152 public:
153     Private(KReportRuler *parent, const KReportZoomHandler &zoomHandler, Qt::Orientation orientation);
154     ~Private();
155 
156     void emitTabChanged();
157 
158     KReportUnit unit;
159     const Qt::Orientation orientation;
160     const KReportZoomHandler * const viewConverter;
161 
162     int offset;
163     qreal rulerLength;
164     qreal activeRangeStart;
165     qreal activeRangeEnd;
166     qreal activeOverrideRangeStart;
167     qreal activeOverrideRangeEnd;
168 
169     int mouseCoordinate;
170     int showMousePosition;
171 
172     bool showSelectionBorders;
173     qreal firstSelectionBorder;
174     qreal secondSelectionBorder;
175 
176     bool showIndents;
177     qreal firstLineIndent;
178     qreal paragraphIndent;
179     qreal endIndent;
180 
181     bool showTabs;
182     bool relativeTabs;
183     bool tabMoved; // set to true on first move of a selected tab
184     QList<KReportRuler::Tab> tabs;
185     int originalIndex; //index of selected tab before we started dragging it.
186     int currentIndex; //index of selected tab or selected HotSpot - only valid when selected indicates tab or hotspot
187     KReportRuler::Tab deletedTab;
188     qreal tabDistance;
189 
190     struct HotSpotData {
191         qreal position;
192         int id;
193     };
194     QList<HotSpotData> hotspots;
195 
196     bool rightToLeft;
197     enum class Selection {
198         None,
199         Tab,
200         FirstLineIndent,
201         ParagraphIndent,
202         EndIndent,
203         HotSpot
204     };
205     Selection selected;
206     int selectOffset;
207 
208     QList<QAction*> popupActions;
209 
210     RulerTabChooser *tabChooser;
211 
212     // Cached painting strategies
213     PaintingStrategy * normalPaintingStrategy;
214     PaintingStrategy * distancesPaintingStrategy;
215 
216     // Current painting strategy
217     PaintingStrategy * paintingStrategy;
218 
219     KReportRuler *ruler;
220 
221     qreal numberStepForUnit() const;
222     /// @return The rounding of value to the nearest multiple of stepValue
223     qreal doSnapping(qreal value) const;
224     Selection selectionAtPosition(const QPoint &pos, int *selectOffset = nullptr);
225     int hotSpotIndex(const QPoint &pos);
226     qreal effectiveActiveRangeStart() const;
227     qreal effectiveActiveRangeEnd() const;
228 
229     friend class VerticalPaintingStrategy;
230     friend class HorizontalPaintingStrategy;
231 };
232 
233 // ----
234 
mousePressEvent(QMouseEvent *)235 void RulerTabChooser::mousePressEvent(QMouseEvent *)
236 {
237     if (! m_showTabs) {
238         return;
239     }
240 
241     switch(m_type) {
242     case QTextOption::LeftTab:
243         m_type = QTextOption::RightTab;
244         break;
245     case QTextOption::RightTab:
246         m_type = QTextOption::CenterTab;
247         break;
248     case QTextOption::CenterTab:
249         m_type = QTextOption::DelimiterTab;
250         break;
251     case QTextOption::DelimiterTab:
252         m_type = QTextOption::LeftTab;
253         break;
254     }
255     update();
256 }
257 
paintEvent(QPaintEvent *)258 void RulerTabChooser::paintEvent(QPaintEvent *)
259 {
260     if (! m_showTabs) {
261         return;
262     }
263 
264     QPainter painter(this);
265     QPolygonF polygon;
266 
267     painter.setPen(palette().color(QPalette::Text));
268     painter.setBrush(palette().color(QPalette::Text));
269     painter.setRenderHint( QPainter::Antialiasing );
270 
271     qreal x = qreal(width())/2.0;
272     painter.translate(0,-height()/2+5);
273 
274     switch (m_type) {
275     case QTextOption::LeftTab:
276         polygon << QPointF(x+0.5, height() - 8.5)
277             << QPointF(x+6.5, height() - 2.5)
278             << QPointF(x+0.5, height() - 2.5);
279         painter.drawPolygon(polygon);
280         break;
281     case QTextOption::RightTab:
282         polygon << QPointF(x+0.5, height() - 8.5)
283             << QPointF(x-5.5, height() - 2.5)
284             << QPointF(x+0.5, height() - 2.5);
285         painter.drawPolygon(polygon);
286         break;
287     case QTextOption::CenterTab:
288         polygon << QPointF(x+0.5, height() - 8.5)
289             << QPointF(x-5.5, height() - 2.5)
290             << QPointF(x+6.5, height() - 2.5);
291         painter.drawPolygon(polygon);
292         break;
293     case QTextOption::DelimiterTab:
294         polygon << QPointF(x-5.5, height() - 2.5)
295             << QPointF(x+6.5, height() - 2.5);
296         painter.drawPolyline(polygon);
297         polygon << QPointF(x+0.5, height() - 2.5)
298             << QPointF(x+0.5, height() - 8.5);
299         painter.drawPolyline(polygon);
300         break;
301     default:
302         break;
303     }
304 }
305 
compareTabs(const KReportRuler::Tab & tab1,const KReportRuler::Tab & tab2)306 static int compareTabs(const KReportRuler::Tab &tab1, const KReportRuler::Tab &tab2)
307 {
308     return tab1.position < tab2.position;
309 }
310 
drawBackground(const KReportRuler::Private * d,QPainter * painter)311 QRectF HorizontalPaintingStrategy::drawBackground(const KReportRuler::Private *d, QPainter *painter)
312 {
313     lengthInPixel = d->viewConverter->documentToViewX(d->rulerLength);
314     QRectF rectangle;
315     rectangle.setX(qMax(0, d->offset));
316     rectangle.setY(0);
317     rectangle.setWidth(qMin(qreal(d->ruler->width() - 1.0 - rectangle.x()),
318                             (d->offset >= 0) ? lengthInPixel : lengthInPixel + d->offset));
319     rectangle.setHeight(d->ruler->height() - 1);
320     QRectF activeRangeRectangle;
321     activeRangeRectangle.setX(qMax(rectangle.x() + 1,
322           d->viewConverter->documentToViewX(d->effectiveActiveRangeStart()) + d->offset));
323     activeRangeRectangle.setY(rectangle.y() + 1);
324     activeRangeRectangle.setRight(qMin(rectangle.right() - 1,
325           d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd()) + d->offset));
326     activeRangeRectangle.setHeight(rectangle.height() - 2);
327 
328     painter->setPen(d->ruler->palette().color(QPalette::Mid));
329     painter->drawRect(rectangle);
330 
331     if(d->effectiveActiveRangeStart() != d->effectiveActiveRangeEnd())
332         painter->fillRect(activeRangeRectangle, d->ruler->palette().brush(QPalette::Base));
333 
334     if(d->showSelectionBorders) {
335         // Draw first selection border
336         if(d->firstSelectionBorder > 0) {
337             qreal border = d->viewConverter->documentToViewX(d->firstSelectionBorder) + d->offset;
338             painter->drawLine(QPointF(border, rectangle.y() + 1), QPointF(border, rectangle.bottom() - 1));
339         }
340         // Draw second selection border
341         if(d->secondSelectionBorder > 0) {
342             qreal border = d->viewConverter->documentToViewX(d->secondSelectionBorder) + d->offset;
343             painter->drawLine(QPointF(border, rectangle.y() + 1), QPointF(border, rectangle.bottom() - 1));
344         }
345     }
346 
347     return rectangle;
348 }
349 
drawTabs(const KReportRuler::Private * d,QPainter * painter)350 void HorizontalPaintingStrategy::drawTabs(const KReportRuler::Private *d, QPainter *painter)
351 {
352     if (! d->showTabs)
353         return;
354     QPolygonF polygon;
355 
356     const QColor tabColor = d->ruler->palette().color(QPalette::Text);
357     painter->setPen(tabColor);
358     painter->setBrush(tabColor);
359     painter->setRenderHint( QPainter::Antialiasing );
360 
361     qreal position = -10000;
362 
363     foreach (const KReportRuler::Tab & t, d->tabs) {
364         qreal x;
365         if (d->rightToLeft) {
366             x = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd()
367                     - (d->relativeTabs ? d->paragraphIndent : 0) - t.position) + d->offset;
368         } else {
369             x = d->viewConverter->documentToViewX(d->effectiveActiveRangeStart()
370                     + (d->relativeTabs ? d->paragraphIndent : 0) + t.position) + d->offset;
371         }
372         position = qMax(position, t.position);
373 
374         polygon.clear();
375         switch (t.type) {
376         case QTextOption::LeftTab:
377             polygon << QPointF(x+0.5, d->ruler->height() - 6.5)
378                 << QPointF(x+6.5, d->ruler->height() - 0.5)
379                 << QPointF(x+0.5, d->ruler->height() - 0.5);
380             painter->drawPolygon(polygon);
381             break;
382         case QTextOption::RightTab:
383             polygon << QPointF(x+0.5, d->ruler->height() - 6.5)
384                 << QPointF(x-5.5, d->ruler->height() - 0.5)
385                 << QPointF(x+0.5, d->ruler->height() - 0.5);
386             painter->drawPolygon(polygon);
387             break;
388         case QTextOption::CenterTab:
389             polygon << QPointF(x+0.5, d->ruler->height() - 6.5)
390                 << QPointF(x-5.5, d->ruler->height() - 0.5)
391                 << QPointF(x+6.5, d->ruler->height() - 0.5);
392             painter->drawPolygon(polygon);
393             break;
394         case QTextOption::DelimiterTab:
395             polygon << QPointF(x-5.5, d->ruler->height() - 0.5)
396                 << QPointF(x+6.5, d->ruler->height() - 0.5);
397             painter->drawPolyline(polygon);
398             polygon << QPointF(x+0.5, d->ruler->height() - 0.5)
399                 << QPointF(x+0.5, d->ruler->height() - 6.5);
400             painter->drawPolyline(polygon);
401             break;
402         default:
403             break;
404         }
405     }
406 
407     // and also draw the regular interval tab that are non editable
408     if (d->tabDistance > 0.0) {
409         // first possible position
410         position = qMax(position, d->relativeTabs ? 0 : d->paragraphIndent);
411         if (position < 0) {
412             position = int(position / d->tabDistance) * d->tabDistance;
413         } else {
414             position = (int(position / d->tabDistance) + 1) * d->tabDistance;
415         }
416         while (position < d->effectiveActiveRangeEnd() - d->effectiveActiveRangeStart()
417                 - d->endIndent) {
418             qreal x;
419             if (d->rightToLeft) {
420                 x = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd()
421                         - (d->relativeTabs ? d->paragraphIndent : 0) - position) + d->offset;
422             } else {
423                 x = d->viewConverter->documentToViewX(d->effectiveActiveRangeStart()
424                         + (d->relativeTabs ? d->paragraphIndent : 0) + position) + d->offset;
425             }
426 
427             polygon.clear();
428             polygon << QPointF(x+0.5, d->ruler->height() - 3.5)
429                 << QPointF(x+4.5, d->ruler->height() - 0.5)
430                 << QPointF(x+0.5, d->ruler->height() - 0.5);
431             painter->drawPolygon(polygon);
432 
433             position += d->tabDistance;
434         }
435     }
436 }
437 
drawMeasurements(const KReportRuler::Private * d,QPainter * painter,const QRectF & rectangle)438 void HorizontalPaintingStrategy::drawMeasurements(const KReportRuler::Private *d, QPainter *painter, const QRectF &rectangle)
439 {
440     qreal numberStep = d->numberStepForUnit(); // number step in unit
441 //    QRectF activeRangeRectangle;
442     int numberStepPixel = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue(numberStep)));
443 //    const bool adjustMillimeters = (d->unit.type() == KReportUnit::Millimeter);
444 
445     const QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont);
446     const QFontMetrics fontMetrics(font);
447     painter->setFont(font);
448 
449     if (numberStepPixel == 0 || numberStep == 0)
450         return;
451 
452     // Calc the longest text length
453     int textLength = 0;
454     for(int i = 0; i < lengthInPixel; i += numberStepPixel) {
455         int number = qRound((i / numberStepPixel) * numberStep);
456 
457         textLength = qMax(textLength, fontMetrics.width(QString::number(number)));
458     }
459     textLength += 4;  // Add some padding
460 
461     // Change number step so all digits fits
462     while(textLength > numberStepPixel) {
463         numberStepPixel += numberStepPixel;
464         numberStep += numberStep;
465     }
466 
467     int start=0;
468     // Calc the first number step
469     if(d->offset < 0)
470         start = qAbs(d->offset);
471 
472     // make a little hack so rulers shows correctly inversed number aligned
473     const qreal lengthInUnit = d->unit.toUserValue(d->rulerLength);
474     const qreal hackyLength = lengthInUnit - fmod(lengthInUnit, numberStep);
475     if(d->rightToLeft) {
476         start -= int(d->viewConverter->documentToViewX(fmod(d->rulerLength,
477                     d->unit.fromUserValue(numberStep))));
478     }
479 
480     int stepCount = (start / numberStepPixel) + 1;
481     int halfStepCount = (start / qRound(numberStepPixel * 0.5)) + 1;
482     int quarterStepCount = (start / qRound(numberStepPixel * 0.25)) + 1;
483 
484     int pos = 0;
485     const QPen numberPen(d->ruler->palette().color(QPalette::Text));
486     const QPen markerPen(d->ruler->palette().color(QPalette::Inactive, QPalette::Text));
487     painter->setPen(markerPen);
488 
489     if(d->offset > 0)
490         painter->translate(d->offset, 0);
491 
492     const int len = qRound(rectangle.width()) + start;
493     int nextStep = qRound(d->viewConverter->documentToViewX(
494         d->unit.fromUserValue(numberStep * stepCount)));
495     int nextHalfStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue(
496         numberStep * 0.5 * halfStepCount)));
497     int nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue(
498         numberStep * 0.25 * quarterStepCount)));
499 
500     for(int i = start; i < len; ++i) {
501         pos = i - start;
502 
503         if(i == nextStep) {
504             if(pos != 0)
505                 painter->drawLine(QPointF(pos, rectangle.bottom()-1),
506                                  QPointF(pos, rectangle.bottom() - fullStepMarkerLength));
507 
508             int number = qRound(stepCount * numberStep);
509 
510             QString numberText = QString::number(number);
511             int x = pos;
512             if (d->rightToLeft) { // this is done in a hacky way with the fine tuning done above
513                 numberText = QString::number(hackyLength - stepCount * numberStep);
514             }
515             painter->setPen(numberPen);
516             painter->drawText(QPointF(x-fontMetrics.width(numberText)/2.0,
517                                      rectangle.bottom() -fullStepMarkerLength -measurementTextAboveBelowMargin),
518                              numberText);
519             painter->setPen(markerPen);
520 
521             ++stepCount;
522             nextStep = qRound(d->viewConverter->documentToViewX(
523                 d->unit.fromUserValue(numberStep * stepCount)));
524             ++halfStepCount;
525             nextHalfStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue(
526                 numberStep * 0.5 * halfStepCount)));
527             ++quarterStepCount;
528             nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue(
529                 numberStep * 0.25 * quarterStepCount)));
530         }
531         else if(i == nextHalfStep) {
532             if(pos != 0)
533                 painter->drawLine(QPointF(pos, rectangle.bottom()-1),
534                                  QPointF(pos, rectangle.bottom() - halfStepMarkerLength));
535 
536             ++halfStepCount;
537             nextHalfStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue(
538                 numberStep * 0.5 * halfStepCount)));
539             ++quarterStepCount;
540             nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue(
541                 numberStep * 0.25 * quarterStepCount)));
542         }
543         else if(i == nextQuarterStep) {
544             if(pos != 0)
545                 painter->drawLine(QPointF(pos, rectangle.bottom()-1),
546                                  QPointF(pos, rectangle.bottom() - quarterStepMarkerLength));
547 
548             ++quarterStepCount;
549             nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue(
550                 numberStep * 0.25 * quarterStepCount)));
551         }
552     }
553 
554     // Draw the mouse indicator
555     const int mouseCoord = d->mouseCoordinate - start;
556     if (d->selected == KReportRuler::Private::Selection::None
557         || d->selected == KReportRuler::Private::Selection::HotSpot)
558     {
559         const qreal top = rectangle.y() + 1;
560         const qreal bottom = rectangle.bottom() -1;
561         if (d->selected == KReportRuler::Private::Selection::None && d->showMousePosition
562             && mouseCoord > 0 && mouseCoord < rectangle.width())
563         {
564             painter->drawLine(QPointF(mouseCoord, top), QPointF(mouseCoord, bottom));
565         }
566         foreach (const KReportRuler::Private::HotSpotData & hp, d->hotspots) {
567             const qreal x = d->viewConverter->documentToViewX(hp.position) + d->offset;
568             painter->drawLine(QPointF(x, top), QPointF(x, bottom));
569         }
570     }
571 }
572 
drawIndents(const KReportRuler::Private * d,QPainter * painter)573 void HorizontalPaintingStrategy::drawIndents(const KReportRuler::Private *d, QPainter *painter)
574 {
575     QPolygonF polygon;
576 
577     painter->setBrush(d->ruler->palette().brush(QPalette::Base));
578     painter->setRenderHint( QPainter::Antialiasing );
579 
580     qreal x;
581     // Draw first line start indent
582     if (d->rightToLeft)
583         x = d->effectiveActiveRangeEnd() - d->firstLineIndent - d->paragraphIndent;
584     else
585         x = d->effectiveActiveRangeStart() + d->firstLineIndent + d->paragraphIndent;
586     // convert and use the +0.5 to go to nearest integer so that the 0.5 added below ensures sharp lines
587     x = int(d->viewConverter->documentToViewX(x) + d->offset + 0.5);
588     polygon << QPointF(x+6.5, 0.5)
589         << QPointF(x+0.5, 8.5)
590         << QPointF(x-5.5, 0.5)
591         << QPointF(x+5.5, 0.5);
592     painter->drawPolygon(polygon);
593 
594     // draw the hanging indent.
595     if (d->rightToLeft)
596         x = d->effectiveActiveRangeStart() + d->endIndent;
597     else
598         x = d->effectiveActiveRangeStart() + d->paragraphIndent;
599     // convert and use the +0.5 to go to nearest integer so that the 0.5 added below ensures sharp lines
600     x = int(d->viewConverter->documentToViewX(x) + d->offset + 0.5);
601     const int bottom = d->ruler->height();
602     polygon.clear();
603     polygon << QPointF(x+6.5, bottom - 0.5)
604         << QPointF(x+0.5, bottom - 8.5)
605         << QPointF(x-5.5, bottom - 0.5)
606         << QPointF(x+5.5, bottom - 0.5);
607     painter->drawPolygon(polygon);
608 
609     // Draw end-indent or paragraph indent if mode is rightToLeft
610     qreal diff;
611     if (d->rightToLeft)
612         diff = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd()
613                      - d->paragraphIndent) + d->offset - x;
614     else
615         diff = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - d->endIndent)
616                 + d->offset - x;
617     polygon.translate(diff, 0);
618     painter->drawPolygon(polygon);
619 }
620 
sizeHint()621 QSize HorizontalPaintingStrategy::sizeHint()
622 {
623     // assumes that digits for the number only use glyphs which do not go below the baseline
624     const QFontMetrics fm(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
625     const int digitsHeight = fm.ascent() + 1; // +1 for baseline
626     const int minimum = digitsHeight + fullStepMarkerLength + 2*measurementTextAboveBelowMargin;
627 
628     return QSize(0, minimum);
629 }
630 
drawBackground(const KReportRuler::Private * d,QPainter * painter)631 QRectF VerticalPaintingStrategy::drawBackground(const KReportRuler::Private *d, QPainter *painter)
632 {
633     lengthInPixel = d->viewConverter->documentToViewY(d->rulerLength);
634     QRectF rectangle;
635     rectangle.setX(0);
636     rectangle.setY(qMax(0, d->offset));
637     rectangle.setWidth(d->ruler->width() - 1.0);
638     rectangle.setHeight(qMin(qreal(d->ruler->height() - 1.0 - rectangle.y()),
639                              (d->offset >= 0) ? lengthInPixel : lengthInPixel + d->offset));
640 
641     QRectF activeRangeRectangle;
642     activeRangeRectangle.setX(rectangle.x() + 1);
643     activeRangeRectangle.setY(qMax(rectangle.y() + 1,
644         d->viewConverter->documentToViewY(d->effectiveActiveRangeStart()) + d->offset));
645     activeRangeRectangle.setWidth(rectangle.width() - 2);
646     activeRangeRectangle.setBottom(qMin(rectangle.bottom() - 1,
647         d->viewConverter->documentToViewY(d->effectiveActiveRangeEnd()) + d->offset));
648 
649     painter->setPen(d->ruler->palette().color(QPalette::Mid));
650     painter->drawRect(rectangle);
651 
652     if(d->effectiveActiveRangeStart() != d->effectiveActiveRangeEnd())
653         painter->fillRect(activeRangeRectangle, d->ruler->palette().brush(QPalette::Base));
654 
655     if(d->showSelectionBorders) {
656         // Draw first selection border
657         if(d->firstSelectionBorder > 0) {
658             qreal border = d->viewConverter->documentToViewY(d->firstSelectionBorder) + d->offset;
659             painter->drawLine(QPointF(rectangle.x() + 1, border), QPointF(rectangle.right() - 1, border));
660         }
661         // Draw second selection border
662         if(d->secondSelectionBorder > 0) {
663             qreal border = d->viewConverter->documentToViewY(d->secondSelectionBorder) + d->offset;
664             painter->drawLine(QPointF(rectangle.x() + 1, border), QPointF(rectangle.right() - 1, border));
665         }
666     }
667 
668     return rectangle;
669 }
670 
drawMeasurements(const KReportRuler::Private * d,QPainter * painter,const QRectF & rectangle)671 void VerticalPaintingStrategy::drawMeasurements(const KReportRuler::Private *d, QPainter *painter, const QRectF &rectangle)
672 {
673     qreal numberStep = d->numberStepForUnit(); // number step in unit
674     int numberStepPixel = qRound(d->viewConverter->documentToViewY( d->unit.fromUserValue(numberStep)));
675     if (numberStepPixel <= 0)
676         return;
677 
678     const QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont);
679     const QFontMetrics fontMetrics(font);
680     painter->setFont(font);
681 
682     // Calc the longest text length
683     int textLength = 0;
684 
685     for(int i = 0; i < lengthInPixel; i += numberStepPixel) {
686         int number = qRound((i / numberStepPixel) * numberStep);
687         textLength = qMax(textLength, fontMetrics.width(QString::number(number)));
688     }
689     textLength += 4;  // Add some padding
690 
691     if (numberStepPixel == 0 || numberStep == 0)
692         return;
693     // Change number step so all digits will fit
694     while(textLength > numberStepPixel) {
695         numberStepPixel += numberStepPixel;
696         numberStep += numberStep;
697     }
698 
699     // Calc the first number step
700     const int start = d->offset < 0 ? qAbs(d->offset) : 0;
701 
702     // make a little hack so rulers shows correctly inversed number aligned
703     int stepCount = (start / numberStepPixel) + 1;
704     int halfStepCount = (start / qRound(numberStepPixel * 0.5)) + 1;
705     int quarterStepCount = (start / qRound(numberStepPixel * 0.25)) + 1;
706 
707     const QPen numberPen(d->ruler->palette().color(QPalette::Text));
708     const QPen markerPen(d->ruler->palette().color(QPalette::Inactive, QPalette::Text));
709     painter->setPen(markerPen);
710 
711     if(d->offset > 0)
712         painter->translate(0, d->offset);
713 
714     const int len = qRound(rectangle.height()) + start;
715     int nextStep = qRound(d->viewConverter->documentToViewY(
716         d->unit.fromUserValue(numberStep * stepCount)));
717     int nextHalfStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue(
718         numberStep * 0.5 * halfStepCount)));
719     int nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue(
720         numberStep * 0.25 * quarterStepCount)));
721 
722     int pos = 0;
723     for(int i = start; i < len; ++i) {
724         pos = i - start;
725 
726         if(i == nextStep) {
727             painter->save();
728             painter->translate(rectangle.right()-fullStepMarkerLength, pos);
729             if(pos != 0)
730                 painter->drawLine(QPointF(0, 0), QPointF(fullStepMarkerLength-1, 0));
731 
732             painter->rotate(-90);
733             int number = qRound(stepCount * numberStep);
734             QString numberText = QString::number(number);
735             painter->setPen(numberPen);
736             painter->drawText(QPointF(-fontMetrics.width(numberText) / 2.0, -measurementTextAboveBelowMargin), numberText);
737             painter->restore();
738 
739             ++stepCount;
740             nextStep = qRound(d->viewConverter->documentToViewY(
741                 d->unit.fromUserValue(numberStep * stepCount)));
742             ++halfStepCount;
743             nextHalfStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue(
744                 numberStep * 0.5 * halfStepCount)));
745             ++quarterStepCount;
746             nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue(
747                 numberStep * 0.25 * quarterStepCount)));
748         } else if(i == nextHalfStep) {
749             if(pos != 0)
750                 painter->drawLine(QPointF(rectangle.right() - halfStepMarkerLength, pos),
751                                  QPointF(rectangle.right() - 1, pos));
752 
753             ++halfStepCount;
754             nextHalfStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue(
755                 numberStep * 0.5 * halfStepCount)));
756             ++quarterStepCount;
757             nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue(
758                 numberStep * 0.25 * quarterStepCount)));
759         } else if(i == nextQuarterStep) {
760             if(pos != 0)
761                 painter->drawLine(QPointF(rectangle.right() - quarterStepMarkerLength, pos),
762                                  QPointF(rectangle.right() - 1, pos));
763 
764             ++quarterStepCount;
765             nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue(
766                 numberStep * 0.25 * quarterStepCount)));
767         }
768     }
769 
770     // Draw the mouse indicator
771     const int mouseCoord = d->mouseCoordinate - start;
772     if (d->selected == KReportRuler::Private::Selection::None
773         || d->selected == KReportRuler::Private::Selection::HotSpot)
774     {
775         const qreal left = rectangle.left() + 1;
776         const qreal right = rectangle.right() -1;
777         if (d->selected == KReportRuler::Private::Selection::None && d->showMousePosition
778             && mouseCoord > 0 && mouseCoord < rectangle.height())
779         {
780             painter->drawLine(QPointF(left, mouseCoord), QPointF(right, mouseCoord));
781         }
782         foreach (const KReportRuler::Private::HotSpotData & hp, d->hotspots) {
783             const qreal y = d->viewConverter->documentToViewY(hp.position) + d->offset;
784             painter->drawLine(QPointF(left, y), QPointF(right, y));
785         }
786     }
787 }
788 
sizeHint()789 QSize VerticalPaintingStrategy::sizeHint()
790 {
791     // assumes that digits for the number only use glyphs which do not go below the baseline
792     const QFontMetrics fm(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
793     const int digitsHeight = fm.ascent() + 1; // +1 for baseline
794     const int minimum = digitsHeight + fullStepMarkerLength + 2*measurementTextAboveBelowMargin;
795 
796     return QSize(minimum, 0);
797 }
798 
799 
drawDistanceLine(const KReportRuler::Private * d,QPainter * painter,qreal start,qreal end)800 void HorizontalDistancesPaintingStrategy::drawDistanceLine(const KReportRuler::Private *d,
801                                                            QPainter *painter, qreal start,
802                                                            qreal end)
803 {
804 
805     // Don't draw too short lines
806     if (qMax(start, end) - qMin(start, end) < 1)
807         return;
808 
809     painter->save();
810     painter->translate(d->offset, d->ruler->height() / 2);
811     painter->setPen(d->ruler->palette().color(QPalette::Text));
812     painter->setBrush(d->ruler->palette().color(QPalette::Text));
813 
814     QLineF line(QPointF(d->viewConverter->documentToViewX(start), 0),
815             QPointF(d->viewConverter->documentToViewX(end), 0));
816     QPointF midPoint = line.pointAt(0.5);
817 
818     // Draw the label text
819     const QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont);
820     const QFontMetrics fontMetrics(font);
821     QString label = d->unit.toUserStringValue(
822             d->viewConverter->viewToDocumentX(line.length())) + QLatin1String("") + d->unit.symbol();
823     QPointF labelPosition = QPointF(midPoint.x() - fontMetrics.width(label)/2,
824             midPoint.y() + fontMetrics.ascent()/2);
825     painter->setFont(font);
826     painter->drawText(labelPosition, label);
827 
828     // Draw the arrow lines
829     qreal arrowLength = (line.length() - fontMetrics.width(label)) / 2 - 2;
830     arrowLength = qMax(qreal(0.0), arrowLength);
831     QLineF startArrow(line.p1(), line.pointAt(arrowLength / line.length()));
832     QLineF endArrow(line.p2(), line.pointAt(1.0 - arrowLength / line.length()));
833     painter->drawLine(startArrow);
834     painter->drawLine(endArrow);
835 
836     // Draw the arrow heads
837     QPolygonF arrowHead;
838     arrowHead << line.p1() << QPointF(line.x1()+3, line.y1()-3)
839         << QPointF(line.x1()+3, line.y1()+3);
840     painter->drawPolygon(arrowHead);
841     arrowHead.clear();
842     arrowHead << line.p2() << QPointF(line.x2()-3, line.y2()-3)
843         << QPointF(line.x2()-3, line.y2()+3);
844     painter->drawPolygon(arrowHead);
845 
846     painter->restore();
847 }
848 
drawMeasurements(const KReportRuler::Private * d,QPainter * painter,const QRectF &)849 void HorizontalDistancesPaintingStrategy::drawMeasurements(const KReportRuler::Private *d,
850                                                            QPainter *painter, const QRectF&)
851 {
852     QList<qreal> points;
853     points << 0.0;
854     points << d->effectiveActiveRangeStart() + d->paragraphIndent + d->firstLineIndent;
855     points << d->effectiveActiveRangeStart() + d->paragraphIndent;
856     points << d->effectiveActiveRangeEnd() - d->endIndent;
857     points << d->effectiveActiveRangeStart();
858     points << d->effectiveActiveRangeEnd();
859     points << d->rulerLength;
860     qSort(points.begin(), points.end());
861     QListIterator<qreal> i(points);
862     i.next();
863     while (i.hasNext() && i.hasPrevious()) {
864         drawDistanceLine(d, painter, i.peekPrevious(), i.peekNext());
865         i.next();
866     }
867 }
868 
Private(KReportRuler * parent,const KReportZoomHandler & zoomHandler,Qt::Orientation o)869 KReportRuler::Private::Private(KReportRuler *parent,
870                                          const KReportZoomHandler &zoomHandler, Qt::Orientation o)
871     : unit(DEFAULT_UNIT),
872     orientation(o),
873     viewConverter(&zoomHandler),
874     offset(0),
875     rulerLength(0),
876     activeRangeStart(0),
877     activeRangeEnd(0),
878     activeOverrideRangeStart(0),
879     activeOverrideRangeEnd(0),
880     mouseCoordinate(-1),
881     showMousePosition(0),
882     showSelectionBorders(false),
883     firstSelectionBorder(0),
884     secondSelectionBorder(0),
885     showIndents(false),
886     firstLineIndent(0),
887     paragraphIndent(0),
888     endIndent(0),
889     showTabs(false),
890     relativeTabs(false),
891     tabMoved(false),
892     originalIndex(-1),
893     currentIndex(0),
894     tabDistance(0.0),
895     rightToLeft(false),
896     selected(Selection::None),
897     selectOffset(0),
898     tabChooser(nullptr),
899     normalPaintingStrategy(o == Qt::Horizontal ?
900             (PaintingStrategy*)new HorizontalPaintingStrategy() : (PaintingStrategy*)new VerticalPaintingStrategy()),
901     distancesPaintingStrategy((PaintingStrategy*)new HorizontalDistancesPaintingStrategy()),
902     paintingStrategy(normalPaintingStrategy),
903     ruler(parent)
904 {
905 }
906 
~Private()907 KReportRuler::Private::~Private()
908 {
909     delete normalPaintingStrategy;
910     delete distancesPaintingStrategy;
911 }
912 
numberStepForUnit() const913 qreal KReportRuler::Private::numberStepForUnit() const
914 {
915     switch(unit.type()) {
916         case KReportUnit::Type::Inch:
917         case KReportUnit::Type::Centimeter:
918         case KReportUnit::Type::Decimeter:
919         case KReportUnit::Type::Millimeter:
920             return 1.0;
921         case KReportUnit::Type::Pica:
922         case KReportUnit::Type::Cicero:
923             return 10.0;
924         case KReportUnit::Type::Point:
925         default:
926             return 100.0;
927     }
928 }
929 
doSnapping(qreal value) const930 qreal KReportRuler::Private::doSnapping(qreal value) const
931 {
932     qreal numberStep = unit.fromUserValue(numberStepForUnit()/4.0);
933     return numberStep * qRound(value / numberStep);
934 }
935 
selectionAtPosition(const QPoint & pos,int * selectOffset)936 KReportRuler::Private::Selection KReportRuler::Private::selectionAtPosition(const QPoint & pos, int *selectOffset )
937 {
938     const int height = ruler->height();
939     if (rightToLeft) {
940         int x = int(viewConverter->documentToViewX(effectiveActiveRangeEnd() - firstLineIndent - paragraphIndent) + offset);
941         if (pos.x() >= x - 8 && pos.x() <= x +8 && pos.y() < height / 2) {
942             if (selectOffset)
943                 *selectOffset = x - pos.x();
944             return KReportRuler::Private::Selection::FirstLineIndent;
945         }
946 
947         x = int(viewConverter->documentToViewX(effectiveActiveRangeEnd() - paragraphIndent) + offset);
948         if (pos.x() >= x - 8 && pos.x() <= x +8 && pos.y() > height / 2) {
949             if (selectOffset)
950                 *selectOffset = x - pos.x();
951             return KReportRuler::Private::Selection::ParagraphIndent;
952         }
953 
954         x = int(viewConverter->documentToViewX(effectiveActiveRangeStart() + endIndent) + offset);
955         if (pos.x() >= x - 8 && pos.x() <= x + 8) {
956             if (selectOffset)
957                 *selectOffset = x - pos.x();
958             return KReportRuler::Private::Selection::EndIndent;
959         }
960     }
961     else {
962         int x = int(viewConverter->documentToViewX(effectiveActiveRangeStart() + firstLineIndent + paragraphIndent) + offset);
963         if (pos.x() >= x -8 && pos.x() <= x + 8 && pos.y() < height / 2) {
964             if (selectOffset)
965                 *selectOffset = x - pos.x();
966             return KReportRuler::Private::Selection::FirstLineIndent;
967         }
968 
969         x = int(viewConverter->documentToViewX(effectiveActiveRangeStart() + paragraphIndent) + offset);
970         if (pos.x() >= x - 8 && pos.x() <= x + 8 && pos.y() > height/2) {
971             if (selectOffset)
972                 *selectOffset = x - pos.x();
973             return KReportRuler::Private::Selection::ParagraphIndent;
974         }
975 
976         x = int(viewConverter->documentToViewX(effectiveActiveRangeEnd() - endIndent) + offset);
977         if (pos.x() >= x - 8 && pos.x() <= x + 8) {
978             if (selectOffset)
979                 *selectOffset = x - pos.x();
980             return KReportRuler::Private::Selection::EndIndent;
981         }
982     }
983 
984     return KReportRuler::Private::Selection::None;
985 }
986 
hotSpotIndex(const QPoint & pos)987 int KReportRuler::Private::hotSpotIndex(const QPoint & pos)
988 {
989     for(int counter = 0; counter < hotspots.count(); counter++) {
990         bool hit;
991         if (orientation == Qt::Horizontal)
992             hit = qAbs(viewConverter->documentToViewX(hotspots[counter].position) - pos.x() + offset) < 3;
993         else
994             hit = qAbs(viewConverter->documentToViewY(hotspots[counter].position) - pos.y() + offset) < 3;
995 
996         if (hit)
997             return counter;
998     }
999     return -1;
1000 }
1001 
effectiveActiveRangeStart() const1002 qreal KReportRuler::Private::effectiveActiveRangeStart() const
1003 {
1004     if (activeOverrideRangeStart != activeOverrideRangeEnd) {
1005         return activeOverrideRangeStart;
1006     } else {
1007         return activeRangeStart;
1008     }
1009 }
1010 
effectiveActiveRangeEnd() const1011 qreal KReportRuler::Private::effectiveActiveRangeEnd() const
1012 {
1013     if (activeOverrideRangeStart != activeOverrideRangeEnd) {
1014         return activeOverrideRangeEnd;
1015     } else {
1016         return activeRangeEnd;
1017     }
1018 }
1019 
emitTabChanged()1020 void KReportRuler::Private::emitTabChanged()
1021 {
1022     KReportRuler::Tab tab;
1023     if (currentIndex >= 0)
1024         tab = tabs[currentIndex];
1025     emit ruler->tabChanged(originalIndex, currentIndex >= 0 ? &tab : nullptr);
1026 }
1027 
1028 
KReportRuler(QWidget * parent,Qt::Orientation orientation,const KReportZoomHandler & zoomHandler)1029 KReportRuler::KReportRuler(QWidget* parent, Qt::Orientation orientation,
1030                            const KReportZoomHandler &zoomHandler)
1031   : QWidget(parent)
1032   , d(new KReportRuler::Private(this, zoomHandler, orientation))
1033 {
1034     setMouseTracking( true );
1035 }
1036 
~KReportRuler()1037 KReportRuler::~KReportRuler()
1038 {
1039     delete d;
1040 }
1041 
unit() const1042 KReportUnit KReportRuler::unit() const
1043 {
1044     return d->unit;
1045 }
1046 
setUnit(const KReportUnit & unit)1047 void KReportRuler::setUnit(const KReportUnit &unit)
1048 {
1049     d->unit = unit;
1050     update();
1051 }
1052 
rulerLength() const1053 qreal KReportRuler::rulerLength() const
1054 {
1055     return d->rulerLength;
1056 }
1057 
orientation() const1058 Qt::Orientation KReportRuler::orientation() const
1059 {
1060     return d->orientation;
1061 }
1062 
setOffset(int offset)1063 void KReportRuler::setOffset(int offset)
1064 {
1065     d->offset = offset;
1066     update();
1067 }
1068 
setRulerLength(qreal length)1069 void KReportRuler::setRulerLength(qreal length)
1070 {
1071     d->rulerLength = length;
1072     update();
1073 }
1074 
paintEvent(QPaintEvent * event)1075 void KReportRuler::paintEvent(QPaintEvent* event)
1076 {
1077     QPainter painter(this);
1078     painter.setClipRegion(event->region());
1079     painter.save();
1080     QRectF rectangle = d->paintingStrategy->drawBackground(d, &painter);
1081     painter.restore();
1082     painter.save();
1083     d->paintingStrategy->drawMeasurements(d, &painter, rectangle);
1084     painter.restore();
1085     if (d->showIndents) {
1086         painter.save();
1087         d->paintingStrategy->drawIndents(d, &painter);
1088         painter.restore();
1089     }
1090     d->paintingStrategy->drawTabs(d, &painter);
1091 }
1092 
minimumSizeHint() const1093 QSize KReportRuler::minimumSizeHint() const
1094 {
1095     return d->paintingStrategy->sizeHint();
1096 }
1097 
sizeHint() const1098 QSize KReportRuler::sizeHint() const
1099 {
1100     return d->paintingStrategy->sizeHint();
1101 }
1102 
setActiveRange(qreal start,qreal end)1103 void KReportRuler::setActiveRange(qreal start, qreal end)
1104 {
1105     d->activeRangeStart = start;
1106     d->activeRangeEnd = end;
1107     update();
1108 }
1109 
setOverrideActiveRange(qreal start,qreal end)1110 void KReportRuler::setOverrideActiveRange(qreal start, qreal end)
1111 {
1112     d->activeOverrideRangeStart = start;
1113     d->activeOverrideRangeEnd = end;
1114     update();
1115 }
1116 
updateMouseCoordinate(int coordinate)1117 void KReportRuler::updateMouseCoordinate(int coordinate)
1118 {
1119     if(d->mouseCoordinate == coordinate)
1120         return;
1121     d->mouseCoordinate = coordinate;
1122     update();
1123 }
1124 
setShowMousePosition(bool show)1125 void KReportRuler::setShowMousePosition(bool show)
1126 {
1127     d->showMousePosition = show;
1128     update();
1129 }
1130 
setRightToLeft(bool isRightToLeft)1131 void KReportRuler::setRightToLeft(bool isRightToLeft)
1132 {
1133     d->rightToLeft = isRightToLeft;
1134     update();
1135 }
1136 
setShowIndents(bool show)1137 void KReportRuler::setShowIndents(bool show)
1138 {
1139     d->showIndents = show;
1140     update();
1141 }
1142 
setFirstLineIndent(qreal indent)1143 void KReportRuler::setFirstLineIndent(qreal indent)
1144 {
1145     d->firstLineIndent = indent;
1146     if (d->showIndents) {
1147         update();
1148     }
1149 }
1150 
setParagraphIndent(qreal indent)1151 void KReportRuler::setParagraphIndent(qreal indent)
1152 {
1153     d->paragraphIndent = indent;
1154     if (d->showIndents) {
1155         update();
1156     }
1157 }
1158 
setEndIndent(qreal indent)1159 void KReportRuler::setEndIndent(qreal indent)
1160 {
1161     d->endIndent = indent;
1162     if (d->showIndents) {
1163         update();
1164     }
1165 }
1166 
firstLineIndent() const1167 qreal KReportRuler::firstLineIndent() const
1168 {
1169     return d->firstLineIndent;
1170 }
1171 
paragraphIndent() const1172 qreal KReportRuler::paragraphIndent() const
1173 {
1174     return d->paragraphIndent;
1175 }
1176 
endIndent() const1177 qreal KReportRuler::endIndent() const
1178 {
1179     return d->endIndent;
1180 }
1181 
tabChooser()1182 QWidget *KReportRuler::tabChooser()
1183 {
1184     if ((d->tabChooser == nullptr) && (d->orientation == Qt::Horizontal)) {
1185         d->tabChooser = new RulerTabChooser(parentWidget());
1186         d->tabChooser->setShowTabs(d->showTabs);
1187     }
1188 
1189     return d->tabChooser;
1190 }
1191 
setShowSelectionBorders(bool show)1192 void KReportRuler::setShowSelectionBorders(bool show)
1193 {
1194     d->showSelectionBorders = show;
1195     update();
1196 }
1197 
updateSelectionBorders(qreal first,qreal second)1198 void KReportRuler::updateSelectionBorders(qreal first, qreal second)
1199 {
1200     d->firstSelectionBorder = first;
1201     d->secondSelectionBorder = second;
1202 
1203     if(d->showSelectionBorders)
1204         update();
1205 }
1206 
setShowTabs(bool show)1207 void KReportRuler::setShowTabs(bool show)
1208 {
1209     if (d->showTabs == show) {
1210         return;
1211     }
1212 
1213     d->showTabs = show;
1214     if (d->tabChooser) {
1215         d->tabChooser->setShowTabs(show);
1216     }
1217     update();
1218 }
1219 
setRelativeTabs(bool relative)1220 void KReportRuler::setRelativeTabs(bool relative)
1221 {
1222     d->relativeTabs = relative;
1223     if (d->showTabs) {
1224         update();
1225     }
1226 }
1227 
updateTabs(const QList<KReportRuler::Tab> & tabs,qreal tabDistance)1228 void KReportRuler::updateTabs(const QList<KReportRuler::Tab> &tabs, qreal tabDistance)
1229 {
1230     d->tabs = tabs;
1231     d->tabDistance = tabDistance;
1232     if (d->showTabs) {
1233         update();
1234     }
1235 }
1236 
tabs() const1237 QList<KReportRuler::Tab> KReportRuler::tabs() const
1238 {
1239     QList<Tab> answer = d->tabs;
1240     qSort(answer.begin(), answer.end(), compareTabs);
1241 
1242     return answer;
1243 }
1244 
setPopupActionList(const QList<QAction * > & popupActionList)1245 void KReportRuler::setPopupActionList(const QList<QAction*> &popupActionList)
1246 {
1247     d->popupActions = popupActionList;
1248 }
1249 
popupActionList() const1250 QList<QAction*> KReportRuler::popupActionList() const
1251 {
1252     return d->popupActions;
1253 }
1254 
mousePressEvent(QMouseEvent * ev)1255 void KReportRuler::mousePressEvent ( QMouseEvent* ev )
1256 {
1257     d->tabMoved = false;
1258     d->selected = KReportRuler::Private::Selection::None;
1259     if (ev->button() == Qt::RightButton && !d->popupActions.isEmpty())
1260         QMenu::exec(d->popupActions, ev->globalPos());
1261     if (ev->button() != Qt::LeftButton) {
1262         ev->ignore();
1263         return;
1264     }
1265 
1266     QPoint pos = ev->pos();
1267 
1268     if (d->showTabs) {
1269         int i = 0;
1270         int x;
1271         foreach (const Tab & t, d->tabs) {
1272             if (d->rightToLeft) {
1273                 x = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd()
1274                         - (d->relativeTabs ? d->paragraphIndent : 0) - t.position) + d->offset;
1275             } else {
1276                 x = d->viewConverter->documentToViewX(d->effectiveActiveRangeStart()
1277                         + (d->relativeTabs ? d->paragraphIndent : 0) + t.position) + d->offset;
1278             }
1279             if (pos.x() >= x-6 && pos.x() <= x+6) {
1280                 d->selected = KReportRuler::Private::Selection::Tab;
1281                 d->selectOffset = x - pos.x();
1282                 d->currentIndex = i;
1283                 break;
1284             }
1285             i++;
1286         }
1287         d->originalIndex = d->currentIndex;
1288     }
1289 
1290     if (d->selected == KReportRuler::Private::Selection::None)
1291         d->selected = d->selectionAtPosition(ev->pos(), &d->selectOffset);
1292     if (d->selected == KReportRuler::Private::Selection::None) {
1293         int hotSpotIndex = d->hotSpotIndex(ev->pos());
1294         if (hotSpotIndex >= 0) {
1295             d->selected = KReportRuler::Private::Selection::HotSpot;
1296             update();
1297         }
1298     }
1299 
1300     if (d->showTabs && d->selected == KReportRuler::Private::Selection::None) {
1301         // still haven't found something so let assume the user wants to add a tab
1302         qreal tabpos;
1303         if (d->rightToLeft) {
1304             tabpos = d->viewConverter->viewToDocumentX(pos.x() - d->offset)
1305                     + d->effectiveActiveRangeEnd() + (d->relativeTabs ? d->paragraphIndent : 0);
1306         } else {
1307             tabpos = d->viewConverter->viewToDocumentX(pos.x() - d->offset)
1308                     - d->effectiveActiveRangeStart() - (d->relativeTabs ? d->paragraphIndent : 0);
1309         }
1310         Tab t(tabpos, d->tabChooser ?  d->tabChooser->type() :
1311                          d->rightToLeft ? QTextOption::RightTab :
1312                                           QTextOption::LeftTab);
1313         d->tabs.append(t);
1314         d->selectOffset = 0;
1315         d->selected = KReportRuler::Private::Selection::Tab;
1316         d->currentIndex = d->tabs.count() - 1;
1317         d->originalIndex = -1; // new!
1318         update();
1319     }
1320     if (d->orientation == Qt::Horizontal && (ev->modifiers() & Qt::ShiftModifier) &&
1321             (d->selected == KReportRuler::Private::Selection::FirstLineIndent ||
1322              d->selected == KReportRuler::Private::Selection::ParagraphIndent ||
1323              d->selected == KReportRuler::Private::Selection::Tab ||
1324              d->selected == KReportRuler::Private::Selection::EndIndent))
1325         d->paintingStrategy = d->distancesPaintingStrategy;
1326 
1327     if (d->selected != KReportRuler::Private::Selection::None)
1328         emit aboutToChange();
1329 }
1330 
mouseReleaseEvent(QMouseEvent * ev)1331 void KReportRuler::mouseReleaseEvent ( QMouseEvent* ev )
1332 {
1333     ev->accept();
1334     if (d->selected == KReportRuler::Private::Selection::Tab) {
1335         if (d->originalIndex >= 0 && !d->tabMoved) {
1336             int type = d->tabs[d->currentIndex].type;
1337             type++;
1338             if (type > 3)
1339                 type = 0;
1340             d->tabs[d->currentIndex].type = static_cast<QTextOption::TabType> (type);
1341             update();
1342         }
1343         d->emitTabChanged();
1344     }
1345     else if( d->selected != KReportRuler::Private::Selection::None)
1346         emit indentsChanged(true);
1347     else
1348         ev->ignore();
1349 
1350     d->paintingStrategy = d->normalPaintingStrategy;
1351     d->selected = KReportRuler::Private::Selection::None;
1352 }
1353 
mouseMoveEvent(QMouseEvent * ev)1354 void KReportRuler::mouseMoveEvent ( QMouseEvent* ev )
1355 {
1356     QPoint pos = ev->pos();
1357 
1358     qreal activeLength = d->effectiveActiveRangeEnd() - d->effectiveActiveRangeStart();
1359 
1360     switch (d->selected) {
1361     case KReportRuler::Private::Selection::FirstLineIndent:
1362         if (d->rightToLeft)
1363             d->firstLineIndent = d->effectiveActiveRangeEnd() - d->paragraphIndent -
1364                 d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset);
1365         else
1366             d->firstLineIndent = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset
1367                 - d->offset) - d->effectiveActiveRangeStart() - d->paragraphIndent;
1368         if( ! (ev->modifiers() & Qt::ShiftModifier)) {
1369             d->firstLineIndent = d->doSnapping(d->firstLineIndent);
1370             d->paintingStrategy = d->normalPaintingStrategy;
1371         } else {
1372             if (d->orientation == Qt::Horizontal)
1373                 d->paintingStrategy = d->distancesPaintingStrategy;
1374         }
1375 
1376         emit indentsChanged(false);
1377         break;
1378     case KReportRuler::Private::Selection::ParagraphIndent:
1379         if (d->rightToLeft)
1380             d->paragraphIndent = d->effectiveActiveRangeEnd() -
1381                 d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset);
1382         else
1383             d->paragraphIndent = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset
1384                 - d->offset) - d->effectiveActiveRangeStart();
1385         if( ! (ev->modifiers() & Qt::ShiftModifier)) {
1386             d->paragraphIndent = d->doSnapping(d->paragraphIndent);
1387             d->paintingStrategy = d->normalPaintingStrategy;
1388         } else {
1389             if (d->orientation == Qt::Horizontal)
1390                 d->paintingStrategy = d->distancesPaintingStrategy;
1391         }
1392 
1393         if (d->paragraphIndent + d->endIndent > activeLength)
1394             d->paragraphIndent = activeLength - d->endIndent;
1395         emit indentsChanged(false);
1396         break;
1397     case KReportRuler::Private::Selection::EndIndent:
1398         if (d->rightToLeft)
1399             d->endIndent = d->viewConverter->viewToDocumentX(pos.x()
1400                  + d->selectOffset - d->offset) - d->effectiveActiveRangeStart();
1401         else
1402             d->endIndent = d->effectiveActiveRangeEnd() - d->viewConverter->viewToDocumentX(pos.x()
1403                  + d->selectOffset - d->offset);
1404         if (!(ev->modifiers() & Qt::ShiftModifier)) {
1405             d->endIndent = d->doSnapping(d->endIndent);
1406             d->paintingStrategy = d->normalPaintingStrategy;
1407         } else {
1408             if (d->orientation == Qt::Horizontal)
1409                 d->paintingStrategy = d->distancesPaintingStrategy;
1410         }
1411 
1412         if (d->paragraphIndent + d->endIndent > activeLength)
1413             d->endIndent = activeLength - d->paragraphIndent;
1414         emit indentsChanged(false);
1415         break;
1416     case KReportRuler::Private::Selection::Tab:
1417         d->tabMoved = true;
1418         if (d->currentIndex < 0) { // tab is deleted.
1419             if (ev->pos().y() < height()) { // reinstante it.
1420                 d->currentIndex = d->tabs.count();
1421                 d->tabs.append(d->deletedTab);
1422             } else {
1423                 break;
1424             }
1425         }
1426         if (d->rightToLeft)
1427             d->tabs[d->currentIndex].position = d->effectiveActiveRangeEnd() -
1428                 d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset);
1429         else
1430             d->tabs[d->currentIndex].position = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset
1431                 - d->offset) - d->effectiveActiveRangeStart();
1432         if (!(ev->modifiers() & Qt::ShiftModifier))
1433             d->tabs[d->currentIndex].position = d->doSnapping(d->tabs[d->currentIndex].position);
1434         if (d->tabs[d->currentIndex].position < 0)
1435             d->tabs[d->currentIndex].position = 0;
1436         if (d->tabs[d->currentIndex].position > activeLength)
1437             d->tabs[d->currentIndex].position = activeLength;
1438 
1439         if (ev->pos().y() > height() + OutsideRulerThreshold ) { // moved out of the ruler, delete it.
1440             d->deletedTab = d->tabs.takeAt(d->currentIndex);
1441             d->currentIndex = -1;
1442             // was that a temporary added tab?
1443             if ( d->originalIndex == -1 )
1444                 emit guideLineCreated(d->orientation,
1445                         d->orientation == Qt::Horizontal
1446                         ? d->viewConverter->viewToDocumentY(ev->pos().y())
1447                         : d->viewConverter->viewToDocumentX(ev->pos().x()));
1448         }
1449 
1450         d->emitTabChanged();
1451         break;
1452     case KReportRuler::Private::Selection::HotSpot:
1453         qreal newPos;
1454         if (d->orientation == Qt::Horizontal)
1455             newPos= d->viewConverter->viewToDocumentX(pos.x() - d->offset);
1456         else
1457             newPos= d->viewConverter->viewToDocumentY(pos.y() - d->offset);
1458         d->hotspots[d->currentIndex].position = newPos;
1459         emit hotSpotChanged(d->hotspots[d->currentIndex].id, newPos);
1460         break;
1461     case KReportRuler::Private::Selection::None:
1462         d->mouseCoordinate = (d->orientation == Qt::Horizontal ?  pos.x() : pos.y()) - d->offset;
1463         int hotSpotIndex = d->hotSpotIndex(pos);
1464         if (hotSpotIndex >= 0) {
1465             setCursor(QCursor( d->orientation == Qt::Horizontal ? Qt::SplitHCursor : Qt::SplitVCursor ));
1466             break;
1467         }
1468         unsetCursor();
1469 
1470         KReportRuler::Private::Selection selection = d->selectionAtPosition(pos);
1471         QString text;
1472         switch(selection) {
1473         case KReportRuler::Private::Selection::FirstLineIndent: text = tr("First line indent"); break;
1474         case KReportRuler::Private::Selection::ParagraphIndent: text = tr("Left indent"); break;
1475         case KReportRuler::Private::Selection::EndIndent: text = tr("Right indent"); break;
1476         case KReportRuler::Private::Selection::None:
1477             if (ev->buttons() & Qt::LeftButton) {
1478                 if (d->orientation == Qt::Horizontal && ev->pos().y() > height() + OutsideRulerThreshold)
1479                     emit guideLineCreated(d->orientation, d->viewConverter->viewToDocumentY(ev->pos().y()));
1480                 else if (d->orientation == Qt::Vertical && ev->pos().x() > width() + OutsideRulerThreshold)
1481                     emit guideLineCreated(d->orientation, d->viewConverter->viewToDocumentX(ev->pos().x()));
1482             }
1483             break;
1484         default:
1485             break;
1486         }
1487         setToolTip(text);
1488     }
1489     update();
1490 }
1491 
clearHotSpots()1492 void KReportRuler::clearHotSpots()
1493 {
1494     if (d->hotspots.isEmpty())
1495         return;
1496     d->hotspots.clear();
1497     update();
1498 }
1499 
setHotSpot(qreal position,int id)1500 void KReportRuler::setHotSpot(qreal position, int id)
1501 {
1502     int hotspotCount = d->hotspots.count();
1503     for (int i = 0; i < hotspotCount; ++i) {
1504         KReportRuler::Private::HotSpotData & hs = d->hotspots[i];
1505         if (hs.id == id) {
1506             hs.position = position;
1507             update();
1508             return;
1509         }
1510     }
1511     // not there yet, then insert it.
1512     KReportRuler::Private::HotSpotData hs;
1513     hs.position = position;
1514     hs.id = id;
1515     d->hotspots.append(hs);
1516 }
1517 
removeHotSpot(int id)1518 bool KReportRuler::removeHotSpot(int id)
1519 {
1520     QList<KReportRuler::Private::HotSpotData>::Iterator iter = d->hotspots.begin();
1521     while(iter != d->hotspots.end()) {
1522         if (iter->id == id) {
1523             d->hotspots.erase(iter);
1524             update();
1525             return true;
1526         }
1527     }
1528     return false;
1529 }
1530