]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiPainter.cpp
Add support to cross out characters
[lyx.git] / src / frontends / qt4 / GuiPainter.cpp
1 /**
2  * \file GuiPainter.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author John Levon
7  * \author Abdelrazak Younes
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "GuiPainter.h"
15
16 #include "ColorCache.h"
17 #include "GuiApplication.h"
18 #include "GuiFontLoader.h"
19 #include "GuiFontMetrics.h"
20 #include "GuiImage.h"
21 #include "qt_helpers.h"
22
23 #include "Font.h"
24 #include "Language.h"
25 #include "LyXRC.h"
26
27 #include "insets/Inset.h"
28
29 #include "support/lassert.h"
30 #include "support/debug.h"
31
32 #include <algorithm>
33
34 #include <QPixmapCache>
35 #include <QTextLayout>
36
37 // Set USE_PIXMAP_CACHE to 1 for enabling the use of a Pixmap cache when
38 // drawing text. This is especially useful for older PPC/Mac systems.
39 #if defined(Q_WS_X11) || defined(QPA_XCB)
40 #define USE_PIXMAP_CACHE 0
41 #else
42 #define USE_PIXMAP_CACHE 1
43 #endif
44
45 using namespace std;
46 using namespace lyx::support;
47
48 namespace lyx {
49 namespace frontend {
50
51 const int Painter::thin_line = 1;
52
53 GuiPainter::GuiPainter(QPaintDevice * device, double pixel_ratio)
54         : QPainter(device), Painter(pixel_ratio),
55           use_pixmap_cache_(lyxrc.use_pixmap_cache && USE_PIXMAP_CACHE)
56 {
57         // new QPainter has default QPen:
58         current_color_ = guiApp->colorCache().get(Color_black);
59         current_ls_ = line_solid;
60         current_lw_ = thin_line;
61 }
62
63
64 GuiPainter::~GuiPainter()
65 {
66         QPainter::end();
67         //lyxerr << "GuiPainter::end()" << endl;
68 }
69
70
71 void GuiPainter::setQPainterPen(QColor const & col,
72         Painter::line_style ls, int lw)
73 {
74         if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
75                 return;
76
77         current_color_ = col;
78         current_ls_ = ls;
79         current_lw_ = lw;
80
81         QPen pen = QPainter::pen();
82         pen.setColor(col);
83
84         switch (ls) {
85         case line_solid:
86         case line_solid_aliased:
87                 pen.setStyle(Qt::SolidLine); break;
88         case line_onoffdash:
89                 pen.setStyle(Qt::DotLine); break;
90         }
91
92         pen.setWidth(lw);
93
94         setPen(pen);
95 }
96
97
98 QString GuiPainter::generateStringSignature(QString const & str,
99                                             FontInfo const & f,
100                                             double wordspacing)
101 {
102         QString sig = str;
103         sig.append(QChar(static_cast<short>(f.family())));
104         sig.append(QChar(static_cast<short>(f.series())));
105         sig.append(QChar(static_cast<short>(f.realShape())));
106         sig.append(QChar(static_cast<short>(f.size())));
107         Color const & color = f.realColor();
108         sig.append(QChar(static_cast<short>(color.baseColor)));
109         sig.append(QChar(static_cast<short>(color.mergeColor)));
110         sig.append(QString::number(wordspacing));
111         if (!monochrome_min_.empty()) {
112                 QColor const & min = monochrome_min_.top();
113                 QColor const & max = monochrome_max_.top();
114                 sig.append(QChar(static_cast<short>(min.red())));
115                 sig.append(QChar(static_cast<short>(min.green())));
116                 sig.append(QChar(static_cast<short>(min.blue())));
117                 sig.append(QChar(static_cast<short>(max.red())));
118                 sig.append(QChar(static_cast<short>(max.green())));
119                 sig.append(QChar(static_cast<short>(max.blue())));
120         }
121         return sig;
122 }
123
124
125 QColor GuiPainter::computeColor(Color col)
126 {
127         return filterColor(guiApp->colorCache().get(col));
128 }
129
130
131 QColor GuiPainter::filterColor(QColor const & col)
132 {
133         if (monochrome_min_.empty())
134                 return col;
135
136         // map into [min,max] interval
137         QColor const & min = monochrome_min_.top();
138         QColor const & max = monochrome_max_.top();
139                         
140         qreal v = col.valueF();
141         v *= v; // make it a bit steeper (i.e. darker)
142                 
143         qreal minr, ming, minb;
144         qreal maxr, maxg, maxb;
145         min.getRgbF(&minr, &ming, &minb);
146         max.getRgbF(&maxr, &maxg, &maxb);
147                         
148         QColor c;
149         c.setRgbF(
150                 v * (minr - maxr) + maxr,
151                 v * (ming - maxg) + maxg,
152                 v * (minb - maxb) + maxb);
153         return c;
154 }
155
156
157 void GuiPainter::enterMonochromeMode(Color const & min, Color const & max)
158 {
159         QColor qmin = filterColor(guiApp->colorCache().get(min));
160         QColor qmax = filterColor(guiApp->colorCache().get(max));
161         monochrome_min_.push(qmin);
162         monochrome_max_.push(qmax);
163 }
164
165
166 void GuiPainter::leaveMonochromeMode()
167 {
168         LASSERT(!monochrome_min_.empty(), return);
169         monochrome_min_.pop();
170         monochrome_max_.pop();
171 }
172
173
174 void GuiPainter::point(int x, int y, Color col)
175 {
176         if (!isDrawingEnabled())
177                 return;
178
179         setQPainterPen(computeColor(col));
180         drawPoint(x, y);
181 }
182
183
184 void GuiPainter::line(int x1, int y1, int x2, int y2,
185         Color col,
186         line_style ls,
187         int lw)
188 {
189         if (!isDrawingEnabled())
190                 return;
191
192         setQPainterPen(computeColor(col), ls, lw);
193         bool const do_antialiasing = renderHints() & TextAntialiasing
194                 && x1 != x2 && y1 != y2 && ls != line_solid_aliased;
195         setRenderHint(Antialiasing, do_antialiasing);
196         drawLine(x1, y1, x2, y2);
197         setRenderHint(Antialiasing, false);
198 }
199
200
201 void GuiPainter::lines(int const * xp, int const * yp, int np,
202         Color col,
203         fill_style fs,
204         line_style ls,
205         int lw)
206 {
207         if (!isDrawingEnabled())
208                 return;
209
210         // double the size if needed
211         // FIXME THREAD
212         static QVector<QPoint> points(32);
213         if (np > points.size())
214                 points.resize(2 * np);
215
216         // Note: the proper way to not get blurry vertical and horizontal lines is
217         // to add 0.5 to all coordinates.
218         bool antialias = false;
219         for (int i = 0; i < np; ++i) {
220                 points[i].setX(xp[i]);
221                 points[i].setY(yp[i]);
222                 if (i != 0)
223                         antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
224         }
225         QColor const color = computeColor(col);
226         setQPainterPen(color, ls, lw);
227         bool const text_is_antialiased = renderHints() & TextAntialiasing;
228         setRenderHint(Antialiasing,
229                       antialias && text_is_antialiased && ls != line_solid_aliased);
230         if (fs == fill_none) {
231                 drawPolyline(points.data(), np);
232         } else {
233                 QBrush const oldbrush = brush();
234                 setBrush(QBrush(color));
235                 drawPolygon(points.data(), np, fs == fill_oddeven ?
236                             Qt::OddEvenFill : Qt::WindingFill);
237                 setBrush(oldbrush);
238         }
239         setRenderHint(Antialiasing, false);
240 }
241
242
243 void GuiPainter::path(int const * xp, int const * yp,
244         int const * c1x, int const * c1y,
245         int const * c2x, int const * c2y,
246         int np,
247         Color col,
248         fill_style fs,
249         line_style ls,
250         int lw)
251 {
252         if (!isDrawingEnabled())
253                 return;
254
255         QPainterPath bpath;
256         // This is the starting point, so its control points are meaningless
257         bpath.moveTo(xp[0], yp[0]);
258
259         for (int i = 1; i < np; ++i) {
260                 bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
261                             c2x[i] == xp[i] && c2y[i] == yp[i];
262                 if (line)
263                         bpath.lineTo(xp[i], yp[i]);
264                 else
265                         bpath.cubicTo(c1x[i], c1y[i],  c2x[i], c2y[i], xp[i], yp[i]);
266         }
267         QColor const color = computeColor(col);
268         setQPainterPen(color, ls, lw);
269         bool const text_is_antialiased = renderHints() & TextAntialiasing;
270         setRenderHint(Antialiasing, text_is_antialiased && ls != line_solid_aliased);
271         drawPath(bpath);
272         if (fs != fill_none)
273                 fillPath(bpath, QBrush(color));
274         setRenderHint(Antialiasing, false);
275 }
276
277
278 void GuiPainter::rectangle(int x, int y, int w, int h,
279         Color col,
280         line_style ls,
281         int lw)
282 {
283         if (!isDrawingEnabled())
284                 return;
285
286         setQPainterPen(computeColor(col), ls, lw);
287         drawRect(x, y, w, h);
288 }
289
290
291 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
292 {
293         if (!isDrawingEnabled())
294                 return;
295
296         fillRect(x, y, w, h, guiApp->colorCache().get(col));
297 }
298
299
300 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
301         int a1, int a2, Color col)
302 {
303         if (!isDrawingEnabled())
304                 return;
305
306         // LyX usings 1/64ths degree, Qt usings 1/16th
307         setQPainterPen(computeColor(col));
308         bool const do_antialiasing = renderHints() & TextAntialiasing;
309         setRenderHint(Antialiasing, do_antialiasing);
310         drawArc(x, y, w, h, a1 / 4, a2 / 4);
311         setRenderHint(Antialiasing, false);
312 }
313
314
315 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
316 {
317         graphics::GuiImage const & qlimage =
318                 static_cast<graphics::GuiImage const &>(i);
319
320         fillRectangle(x, y, w, h, Color_graphicsbg);
321
322         if (!isDrawingEnabled())
323                 return;
324
325         QImage const image = qlimage.image();
326         QRectF const drect = QRectF(x, y, w, h);
327         QRectF const srect = QRectF(0, 0, image.width(), image.height());
328         // Bilinear filtering is needed on a rare occasion for instant previews when
329         // the user's configuration mixes low-dpi and high-dpi monitors (#10114).
330         // This filter is optimised by qt on pixel-aligned images, so this does not
331         // affect performances in other cases.
332         setRenderHint(SmoothPixmapTransform);
333         drawImage(drect, image, srect);
334         setRenderHint(SmoothPixmapTransform, false);
335 }
336
337
338 void GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
339 {
340         text(x, y, docstring(1, c), f);
341 }
342
343
344 void GuiPainter::text(int x, int y, docstring const & s, FontInfo const & f)
345 {
346         text(x, y, s, f, Auto, 0.0, 0.0);
347 }
348
349
350 void GuiPainter::do_drawText(int x, int y, QString str,
351                              GuiPainter::Direction const dir,
352                              FontInfo const & f, QFont ff)
353 {
354         setQPainterPen(computeColor(f.realColor()));
355         if (font() != ff)
356                 setFont(ff);
357
358          /* In LyX, the character direction is forced by the language.
359           * Therefore, we have to signal that fact to Qt.
360           */
361 #if 1
362         /* Use unicode override characters to enforce drawing direction
363          * Source: http://www.iamcal.com/understanding-bidirectional-text/
364          */
365         if (dir == RtL)
366                 // Right-to-left override: forces to draw text right-to-left
367                 str = QChar(0x202E) + str;
368         else if (dir == LtR)
369                 // Left-to-right override: forces to draw text left-to-right
370                 str =  QChar(0x202D) + str;
371         drawText(x, y, str);
372 #else
373         /* This looks like a cleaner solution, but it has drawbacks
374          * - does not work reliably (Mac OS X, ...)
375          * - it is not really documented
376          * Keep it here for now, in case it can be helpful
377          */
378         //This is much stronger than setLayoutDirection.
379         int flag = 0;
380         if (dir == RtL)
381                 flag = Qt::TextForceRightToLeft;
382         else if (dir == LtR)
383                 flag = Qt::TextForceLeftToRight;
384         drawText(x + ((dir == RtL) ? textwidth : 0), y - fm.maxAscent(), 0, 0,
385                  flag | Qt::TextDontClip,
386                  str);
387 #endif
388 }
389
390
391 void GuiPainter::text(int x, int y, docstring const & s,
392                       FontInfo const & f, Direction const dir,
393                       double const wordspacing, double const tw)
394 {
395         //LYXERR0("text: x=" << x << ", s=" << s);
396         if (s.empty() || !isDrawingEnabled())
397                 return;
398
399         /* Caution: The following ucs4 to QString conversions work for symbol fonts
400         only because they are no real conversions but simple casts in reality.
401         When we want to draw a symbol or calculate the metrics we pass the position
402         of the symbol in the font (as given in lib/symbols) as a char_type to the
403         frontend. This is just wrong, because the symbol is no UCS4 character at
404         all. You can think of this number as the code point of the symbol in a
405         custom symbol encoding. It works because this char_type is later on again
406         interpreted as a position in the font.
407         The correct solution would be to have extra functions for symbols, but that
408         would require to duplicate a lot of frontend and mathed support code.
409         */
410         QString str = toqstr(s);
411
412 #if 0
413         // HACK: QT3 refuses to show single compose characters
414         //       Still needed with Qt4?
415         if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
416                 str = ' ' + str;
417 #endif
418
419         QFont ff = getFont(f);
420         ff.setWordSpacing(wordspacing);
421         GuiFontMetrics const & fm = getFontMetrics(f);
422
423         int textwidth = 0;
424         if (tw == 0.0)
425                 // Note that we have to take in account space stretching (word spacing)
426                 textwidth = fm.width(s) +
427                         static_cast<int>(fm.countExpanders(s) * wordspacing);
428         else
429                 textwidth = static_cast<int>(tw);
430
431         textDecoration(f, x, y, textwidth);
432
433         if (use_pixmap_cache_) {
434                 QPixmap pm;
435                 QString key = generateStringSignature(str, f, wordspacing);
436
437                 // Warning: Left bearing is in general negative! Only the case
438                 // where left bearing is negative is of interest WRT the
439                 // pixmap width and the text x-position.
440                 // Only the left bearing of the first character is important
441                 // as we always write from left to right, even for
442                 // right-to-left languages.
443                 // FIXME: this is probably broken for RTL now that we draw full strings.
444                 // Morover the first/last element is possibly not the right one since the glyph may have changed.
445                 int const lb = min(fm.lbearing(s[0]), 0);
446                 int const mA = fm.maxAscent();
447                 if (QPixmapCache::find(key, pm)) {
448                         // Draw the cached pixmap.
449                         drawPixmap(x + lb, y - mA, pm);
450                         return;
451                 }
452
453                 // Only the right bearing of the last character is
454                 // important as we always write from left to right,
455                 // even for right-to-left languages.
456                 int const rb = fm.rbearing(s[s.size()-1]);
457                 int const w = textwidth + rb - lb;
458                 int const mD = fm.maxDescent();
459                 int const h = mA + mD;
460                 if (w > 0 && h > 0) {
461                         pm = QPixmap(static_cast<int>(pixelRatio() * w),
462                                                  static_cast<int>(pixelRatio() * h));
463 #if QT_VERSION >= 0x050000
464                         pm.setDevicePixelRatio(pixelRatio());
465 #endif
466                         pm.fill(Qt::transparent);
467                         GuiPainter p(&pm, pixelRatio());
468                         p.do_drawText(-lb, mA, str, dir, f, ff);
469                         QPixmapCache::insert(key, pm);
470                         //LYXERR(Debug::PAINTING, "h=" << h << "  mA=" << mA << "  mD=" << mD
471                         //      << "  w=" << w << "  lb=" << lb << "  tw=" << textwidth
472                         //      << "  rb=" << rb);
473
474                         // Draw the new cached pixmap.
475                         drawPixmap(x + lb, y - mA, pm);
476                         //rectangle(x-lb, y-mA, w, h, Color_green);
477                 }
478                 return;
479         }
480
481         // don't use the pixmap cache
482         setQPainterPen(computeColor(f.realColor()));
483         if (dir != Auto) {
484                 shared_ptr<QTextLayout const> ptl =
485                         fm.getTextLayout(s, dir == RtL, wordspacing);
486                 ptl->draw(this, QPointF(x, y - fm.maxAscent()));
487         }
488         else {
489                 if (font() != ff)
490                         setFont(ff);
491                 drawText(x, y, str);
492         }
493         //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
494         //      << " at " << x << "," << y);
495 }
496
497
498 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
499                       double const wordspacing, double const tw)
500 {
501         text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
502              wordspacing, tw);
503 }
504
505
506 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
507                       Color other, size_type const from, size_type const to,
508                       double const wordspacing, double const tw)
509 {
510         GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
511         FontInfo fi = f.fontInfo();
512         Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
513
514         // dimensions
515         int const ascent = fm.maxAscent();
516         int const height = fm.maxAscent() + fm.maxDescent();
517         int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
518         int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
519         if (xmin > xmax)
520                 swap(xmin, xmax);
521
522         // First the part in other color
523         Color const orig = fi.realColor();
524         fi.setPaintColor(other);
525         QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
526         setClipRegion(clip);
527         text(x, y, str, fi, dir, wordspacing, tw);
528
529         // Then the part in normal color
530         // Note that in Qt5, it is not possible to use Qt::UniteClip,
531         // therefore QRegion is used.
532         fi.setPaintColor(orig);
533         QRegion region(viewport());
534         setClipRegion(region - clip);
535         text(x, y, str, fi, dir, wordspacing, tw);
536         setClipping(false);
537 }
538
539
540 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
541 {
542         if (f.underbar() == FONT_ON)
543                 underline(f, x, y, width);
544         if (f.strikeout() == FONT_ON)
545                 strikeoutLine(f, x, y, width);
546         if (f.xout() == FONT_ON)
547                 crossoutLines(f, x, y, width);
548         if (f.uuline() == FONT_ON)
549                 doubleUnderline(f, x, y, width);
550         if (f.uwave() == FONT_ON)
551                 // f.color() doesn't work on some circumstances
552                 wavyHorizontalLine(x, y, width,  f.realColor().baseColor);
553 }
554
555
556 static int max(int a, int b) { return a > b ? a : b; }
557
558
559 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
560 {
561         if (mouseHover)
562                 fillRectangle(x, y, w, h, Color_buttonhoverbg);
563         else
564                 fillRectangle(x, y, w, h, Color_buttonbg);
565         buttonFrame(x, y, w, h);
566 }
567
568
569 void GuiPainter::buttonFrame(int x, int y, int w, int h)
570 {
571         line(x, y, x, y + h - 1, Color_buttonframe);
572         line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
573         line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
574         line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
575 }
576
577
578 void GuiPainter::rectText(int x, int y, docstring const & str,
579         FontInfo const & font, Color back, Color frame)
580 {
581         int width;
582         int ascent;
583         int descent;
584
585         FontMetrics const & fm = theFontMetrics(font);
586         fm.rectText(str, width, ascent, descent);
587
588         if (back != Color_none)
589                 fillRectangle(x + 1, y - ascent + 1, width - 1,
590                               ascent + descent - 1, back);
591
592         if (frame != Color_none)
593                 rectangle(x, y - ascent, width, ascent + descent, frame);
594
595         text(x + 3, y, str, font);
596 }
597
598
599 void GuiPainter::buttonText(int x, int y, docstring const & str,
600         FontInfo const & font, bool mouseHover)
601 {
602         int width;
603         int ascent;
604         int descent;
605
606         FontMetrics const & fm = theFontMetrics(font);
607         fm.buttonText(str, width, ascent, descent);
608
609         static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
610
611         button(x + d, y - ascent, width - Inset::TEXT_TO_INSET_OFFSET, descent + ascent, mouseHover);
612         text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
613 }
614
615
616 int GuiPainter::preeditText(int x, int y, char_type c,
617         FontInfo const & font, preedit_style style)
618 {
619         FontInfo temp_font = font;
620         FontMetrics const & fm = theFontMetrics(font);
621         int ascent = fm.maxAscent();
622         int descent = fm.maxDescent();
623         int height = ascent + descent;
624         int width = fm.width(c);
625
626         switch (style) {
627                 case preedit_default:
628                         // default unselecting mode.
629                         fillRectangle(x, y - height + 1, width, height, Color_background);
630                         dashedUnderline(font, x, y - descent + 1, width);
631                         break;
632                 case preedit_selecting:
633                         // We are in selecting mode: white text on black background.
634                         fillRectangle(x, y - height + 1, width, height, Color_black);
635                         temp_font.setColor(Color_white);
636                         break;
637                 case preedit_cursor:
638                         // The character comes with a cursor.
639                         fillRectangle(x, y - height + 1, width, height, Color_background);
640                         underline(font, x, y - descent + 1, width);
641                         break;
642         }
643         text(x, y - descent + 1, c, temp_font);
644
645         return width;
646 }
647
648
649 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
650                            line_style ls)
651 {
652         FontMetrics const & fm = theFontMetrics(f);
653         int const pos = fm.underlinePos();
654
655         line(x, y + pos, x + width, y + pos,
656              f.realColor(), ls, fm.lineWidth());
657 }
658
659
660 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
661 {
662         FontMetrics const & fm = theFontMetrics(f);
663         int const pos = fm.strikeoutPos();
664
665         line(x, y - pos, x + width, y - pos,
666              f.realColor(), line_solid, fm.lineWidth());
667 }
668
669
670 void GuiPainter::crossoutLines(FontInfo const & f, int x, int y, int width)
671 {
672         FontMetrics const & fm = theFontMetrics(f);
673         int const bottom = fm.underlinePos();
674         int const middle = fm.strikeoutPos();
675
676         // we draw several lines to fill the whole selection with strokes
677         // use 5 as diagonal width since this is close to the PDf output
678         // with normal font zoom levels
679         for(int x_current = x; x_current < x + width - 5; x_current = x_current + 5) {
680                 line(x_current, y + bottom, x_current + 5, y - 2 * middle - 2 * bottom,
681                         f.realColor(), line_solid, fm.lineWidth());
682         }
683 }
684
685
686 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
687 {
688         FontMetrics const & fm = theFontMetrics(f);
689         int const pos1 = fm.underlinePos() + fm.lineWidth();
690         int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
691
692         line(x, y + pos1, x + width, y + pos1,
693                  f.realColor(), line_solid, fm.lineWidth());
694         line(x, y + pos2, x + width, y + pos2,
695                  f.realColor(), line_solid, fm.lineWidth());
696 }
697
698
699 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
700 {
701         FontMetrics const & fm = theFontMetrics(f);
702
703         int const below = max(fm.maxDescent() / 2, 2);
704         int height = max((fm.maxDescent() / 4) - 1, 1);
705
706         if (height >= 2)
707                 height += below;
708
709         for (int n = 0; n != height; ++n)
710                 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
711 }
712
713
714 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
715 {
716         setQPainterPen(computeColor(col));
717         int const step = 2;
718         int const xend = x + width;
719         int height = 1;
720         //FIXME: I am not sure if Antialiasing gives the best effect.
721         //setRenderHint(Antialiasing, true);
722         while (x < xend) {
723                 height = - height;
724                 drawLine(x, y - height, x + step, y + height);
725                 x += step;
726                 drawLine(x, y + height, x + step/2, y + height);
727                 x += step/2;
728         }
729         //setRenderHint(Antialiasing, false);
730 }
731
732 } // namespace frontend
733 } // namespace lyx