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