]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiPainter.cpp
tex2lyx: support for \item with opt arg in itemize environment
[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)
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         // Qt4 does not display a glyph whose codepoint is the
337         // same as that of a soft-hyphen (0x00ad), unless it
338         // occurs at a line-break. As a kludge, we force Qt to
339         // render this glyph using a one-column line.
340         // This is needed for some math glyphs.
341         // Should the soft hyphen char be displayed at all?
342         // I don't think so (i.e., Qt is correct as far as
343         // texted is concerned). /spitz
344         if (s.size() == 1 && str[0].unicode() == 0x00ad) {
345                 setQPainterPen(computeColor(f.realColor()));
346                 QTextLayout adsymbol(str);
347                 adsymbol.setFont(ff);
348                 adsymbol.beginLayout();
349                 QTextLine line = adsymbol.createLine();
350                 line.setNumColumns(1);
351                 line.setPosition(QPointF(0, -line.ascent()));
352                 adsymbol.endLayout();
353                 line.draw(this, QPointF(x, y));
354                 return textwidth;
355         }
356
357         if (use_pixmap_cache_) {
358                 QPixmap pm;
359                 QString key = generateStringSignature(str, f);
360
361                 // Warning: Left bearing is in general negative! Only the case
362                 // where left bearing is negative is of interest WRT the
363                 // pixmap width and the text x-position.
364                 // Only the left bearing of the first character is important
365                 // as we always write from left to right, even for
366                 // right-to-left languages.
367                 int const lb = min(fm.lbearing(s[0]), 0);
368                 int const mA = fm.maxAscent();
369                 if (QPixmapCache::find(key, pm)) {
370                         // Draw the cached pixmap.
371                         drawPixmap(x + lb, y - mA, pm);
372                         return textwidth;
373                 }
374
375                 // Only the right bearing of the last character is
376                 // important as we always write from left to right,
377                 // even for right-to-left languages.
378                 int const rb = fm.rbearing(s[s.size()-1]);
379                 int const w = textwidth + rb - lb;
380                 int const mD = fm.maxDescent();
381                 int const h = mA + mD;
382                 if (w > 0 && h > 0) {
383                         pm = QPixmap(pixelRatio() * w , pixelRatio() * h);
384 #if QT_VERSION >= 0x050000
385                         pm.setDevicePixelRatio(pixelRatio());
386 #endif
387                         pm.fill(Qt::transparent);
388                         GuiPainter p(&pm, pixelRatio());
389                         p.setQPainterPen(computeColor(f.realColor()));
390                         if (p.font() != ff)
391                                 p.setFont(ff);
392                         // We need to draw the text as LTR as we use our own bidi code.
393                         p.setLayoutDirection(Qt::LeftToRight);
394                         p.drawText(-lb, mA, str);
395                         QPixmapCache::insert(key, pm);
396                         //LYXERR(Debug::PAINTING, "h=" << h << "  mA=" << mA << "  mD=" << mD
397                         //      << "  w=" << w << "  lb=" << lb << "  tw=" << textwidth
398                         //      << "  rb=" << rb);
399
400                         // Draw the new cached pixmap.
401                         drawPixmap(x + lb, y - mA, pm);
402
403                         return textwidth;
404                 }
405         }
406
407         // don't use the pixmap cache,
408         // draw directly onto the painting device
409         setQPainterPen(computeColor(f.realColor()));
410         if (font() != ff)
411                 setFont(ff);
412
413          /* In LyX, the character direction is forced by the language.
414           * Therefore, we have to signal that fact to Qt.
415           */
416 #ifdef USE_RTL_OVERRIDE
417         /* Use unicode override characters to enforce drawing direction
418          * Source: http://www.iamcal.com/understanding-bidirectional-text/
419          */
420         if (rtl)
421                 // Right-to-left override: forces to draw text right-to-left
422                 str = QChar(0x202E) + str;
423         else
424                 // Left-to-right override: forces to draw text left-to-right
425                 str =  QChar(0x202D) + str;
426         drawText(x, y, str);
427 #else
428         /* This is a cleanr solution, but it has two drawbacks
429          * - it seems that it does not work under Mac OS X
430          * - it is not really documented
431          */
432         //This is much stronger than setLayoutDirection.
433         int flag = rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight;
434         drawText(x + (rtl ? textwidth : 0), y - fm.maxAscent(), 0, 0,
435                  flag | Qt::TextDontClip,
436                  str);
437 #endif
438         //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
439         //      << " at " << x << "," << y);
440         return textwidth;
441 }
442
443
444 int GuiPainter::text(int x, int y, docstring const & str, Font const & f)
445 {
446         return text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft());
447 }
448
449
450 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
451                      Color other, size_type from, size_type to)
452 {
453         GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
454         FontInfo fi = f.fontInfo();
455         bool const rtl = f.isVisibleRightToLeft();
456
457         // dimensions
458         int const ascent = fm.maxAscent();
459         int const height = fm.maxAscent() + fm.maxDescent();
460         int xmin = fm.pos2x(str, from, rtl);
461         int xmax = fm.pos2x(str, to, rtl);
462         if (xmin > xmax)
463                 swap(xmin, xmax);
464
465         // First the part in other color
466         Color const orig = fi.realColor();
467         fi.setPaintColor(other);
468         setClipRect(QRect(x + xmin, y - ascent, xmax - xmin, height));
469         int const textwidth = text(x, y, str, fi, rtl);
470
471         // Then the part in normal color
472         // Note that in Qt5, it is not possible to use Qt::UniteClip
473         fi.setPaintColor(orig);
474         setClipRect(QRect(x, y - ascent, xmin, height));
475         text(x, y, str, fi, rtl);
476         setClipRect(QRect(x + xmax, y - ascent, textwidth - xmax, height));
477         text(x, y, str, fi, rtl);
478         setClipping(false);
479
480         return textwidth;
481 }
482
483
484 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
485 {
486         if (f.underbar() == FONT_ON)
487                 underline(f, x, y, width);
488         if (f.strikeout() == FONT_ON)
489                 strikeoutLine(f, x, y, width);
490         if (f.uuline() == FONT_ON)
491                 doubleUnderline(f, x, y, width);
492         if (f.uwave() == FONT_ON)
493                 // f.color() doesn't work on some circumstances
494                 wavyHorizontalLine(x, y, width,  f.realColor().baseColor);
495 }
496
497
498 static int max(int a, int b) { return a > b ? a : b; }
499
500
501 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
502 {
503         if (mouseHover)
504                 fillRectangle(x, y, w, h, Color_buttonhoverbg);
505         else
506                 fillRectangle(x, y, w, h, Color_buttonbg);
507         buttonFrame(x, y, w, h);
508 }
509
510
511 void GuiPainter::buttonFrame(int x, int y, int w, int h)
512 {
513         line(x, y, x, y + h - 1, Color_buttonframe);
514         line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
515         line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
516         line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
517 }
518
519
520 void GuiPainter::rectText(int x, int y, docstring const & str,
521         FontInfo const & font, Color back, Color frame)
522 {
523         int width;
524         int ascent;
525         int descent;
526
527         FontMetrics const & fm = theFontMetrics(font);
528         fm.rectText(str, width, ascent, descent);
529
530         if (back != Color_none)
531                 fillRectangle(x + 1, y - ascent + 1, width - 1,
532                               ascent + descent - 1, back);
533
534         if (frame != Color_none)
535                 rectangle(x, y - ascent, width, ascent + descent, frame);
536
537         text(x + 3, y, str, font);
538 }
539
540
541 void GuiPainter::buttonText(int x, int y, docstring const & str,
542         FontInfo const & font, bool mouseHover)
543 {
544         int width;
545         int ascent;
546         int descent;
547
548         FontMetrics const & fm = theFontMetrics(font);
549         fm.buttonText(str, width, ascent, descent);
550
551         static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
552
553         button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
554         text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
555 }
556
557
558 int GuiPainter::preeditText(int x, int y, char_type c,
559         FontInfo const & font, preedit_style style)
560 {
561         FontInfo temp_font = font;
562         FontMetrics const & fm = theFontMetrics(font);
563         int ascent = fm.maxAscent();
564         int descent = fm.maxDescent();
565         int height = ascent + descent;
566         int width = fm.width(c);
567
568         switch (style) {
569                 case preedit_default:
570                         // default unselecting mode.
571                         fillRectangle(x, y - height + 1, width, height, Color_background);
572                         dashedUnderline(font, x, y - descent + 1, width);
573                         break;
574                 case preedit_selecting:
575                         // We are in selecting mode: white text on black background.
576                         fillRectangle(x, y - height + 1, width, height, Color_black);
577                         temp_font.setColor(Color_white);
578                         break;
579                 case preedit_cursor:
580                         // The character comes with a cursor.
581                         fillRectangle(x, y - height + 1, width, height, Color_background);
582                         underline(font, x, y - descent + 1, width);
583                         break;
584         }
585         text(x, y - descent + 1, c, temp_font);
586
587         return width;
588 }
589
590
591 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
592 {
593         FontMetrics const & fm = theFontMetrics(f);
594
595         int const below = max(fm.maxDescent() / 2, 2);
596
597         line(x, y + below, x + width, y + below, f.realColor());
598         line(x, y + below - 2, x + width, y + below - 2, f.realColor());
599 }
600
601
602 void GuiPainter::underline(FontInfo const & f, int x, int y, int width)
603 {
604         FontMetrics const & fm = theFontMetrics(f);
605
606         int const below = max(fm.maxDescent() / 2, 2);
607         int const height = max((fm.maxDescent() / 4) - 1, 1);
608
609         if (height < 2)
610                 line(x, y + below, x + width, y + below, f.realColor());
611         else
612                 fillRectangle(x, y + below, width, below + height, f.realColor());
613 }
614
615
616 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
617 {
618         FontMetrics const & fm = theFontMetrics(f);
619
620         int const middle = max((fm.maxHeight() / 4), 1);
621         int const height =  middle/3;
622
623         if (height < 2)
624                 line(x, y - middle, x + width, y - middle, f.realColor());
625         else
626                 fillRectangle(x, y - middle, width, height, f.realColor());
627 }
628
629
630 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
631 {
632         FontMetrics const & fm = theFontMetrics(f);
633
634         int const below = max(fm.maxDescent() / 2, 2);
635         int height = max((fm.maxDescent() / 4) - 1, 1);
636
637         if (height >= 2)
638                 height += below;
639
640         for (int n = 0; n != height; ++n)
641                 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
642 }
643
644
645 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
646 {
647         setQPainterPen(computeColor(col));
648         int const step = 2;
649         int const xend = x + width;
650         int height = 1;
651         //FIXME: I am not sure if Antialiasing gives the best effect.
652         //setRenderHint(Antialiasing, true);
653         while (x < xend) {
654                 height = - height;
655                 drawLine(x, y - height, x + step, y + height);
656                 x += step;
657                 drawLine(x, y + height, x + step/2, y + height);
658                 x += step/2;
659         }
660         //setRenderHint(Antialiasing, false);
661 }
662
663 } // namespace frontend
664 } // namespace lyx