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