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