Kate
katerenderer.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries 00002 Copyright (C) 2007 Mirko Stocker <me@misto.ch> 00003 Copyright (C) 2003-2005 Hamish Rodda <rodda@kde.org> 00004 Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org> 00005 Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> 00006 Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> 00007 00008 This library is free software; you can redistribute it and/or 00009 modify it under the terms of the GNU Library General Public 00010 License version 2 as published by the Free Software Foundation. 00011 00012 This library is distributed in the hope that it will be useful, 00013 but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00015 Library General Public License for more details. 00016 00017 You should have received a copy of the GNU Library General Public License 00018 along with this library; see the file COPYING.LIB. If not, write to 00019 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00020 Boston, MA 02110-1301, USA. 00021 */ 00022 00023 #include "katerenderer.h" 00024 00025 #include "katedocument.h" 00026 #include "kateconfig.h" 00027 #include "katehighlight.h" 00028 #include "kateview.h" 00029 #include "katerenderrange.h" 00030 #include "katetextlayout.h" 00031 #include "katebuffer.h" 00032 00033 #include "katevivisualmode.h" 00034 00035 #include <limits.h> 00036 00037 #include <kdebug.h> 00038 00039 #include <QtGui/QPainter> 00040 #include <QtGui/QTextLine> 00041 #include <QtCore/QStack> 00042 #include <QtGui/QBrush> 00043 00044 #include <ktexteditor/highlightinterface.h> 00045 00046 static const QChar tabChar('\t'); 00047 static const QChar spaceChar(' '); 00048 static const QChar nbSpaceChar(0xa0); // non-breaking space 00049 00050 KateRenderer::KateRenderer(KateDocument* doc, KateView *view) 00051 : m_doc(doc) 00052 , m_view (view) 00053 , m_tabWidth(m_doc->config()->tabWidth()) 00054 , m_indentWidth(m_doc->config()->indentationWidth()) 00055 , m_caretStyle(KateRenderer::Line) 00056 , m_drawCaret(true) 00057 , m_showSelections(true) 00058 , m_showTabs(true) 00059 , m_showSpaces(true) 00060 , m_printerFriendly(false) 00061 , m_config(new KateRendererConfig(this)) 00062 { 00063 updateAttributes (); 00064 } 00065 00066 KateRenderer::~KateRenderer() 00067 { 00068 delete m_config; 00069 } 00070 00071 void KateRenderer::updateAttributes () 00072 { 00073 m_attributes = m_doc->highlight()->attributes (config()->schema ()); 00074 } 00075 00076 KTextEditor::Attribute::Ptr KateRenderer::attribute(uint pos) const 00077 { 00078 if (pos < (uint)m_attributes.count()) 00079 return m_attributes[pos]; 00080 00081 return m_attributes[0]; 00082 } 00083 00084 KTextEditor::Attribute::Ptr KateRenderer::specificAttribute( int context ) const 00085 { 00086 if (context >= 0 && context < m_attributes.count()) 00087 return m_attributes[context]; 00088 00089 return m_attributes[0]; 00090 } 00091 00092 void KateRenderer::setDrawCaret(bool drawCaret) 00093 { 00094 m_drawCaret = drawCaret; 00095 } 00096 00097 void KateRenderer::setCaretStyle(KateRenderer::caretStyles style) 00098 { 00099 m_caretStyle = style; 00100 } 00101 00102 void KateRenderer::setShowTabs(bool showTabs) 00103 { 00104 m_showTabs = showTabs; 00105 } 00106 00107 void KateRenderer::setShowTrailingSpaces(bool showSpaces) 00108 { 00109 m_showSpaces = showSpaces; 00110 } 00111 00112 void KateRenderer::setTabWidth(int tabWidth) 00113 { 00114 m_tabWidth = tabWidth; 00115 } 00116 00117 bool KateRenderer::showIndentLines() const 00118 { 00119 return m_config->showIndentationLines(); 00120 } 00121 00122 void KateRenderer::setShowIndentLines(bool showIndentLines) 00123 { 00124 m_config->setShowIndentationLines(showIndentLines); 00125 } 00126 00127 void KateRenderer::setIndentWidth(int indentWidth) 00128 { 00129 m_indentWidth = indentWidth; 00130 } 00131 00132 void KateRenderer::setShowSelections(bool showSelections) 00133 { 00134 m_showSelections = showSelections; 00135 } 00136 00137 void KateRenderer::increaseFontSizes() 00138 { 00139 QFont f ( config()->font () ); 00140 f.setPointSize (f.pointSize ()+1); 00141 00142 config()->setFont (f); 00143 } 00144 00145 void KateRenderer::decreaseFontSizes() 00146 { 00147 QFont f ( config()->font () ); 00148 00149 if ((f.pointSize ()-1) > 0) 00150 f.setPointSize (f.pointSize ()-1); 00151 00152 config()->setFont (f); 00153 } 00154 00155 bool KateRenderer::isPrinterFriendly() const 00156 { 00157 return m_printerFriendly; 00158 } 00159 00160 void KateRenderer::setPrinterFriendly(bool printerFriendly) 00161 { 00162 m_printerFriendly = printerFriendly; 00163 setShowTabs(false); 00164 setShowTrailingSpaces(false); 00165 setShowSelections(false); 00166 setDrawCaret(false); 00167 } 00168 00169 void KateRenderer::paintTextLineBackground(QPainter& paint, KateLineLayoutPtr layout, int currentViewLine, int xStart, int xEnd) 00170 { 00171 if (isPrinterFriendly()) 00172 return; 00173 00174 // Normal background color 00175 QColor backgroundColor( config()->backgroundColor() ); 00176 00177 // paint the current line background if we're on the current line 00178 QColor currentLineColor = config()->highlightedLineColor(); 00179 00180 // Check for mark background 00181 int markRed = 0, markGreen = 0, markBlue = 0, markCount = 0; 00182 00183 // Retrieve marks for this line 00184 uint mrk = m_doc->mark( layout->line() ); 00185 if (mrk) 00186 { 00187 for (uint bit = 0; bit < 32; bit++) 00188 { 00189 KTextEditor::MarkInterface::MarkTypes markType = (KTextEditor::MarkInterface::MarkTypes)(1<<bit); 00190 if (mrk & markType) 00191 { 00192 QColor markColor = config()->lineMarkerColor(markType); 00193 00194 if (markColor.isValid()) { 00195 markCount++; 00196 markRed += markColor.red(); 00197 markGreen += markColor.green(); 00198 markBlue += markColor.blue(); 00199 } 00200 } 00201 } // for 00202 } // Marks 00203 00204 if (markCount) { 00205 markRed /= markCount; 00206 markGreen /= markCount; 00207 markBlue /= markCount; 00208 backgroundColor.setRgb( 00209 int((backgroundColor.red() * 0.9) + (markRed * 0.1)), 00210 int((backgroundColor.green() * 0.9) + (markGreen * 0.1)), 00211 int((backgroundColor.blue() * 0.9) + (markBlue * 0.1)) 00212 ); 00213 } 00214 00215 // Draw line background 00216 paint.fillRect(0, 0, xEnd - xStart, config()->fontMetrics().height() * layout->viewLineCount(), backgroundColor); 00217 00218 // paint the current line background if we're on the current line 00219 if (currentViewLine != -1) { 00220 if (markCount) { 00221 markRed /= markCount; 00222 markGreen /= markCount; 00223 markBlue /= markCount; 00224 currentLineColor.setRgb( 00225 int((currentLineColor.red() * 0.9) + (markRed * 0.1)), 00226 int((currentLineColor.green() * 0.9) + (markGreen * 0.1)), 00227 int((currentLineColor.blue() * 0.9) + (markBlue * 0.1)) 00228 ); 00229 } 00230 00231 paint.fillRect(0, config()->fontMetrics().height() * currentViewLine, xEnd - xStart, config()->fontMetrics().height(), currentLineColor); 00232 } 00233 } 00234 00235 void KateRenderer::paintTabstop(QPainter &paint, qreal x, qreal y) 00236 { 00237 QPen penBackup( paint.pen() ); 00238 QPen pen( config()->tabMarkerColor() ); 00239 pen.setWidth(qMax(1u, spaceWidth() / 10)); 00240 paint.setPen( pen ); 00241 paint.setRenderHint(QPainter::Antialiasing, false); 00242 00243 int dist = spaceWidth() * 0.3; 00244 QPoint points[8]; 00245 points[0] = QPoint(x - dist, y - dist); 00246 points[1] = QPoint(x, y); 00247 points[2] = QPoint(x, y); 00248 points[3] = QPoint(x - dist, y + dist); 00249 x += spaceWidth() / 3.0; 00250 points[4] = QPoint(x - dist, y - dist); 00251 points[5] = QPoint(x, y); 00252 points[6] = QPoint(x, y); 00253 points[7] = QPoint(x - dist, y + dist); 00254 paint.drawLines(points, 4); 00255 paint.setPen( penBackup ); 00256 } 00257 00258 void KateRenderer::paintTrailingSpace(QPainter &paint, qreal x, qreal y) 00259 { 00260 QPen penBackup( paint.pen() ); 00261 QPen pen( config()->tabMarkerColor() ); 00262 pen.setWidthF(spaceWidth() / 3.5); 00263 pen.setCapStyle(Qt::RoundCap); 00264 paint.setPen( pen ); 00265 00266 paint.drawPoint( QPointF(x, y) ); 00267 paint.setPen( penBackup ); 00268 } 00269 00270 void KateRenderer::paintNonBreakSpace(QPainter &paint, qreal x, qreal y) 00271 { 00272 QPen penBackup( paint.pen() ); 00273 QPen pen( config()->tabMarkerColor() ); 00274 pen.setWidth(qMax(1u, spaceWidth() / 10)); 00275 paint.setPen( pen ); 00276 paint.setRenderHint(QPainter::Antialiasing, false); 00277 00278 const int height = config()->fontMetrics().height(); 00279 const int width = spaceWidth(); 00280 00281 QPoint points[6]; 00282 points[0] = QPoint(x+width/10, y+height/4); 00283 points[1] = QPoint(x+width/10, y+height/3); 00284 points[2] = QPoint(x+width/10, y+height/3); 00285 points[3] = QPoint(x+width-width/10, y+height/3); 00286 points[4] = QPoint(x+width-width/10, y+height/3); 00287 points[5] = QPoint(x+width-width/10, y+height/4); 00288 paint.drawLines(points, 3); 00289 paint.setPen( penBackup ); 00290 } 00291 00292 void KateRenderer::paintIndentMarker(QPainter &paint, uint x, uint row) 00293 { 00294 QPen penBackup( paint.pen() ); 00295 paint.setPen( config()->tabMarkerColor() ); 00296 00297 const int height = config()->fontMetrics().height(); 00298 const int top = 0; 00299 const int bottom = height-1; 00300 const int h = bottom - top + 1; 00301 00302 // Dot padding. 00303 int pad = 0; 00304 if(row & 1 && h & 1) pad = 1; 00305 00306 for(int i = top; i <= bottom; i++) 00307 { 00308 if((i + pad) & 1) 00309 { 00310 paint.drawPoint(x + 2, i); 00311 } 00312 } 00313 00314 paint.setPen( penBackup ); 00315 } 00316 00317 static bool rangeLessThanForRenderer (const Kate::TextRange *a, const Kate::TextRange *b) 00318 { 00319 // compare Z-Depth first 00320 // smaller Z-Depths should win! 00321 if (a->zDepth() > b->zDepth()) 00322 return true; 00323 else if (a->zDepth() < b->zDepth()) 00324 return false; 00325 00326 // end of a > end of b? 00327 if (a->end().toCursor() > b->end().toCursor()) 00328 return true; 00329 00330 // if ends are equal, start of a < start of b? 00331 if (a->end().toCursor() == b->end().toCursor()) 00332 return a->start().toCursor() < b->start().toCursor(); 00333 00334 return false; 00335 } 00336 00337 QList<QTextLayout::FormatRange> KateRenderer::decorationsForLine( const Kate::TextLine& textLine, int line, bool selectionsOnly, KateRenderRange* completionHighlight, bool completionSelected ) const 00338 { 00339 QList<QTextLayout::FormatRange> newHighlight; 00340 00341 // Don't compute the highlighting if there isn't going to be any highlighting 00342 QList<Kate::TextRange *> rangesWithAttributes = m_doc->buffer().rangesForLine (line, m_printerFriendly ? 0 : m_view, true); 00343 if (selectionsOnly || textLine->attributesList().count() || rangesWithAttributes.count()) { 00344 RenderRangeList renderRanges; 00345 00346 // Add the inbuilt highlighting to the list 00347 NormalRenderRange* inbuiltHighlight = new NormalRenderRange(); 00348 const QVector<int> &al = textLine->attributesList(); 00349 for (int i = 0; i+2 < al.count(); i += 3) { 00350 inbuiltHighlight->addRange(new KTextEditor::Range(KTextEditor::Cursor(line, al[i]), al[i+1]), specificAttribute(al[i+2])); 00351 } 00352 renderRanges.append(inbuiltHighlight); 00353 00354 if (!completionHighlight) { 00355 // check for dynamic hl stuff 00356 const QSet<Kate::TextRange *> *rangesMouseIn = m_view ? m_view->rangesMouseIn () : 0; 00357 const QSet<Kate::TextRange *> *rangesCaretIn = m_view ? m_view->rangesCaretIn () : 0; 00358 bool anyDynamicHlsActive = m_view && (!rangesMouseIn->empty() || !rangesCaretIn->empty()); 00359 00360 // sort all ranges, we want that the most specific ranges win during rendering, multiple equal ranges are kind of random, still better than old smart rangs behavior ;) 00361 qSort (rangesWithAttributes.begin(), rangesWithAttributes.end(), rangeLessThanForRenderer); 00362 00363 // loop over all ranges 00364 for (int i = 0; i < rangesWithAttributes.size(); ++i) { 00365 // real range 00366 Kate::TextRange *kateRange = rangesWithAttributes[i]; 00367 00368 // calculate attribute, default: normal attribute 00369 KTextEditor::Attribute::Ptr attribute = kateRange->attribute(); 00370 if (anyDynamicHlsActive) { 00371 // check mouse in 00372 if (KTextEditor::Attribute::Ptr attributeMouseIn = attribute->dynamicAttribute (KTextEditor::Attribute::ActivateMouseIn)) { 00373 if (rangesMouseIn->contains (kateRange)) 00374 attribute = attributeMouseIn; 00375 } 00376 00377 // check caret in 00378 if (KTextEditor::Attribute::Ptr attributeCaretIn = attribute->dynamicAttribute (KTextEditor::Attribute::ActivateCaretIn)) { 00379 if (rangesCaretIn->contains (kateRange)) 00380 attribute = attributeCaretIn; 00381 } 00382 } 00383 00384 // span range 00385 NormalRenderRange *additionaHl = new NormalRenderRange(); 00386 additionaHl->addRange(new KTextEditor::Range (*kateRange), attribute); 00387 renderRanges.append(additionaHl); 00388 } 00389 } else { 00390 // Add the code completion arbitrary highlight to the list 00391 renderRanges.append(completionHighlight); 00392 } 00393 00394 // Add selection highlighting if we're creating the selection decorations 00395 if ((selectionsOnly && showSelections() && m_view->selection()) || (completionHighlight && completionSelected) || m_view->blockSelection()) { 00396 NormalRenderRange* selectionHighlight = new NormalRenderRange(); 00397 00398 // Set up the selection background attribute TODO: move this elsewhere, eg. into the config? 00399 static KTextEditor::Attribute::Ptr backgroundAttribute; 00400 if (!backgroundAttribute) 00401 backgroundAttribute = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); 00402 00403 backgroundAttribute->setBackground(config()->selectionColor()); 00404 backgroundAttribute->setForeground(attribute(KTextEditor::HighlightInterface::dsNormal)->selectedForeground().color()); 00405 00406 // Create a range for the current selection 00407 if (completionHighlight && completionSelected) 00408 selectionHighlight->addRange(new KTextEditor::Range(line, 0, line + 1, 0), backgroundAttribute); 00409 else 00410 if(m_view->blockSelection() && m_view->selectionRange().overlapsLine(line)) 00411 selectionHighlight->addRange(new KTextEditor::Range(m_doc->rangeOnLine(m_view->selectionRange(), line)), backgroundAttribute); 00412 else { 00413 selectionHighlight->addRange(new KTextEditor::Range(m_view->selectionRange()), backgroundAttribute); 00414 } 00415 00416 renderRanges.append(selectionHighlight); 00417 // hihglighting for the vi visual modes 00418 } else if ( m_view->getViInputModeManager()->getCurrentViMode() == VisualMode 00419 || m_view->getViInputModeManager()->getCurrentViMode() == VisualLineMode 00420 || m_view->getViInputModeManager()->getCurrentViMode() == VisualBlockMode ) { 00421 00422 KTextEditor::Range r = m_view->getViInputModeManager()->getViVisualMode()->getVisualRange(); 00423 00424 if ( r.isValid() && (r.end().line() == line || r.start().line() == line || r.containsLine( line ) )) { 00425 NormalRenderRange* selectionHighlight = new NormalRenderRange(); 00426 static KTextEditor::Attribute::Ptr backgroundAttribute; 00427 if (!backgroundAttribute) 00428 backgroundAttribute = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); 00429 00430 backgroundAttribute->setBackground(config()->selectionColor()); 00431 00432 if ( m_view->getViInputModeManager()->getCurrentViMode() == VisualBlockMode ) { 00433 selectionHighlight->addRange(new KTextEditor::Range(line, r.start().column(), line, r.end().column()+1), backgroundAttribute); 00434 } else if ( m_view->getViInputModeManager()->getCurrentViMode() == VisualLineMode ) { 00435 selectionHighlight->addRange(new KTextEditor::Range(line, 0, line, m_view->doc()->lineLength( line )), backgroundAttribute); 00436 } else { 00437 selectionHighlight->addRange(new KTextEditor::Range(r), backgroundAttribute); 00438 } 00439 renderRanges.append(selectionHighlight); 00440 } 00441 } 00442 00443 KTextEditor::Cursor currentPosition, endPosition; 00444 00445 // Calculate the range which we need to iterate in order to get the highlighting for just this line 00446 if (selectionsOnly) { 00447 if(m_view->blockSelection()) { 00448 KTextEditor::Range subRange = m_doc->rangeOnLine(m_view->selectionRange(), line); 00449 currentPosition = subRange.start(); 00450 endPosition = subRange.end(); 00451 } else { 00452 KTextEditor::Range rangeNeeded = m_view->selectionRange() & KTextEditor::Range(line, 0, line + 1, 0); 00453 00454 currentPosition = qMax(KTextEditor::Cursor(line, 0), rangeNeeded.start()); 00455 endPosition = qMin(KTextEditor::Cursor(line + 1, 0), rangeNeeded.end()); 00456 } 00457 } else { 00458 currentPosition = KTextEditor::Cursor(line, 0); 00459 endPosition = KTextEditor::Cursor(line + 1, 0); 00460 } 00461 00462 // Main iterative loop. This walks through each set of highlighting ranges, and stops each 00463 // time the highlighting changes. It then creates the corresponding QTextLayout::FormatRanges. 00464 while (currentPosition < endPosition) { 00465 renderRanges.advanceTo(currentPosition); 00466 00467 if (!renderRanges.hasAttribute()) { 00468 // No attribute, don't need to create a FormatRange for this text range 00469 currentPosition = renderRanges.nextBoundary(); 00470 continue; 00471 } 00472 00473 KTextEditor::Cursor nextPosition = renderRanges.nextBoundary(); 00474 00475 // Create the format range and populate with the correct start, length and format info 00476 QTextLayout::FormatRange fr; 00477 fr.start = currentPosition.column(); 00478 00479 if (nextPosition < endPosition || endPosition.line() <= line) { 00480 fr.length = nextPosition.column() - currentPosition.column(); 00481 00482 } else { 00483 // +1 to force background drawing at the end of the line when it's warranted 00484 fr.length = textLine->length() - currentPosition.column() + 1; 00485 } 00486 00487 KTextEditor::Attribute::Ptr a = renderRanges.generateAttribute(); 00488 if (a) { 00489 fr.format = *a; 00490 00491 if(selectionsOnly) { 00492 assignSelectionBrushesFromAttribute(fr, *a); 00493 } else if ( m_view->getCurrentViMode() == VisualMode || m_view->getCurrentViMode() == VisualLineMode ) { 00494 if (m_view->getViInputModeManager()->getViVisualMode()->getVisualRange().contains(currentPosition)) { 00495 assignSelectionBrushesFromAttribute(fr, *a); 00496 } 00497 } else if ( m_view->getCurrentViMode() == VisualBlockMode ) { 00498 if (m_view->getViInputModeManager()->getViVisualMode()->getVisualRange().contains(currentPosition) 00499 || m_view->getViInputModeManager()->getViVisualMode()->getVisualRange().start().line() == currentPosition.line() 00500 || m_view->getViInputModeManager()->getViVisualMode()->getVisualRange().end().line() == currentPosition.line()) { 00501 int c1 = m_view->getViInputModeManager()->getViVisualMode()->getVisualRange().start().column(); 00502 int c2 = m_view->getViInputModeManager()->getViVisualMode()->getVisualRange().end().column(); 00503 00504 if(currentPosition.column() >= c1 && currentPosition.column() <= c2) { 00505 assignSelectionBrushesFromAttribute(fr, *a); 00506 } 00507 } 00508 } 00509 } 00510 00511 newHighlight.append(fr); 00512 00513 currentPosition = nextPosition; 00514 } 00515 00516 if (completionHighlight) 00517 // Don't delete external completion render range 00518 renderRanges.removeAll(completionHighlight); 00519 00520 qDeleteAll(renderRanges); 00521 } 00522 00523 return newHighlight; 00524 } 00525 00526 void KateRenderer::assignSelectionBrushesFromAttribute(QTextLayout::FormatRange& target, const KTextEditor::Attribute& attribute) const 00527 { 00528 if(attribute.hasProperty(KTextEditor::Attribute::SelectedForeground)) { 00529 target.format.setForeground(attribute.selectedForeground()); 00530 } 00531 if(attribute.hasProperty(KTextEditor::Attribute::SelectedBackground)) { 00532 target.format.setBackground(attribute.selectedBackground()); 00533 } 00534 } 00535 00536 /* 00537 The ultimate line painting function. 00538 Currently missing features: 00539 - draw indent lines 00540 */ 00541 void KateRenderer::paintTextLine(QPainter& paint, KateLineLayoutPtr range, int xStart, int xEnd, const KTextEditor::Cursor* cursor) 00542 { 00543 Q_ASSERT(range->isValid()); 00544 00545 // kDebug( 13033 )<<"KateRenderer::paintTextLine"; 00546 00547 // font data 00548 const QFontMetrics& fm = config()->fontMetrics(); 00549 00550 int currentViewLine = -1; 00551 if (cursor && cursor->line() == range->line()) 00552 currentViewLine = range->viewLineForColumn(cursor->column()); 00553 00554 paintTextLineBackground(paint, range, currentViewLine, xStart, xEnd); 00555 00556 if (range->layout()) { 00557 QVector<QTextLayout::FormatRange> additionalFormats; 00558 if (range->length() > 0) { 00559 // We may have changed the pen, be absolutely sure it gets set back to 00560 // normal foreground color before drawing text for text that does not 00561 // set the pen color 00562 paint.setPen(attribute(KTextEditor::HighlightInterface::dsNormal)->foreground().color()); 00563 // Draw the text :) 00564 if (m_view->selection() && showSelections() && m_view->selectionRange().overlapsLine(range->line())) { 00565 // FIXME toVector() may be a performance issue 00566 additionalFormats = decorationsForLine(range->textLine(), range->line(), true).toVector(); 00567 range->layout()->draw(&paint, QPoint(-xStart,0), additionalFormats); 00568 00569 } else { 00570 range->layout()->draw(&paint, QPoint(-xStart,0)); 00571 } 00572 } 00573 00574 QBrush backgroundBrush; 00575 bool backgroundBrushSet = false; 00576 00577 // Loop each individual line for additional text decoration etc. 00578 QListIterator<QTextLayout::FormatRange> it = range->layout()->additionalFormats(); 00579 QVectorIterator<QTextLayout::FormatRange> it2 = additionalFormats; 00580 for (int i = 0; i < range->viewLineCount(); ++i) { 00581 KateTextLayout line = range->viewLine(i); 00582 00583 // Determine the background to use, if any, for the end of this view line 00584 backgroundBrushSet = false; 00585 while (it2.hasNext()) { 00586 const QTextLayout::FormatRange& fr = it2.peekNext(); 00587 if (fr.start > line.endCol()) 00588 break; 00589 00590 if (fr.start + fr.length > line.endCol()) { 00591 if (fr.format.hasProperty(QTextFormat::BackgroundBrush)) { 00592 backgroundBrushSet = true; 00593 backgroundBrush = fr.format.background(); 00594 } 00595 00596 goto backgroundDetermined; 00597 } 00598 00599 it2.next(); 00600 } 00601 00602 while (it.hasNext()) { 00603 const QTextLayout::FormatRange& fr = it.peekNext(); 00604 if (fr.start > line.endCol()) 00605 break; 00606 00607 if (fr.start + fr.length > line.endCol()) { 00608 if (fr.format.hasProperty(QTextFormat::BackgroundBrush)) { 00609 backgroundBrushSet = true; 00610 backgroundBrush = fr.format.background(); 00611 } 00612 00613 break; 00614 } 00615 00616 it.next(); 00617 } 00618 00619 backgroundDetermined: 00620 00621 // Draw selection or background color outside of areas where text is rendered 00622 if (!m_printerFriendly ) { 00623 bool draw = false; 00624 QBrush drawBrush; 00625 if (m_view->selection() && !m_view->blockSelection() && m_view->lineEndSelected(line.end(true))) { 00626 draw = true; 00627 drawBrush = config()->selectionColor(); 00628 } else if (backgroundBrushSet && !m_view->blockSelection()) { 00629 draw = true; 00630 drawBrush = backgroundBrush; 00631 } 00632 00633 if (draw) { 00634 int fillStartX = line.endX() - line.startX() + line.xOffset() - xStart; 00635 int fillStartY = lineHeight() * i; 00636 int width= xEnd - xStart - fillStartX; 00637 int height= lineHeight(); 00638 00639 // reverse X for right-aligned lines 00640 if (range->layout()->textOption().alignment() == Qt::AlignRight) 00641 fillStartX = 0; 00642 00643 if (width > 0) { 00644 QRect area(fillStartX, fillStartY, width, height); 00645 paint.fillRect(area, drawBrush); 00646 } 00647 } 00648 } 00649 // Draw indent lines 00650 if (showIndentLines() && i == 0) 00651 { 00652 const int w = spaceWidth(); 00653 const int lastIndentColumn = range->textLine()->indentDepth(m_tabWidth); 00654 00655 for (int x = m_indentWidth; x < lastIndentColumn; x += m_indentWidth) 00656 { 00657 paintIndentMarker(paint, x * w + 1 - xStart, range->line()); 00658 } 00659 } 00660 00661 // draw an open box to mark non-breaking spaces 00662 const QString& text = range->textLine()->string(); 00663 int y = lineHeight() * i + fm.ascent() - fm.strikeOutPos(); 00664 int nbSpaceIndex = text.indexOf(nbSpaceChar, line.lineLayout().xToCursor(xStart)); 00665 00666 while (nbSpaceIndex != -1 && nbSpaceIndex < line.endCol()) { 00667 int x = line.lineLayout().cursorToX(nbSpaceIndex); 00668 if (x > xEnd) 00669 break; 00670 paintNonBreakSpace(paint, x - xStart, y); 00671 nbSpaceIndex = text.indexOf(nbSpaceChar, nbSpaceIndex + 1); 00672 } 00673 00674 // Draw tab stops and trailing spaces 00675 if (showTabs() || showTrailingSpaces()) { 00676 if (showTabs()) { 00677 int tabIndex = text.indexOf(tabChar, line.lineLayout().xToCursor(xStart)); 00678 while (tabIndex != -1 && tabIndex < line.endCol()) { 00679 int x = line.lineLayout().cursorToX(tabIndex); 00680 if (x > xEnd) 00681 break; 00682 paintTabstop(paint, x - xStart + spaceWidth()/2.0, y); 00683 tabIndex = text.indexOf(tabChar, tabIndex + 1); 00684 } 00685 } 00686 00687 if (showTrailingSpaces()) { 00688 int spaceIndex = line.endCol() - 1; 00689 int trailingPos = range->textLine()->lastChar(); 00690 if (trailingPos < 0) 00691 trailingPos = 0; 00692 if (spaceIndex >= trailingPos) { 00693 while (spaceIndex >= line.startCol() && text.at(spaceIndex).isSpace()) { 00694 if (text.at(spaceIndex) != '\t' || !showTabs()) 00695 paintTrailingSpace(paint, line.lineLayout().cursorToX(spaceIndex) - xStart + spaceWidth()/2.0, y); 00696 --spaceIndex; 00697 } 00698 } 00699 } 00700 } 00701 } 00702 00703 // draw word-wrap-honor-indent filling 00704 if ( (range->viewLineCount() > 1) && range->shiftX() && (range->shiftX() > xStart) ) 00705 { 00706 if (backgroundBrushSet) 00707 paint.fillRect(0, lineHeight(), range->shiftX() - xStart, lineHeight() * (range->viewLineCount() - 1), 00708 backgroundBrush); 00709 paint.fillRect(0, lineHeight(), range->shiftX() - xStart, lineHeight() * (range->viewLineCount() - 1), 00710 QBrush(config()->wordWrapMarkerColor(), Qt::Dense4Pattern)); 00711 } 00712 00713 // Draw caret 00714 if (drawCaret() && cursor && range->includesCursor(*cursor)) { 00715 // Make the caret the desired width 00716 int caretWidth = 2; 00717 QTextLine line = range->layout()->lineForTextPosition(cursor->column()); 00718 if (caretStyle() == Block || (m_view->viInputMode() && m_view->getCurrentViMode() != InsertMode)) { 00719 if (line.isValid() && cursor->column() < range->length()) { 00720 caretWidth = int(line.cursorToX(cursor->column() + 1) - line.cursorToX(cursor->column())); 00721 if (caretWidth < 0) 00722 caretWidth = -caretWidth; 00723 00724 } else { 00725 caretWidth = spaceWidth(); 00726 } 00727 } 00728 00729 QColor c; 00730 // Could actually use the real highlighting system for this... would be slower but more accurate for corner cases 00731 if (m_caretOverrideColor.isValid()) { 00732 c = m_caretOverrideColor; 00733 00734 } else { 00735 // search for the FormatRange that includes the cursor 00736 foreach (const QTextLayout::FormatRange &r, range->layout()->additionalFormats()) { 00737 if ( (r.start <= cursor->column() ) && ( (r.start + r.length) > cursor->column()) ) { 00738 // check for Qt::NoBrush, as the returned color is black() and no invalid QColor 00739 QBrush foregroundBrush = r.format.foreground(); 00740 if (foregroundBrush != Qt::NoBrush) { 00741 c = r.format.foreground().color(); 00742 } 00743 break; 00744 } 00745 } 00746 00747 // still no color found, fall back to default style 00748 if (!c.isValid()) 00749 c = attribute(KTextEditor::HighlightInterface::dsNormal)->foreground().color(); 00750 } 00751 00752 // make it possible to see the selected character in the vi input mode's normal/visual mode 00753 if (m_view->viInputMode() && m_view->getCurrentViMode() != InsertMode ) { 00754 c.setAlpha(128); 00755 } 00756 00757 if (cursor->column() <= range->length()) { 00758 paint.save(); 00759 paint.setPen(QPen(c, caretWidth)); 00760 00761 // Clip the caret - Qt's caret has a habit of intruding onto other lines 00762 paint.setClipRect(0, line.lineNumber() * lineHeight(), xEnd - xStart, lineHeight()); 00763 00764 range->layout()->drawCursor(&paint, QPoint(-xStart,0), cursor->column(), caretWidth); 00765 00766 paint.restore(); 00767 00768 } else { 00769 // Off the end of the line... must be block mode. Draw the caret ourselves. 00770 const KateTextLayout& lastLine = range->viewLine(range->viewLineCount() - 1); 00771 int x = range->widthOfLastLine() + spaceWidth() * (cursor->column() - range->length()); 00772 if ( (x >= xStart) && (x <= xEnd)) 00773 paint.fillRect(x-xStart, (int)lastLine.lineLayout().y(), caretWidth, lineHeight(), c); 00774 } 00775 } 00776 } 00777 00778 // Draws the dashed underline at the start of a folded block of text. 00779 if (range->startsInvisibleBlock()) { 00780 paint.setRenderHint(QPainter::Antialiasing, false); 00781 QPen pen(config()->wordWrapMarkerColor()); 00782 pen.setCosmetic(true); 00783 pen.setStyle(Qt::DashLine); 00784 pen.setDashOffset(xStart); 00785 paint.setPen(pen); 00786 paint.drawLine(0, (lineHeight() * range->viewLineCount()) - 1, xEnd - xStart, (lineHeight() * range->viewLineCount()) - 1); 00787 } 00788 00789 // show word wrap marker if desirable 00790 if ((!isPrinterFriendly()) && config()->wordWrapMarker() && QFontInfo(config()->font()).fixedPitch()) 00791 { 00792 paint.setRenderHint(QPainter::Antialiasing, false); 00793 paint.setPen( config()->wordWrapMarkerColor() ); 00794 int _x = m_doc->config()->wordWrapAt() * fm.width('x') - xStart; 00795 paint.drawLine( _x,0,_x,lineHeight() ); 00796 } 00797 } 00798 00799 const QFont& KateRenderer::currentFont() const 00800 { 00801 return config()->font(); 00802 } 00803 00804 const QFontMetrics& KateRenderer::currentFontMetrics() const 00805 { 00806 return config()->fontMetrics(); 00807 } 00808 00809 uint KateRenderer::fontHeight() 00810 { 00811 return config()->fontMetrics().height(); 00812 } 00813 00814 uint KateRenderer::documentHeight() 00815 { 00816 return m_doc->lines() * lineHeight(); 00817 } 00818 00819 int KateRenderer::lineHeight() 00820 { 00821 return fontHeight(); // for now 00822 } 00823 00824 bool KateRenderer::getSelectionBounds(int line, int lineLength, int &start, int &end) const 00825 { 00826 bool hasSel = false; 00827 00828 if (m_view->selection() && !m_view->blockSelectionMode()) 00829 { 00830 if (m_view->lineIsSelection(line)) 00831 { 00832 start = m_view->selectionRange().start().column(); 00833 end = m_view->selectionRange().end().column(); 00834 hasSel = true; 00835 } 00836 else if (line == m_view->selectionRange().start().line()) 00837 { 00838 start = m_view->selectionRange().start().column(); 00839 end = lineLength; 00840 hasSel = true; 00841 } 00842 else if (m_view->selectionRange().containsLine(line)) 00843 { 00844 start = 0; 00845 end = lineLength; 00846 hasSel = true; 00847 } 00848 else if (line == m_view->selectionRange().end().line()) 00849 { 00850 start = 0; 00851 end = m_view->selectionRange().end().column(); 00852 hasSel = true; 00853 } 00854 } 00855 else if (m_view->lineHasSelected(line)) 00856 { 00857 start = m_view->selectionRange().start().column(); 00858 end = m_view->selectionRange().end().column(); 00859 hasSel = true; 00860 } 00861 00862 if (start > end) { 00863 int temp = end; 00864 end = start; 00865 start = temp; 00866 } 00867 00868 return hasSel; 00869 } 00870 00871 void KateRenderer::updateConfig () 00872 { 00873 // update the attibute list pointer 00874 updateAttributes (); 00875 00876 if (m_view) 00877 m_view->updateRendererConfig(); 00878 } 00879 00880 uint KateRenderer::spaceWidth() const 00881 { 00882 return config()->fontMetrics().width(spaceChar); 00883 } 00884 00885 void KateRenderer::layoutLine(KateLineLayoutPtr lineLayout, int maxwidth, bool cacheLayout) const 00886 { 00887 // if maxwidth == -1 we have no wrap 00888 00889 Kate::TextLine textLine = lineLayout->textLine(); 00890 Q_ASSERT(textLine); 00891 00892 QTextLayout* l = lineLayout->layout(); 00893 if (!l) { 00894 l = new QTextLayout(textLine->string(), config()->font()); 00895 } else { 00896 l->setText(textLine->string()); 00897 l->setFont(config()->font()); 00898 } 00899 00900 l->setCacheEnabled(cacheLayout); 00901 00902 // Initial setup of the QTextLayout. 00903 00904 // Tab width 00905 QTextOption opt; 00906 opt.setFlags(QTextOption::IncludeTrailingSpaces); 00907 opt.setTabStop(m_tabWidth * config()->fontMetrics().width(spaceChar)); 00908 opt.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); 00909 00910 // Find the first strong character in the string. 00911 // If it is an RTL character, set the base layout direction of the string to RTL. 00912 // 00913 // See http://www.unicode.org/reports/tr9/#The_Paragraph_Level (Sections P2 & P3). 00914 // Qt's text renderer ("scribe") version 4.2 assumes a "higher-level protocol" 00915 // (such as KatePart) will specify the paragraph level, so it does not apply P2 & P3 00916 // by itself. If this ever change in Qt, the next code block could be removed. 00917 if (isLineRightToLeft(lineLayout)) { 00918 opt.setAlignment( Qt::AlignRight ); 00919 opt.setTextDirection( Qt::RightToLeft ); 00920 } 00921 else { 00922 opt.setAlignment( Qt::AlignLeft ); 00923 opt.setTextDirection( Qt::LeftToRight ); 00924 } 00925 00926 l->setTextOption(opt); 00927 00928 // Syntax highlighting, inbuilt and arbitrary 00929 l->setAdditionalFormats(decorationsForLine(textLine, lineLayout->line())); 00930 00931 // Begin layouting 00932 l->beginLayout(); 00933 00934 int height = 0; 00935 int shiftX = 0; 00936 00937 bool needShiftX = (maxwidth != -1) 00938 && (m_view->config()->dynWordWrapAlignIndent() > 0); 00939 00940 forever { 00941 QTextLine line = l->createLine(); 00942 if (!line.isValid()) 00943 break; 00944 00945 if (maxwidth > 0) 00946 line.setLineWidth(maxwidth); 00947 00948 line.setPosition(QPoint(line.lineNumber() ? shiftX : 0, height)); 00949 00950 if (needShiftX) { 00951 needShiftX = false; 00952 // Determine x offset for subsequent-lines-of-paragraph indenting 00953 int pos = textLine->nextNonSpaceChar(0); 00954 00955 if (pos > 0) { 00956 shiftX = (int)line.cursorToX(pos); 00957 } 00958 00959 // check for too deep shift value and limit if necessary 00960 if (shiftX > ((double)maxwidth / 100 * m_view->config()->dynWordWrapAlignIndent())) 00961 shiftX = 0; 00962 00963 // if shiftX > 0, the maxwidth has to adapted 00964 maxwidth -= shiftX; 00965 00966 lineLayout->setShiftX(shiftX); 00967 } 00968 00969 height += config()->fontMetrics().height(); 00970 } 00971 00972 l->endLayout(); 00973 00974 lineLayout->setLayout(l); 00975 } 00976 00977 00978 // 1) QString::isRightToLeft() sux 00979 // 2) QString::isRightToLeft() is marked as internal (WTF?) 00980 // 3) QString::isRightToLeft() does not seem to work on my setup 00981 // 4) isStringRightToLeft() should behave much better than QString::isRightToLeft() therefore: 00982 // 5) isStringRightToLeft() kicks ass 00983 bool KateRenderer::isLineRightToLeft( KateLineLayoutPtr lineLayout ) const 00984 { 00985 QString s = lineLayout->textLine()->string(); 00986 int i = 0; 00987 00988 // borrowed from QString::updateProperties() 00989 while( i != s.length() ) 00990 { 00991 QChar c = s.at(i); 00992 00993 switch(c.direction()) { 00994 case QChar::DirL: 00995 case QChar::DirLRO: 00996 case QChar::DirLRE: 00997 return false; 00998 00999 case QChar::DirR: 01000 case QChar::DirAL: 01001 case QChar::DirRLO: 01002 case QChar::DirRLE: 01003 return true; 01004 01005 default: 01006 break; 01007 } 01008 i ++; 01009 } 01010 01011 return false; 01012 #if 0 01013 // or should we use the direction of the widget? 01014 QWidget* display = qobject_cast<QWidget*>(view()->parent()); 01015 if (!display) 01016 return false; 01017 return display->layoutDirection() == Qt::RightToLeft; 01018 #endif 01019 } 01020 01021 int KateRenderer::cursorToX(const KateTextLayout& range, int col) const 01022 { 01023 return cursorToX(range, KTextEditor::Cursor(range.line(), col)); 01024 } 01025 01026 int KateRenderer::cursorToX(const KateTextLayout& range, const KTextEditor::Cursor & pos) const 01027 { 01028 Q_ASSERT(range.isValid()); 01029 01030 return (int)range.lineLayout().cursorToX(pos.column()); 01031 } 01032 01033 int KateRenderer::cursorToX(const KateTextLayout& range, const KTextEditor::Cursor & pos, bool returnPastLine) const 01034 { 01035 int x = cursorToX(range, pos); 01036 int over = pos.column() - range.endCol(); 01037 01038 if (returnPastLine && over > 0) 01039 x += over * spaceWidth(); 01040 01041 return x; 01042 } 01043 01044 KTextEditor::Cursor KateRenderer::xToCursor(const KateTextLayout & range, int x, bool returnPastLine ) const 01045 { 01046 Q_ASSERT(range.isValid()); 01047 KTextEditor::Cursor ret(range.line(), range.lineLayout().xToCursor(x)); 01048 01049 // TODO wrong for RTL lines? 01050 if (returnPastLine && range.endCol(true) == -1 && x > range.width() + range.xOffset()) 01051 ret.setColumn(ret.column() + ((x - (range.width() + range.xOffset())) / spaceWidth())); 01052 01053 return ret; 01054 } 01055 01056 void KateRenderer::setCaretOverrideColor(const QColor& color) 01057 { 01058 m_caretOverrideColor = color; 01059 } 01060 01061 // kate: space-indent on; indent-width 2; replace-tabs on;
KDE 4.6 API Reference