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