]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiPainter.cpp
Introducing TextClassPtr.h to minimize header dependencies.
[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 "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 (QT_VERSION < 0x040200) || defined(Q_WS_X11)
35 #define USE_PIXMAP_CACHE 0
36 #else
37 #define USE_PIXMAP_CACHE 1
38 #endif
39
40 using std::endl;
41 using std::string;
42
43
44 namespace lyx {
45 namespace frontend {
46
47 GuiPainter::GuiPainter(QPaintDevice * device)
48         : QPainter(device), Painter(),
49           use_pixmap_cache_(lyxrc.use_pixmap_cache && USE_PIXMAP_CACHE)
50 {
51         // new QPainter has default QPen:
52         current_color_ = guiApp->colorCache().get(Color_black);
53         current_ls_ = line_solid;
54         current_lw_ = line_thin;
55 }
56
57
58 GuiPainter::~GuiPainter()
59 {
60         QPainter::end();
61         //lyxerr << "GuiPainter::end()" << endl;
62 }
63
64
65 void GuiPainter::setQPainterPen(QColor const & col,
66         Painter::line_style ls, Painter::line_width lw)
67 {
68         if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
69                 return;
70
71         current_color_ = col;
72         current_ls_ = ls;
73         current_lw_ = lw;
74
75         QPen pen = QPainter::pen();
76         pen.setColor(col);
77
78         switch (ls) {
79                 case line_solid: pen.setStyle(Qt::SolidLine); break;
80                 case line_onoffdash: pen.setStyle(Qt::DotLine); break;
81         }
82
83         switch (lw) {
84                 case line_thin: pen.setWidth(0); break;
85                 case line_thick: pen.setWidth(3); break;
86         }
87
88         setPen(pen);
89 }
90
91
92 QString GuiPainter::generateStringSignature(QString const & str, FontInfo const & f)
93 {
94         QString sig = str;
95         sig.append(QChar(static_cast<short>(f.family())));
96         sig.append(QChar(static_cast<short>(f.series())));
97         sig.append(QChar(static_cast<short>(f.realShape())));
98         sig.append(QChar(static_cast<short>(f.size())));
99         sig.append(QChar(static_cast<short>(f.color())));
100         if (!monochrome_min_.empty()) {
101                 QColor const & min = monochrome_min_.top();
102                 QColor const & max = monochrome_max_.top();
103                 sig.append(QChar(static_cast<short>(min.red())));
104                 sig.append(QChar(static_cast<short>(min.green())));
105                 sig.append(QChar(static_cast<short>(min.blue())));
106                 sig.append(QChar(static_cast<short>(max.red())));
107                 sig.append(QChar(static_cast<short>(max.green())));
108                 sig.append(QChar(static_cast<short>(max.blue())));
109         }
110         return sig;
111 }
112
113
114 QColor GuiPainter::computeColor(ColorCode col)
115 {
116         return filterColor(guiApp->colorCache().get(col));
117 }
118
119
120 QColor GuiPainter::filterColor(QColor const & col)
121 {
122         if (monochrome_min_.empty())
123                 return col;
124
125         // map into [min,max] interval
126         QColor const & min = monochrome_min_.top();
127         QColor const & max = monochrome_max_.top();
128                         
129         qreal v = col.valueF();
130         v = v * v; // make it a bit steeper (i.e. darker)
131                 
132         qreal minr, ming, minb;
133         qreal maxr, maxg, maxb;
134         min.getRgbF(&minr, &ming, &minb);
135         max.getRgbF(&maxr, &maxg, &maxb);
136                         
137         QColor c;
138         c.setRgbF(v*minr+(1-v)*maxr, v*ming+(1-v)*maxg, v*minb+(1-v)*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         BOOST_ASSERT(!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 = guiApp->guiFontLoader().get(f);
277         QFont const & qsmallfont = guiApp->guiFontLoader().get(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         GuiFontInfo & fi = guiApp->guiFontLoader().fontinfo(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 = fi.metrics->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(fi.font);
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() != fi.font)
366                         setFont(fi.font);
367                 // We need to draw the text as LTR as we use our own bidi code.
368                 setLayoutDirection(Qt::LeftToRight);
369                 // We need to draw the text as LTR as we use our own bidi code.
370                 setLayoutDirection(Qt::LeftToRight);
371                 drawText(x, y, str);
372                 //LYXERR(Debug::PAINTING) << "draw " << std::string(str.toUtf8())
373                 //      << " at " << x << "," << y << std::endl;
374                 return textwidth;
375         }
376
377         QPixmap pm;
378         QString key = generateStringSignature(str, f);
379         // Warning: Left bearing is in general negative! Only the case
380         // where left bearing is negative is of interest WRT the the 
381         // pixmap width and the text x-position.
382         // Only the left bearing of the first character is important
383         // as we always write from left to right, even for
384         // right-to-left languages.
385         int const lb = std::min(fi.metrics->lbearing(s[0]), 0);
386         int const mA = fi.metrics->maxAscent();
387         if (!QPixmapCache::find(key, pm)) {
388                 // Only the right bearing of the last character is
389                 // important as we always write from left to right,
390                 // even for right-to-left languages.
391                 int const rb = fi.metrics->rbearing(s[s.size()-1]);
392                 int const w = textwidth + rb - lb;
393                 int const mD = fi.metrics->maxDescent();
394                 int const h = mA + mD;
395                 pm = QPixmap(w, h);
396                 pm.fill(Qt::transparent);
397                 GuiPainter p(&pm);
398                 p.setQPainterPen(computeColor(f.realColor()));
399                 if (p.font() != fi.font)
400                         p.setFont(fi.font);
401                 // We need to draw the text as LTR as we use our own bidi code.
402                 p.setLayoutDirection(Qt::LeftToRight);
403                 p.drawText(-lb, mA, str);
404                 QPixmapCache::insert(key, pm);
405                 //LYXERR(Debug::PAINTING) << "h=" << h << "  mA=" << mA << "  mD=" << mD
406                 //      << "  w=" << w << "  lb=" << lb << "  tw=" << textwidth 
407                 //      << "  rb=" << rb << endl;
408         }
409         // Draw the cached pixmap.
410         drawPixmap(x + lb, y - mA, pm);
411
412         return textwidth;
413 }
414
415
416 } // namespace frontend
417 } // namespace lyx