• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

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;

Kate

Skip menu "Kate"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal