]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiPainter.cpp
16631003c0efa931fb874d045bdacf4b4cf9ed53
[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
26 #include "support/unicode.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 (QT_VERSION < 0x040200) || defined(Q_WS_X11)
34 #define USE_PIXMAP_CACHE 0
35 #else
36 #define USE_PIXMAP_CACHE 1
37 #endif
38
39 using std::endl;
40 using std::string;
41
42 namespace lyx {
43 namespace frontend {
44
45 namespace {
46
47 bool const usePixmapCache = USE_PIXMAP_CACHE;
48
49 } // anon namespace
50
51 GuiPainter::GuiPainter(QPaintDevice * device)
52         : QPainter(device), Painter()
53 {
54         // new QPainter has default QPen:
55         current_color_ = guiApp->colorCache().get(Color_black);
56         current_ls_ = line_solid;
57         current_lw_ = line_thin;
58 }
59
60
61 GuiPainter::~GuiPainter()
62 {
63         QPainter::end();
64         //lyxerr << "GuiPainter::end()" << endl;
65 }
66
67
68 void GuiPainter::setQPainterPen(QColor const & col,
69         Painter::line_style ls, Painter::line_width lw)
70 {
71         if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
72                 return;
73
74         current_color_ = col;
75         current_ls_ = ls;
76         current_lw_ = lw;
77
78         QPen pen = QPainter::pen();
79         pen.setColor(col);
80
81         switch (ls) {
82                 case line_solid: pen.setStyle(Qt::SolidLine); break;
83                 case line_onoffdash: pen.setStyle(Qt::DotLine); break;
84         }
85
86         switch (lw) {
87                 case line_thin: pen.setWidth(0); break;
88                 case line_thick: pen.setWidth(3); break;
89         }
90
91         setPen(pen);
92 }
93
94
95 QString GuiPainter::generateStringSignature(QString const & str, FontInfo const & f)
96 {
97         QString sig = str;
98         sig.append(QChar(static_cast<short>(f.family())));
99         sig.append(QChar(static_cast<short>(f.series())));
100         sig.append(QChar(static_cast<short>(f.realShape())));
101         sig.append(QChar(static_cast<short>(f.size())));
102         sig.append(QChar(static_cast<short>(f.color())));
103         if (!monochrome_min_.empty()) {
104                 QColor const & min = monochrome_min_.top();
105                 QColor const & max = monochrome_max_.top();
106                 sig.append(QChar(static_cast<short>(min.red())));
107                 sig.append(QChar(static_cast<short>(min.green())));
108                 sig.append(QChar(static_cast<short>(min.blue())));
109                 sig.append(QChar(static_cast<short>(max.red())));
110                 sig.append(QChar(static_cast<short>(max.green())));
111                 sig.append(QChar(static_cast<short>(max.blue())));
112         }
113         return sig;
114 }
115
116
117 QColor GuiPainter::computeColor(ColorCode col)
118 {
119         return filterColor(guiApp->colorCache().get(col));
120 }
121
122
123 QColor GuiPainter::filterColor(QColor const & col)
124 {
125         if (monochrome_min_.empty())
126                 return col;
127
128         // map into [min,max] interval
129         QColor const & min = monochrome_min_.top();
130         QColor const & max = monochrome_max_.top();
131                         
132         qreal v = col.valueF();
133         v = v * v; // make it a bit steeper (i.e. darker)
134                 
135         qreal minr, ming, minb;
136         qreal maxr, maxg, maxb;
137         min.getRgbF(&minr, &ming, &minb);
138         max.getRgbF(&maxr, &maxg, &maxb);
139                         
140         return QColor(v*minr+(1-v)*maxr, v*ming+(1-v)*maxg, v*minb+(1-v)*maxb);
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 (!usePixmapCache) {
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 " << std::string(str.toUtf8())
374                 //      << " at " << x << "," << y << std::endl;
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 = std::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 << endl;
409         }
410         // Draw the cached pixmap.
411         drawPixmap(x + lb, y - mA, pm);
412
413         return textwidth;
414 }
415
416
417 } // namespace frontend
418 } // namespace lyx