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