]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiPainter.cpp
* Doxy: polish html output.
[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
437 void GuiPainter::rectText(int x, int y, docstring const & str,
438         FontInfo const & font, ColorCode back, ColorCode frame)
439 {
440         int width;
441         int ascent;
442         int descent;
443
444         FontMetrics const & fm = theFontMetrics(font);
445         fm.rectText(str, width, ascent, descent);
446
447         if (back != Color_none)
448                 fillRectangle(x + 1, y - ascent + 1, width - 1,
449                               ascent + descent - 1, back);
450
451         if (frame != Color_none)
452                 rectangle(x, y - ascent, width, ascent + descent, frame);
453
454         text(x + 3, y, str, font);
455 }
456
457
458 void GuiPainter::buttonText(int x, int y, docstring const & str,
459         FontInfo const & font, bool mouseHover)
460 {
461         int width;
462         int ascent;
463         int descent;
464
465         FontMetrics const & fm = theFontMetrics(font);
466         fm.buttonText(str, width, ascent, descent);
467
468         button(x + 1, y - ascent, width - 2, descent + ascent, mouseHover);
469         text(x + 4, y - 1, str, font);
470 }
471
472
473 int GuiPainter::preeditText(int x, int y, char_type c,
474         FontInfo const & font, preedit_style style)
475 {
476         FontInfo temp_font = font;
477         FontMetrics const & fm = theFontMetrics(font);
478         int ascent = fm.maxAscent();
479         int descent = fm.maxDescent();
480         int height = ascent + descent;
481         int width = fm.width(c);
482
483         switch (style) {
484                 case preedit_default:
485                         // default unselecting mode.
486                         fillRectangle(x, y - height + 1, width, height, Color_background);
487                         dashedUnderline(font, x, y - descent + 1, width);
488                         break;
489                 case preedit_selecting:
490                         // We are in selecting mode: white text on black background.
491                         fillRectangle(x, y - height + 1, width, height, Color_black);
492                         temp_font.setColor(Color_white);
493                         break;
494                 case preedit_cursor:
495                         // The character comes with a cursor.
496                         fillRectangle(x, y - height + 1, width, height, Color_background);
497                         underline(font, x, y - descent + 1, width);
498                         break;
499         }
500         text(x, y - descent + 1, c, temp_font);
501
502         return width;
503 }
504
505
506 void GuiPainter::underline(FontInfo const & f, int x, int y, int width)
507 {
508         FontMetrics const & fm = theFontMetrics(f);
509
510         int const below = max(fm.maxDescent() / 2, 2);
511         int const height = max((fm.maxDescent() / 4) - 1, 1);
512
513         if (height < 2)
514                 line(x, y + below, x + width, y + below, f.color());
515         else
516                 fillRectangle(x, y + below, width, below + height, f.color());
517 }
518
519
520 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
521 {
522         FontMetrics const & fm = theFontMetrics(f);
523
524         int const below = max(fm.maxDescent() / 2, 2);
525         int height = max((fm.maxDescent() / 4) - 1, 1);
526
527         if (height >= 2)
528                 height += below;
529
530         for (int n = 0; n != height; ++n)
531                 line(x, y + below + n, x + width, y + below + n, f.color(), line_onoffdash);
532 }
533
534 } // namespace frontend
535 } // namespace lyx