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