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