]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiPainter.cpp
2499b0eb4ceafc92f44a1691a4b8185b15ca82e3
[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 "GuiFontLoader.h"
18 #include "GuiFontMetrics.h"
19 #include "GuiImage.h"
20 #include "qt_helpers.h"
21
22 #include "Language.h"
23 #include "LyXRC.h"
24
25 #include "support/debug.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 = getFont(f);
276         QFont const & qsmallfont = getFont(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         QFont const & ff = getFont(f); 
323         GuiFontMetrics const & fm = getFontMetrics(f); 
324
325         int textwidth;
326
327         if (f.realShape() == SMALLCAPS_SHAPE) {
328                 textwidth = smallCapsText(x, y, str, f);
329                 if (f.underbar() == FONT_ON)
330                         underline(f, x, y, textwidth);
331                 return textwidth;
332         }
333
334         // Here we use the font width cache instead of
335         //   textwidth = fontMetrics().width(str);
336         // because the above is awfully expensive on MacOSX
337         textwidth = fm.width(s);
338         if (f.underbar() == FONT_ON)
339                 underline(f, x, y, textwidth);
340
341         if (!isDrawingEnabled())
342                 return textwidth;
343
344         // Qt4 does not display a glyph whose codepoint is the
345         // same as that of a soft-hyphen (0x00ad), unless it
346         // occurs at a line-break. As a kludge, we force Qt to
347         // render this glyph using a one-column line.
348         if (s.size() == 1 && str[0].unicode() == 0x00ad) {
349                 setQPainterPen(computeColor(f.realColor()));
350                 QTextLayout adsymbol(str);
351                 adsymbol.setFont(ff);
352                 adsymbol.beginLayout();
353                 QTextLine line = adsymbol.createLine();
354                 line.setNumColumns(1);
355                 line.setPosition(QPointF(0, -line.ascent()));
356                 adsymbol.endLayout();
357                 line.draw(this, QPointF(x, y));
358                 return textwidth;
359         }
360
361         if (!use_pixmap_cache_) {
362                 // don't use the pixmap cache,
363                 // draw directly onto the painting device
364                 setQPainterPen(computeColor(f.realColor()));
365                 if (font() != ff)
366                         setFont(ff);
367                 // We need to draw the text as LTR as we use our own bidi code.
368                 setLayoutDirection(Qt::LeftToRight);
369                 drawText(x, y, str);
370                 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
371                 //      << " at " << x << "," << y);
372                 return textwidth;
373         }
374
375         QPixmap pm;
376         QString key = generateStringSignature(str, f);
377         // Warning: Left bearing is in general negative! Only the case
378         // where left bearing is negative is of interest WRT the the 
379         // pixmap width and the text x-position.
380         // Only the left bearing of the first character is important
381         // as we always write from left to right, even for
382         // right-to-left languages.
383         int const lb = min(fm.lbearing(s[0]), 0);
384         int const mA = fm.maxAscent();
385         if (!QPixmapCache::find(key, pm)) {
386                 // Only the right bearing of the last character is
387                 // important as we always write from left to right,
388                 // even for right-to-left languages.
389                 int const rb = fm.rbearing(s[s.size()-1]);
390                 int const w = textwidth + rb - lb;
391                 int const mD = fm.maxDescent();
392                 int const h = mA + mD;
393                 pm = QPixmap(w, h);
394                 pm.fill(Qt::transparent);
395                 GuiPainter p(&pm);
396                 p.setQPainterPen(computeColor(f.realColor()));
397                 if (p.font() != ff)
398                         p.setFont(ff);
399                 // We need to draw the text as LTR as we use our own bidi code.
400                 p.setLayoutDirection(Qt::LeftToRight);
401                 p.drawText(-lb, mA, str);
402                 QPixmapCache::insert(key, pm);
403                 //LYXERR(Debug::PAINTING, "h=" << h << "  mA=" << mA << "  mD=" << mD
404                 //      << "  w=" << w << "  lb=" << lb << "  tw=" << textwidth 
405                 //      << "  rb=" << rb);
406         }
407         // Draw the cached pixmap.
408         drawPixmap(x + lb, y - mA, pm);
409
410         return textwidth;
411 }
412
413
414 static int max(int a, int b) { return a > b ? a : b; }
415
416
417 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
418 {
419         if (mouseHover)
420                 fillRectangle(x, y, w, h, Color_buttonhoverbg);
421         else
422                 fillRectangle(x, y, w, h, Color_buttonbg);
423         buttonFrame(x, y, w, h);
424 }
425
426
427 void GuiPainter::buttonFrame(int x, int y, int w, int h)
428 {
429         line(x, y, x, y + h - 1, Color_buttonframe);
430         line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
431         line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
432         line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
433 }
434
435
436 void GuiPainter::rectText(int x, int y, docstring const & str,
437         FontInfo const & font, ColorCode back, ColorCode frame)
438 {
439         int width;
440         int ascent;
441         int descent;
442
443         FontMetrics const & fm = theFontMetrics(font);
444         fm.rectText(str, width, ascent, descent);
445
446         if (back != Color_none)
447                 fillRectangle(x + 1, y - ascent + 1, width - 1,
448                               ascent + descent - 1, back);
449
450         if (frame != Color_none)
451                 rectangle(x, y - ascent, width, ascent + descent, frame);
452
453         text(x + 3, y, str, font);
454 }
455
456
457 void GuiPainter::buttonText(int x, int y, docstring const & str,
458         FontInfo const & font, bool mouseHover)
459 {
460         int width;
461         int ascent;
462         int descent;
463
464         FontMetrics const & fm = theFontMetrics(font);
465         fm.buttonText(str, width, ascent, descent);
466
467         static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
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