1 | /* |
2 | SPDX-FileCopyrightText: 2002-2010 Anders Lund <anders@alweb.dk> |
3 | |
4 | Rewritten based on code of: |
5 | SPDX-FileCopyrightText: 2002 Michael Goffioul <kdeprint@swing.be> |
6 | |
7 | SPDX-License-Identifier: LGPL-2.0-or-later |
8 | */ |
9 | |
10 | #include "printpainter.h" |
11 | |
12 | #include "katebuffer.h" |
13 | #include "katedocument.h" |
14 | #include "katehighlight.h" |
15 | #include "katepartdebug.h" |
16 | #include "katerenderer.h" |
17 | #include "katetextfolding.h" |
18 | #include "katetextlayout.h" |
19 | #include "kateview.h" |
20 | |
21 | #include <KLocalizedString> |
22 | #include <KUser> |
23 | |
24 | #include <QPainter> |
25 | #include <QPrinter> |
26 | |
27 | using namespace KatePrinter; |
28 | |
29 | class KatePrinter::PageLayout |
30 | { |
31 | public: |
32 | PageLayout() |
33 | : headerTagList() |
34 | , footerTagList() |
35 | , selectionRange() |
36 | { |
37 | } |
38 | |
39 | uint pageWidth = 0; |
40 | uint pageHeight = 0; |
41 | uint = 0; |
42 | uint maxWidth = 0; |
43 | uint maxHeight = 0; |
44 | int xstart = 0; // beginning point for painting lines |
45 | int innerMargin = 0; |
46 | |
47 | bool selectionOnly = false; |
48 | |
49 | uint firstline = 0; |
50 | uint lastline = 0; |
51 | |
52 | // Header/Footer Page |
53 | uint = 0; |
54 | QStringList ; |
55 | uint = 0; |
56 | QStringList ; |
57 | |
58 | KTextEditor::Range selectionRange; |
59 | }; |
60 | |
61 | PrintPainter::PrintPainter(KTextEditor::DocumentPrivate *doc, KTextEditor::ViewPrivate *view) |
62 | : m_view(view) |
63 | , m_doc(doc) |
64 | , m_printGuide(false) |
65 | , m_printLineNumbers(false) |
66 | , m_useHeader(false) |
67 | , m_useFooter(false) |
68 | , m_useBackground(false) |
69 | , m_useBox(false) |
70 | , m_useHeaderBackground(false) |
71 | , m_useFooterBackground(false) |
72 | , m_boxMargin(0) |
73 | , m_boxWidth(1) |
74 | , m_boxColor(Qt::black) |
75 | , m_headerBackground(Qt::lightGray) |
76 | , m_headerForeground(Qt::black) |
77 | , m_footerBackground(Qt::lightGray) |
78 | , m_footerForeground(Qt::black) |
79 | , m_fhFont() |
80 | , m_headerFormat() |
81 | , m_footerFormat() |
82 | { |
83 | m_renderer = new KateRenderer(m_doc, m_view->renderer()->folding(), m_view); |
84 | m_renderer->setPrinterFriendly(true); |
85 | |
86 | updateCache(); |
87 | } |
88 | |
89 | PrintPainter::~PrintPainter() |
90 | { |
91 | delete m_renderer; |
92 | } |
93 | |
94 | void PrintPainter::setTextFont(const QFont &font) |
95 | { |
96 | m_view->rendererConfig()->setFont(font); |
97 | } |
98 | |
99 | void PrintPainter::setUseBox(const bool on) |
100 | { |
101 | m_useBox = on; |
102 | setBoxWidth(m_boxWidth); // reset the width |
103 | } |
104 | |
105 | void PrintPainter::setBoxWidth(const int width) |
106 | { |
107 | if (m_useBox) { |
108 | m_boxWidth = width; |
109 | if (width < 1) { |
110 | m_boxWidth = 1; |
111 | } |
112 | } else { |
113 | m_boxWidth = 0; |
114 | } |
115 | } |
116 | |
117 | void PrintPainter::setBoxColor(const QColor &color) |
118 | { |
119 | if (color.isValid()) { |
120 | m_boxColor = color; |
121 | } |
122 | } |
123 | |
124 | void PrintPainter::(const QColor &color) |
125 | { |
126 | if (color.isValid()) { |
127 | m_headerBackground = color; |
128 | } |
129 | } |
130 | |
131 | void PrintPainter::(const QColor &color) |
132 | { |
133 | if (color.isValid()) { |
134 | m_headerForeground = color; |
135 | } |
136 | } |
137 | |
138 | void PrintPainter::(const QColor &color) |
139 | { |
140 | if (color.isValid()) { |
141 | m_footerBackground = color; |
142 | } |
143 | } |
144 | void PrintPainter::(const QColor &color) |
145 | { |
146 | if (color.isValid()) { |
147 | m_footerForeground = color; |
148 | } |
149 | } |
150 | |
151 | void PrintPainter::setColorScheme(const QString &scheme) |
152 | { |
153 | // directly set that for the renderer |
154 | m_view->rendererConfig()->setSchema(scheme); |
155 | |
156 | // changed renderer requires cache updates |
157 | updateCache(); |
158 | } |
159 | |
160 | void PrintPainter::updateCache() |
161 | { |
162 | m_fontHeight = m_renderer->fontHeight(); |
163 | |
164 | // figure out the horizontal space required |
165 | QString s = QStringLiteral("%1 " ).arg(a: m_doc->lines()); |
166 | s.fill(c: QLatin1Char('5'), size: -1); // some non-fixed fonts haven't equally wide numbers |
167 | // FIXME calculate which is actually the widest... |
168 | m_lineNumberWidth = m_renderer->currentFontMetrics().boundingRect(string: s).width(); |
169 | } |
170 | |
171 | void PrintPainter::paint(QPrinter *printer) const |
172 | { |
173 | QPainter painter(printer); |
174 | PageLayout pl; |
175 | |
176 | configure(printer, layout&: pl); |
177 | |
178 | uint lineCount = pl.firstline; |
179 | uint y = 0; |
180 | uint currentPage = (printer->fromPage() == 0) ? 1 : printer->fromPage(); |
181 | bool pageStarted = true; |
182 | uint remainder = 0; |
183 | |
184 | auto &f = m_view->renderer()->folding(); |
185 | |
186 | // On to draw something :-) |
187 | while (lineCount <= pl.lastline) { |
188 | if (y + m_fontHeight > pl.maxHeight) { |
189 | if ((int)currentPage == printer->toPage()) { // we've reached the page break of last page to be printed |
190 | break; |
191 | } |
192 | printer->newPage(); |
193 | painter.resetTransform(); |
194 | currentPage++; |
195 | pageStarted = true; |
196 | y = 0; |
197 | } |
198 | |
199 | if (pageStarted) { |
200 | qCDebug(LOG_KTE) << "Starting new page," << lineCount << "lines up to now." ; |
201 | paintNewPage(painter, currentPage, y, pl); |
202 | pageStarted = false; |
203 | painter.translate(dx: pl.xstart, dy: y); |
204 | } |
205 | |
206 | const bool skipLine = m_dontPrintFoldedCode && !f.isLineVisible(line: lineCount); |
207 | |
208 | if (!skipLine && m_printLineNumbers /*&& ! startCol*/) { // don't repeat! |
209 | paintLineNumber(painter, number: lineCount, pl); |
210 | } |
211 | |
212 | if (!skipLine) { |
213 | paintLine(painter, line: lineCount, y, remainder, pl); |
214 | } |
215 | |
216 | if (!remainder) { |
217 | lineCount++; |
218 | } |
219 | } |
220 | |
221 | painter.end(); |
222 | } |
223 | |
224 | void PrintPainter::configure(const QPrinter *printer, PageLayout &pl) const |
225 | { |
226 | pl.pageHeight = printer->height(); |
227 | pl.pageWidth = printer->width(); |
228 | pl.headerWidth = printer->width(); |
229 | pl.innerMargin = m_useBox ? m_boxMargin : 6; |
230 | pl.maxWidth = printer->width(); |
231 | pl.maxHeight = (m_useBox ? printer->height() - pl.innerMargin : printer->height()); |
232 | pl.selectionOnly = (printer->printRange() == QPrinter::Selection); |
233 | pl.lastline = m_doc->lastLine(); |
234 | |
235 | if (m_view && pl.selectionOnly) { |
236 | // set a line range from the first selected line to the last |
237 | pl.selectionRange = m_view->selectionRange(); |
238 | pl.firstline = pl.selectionRange.start().line(); |
239 | pl.lastline = pl.selectionRange.end().line(); |
240 | } |
241 | |
242 | if (m_printLineNumbers) { |
243 | // a small space between the line numbers and the text |
244 | int _adj = m_renderer->currentFontMetrics().horizontalAdvance(QStringLiteral("5" )); |
245 | // adjust available width and set horizontal start point for data |
246 | pl.maxWidth -= m_lineNumberWidth + _adj; |
247 | pl.xstart += m_lineNumberWidth + _adj; |
248 | } |
249 | |
250 | if (m_useHeader || m_useFooter) { |
251 | // Set up a tag map |
252 | // This retrieves all tags, used or not, but |
253 | // none of these operations should be expensive, |
254 | // and searching each tag in the format strings is avoided. |
255 | QDateTime dt = QDateTime::currentDateTime(); |
256 | std::map<QString, QString> tags; |
257 | |
258 | KUser u(KUser::UseRealUserID); |
259 | tags[QStringLiteral("u" )] = u.loginName(); |
260 | |
261 | tags[QStringLiteral("d" )] = QLocale().toString(dateTime: dt, format: QLocale::ShortFormat); |
262 | tags[QStringLiteral("D" )] = QLocale().toString(dateTime: dt, format: QLocale::LongFormat); |
263 | tags[QStringLiteral("h" )] = QLocale().toString(time: dt.time(), format: QLocale::ShortFormat); |
264 | tags[QStringLiteral("y" )] = QLocale().toString(date: dt.date(), format: QLocale::ShortFormat); |
265 | tags[QStringLiteral("Y" )] = QLocale().toString(date: dt.date(), format: QLocale::LongFormat); |
266 | tags[QStringLiteral("f" )] = m_doc->url().fileName(); |
267 | tags[QStringLiteral("U" )] = m_doc->url().toString(); |
268 | if (pl.selectionOnly) { |
269 | QString s(i18n("(Selection of) " )); |
270 | tags[QStringLiteral("f" )].prepend(s); |
271 | tags[QStringLiteral("U" )].prepend(s); |
272 | } |
273 | |
274 | static const QRegularExpression reTags(QStringLiteral("%([dDfUhuyY])" )); // TODO check for "%%<TAG>" |
275 | |
276 | if (m_useHeader) { |
277 | pl.headerHeight = QFontMetrics(m_fhFont).height(); |
278 | if (m_useBox || m_useHeaderBackground) { |
279 | pl.headerHeight += pl.innerMargin * 2; |
280 | } else { |
281 | pl.headerHeight += 1 + QFontMetrics(m_fhFont).leading(); |
282 | } |
283 | |
284 | pl.headerTagList = m_headerFormat; |
285 | QMutableStringListIterator it(pl.headerTagList); |
286 | while (it.hasNext()) { |
287 | QString tag = it.next(); |
288 | QRegularExpressionMatch match; |
289 | int pos = tag.indexOf(re: reTags, from: 0, rmatch: &match); |
290 | QString rep; |
291 | while (pos > -1) { |
292 | rep = tags[match.captured(nth: 1)]; |
293 | tag.replace(i: (uint)pos, len: 2, after: rep); |
294 | pos += rep.length(); |
295 | pos = tag.indexOf(re: reTags, from: pos, rmatch: &match); |
296 | } |
297 | it.setValue(tag); |
298 | } |
299 | } |
300 | |
301 | if (m_useFooter) { |
302 | pl.footerHeight = QFontMetrics(m_fhFont).height(); |
303 | if (m_useBox || m_useFooterBackground) { |
304 | pl.footerHeight += 2 * pl.innerMargin; |
305 | } else { |
306 | pl.footerHeight += 1; // line only |
307 | } |
308 | |
309 | pl.footerTagList = m_footerFormat; |
310 | QMutableStringListIterator it(pl.footerTagList); |
311 | while (it.hasNext()) { |
312 | QString tag = it.next(); |
313 | QRegularExpressionMatch match; |
314 | int pos = tag.indexOf(re: reTags, from: 0, rmatch: &match); |
315 | QString rep; |
316 | while (pos > -1) { |
317 | rep = tags[match.captured(nth: 1)]; |
318 | tag.replace(i: (uint)pos, len: 2, after: rep); |
319 | pos += rep.length(); |
320 | pos = tag.indexOf(re: reTags, from: pos, rmatch: &match); |
321 | } |
322 | it.setValue(tag); |
323 | } |
324 | |
325 | pl.maxHeight -= pl.footerHeight; |
326 | } |
327 | } // if ( useHeader || useFooter ) |
328 | |
329 | if (m_useBackground) { |
330 | if (!m_useBox) { |
331 | pl.xstart += pl.innerMargin; |
332 | pl.maxWidth -= pl.innerMargin * 2; |
333 | } |
334 | } |
335 | |
336 | if (m_useBox) { |
337 | // set maxwidth to something sensible |
338 | pl.maxWidth -= (m_boxWidth + pl.innerMargin) * 2; |
339 | pl.xstart += m_boxWidth + pl.innerMargin; |
340 | // maxheight too.. |
341 | pl.maxHeight -= m_boxWidth; |
342 | } |
343 | |
344 | int pageHeight = pl.maxHeight; |
345 | if (m_useHeader) { |
346 | pageHeight -= pl.headerHeight + pl.innerMargin; |
347 | } |
348 | if (m_useFooter) { |
349 | pageHeight -= pl.footerHeight + pl.innerMargin; |
350 | } |
351 | |
352 | const int linesPerPage = pageHeight / m_fontHeight; |
353 | |
354 | if (printer->fromPage() > 0) { |
355 | pl.firstline = (printer->fromPage() - 1) * linesPerPage; |
356 | } |
357 | |
358 | // now that we know the vertical amount of space needed, |
359 | // it is possible to calculate the total number of pages |
360 | // if needed, that is if any header/footer tag contains "%P". |
361 | if (!pl.headerTagList.filter(QStringLiteral("%P" )).isEmpty() || !pl.footerTagList.filter(QStringLiteral("%P" )).isEmpty()) { |
362 | qCDebug(LOG_KTE) << "'%P' found! calculating number of pages..." ; |
363 | |
364 | // calculate total layouted lines in the document |
365 | int totalLines = 0; |
366 | // TODO: right now ignores selection printing |
367 | for (unsigned int i = pl.firstline; i <= pl.lastline; ++i) { |
368 | KateLineLayout rangeptr(*m_renderer); |
369 | rangeptr.setLine(line: i); |
370 | m_renderer->layoutLine(line: &rangeptr, maxwidth: (int)pl.maxWidth, cacheLayout: false); |
371 | totalLines += rangeptr.viewLineCount(); |
372 | } |
373 | |
374 | const int totalPages = (totalLines / linesPerPage) + ((totalLines % linesPerPage) > 0 ? 1 : 0); |
375 | |
376 | // TODO: add space for guide if required |
377 | // if ( useGuide ) |
378 | // _lt += (guideHeight + (fontHeight /2)) / fontHeight; |
379 | |
380 | // substitute both tag lists |
381 | QString re(QStringLiteral("%P" )); |
382 | QStringList::Iterator it; |
383 | |
384 | for (it = pl.headerTagList.begin(); it != pl.headerTagList.end(); ++it) { |
385 | it->replace(before: re, after: QString::number(totalPages)); |
386 | } |
387 | |
388 | for (it = pl.footerTagList.begin(); it != pl.footerTagList.end(); ++it) { |
389 | (*it).replace(before: re, after: QString::number(totalPages)); |
390 | } |
391 | } |
392 | } |
393 | |
394 | void PrintPainter::paintNewPage(QPainter &painter, const uint currentPage, uint &y, const PageLayout &pl) const |
395 | { |
396 | if (m_useHeader) { |
397 | paintHeader(painter, currentPage, y, pl); |
398 | } |
399 | |
400 | if (m_useFooter) { |
401 | paintFooter(painter, currentPage, pl); |
402 | } |
403 | |
404 | if (m_useBackground) { |
405 | paintBackground(painter, y, pl); |
406 | } |
407 | |
408 | if (m_useBox) { |
409 | paintBox(painter, y, pl); |
410 | } |
411 | |
412 | if (m_printGuide && currentPage == 1) { |
413 | paintGuide(painter, y, pl); |
414 | } |
415 | } |
416 | |
417 | void PrintPainter::(QPainter &painter, const uint currentPage, uint &y, const PageLayout &pl) const |
418 | { |
419 | painter.save(); |
420 | painter.setPen(QPen(m_headerForeground, 0.5)); |
421 | painter.setFont(m_fhFont); |
422 | |
423 | if (m_useHeaderBackground) { |
424 | painter.fillRect(x: 0, y: 0, w: pl.headerWidth, h: pl.headerHeight, b: m_headerBackground); |
425 | } |
426 | |
427 | if (pl.headerTagList.count() == 3) { |
428 | int valign = (m_useBox || m_useHeaderBackground || m_useBackground) ? Qt::AlignVCenter : Qt::AlignTop; |
429 | int align = valign | Qt::AlignLeft; |
430 | int marg = (m_useBox || m_useHeaderBackground) ? pl.innerMargin : 0; |
431 | if (m_useBox) { |
432 | marg += m_boxWidth; |
433 | } |
434 | |
435 | QString s; |
436 | for (int i = 0; i < 3; i++) { |
437 | s = pl.headerTagList[i]; |
438 | if (s.indexOf(s: QLatin1String("%p" )) != -1) { |
439 | s.replace(before: QLatin1String("%p" ), after: QString::number(currentPage)); |
440 | } |
441 | |
442 | painter.drawText(x: marg, y: 0, w: pl.headerWidth - (marg * 2), h: pl.headerHeight, flags: align, str: s); |
443 | align = valign | (i == 0 ? Qt::AlignHCenter : Qt::AlignRight); |
444 | } |
445 | } |
446 | |
447 | if (!(m_useHeaderBackground || m_useBox || m_useBackground)) { // draw a 1 px (!?) line to separate header from contents |
448 | painter.drawLine(x1: 0, y1: pl.headerHeight - 1, x2: pl.headerWidth, y2: pl.headerHeight - 1); |
449 | // y += 1; now included in headerHeight |
450 | } |
451 | |
452 | painter.restore(); |
453 | |
454 | y += pl.headerHeight + pl.innerMargin; |
455 | } |
456 | |
457 | void PrintPainter::(QPainter &painter, const uint currentPage, const PageLayout &pl) const |
458 | { |
459 | painter.save(); |
460 | painter.setPen(QPen(m_footerForeground, 0.5)); |
461 | painter.setFont(m_fhFont); |
462 | |
463 | if (!(m_useFooterBackground || m_useBox || m_useBackground)) { // draw a 1 px (!?) line to separate footer from contents |
464 | painter.drawLine(x1: 0, y1: pl.pageHeight - pl.footerHeight - 1, x2: pl.headerWidth, y2: pl.pageHeight - pl.footerHeight - 1); |
465 | } |
466 | if (m_useFooterBackground) { |
467 | painter.fillRect(x: 0, y: pl.pageHeight - pl.footerHeight, w: pl.headerWidth, h: pl.footerHeight, b: m_footerBackground); |
468 | } |
469 | |
470 | if (pl.footerTagList.count() == 3) { |
471 | int align = Qt::AlignVCenter | Qt::AlignLeft; |
472 | int marg = (m_useBox || m_useFooterBackground) ? pl.innerMargin : 0; |
473 | if (m_useBox) { |
474 | marg += m_boxWidth; |
475 | } |
476 | |
477 | QString s; |
478 | for (int i = 0; i < 3; i++) { |
479 | s = pl.footerTagList[i]; |
480 | if (s.indexOf(s: QLatin1String("%p" )) != -1) { |
481 | s.replace(before: QLatin1String("%p" ), after: QString::number(currentPage)); |
482 | } |
483 | painter.drawText(x: marg, y: pl.pageHeight - pl.footerHeight, w: pl.headerWidth - (marg * 2), h: pl.footerHeight, flags: align, str: s); |
484 | align = Qt::AlignVCenter | (i == 0 ? Qt::AlignHCenter : Qt::AlignRight); |
485 | } |
486 | } |
487 | painter.restore(); |
488 | } |
489 | |
490 | void PrintPainter::paintGuide(QPainter &painter, uint &y, const PageLayout &pl) const |
491 | { |
492 | // FIXME - this may span more pages... |
493 | // draw a box unless we have boxes, in which case we end with a box line |
494 | int _ystart = y; |
495 | QString _hlName = m_doc->highlight()->name(); |
496 | |
497 | // list of highlight attributes for the legend |
498 | const auto _attributes = m_doc->highlight()->attributesForDefinition(schema: m_view->rendererConfig()->schema()); |
499 | const QColor _defaultPen = _attributes.at(i: 0)->foreground().color(); |
500 | |
501 | painter.save(); |
502 | painter.setPen(_defaultPen); |
503 | |
504 | int _marg = 0; |
505 | if (m_useBox) { |
506 | _marg += (2 * m_boxWidth) + (2 * pl.innerMargin); |
507 | } else { |
508 | if (m_useBackground) { |
509 | _marg += 2 * pl.innerMargin; |
510 | } |
511 | _marg += 1; |
512 | y += 1 + pl.innerMargin; |
513 | } |
514 | |
515 | // draw a title string |
516 | QFont _titleFont = m_renderer->currentFont(); |
517 | _titleFont.setBold(true); |
518 | painter.setFont(_titleFont); |
519 | QRect _r; |
520 | painter.drawText(r: QRect(_marg, y, pl.pageWidth - (2 * _marg), pl.maxHeight - y), |
521 | flags: Qt::AlignTop | Qt::AlignHCenter, |
522 | i18n("Typographical Conventions for %1" , _hlName), |
523 | br: &_r); |
524 | const int _w = pl.pageWidth - (_marg * 2) - (pl.innerMargin * 2); |
525 | const int _x = _marg + pl.innerMargin; |
526 | y += _r.height() + pl.innerMargin; |
527 | painter.drawLine(x1: _x, y1: y, x2: _x + _w, y2: y); |
528 | y += 1 + pl.innerMargin; |
529 | |
530 | int _widest(0); |
531 | for (const KTextEditor::Attribute::Ptr &attribute : std::as_const(t: _attributes)) { |
532 | const QString _name = attribute->name().section(asep: QLatin1Char(':'), astart: 1, aend: 1); |
533 | _widest = qMax(a: QFontMetrics(attribute->font()).boundingRect(text: _name).width(), b: _widest); |
534 | } |
535 | |
536 | const int _guideCols = _w / (_widest + pl.innerMargin); |
537 | |
538 | // draw attrib names using their styles |
539 | const int _cw = _w / _guideCols; |
540 | int _i = 0; |
541 | |
542 | _titleFont.setUnderline(true); |
543 | QString _currentHlName; |
544 | for (const KTextEditor::Attribute::Ptr &attribute : std::as_const(t: _attributes)) { |
545 | QString _hl = attribute->name().section(asep: QLatin1Char(':'), astart: 0, aend: 0); |
546 | QString _name = attribute->name().section(asep: QLatin1Char(':'), astart: 1, aend: 1); |
547 | if (_hl != _hlName && _hl != _currentHlName) { |
548 | _currentHlName = _hl; |
549 | if (_i % _guideCols) { |
550 | y += m_fontHeight; |
551 | } |
552 | y += pl.innerMargin; |
553 | painter.setFont(_titleFont); |
554 | painter.setPen(_defaultPen); |
555 | painter.drawText(x: _x, y, w: _w, h: m_fontHeight, flags: Qt::AlignTop, str: _hl + QLatin1Char(' ') + i18n("text" )); |
556 | y += m_fontHeight; |
557 | _i = 0; |
558 | } |
559 | |
560 | painter.setPen(attribute->foreground().color()); |
561 | painter.setFont(attribute->font()); |
562 | |
563 | if (attribute->hasProperty(propertyId: QTextFormat::BackgroundBrush)) { |
564 | QRect _rect = QFontMetrics(attribute->font()).boundingRect(text: _name); |
565 | _rect.moveTo(ax: _x + ((_i % _guideCols) * _cw), ay: y); |
566 | painter.fillRect(_rect, attribute->background()); |
567 | } |
568 | |
569 | painter.drawText(x: (_x + ((_i % _guideCols) * _cw)), y, w: _cw, h: m_fontHeight, flags: Qt::AlignTop, str: _name); |
570 | |
571 | _i++; |
572 | if (_i && !(_i % _guideCols)) { |
573 | y += m_fontHeight; |
574 | } |
575 | } |
576 | |
577 | if (_i % _guideCols) { |
578 | y += m_fontHeight; // last row not full |
579 | } |
580 | // draw a box around the legend |
581 | painter.setPen(_defaultPen); |
582 | |
583 | if (m_useBox) { |
584 | painter.fillRect(x: 0, y: y + pl.innerMargin, w: pl.headerWidth, h: m_boxWidth, b: m_boxColor); |
585 | } else { |
586 | _marg -= 1; |
587 | painter.drawRect(x: _marg, y: _ystart, w: pl.pageWidth - (2 * _marg), h: y - _ystart + pl.innerMargin); |
588 | } |
589 | |
590 | painter.restore(); |
591 | |
592 | y += (m_useBox ? m_boxWidth : 1) + (pl.innerMargin * 2); |
593 | } |
594 | |
595 | void PrintPainter::paintBox(QPainter &painter, uint &y, const PageLayout &pl) const |
596 | { |
597 | painter.save(); |
598 | painter.setPen(QPen(m_boxColor, m_boxWidth)); |
599 | painter.drawRect(x: 0, y: 0, w: pl.pageWidth, h: pl.pageHeight); |
600 | |
601 | if (m_useHeader) { |
602 | painter.drawLine(x1: 0, y1: pl.headerHeight, x2: pl.headerWidth, y2: pl.headerHeight); |
603 | } else { |
604 | y += pl.innerMargin; |
605 | } |
606 | |
607 | if (m_useFooter) { // drawline is not trustable, grr. |
608 | painter.fillRect(x: 0, y: pl.maxHeight + pl.innerMargin, w: pl.headerWidth, h: m_boxWidth, b: m_boxColor); |
609 | } |
610 | |
611 | painter.restore(); |
612 | } |
613 | |
614 | void PrintPainter::paintBackground(QPainter &painter, const uint y, const PageLayout &pl) const |
615 | { |
616 | // If we have a box, or the header/footer has backgrounds, we want to paint |
617 | // to the border of those. Otherwise just the contents area. |
618 | int _y = y; |
619 | int _h = pl.maxHeight - y; |
620 | if (m_useBox) { |
621 | _y -= pl.innerMargin; |
622 | _h += 2 * pl.innerMargin; |
623 | } else { |
624 | if (m_useHeaderBackground) { |
625 | _y -= pl.innerMargin; |
626 | _h += pl.innerMargin; |
627 | } |
628 | if (m_useFooterBackground) { |
629 | _h += pl.innerMargin; |
630 | } |
631 | } |
632 | painter.fillRect(x: 0, y: _y, w: pl.pageWidth, h: _h, b: m_view->rendererConfig()->backgroundColor()); |
633 | } |
634 | |
635 | void PrintPainter::paintLine(QPainter &painter, const uint line, uint &y, uint &remainder, const PageLayout &pl) const |
636 | { |
637 | // HA! this is where we print [part of] a line ;]] |
638 | KateLineLayout rangeptr(*m_renderer); |
639 | rangeptr.setLine(line); |
640 | m_renderer->layoutLine(line: &rangeptr, maxwidth: (int)pl.maxWidth, cacheLayout: false); |
641 | |
642 | // selectionOnly: clip non-selection parts and adjust painter position if needed |
643 | int _xadjust = 0; |
644 | if (pl.selectionOnly) { |
645 | if (m_view && m_view->blockSelection()) { |
646 | int _x = m_renderer->cursorToX(range: rangeptr.viewLine(viewLine: 0), pos: pl.selectionRange.start()); |
647 | int _x1 = m_renderer->cursorToX(range: rangeptr.viewLine(viewLine: rangeptr.viewLineCount() - 1), pos: pl.selectionRange.end()); |
648 | _xadjust = _x; |
649 | painter.translate(dx: -_xadjust, dy: 0); |
650 | painter.setClipRegion(QRegion(_x, 0, _x1 - _x, rangeptr.viewLineCount() * m_fontHeight)); |
651 | |
652 | } else if (line == pl.firstline || line == pl.lastline) { |
653 | QRegion region(0, 0, pl.maxWidth, rangeptr.viewLineCount() * m_fontHeight); |
654 | |
655 | if (line == pl.firstline) { |
656 | region = region.subtracted(r: QRegion(0, 0, m_renderer->cursorToX(range: rangeptr.viewLine(viewLine: 0), pos: pl.selectionRange.start()), m_fontHeight)); |
657 | } |
658 | |
659 | if (line == pl.lastline) { |
660 | int _x = m_renderer->cursorToX(range: rangeptr.viewLine(viewLine: rangeptr.viewLineCount() - 1), pos: pl.selectionRange.end()); |
661 | region = region.subtracted(r: QRegion(_x, 0, pl.maxWidth - _x, m_fontHeight)); |
662 | } |
663 | |
664 | painter.setClipRegion(region); |
665 | } |
666 | } |
667 | |
668 | // If the line is too long (too many 'viewlines') to fit the remaining vertical space, |
669 | // clip and adjust the painter position as necessary |
670 | int _lines = rangeptr.viewLineCount(); // number of "sublines" to paint. |
671 | |
672 | int proceedLines = _lines; |
673 | if (remainder) { |
674 | proceedLines = qMin(a: (pl.maxHeight - y) / m_fontHeight, b: remainder); |
675 | |
676 | painter.translate(dx: 0, dy: -(_lines - int(remainder)) * m_fontHeight + 1); |
677 | painter.setClipRect(x: 0, |
678 | y: (_lines - int(remainder)) * m_fontHeight + 1, |
679 | w: pl.maxWidth, |
680 | h: proceedLines * m_fontHeight); // ### drop the crosspatch in printerfriendly mode??? |
681 | remainder -= proceedLines; |
682 | } else if (y + m_fontHeight * _lines > pl.maxHeight) { |
683 | remainder = _lines - ((pl.maxHeight - y) / m_fontHeight); |
684 | painter.setClipRect(x: 0, y: 0, w: pl.maxWidth, h: (_lines - int(remainder)) * m_fontHeight + 1); // ### drop the crosspatch in printerfriendly mode??? |
685 | } else if (!pl.selectionOnly) { |
686 | painter.setClipRegion(QRegion()); |
687 | painter.setClipping(false); |
688 | } |
689 | |
690 | KateRenderer::PaintTextLineFlags flags; |
691 | if (!m_dontPrintFoldedCode) { |
692 | flags.setFlag(flag: KateRenderer::PaintTextLineFlag::SkipDrawFirstInvisibleLineUnderlined); |
693 | } |
694 | |
695 | m_renderer->paintTextLine(paint&: painter, range: &rangeptr, xStart: 0, xEnd: (int)pl.maxWidth, textClipRect: QRectF{}, cursor: nullptr, flags); |
696 | |
697 | painter.setClipping(false); |
698 | painter.translate(dx: _xadjust, dy: (m_fontHeight * (_lines - remainder))); |
699 | |
700 | y += m_fontHeight * proceedLines; |
701 | } |
702 | |
703 | void PrintPainter::paintLineNumber(QPainter &painter, const uint number, const PageLayout &pl) const |
704 | { |
705 | const int left = ((m_useBox || m_useBackground) ? pl.innerMargin : 0) - pl.xstart; |
706 | |
707 | painter.save(); |
708 | painter.setFont(m_renderer->currentFont()); |
709 | painter.setPen(m_view->rendererConfig()->lineNumberColor()); |
710 | painter.drawText(x: left, y: 0, w: m_lineNumberWidth, h: m_fontHeight, flags: Qt::AlignRight | Qt::AlignVCenter, str: QString::number(number + 1)); |
711 | painter.restore(); |
712 | } |
713 | |
714 | // END PrintPainter |
715 | |