]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiPainter.cpp
Complete the removal of the embedding stuff. Maybe. It's hard to be sure we got every...
[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/assert.h"
26 #include "support/debug.h"
27
28 #include <QPixmapCache>
29 #include <QTextLayout>
30
31 // Set USE_PIXMAP_CACHE to 1 for enabling the use of a Pixmap cache when
32 // drawing text. This is especially useful for older PPC/Mac systems.
33 #if defined(Q_WS_X11)
34 #define USE_PIXMAP_CACHE 0
35 #else
36 #define USE_PIXMAP_CACHE 1
37 #endif
38
39 using namespace std;
40
41 namespace lyx {
42 namespace frontend {
43
44 GuiPainter::GuiPainter(QPaintDevice * device)
45         : QPainter(device), Painter(),
46           use_pixmap_cache_(lyxrc.use_pixmap_cache && USE_PIXMAP_CACHE)
47 {
48         // new QPainter has default QPen:
49         current_color_ = guiApp->colorCache().get(Color_black);
50         current_ls_ = line_solid;
51         current_lw_ = line_thin;
52 }
53
54
55 GuiPainter::~GuiPainter()
56 {
57         QPainter::end();
58         //lyxerr << "GuiPainter::end()" << endl;
59 }
60
61
62 void GuiPainter::setQPainterPen(QColor const & col,
63         Painter::line_style ls, Painter::line_width lw)
64 {
65         if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
66                 return;
67
68         current_color_ = col;
69         current_ls_ = ls;
70         current_lw_ = lw;
71
72         QPen pen = QPainter::pen();
73         pen.setColor(col);
74
75         switch (ls) {
76                 case line_solid: pen.setStyle(Qt::SolidLine); break;
77                 case line_onoffdash: pen.setStyle(Qt::DotLine); break;
78         }
79
80         switch (lw) {
81                 case line_thin: pen.setWidth(0); break;
82                 case line_thick: pen.setWidth(3); break;
83         }
84
85         setPen(pen);
86 }
87
88
89 QString GuiPainter::generateStringSignature(QString const & str, FontInfo const & f)
90 {
91         QString sig = str;
92         sig.append(QChar(static_cast<short>(f.family())));
93         sig.append(QChar(static_cast<short>(f.series())));
94         sig.append(QChar(static_cast<short>(f.realShape())));
95         sig.append(QChar(static_cast<short>(f.size())));
96         sig.append(QChar(static_cast<short>(f.color())));
97         if (!monochrome_min_.empty()) {
98                 QColor const & min = monochrome_min_.top();
99                 QColor const & max = monochrome_max_.top();
100                 sig.append(QChar(static_cast<short>(min.red())));
101                 sig.append(QChar(static_cast<short>(min.green())));
102                 sig.append(QChar(static_cast<short>(min.blue())));
103                 sig.append(QChar(static_cast<short>(max.red())));
104                 sig.append(QChar(static_cast<short>(max.green())));
105                 sig.append(QChar(static_cast<short>(max.blue())));
106         }
107         return sig;
108 }
109
110
111 QColor GuiPainter::computeColor(ColorCode col)
112 {
113         return filterColor(guiApp->colorCache().get(col));
114 }
115
116
117 QColor GuiPainter::filterColor(QColor const & col)
118 {
119         if (monochrome_min_.empty())
120                 return col;
121
122         // map into [min,max] interval
123         QColor const & min = monochrome_min_.top();
124         QColor const & max = monochrome_max_.top();
125                         
126         qreal v = col.valueF();
127         v *= v; // make it a bit steeper (i.e. darker)
128                 
129         qreal minr, ming, minb;
130         qreal maxr, maxg, maxb;
131         min.getRgbF(&minr, &ming, &minb);
132         max.getRgbF(&maxr, &maxg, &maxb);
133                         
134         QColor c;
135         c.setRgbF(
136                 v * (minr - maxr) + maxr,
137                 v * (ming - maxg) + maxg,
138                 v * (minb - maxb) + maxb);
139         return c;
140 }
141
142
143 void GuiPainter::enterMonochromeMode(ColorCode const & min, ColorCode const & max)
144 {
145         QColor qmin = filterColor(guiApp->colorCache().get(min));
146         QColor qmax = filterColor(guiApp->colorCache().get(max));
147         monochrome_min_.push(qmin);
148         monochrome_max_.push(qmax);
149 }
150
151
152 void GuiPainter::leaveMonochromeMode()
153 {
154         LASSERT(!monochrome_min_.empty(), /**/);
155         monochrome_min_.pop();
156         monochrome_max_.pop();
157 }
158
159
160 void GuiPainter::point(int x, int y, ColorCode col)
161 {
162         if (!isDrawingEnabled())
163                 return;
164
165         setQPainterPen(computeColor(col));
166         drawPoint(x, y);
167 }
168
169
170 void GuiPainter::line(int x1, int y1, int x2, int y2,
171         ColorCode col,
172         line_style ls,
173         line_width lw)
174 {
175         if (!isDrawingEnabled())
176                 return;
177
178         setQPainterPen(computeColor(col), ls, lw);
179         bool const do_antialiasing = renderHints() & TextAntialiasing
180                 && x1 != x2 && y1 != y2;
181         setRenderHint(Antialiasing, do_antialiasing);
182         drawLine(x1, y1, x2, y2);
183         setRenderHint(Antialiasing, false);
184 }
185
186
187 void GuiPainter::lines(int const * xp, int const * yp, int np,
188         ColorCode col,
189         line_style ls,
190         line_width lw)
191 {
192         if (!isDrawingEnabled())
193                 return;
194
195         // double the size if needed
196         static QVector<QPoint> points(32);
197         if (np > points.size())
198                 points.resize(2 * np);
199
200         bool antialias = false;
201         for (int i = 0; i < np; ++i) {
202                 points[i].setX(xp[i]);
203                 points[i].setY(yp[i]);
204                 if (i != 0)
205                         antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
206         }
207         setQPainterPen(computeColor(col), ls, lw);
208         bool const text_is_antialiased = renderHints() & TextAntialiasing;
209         setRenderHint(Antialiasing, antialias && text_is_antialiased);
210         drawPolyline(points.data(), np);
211         setRenderHint(Antialiasing, false);
212 }
213
214
215 void GuiPainter::rectangle(int x, int y, int w, int h,
216         ColorCode col,
217         line_style ls,
218         line_width lw)
219 {
220         if (!isDrawingEnabled())
221                 return;
222
223         setQPainterPen(computeColor(col), ls, lw);
224         drawRect(x, y, w, h);
225 }
226
227
228 void GuiPainter::fillRectangle(int x, int y, int w, int h, ColorCode col)
229 {
230         fillRect(x, y, w, h, guiApp->colorCache().get(col));
231 }
232
233
234 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
235         int a1, int a2, ColorCode col)
236 {
237         if (!isDrawingEnabled())
238                 return;
239
240         // LyX usings 1/64ths degree, Qt usings 1/16th
241         setQPainterPen(computeColor(col));
242         bool const do_antialiasing = renderHints() & TextAntialiasing;
243         setRenderHint(Antialiasing, do_antialiasing);
244         drawArc(x, y, w, h, a1 / 4, a2 / 4);
245         setRenderHint(Antialiasing, false);
246 }
247
248
249 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
250 {
251         graphics::GuiImage const & qlimage =
252                 static_cast<graphics::GuiImage const &>(i);
253
254         fillRectangle(x, y, w, h, Color_graphicsbg);
255
256         if (!isDrawingEnabled())
257                 return;
258
259         drawImage(x, y, qlimage.qimage(), 0, 0, w, h);
260 }
261
262
263 int GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
264 {
265         docstring s(1, c);
266         return text(x, y, s, f);
267 }
268
269
270 int GuiPainter::smallCapsText(int x, int y,
271         QString const & s, FontInfo const & f)
272 {
273         FontInfo smallfont(f);
274         smallfont.decSize().decSize().setShape(UP_SHAPE);
275
276         QFont const & qfont = getFont(f);
277         QFont const & qsmallfont = getFont(smallfont);
278
279         setQPainterPen(computeColor(f.realColor()));
280         int textwidth = 0;
281         size_t const ls = s.length();
282         for (unsigned int i = 0; i < ls; ++i) {
283                 QChar const c = s[i].toUpper();
284                 if (c != s.at(i)) {
285                         setFont(qsmallfont);
286                 } else {
287                         setFont(qfont);
288                 }
289                 if (isDrawingEnabled())
290                         drawText(x + textwidth, y, c);
291                 textwidth += fontMetrics().width(c);
292         }
293         return textwidth;
294 }
295
296
297 int GuiPainter::text(int x, int y, docstring const & s,
298                 FontInfo const & f)
299 {
300         if (s.empty())
301                 return 0;
302
303         /* Caution: The following ucs4 to QString conversions work for symbol fonts
304         only because they are no real conversions but simple casts in reality.
305         When we want to draw a symbol or calculate the metrics we pass the position
306         of the symbol in the font (as given in lib/symbols) as a char_type to the
307         frontend. This is just wrong, because the symbol is no UCS4 character at
308         all. You can think of this number as the code point of the symbol in a
309         custom symbol encoding. It works because this char_type is lateron again
310         interpreted as a position in the font again.
311         The correct solution would be to have extra functions for symbols, but that
312         would require to duplicate a lot of frontend and mathed support code.
313         */
314         QString str = toqstr(s);
315
316 #if 0
317         // HACK: QT3 refuses to show single compose characters
318         //       Still needed with Qt4?
319         if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
320                 str = ' ' + str;
321 #endif
322
323         QFont const & ff = getFont(f); 
324         GuiFontMetrics const & fm = getFontMetrics(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 = fm.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(ff);
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() != ff)
367                         setFont(ff);
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(fm.lbearing(s[0]), 0);
385         int const mA = fm.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 = fm.rbearing(s[s.size()-1]);
391                 int const w = textwidth + rb - lb;
392                 int const mD = fm.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() != ff)
399                         p.setFont(ff);
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         static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
469
470         button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
471         text(x + Inset::TEXT_TO_INSET_OFFSET, y, 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