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