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