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