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