1 /* 2 * Copyright (C) 2009, 2010, 2011, 2012, 2013 Nicolas Bonnefon and other contributors 3 * 4 * This file is part of glogg. 5 * 6 * glogg is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * glogg is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with glogg. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 // This file implements the AbstractLogView base class. 21 // Most of the actual drawing and event management common to the two views 22 // is implemented in this class. The class only calls protected virtual 23 // functions when view specific behaviour is desired, using the template 24 // pattern. 25 26 #include <iostream> 27 #include <cassert> 28 29 #include <QApplication> 30 #include <QClipboard> 31 #include <QFile> 32 #include <QRect> 33 #include <QPaintEvent> 34 #include <QPainter> 35 #include <QFontMetrics> 36 #include <QScrollBar> 37 #include <QMenu> 38 #include <QAction> 39 #include <QtCore> 40 41 #include "log.h" 42 43 #include "persistentinfo.h" 44 #include "filterset.h" 45 #include "logmainview.h" 46 #include "quickfind.h" 47 #include "quickfindpattern.h" 48 #include "overview.h" 49 #include "configuration.h" 50 51 namespace { 52 int mapPullToFollowLength( int length ); 53 }; 54 55 namespace { 56 57 int countDigits( quint64 n ) 58 { 59 if (n == 0) 60 return 1; 61 62 // We must force the compiler to not store intermediate results 63 // in registers because this causes incorrect result on some 64 // systems under optimizations level >0. For the skeptical: 65 // 66 // #include <math.h> 67 // #include <stdlib.h> 68 // int main(int argc, char **argv) { 69 // (void)argc; 70 // long long int n = atoll(argv[1]); 71 // return floor( log( n ) / log( 10 ) + 1 ); 72 // } 73 // 74 // This is on Thinkpad T60 (Genuine Intel(R) CPU T2300). 75 // $ g++ --version 76 // g++ (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3 77 // $ g++ -O0 -Wall -W -o math math.cpp -lm; ./math 10; echo $? 78 // 2 79 // $ g++ -O1 -Wall -W -o math math.cpp -lm; ./math 10; echo $? 80 // 1 81 // 82 // A fix is to (1) explicitly place intermediate results in 83 // variables *and* (2) [A] mark them as 'volatile', or [B] pass 84 // -ffloat-store to g++ (note that approach [A] is more portable). 85 86 volatile qreal ln_n = qLn( n ); 87 volatile qreal ln_10 = qLn( 10 ); 88 volatile qreal lg_n = ln_n / ln_10; 89 volatile qreal lg_n_1 = lg_n + 1; 90 volatile qreal fl_lg_n_1 = qFloor( lg_n_1 ); 91 92 return fl_lg_n_1; 93 } 94 95 } // anon namespace 96 97 98 LineChunk::LineChunk( int first_col, int last_col, ChunkType type ) 99 { 100 // LOG(logDEBUG) << "new LineChunk: " << first_col << " " << last_col; 101 102 start_ = first_col; 103 end_ = last_col; 104 type_ = type; 105 } 106 107 QList<LineChunk> LineChunk::select( int sel_start, int sel_end ) const 108 { 109 QList<LineChunk> list; 110 111 if ( ( sel_start < start_ ) && ( sel_end < start_ ) ) { 112 // Selection BEFORE this chunk: no change 113 list << LineChunk( *this ); 114 } 115 else if ( sel_start > end_ ) { 116 // Selection AFTER this chunk: no change 117 list << LineChunk( *this ); 118 } 119 else /* if ( ( sel_start >= start_ ) && ( sel_end <= end_ ) ) */ 120 { 121 // We only want to consider what's inside THIS chunk 122 sel_start = qMax( sel_start, start_ ); 123 sel_end = qMin( sel_end, end_ ); 124 125 if ( sel_start > start_ ) 126 list << LineChunk( start_, sel_start - 1, type_ ); 127 list << LineChunk( sel_start, sel_end, Selected ); 128 if ( sel_end < end_ ) 129 list << LineChunk( sel_end + 1, end_, type_ ); 130 } 131 132 return list; 133 } 134 135 inline void LineDrawer::addChunk( int first_col, int last_col, 136 QColor fore, QColor back ) 137 { 138 if ( first_col < 0 ) 139 first_col = 0; 140 int length = last_col - first_col + 1; 141 if ( length > 0 ) { 142 list << Chunk ( first_col, length, fore, back ); 143 } 144 } 145 146 inline void LineDrawer::addChunk( const LineChunk& chunk, 147 QColor fore, QColor back ) 148 { 149 int first_col = chunk.start(); 150 int last_col = chunk.end(); 151 152 addChunk( first_col, last_col, fore, back ); 153 } 154 155 inline void LineDrawer::draw( QPainter& painter, 156 int initialXPos, int initialYPos, 157 int line_width, const QString& line, 158 int leftExtraBackgroundPx ) 159 { 160 QFontMetrics fm = painter.fontMetrics(); 161 const int fontHeight = fm.height(); 162 const int fontAscent = fm.ascent(); 163 // For some reason on Qt 4.8.2 for Win, maxWidth() is wrong but the 164 // following give the right result, not sure why: 165 const int fontWidth = fm.width( QChar('a') ); 166 167 int xPos = initialXPos; 168 int yPos = initialYPos; 169 170 foreach ( Chunk chunk, list ) { 171 // Draw each chunk 172 // LOG(logDEBUG) << "Chunk: " << chunk.start() << " " << chunk.length(); 173 QString cutline = line.mid( chunk.start(), chunk.length() ); 174 const int chunk_width = cutline.length() * fontWidth; 175 if ( xPos == initialXPos ) { 176 // First chunk, we extend the left background a bit, 177 // it looks prettier. 178 painter.fillRect( xPos - leftExtraBackgroundPx, yPos, 179 chunk_width + leftExtraBackgroundPx, 180 fontHeight, chunk.backColor() ); 181 } 182 else { 183 // other chunks... 184 painter.fillRect( xPos, yPos, chunk_width, 185 fontHeight, chunk.backColor() ); 186 } 187 painter.setPen( chunk.foreColor() ); 188 painter.drawText( xPos, yPos + fontAscent, cutline ); 189 xPos += chunk_width; 190 } 191 192 // Draw the empty block at the end of the line 193 int blank_width = line_width - xPos; 194 195 if ( blank_width > 0 ) 196 painter.fillRect( xPos, yPos, blank_width, fontHeight, backColor_ ); 197 } 198 199 const int DigitsBuffer::timeout_ = 2000; 200 201 DigitsBuffer::DigitsBuffer() : QObject() 202 { 203 } 204 205 void DigitsBuffer::reset() 206 { 207 LOG(logDEBUG) << "DigitsBuffer::reset()"; 208 209 timer_.stop(); 210 digits_.clear(); 211 } 212 213 void DigitsBuffer::add( char character ) 214 { 215 LOG(logDEBUG) << "DigitsBuffer::add()"; 216 217 digits_.append( QChar( character ) ); 218 timer_.start( timeout_ , this ); 219 } 220 221 int DigitsBuffer::content() 222 { 223 int result = digits_.toInt(); 224 reset(); 225 226 return result; 227 } 228 229 void DigitsBuffer::timerEvent( QTimerEvent* event ) 230 { 231 if ( event->timerId() == timer_.timerId() ) { 232 reset(); 233 } 234 else { 235 QObject::timerEvent( event ); 236 } 237 } 238 239 // Graphic parameters 240 constexpr int AbstractLogView::OVERVIEW_WIDTH = 27; 241 constexpr int AbstractLogView::HOOK_THRESHOLD = 100; 242 243 AbstractLogView::AbstractLogView(const AbstractLogData* newLogData, 244 const QuickFindPattern* const quickFindPattern, QWidget* parent) : 245 QAbstractScrollArea( parent ), 246 lineNumbersVisible_( false ), 247 selectionStartPos_(), 248 selectionCurrentEndPos_(), 249 autoScrollTimer_(), 250 selection_(), 251 quickFindPattern_( quickFindPattern ), 252 quickFind_( newLogData, &selection_, quickFindPattern ), 253 followElasticHook_( HOOK_THRESHOLD ) 254 { 255 logData = newLogData; 256 257 followMode_ = false; 258 259 selectionStarted_ = false; 260 markingClickInitiated_ = false; 261 262 firstLine = 0; 263 lastLine = 0; 264 firstCol = 0; 265 266 overview_ = NULL; 267 overviewWidget_ = NULL; 268 269 // Display 270 nbDigitsInLineNumber_ = 0; 271 leftMarginPx_ = 0; 272 273 // Fonts 274 charWidth_ = 1; 275 charHeight_ = 1; 276 277 // Create the viewport QWidget 278 setViewport( 0 ); 279 280 setAttribute( Qt::WA_StaticContents ); // Does it work? 281 282 // Hovering 283 setMouseTracking( true ); 284 lastHoveredLine_ = -1; 285 286 // Init the popup menu 287 createMenu(); 288 289 // Signals 290 connect( quickFindPattern_, SIGNAL( patternUpdated() ), 291 this, SLOT ( handlePatternUpdated() ) ); 292 connect( verticalScrollBar(), SIGNAL( sliderMoved( int ) ), 293 this, SIGNAL( followDisabled() ) ); 294 connect( &quickFind_, SIGNAL( notify( const QFNotification& ) ), 295 this, SIGNAL( notifyQuickFind( const QFNotification& ) ) ); 296 connect( &quickFind_, SIGNAL( clearNotification() ), 297 this, SIGNAL( clearQuickFindNotification() ) ); 298 connect( &followElasticHook_, SIGNAL( lengthChanged() ), 299 this, SLOT( repaint() ) ); 300 } 301 302 AbstractLogView::~AbstractLogView() 303 { 304 } 305 306 307 // 308 // Received events 309 // 310 311 void AbstractLogView::changeEvent( QEvent* changeEvent ) 312 { 313 QAbstractScrollArea::changeEvent( changeEvent ); 314 315 // Stop the timer if the widget becomes inactive 316 if ( changeEvent->type() == QEvent::ActivationChange ) { 317 if ( ! isActiveWindow() ) 318 autoScrollTimer_.stop(); 319 } 320 viewport()->update(); 321 } 322 323 void AbstractLogView::mousePressEvent( QMouseEvent* mouseEvent ) 324 { 325 static std::shared_ptr<Configuration> config = 326 Persistent<Configuration>( "settings" ); 327 328 if ( mouseEvent->button() == Qt::LeftButton ) 329 { 330 int line = convertCoordToLine( mouseEvent->y() ); 331 332 if ( mouseEvent->modifiers() & Qt::ShiftModifier ) 333 { 334 selection_.selectRangeFromPrevious( line ); 335 emit updateLineNumber( line ); 336 update(); 337 } 338 else 339 { 340 if ( mouseEvent->x() < bulletZoneWidthPx_ ) { 341 // Mark a line if it is clicked in the left margin 342 // (only if click and release in the same area) 343 markingClickInitiated_ = true; 344 markingClickLine_ = line; 345 } 346 else { 347 // Select the line, and start a selection 348 if ( line < logData->getNbLine() ) { 349 selection_.selectLine( line ); 350 emit updateLineNumber( line ); 351 emit newSelection( line ); 352 } 353 354 // Remember the click in case we're starting a selection 355 selectionStarted_ = true; 356 selectionStartPos_ = convertCoordToFilePos( mouseEvent->pos() ); 357 selectionCurrentEndPos_ = selectionStartPos_; 358 } 359 } 360 361 // Invalidate our cache 362 textAreaCache_.invalid_ = true; 363 } 364 else if ( mouseEvent->button() == Qt::RightButton ) 365 { 366 // Prepare the popup depending on selection type 367 if ( selection_.isSingleLine() ) { 368 copyAction_->setText( "&Copy this line" ); 369 } 370 else { 371 copyAction_->setText( "&Copy" ); 372 copyAction_->setStatusTip( tr("Copy the selection") ); 373 } 374 375 if ( selection_.isPortion() ) { 376 findNextAction_->setEnabled( true ); 377 findPreviousAction_->setEnabled( true ); 378 addToSearchAction_->setEnabled( true ); 379 } 380 else { 381 findNextAction_->setEnabled( false ); 382 findPreviousAction_->setEnabled( false ); 383 addToSearchAction_->setEnabled( false ); 384 } 385 386 // "Add to search" only makes sense in regexp mode 387 if ( config->mainRegexpType() != ExtendedRegexp ) 388 addToSearchAction_->setEnabled( false ); 389 390 // Display the popup (blocking) 391 popupMenu_->exec( QCursor::pos() ); 392 } 393 394 emit activity(); 395 } 396 397 void AbstractLogView::mouseMoveEvent( QMouseEvent* mouseEvent ) 398 { 399 // Selection implementation 400 if ( selectionStarted_ ) 401 { 402 // Invalidate our cache 403 textAreaCache_.invalid_ = true; 404 405 QPoint thisEndPos = convertCoordToFilePos( mouseEvent->pos() ); 406 if ( thisEndPos != selectionCurrentEndPos_ ) 407 { 408 // Are we on a different line? 409 if ( selectionStartPos_.y() != thisEndPos.y() ) 410 { 411 if ( thisEndPos.y() != selectionCurrentEndPos_.y() ) 412 { 413 // This is a 'range' selection 414 selection_.selectRange( selectionStartPos_.y(), 415 thisEndPos.y() ); 416 emit updateLineNumber( thisEndPos.y() ); 417 update(); 418 } 419 } 420 // So we are on the same line. Are we moving horizontaly? 421 else if ( thisEndPos.x() != selectionCurrentEndPos_.x() ) 422 { 423 // This is a 'portion' selection 424 selection_.selectPortion( thisEndPos.y(), 425 selectionStartPos_.x(), thisEndPos.x() ); 426 update(); 427 } 428 // On the same line, and moving vertically then 429 else 430 { 431 // This is a 'line' selection 432 selection_.selectLine( thisEndPos.y() ); 433 emit updateLineNumber( thisEndPos.y() ); 434 update(); 435 } 436 selectionCurrentEndPos_ = thisEndPos; 437 438 // Do we need to scroll while extending the selection? 439 QRect visible = viewport()->rect(); 440 if ( visible.contains( mouseEvent->pos() ) ) 441 autoScrollTimer_.stop(); 442 else if ( ! autoScrollTimer_.isActive() ) 443 autoScrollTimer_.start( 100, this ); 444 } 445 } 446 else { 447 considerMouseHovering( mouseEvent->x(), mouseEvent->y() ); 448 } 449 } 450 451 void AbstractLogView::mouseReleaseEvent( QMouseEvent* mouseEvent ) 452 { 453 if ( markingClickInitiated_ ) { 454 markingClickInitiated_ = false; 455 int line = convertCoordToLine( mouseEvent->y() ); 456 if ( line == markingClickLine_ ) { 457 // Invalidate our cache 458 textAreaCache_.invalid_ = true; 459 460 emit markLine( line ); 461 } 462 } 463 else { 464 selectionStarted_ = false; 465 if ( autoScrollTimer_.isActive() ) 466 autoScrollTimer_.stop(); 467 updateGlobalSelection(); 468 } 469 } 470 471 void AbstractLogView::mouseDoubleClickEvent( QMouseEvent* mouseEvent ) 472 { 473 if ( mouseEvent->button() == Qt::LeftButton ) 474 { 475 // Invalidate our cache 476 textAreaCache_.invalid_ = true; 477 478 const QPoint pos = convertCoordToFilePos( mouseEvent->pos() ); 479 selectWordAtPosition( pos ); 480 } 481 482 emit activity(); 483 } 484 485 void AbstractLogView::timerEvent( QTimerEvent* timerEvent ) 486 { 487 if ( timerEvent->timerId() == autoScrollTimer_.timerId() ) { 488 QRect visible = viewport()->rect(); 489 const QPoint globalPos = QCursor::pos(); 490 const QPoint pos = viewport()->mapFromGlobal( globalPos ); 491 QMouseEvent ev( QEvent::MouseMove, pos, globalPos, Qt::LeftButton, 492 Qt::LeftButton, Qt::NoModifier ); 493 mouseMoveEvent( &ev ); 494 int deltaX = qMax( pos.x() - visible.left(), 495 visible.right() - pos.x() ) - visible.width(); 496 int deltaY = qMax( pos.y() - visible.top(), 497 visible.bottom() - pos.y() ) - visible.height(); 498 int delta = qMax( deltaX, deltaY ); 499 500 if ( delta >= 0 ) { 501 if ( delta < 7 ) 502 delta = 7; 503 int timeout = 4900 / ( delta * delta ); 504 autoScrollTimer_.start( timeout, this ); 505 506 if ( deltaX > 0 ) 507 horizontalScrollBar()->triggerAction( 508 pos.x() <visible.center().x() ? 509 QAbstractSlider::SliderSingleStepSub : 510 QAbstractSlider::SliderSingleStepAdd ); 511 512 if ( deltaY > 0 ) 513 verticalScrollBar()->triggerAction( 514 pos.y() <visible.center().y() ? 515 QAbstractSlider::SliderSingleStepSub : 516 QAbstractSlider::SliderSingleStepAdd ); 517 } 518 } 519 QAbstractScrollArea::timerEvent( timerEvent ); 520 } 521 522 void AbstractLogView::keyPressEvent( QKeyEvent* keyEvent ) 523 { 524 LOG(logDEBUG4) << "keyPressEvent received"; 525 bool controlModifier = (keyEvent->modifiers() & Qt::ControlModifier) == Qt::ControlModifier; 526 bool shiftModifier = (keyEvent->modifiers() & Qt::ShiftModifier) == Qt::ShiftModifier; 527 528 if ( keyEvent->key() == Qt::Key_Left ) 529 horizontalScrollBar()->triggerAction(QScrollBar::SliderPageStepSub); 530 else if ( keyEvent->key() == Qt::Key_Right ) 531 horizontalScrollBar()->triggerAction(QScrollBar::SliderPageStepAdd); 532 else if ( keyEvent->key() == Qt::Key_Home && !controlModifier) 533 jumpToStartOfLine(); 534 else if ( keyEvent->key() == Qt::Key_End && !controlModifier) 535 jumpToRightOfScreen(); 536 else if ( (keyEvent->key() == Qt::Key_PageDown && controlModifier) 537 || (keyEvent->key() == Qt::Key_End && controlModifier) ) 538 { 539 emit followDisabled(); // duplicate of 'G' action. 540 selection_.selectLine( logData->getNbLine() - 1 ); 541 emit updateLineNumber( logData->getNbLine() - 1 ); 542 jumpToBottom(); 543 } 544 else if ( (keyEvent->key() == Qt::Key_PageUp && controlModifier) 545 || (keyEvent->key() == Qt::Key_Home && controlModifier) ) 546 { 547 emit followDisabled(); // like 'g' but 0 input first line action. 548 selectAndDisplayLine( 0 ); 549 emit updateLineNumber( 0 ); 550 } 551 else if ( keyEvent->key() == Qt::Key_F3 && !shiftModifier ) 552 searchNext(); // duplicate of 'n' action. 553 else if ( keyEvent->key() == Qt::Key_F3 && shiftModifier ) 554 searchPrevious(); // duplicate of 'N' action. 555 else { 556 const char character = (keyEvent->text())[0].toLatin1(); 557 558 if ( keyEvent->modifiers() == Qt::NoModifier && 559 ( character >= '0' ) && ( character <= '9' ) ) { 560 // Adds the digit to the timed buffer 561 digitsBuffer_.add( character ); 562 } 563 else { 564 switch ( (keyEvent->text())[0].toLatin1() ) { 565 case 'j': 566 { 567 int delta = qMax( 1, digitsBuffer_.content() ); 568 emit followDisabled(); 569 //verticalScrollBar()->triggerAction( 570 //QScrollBar::SliderSingleStepAdd); 571 moveSelection( delta ); 572 break; 573 } 574 case 'k': 575 { 576 int delta = qMin( -1, - digitsBuffer_.content() ); 577 emit followDisabled(); 578 //verticalScrollBar()->triggerAction( 579 //QScrollBar::SliderSingleStepSub); 580 moveSelection( delta ); 581 break; 582 } 583 case 'h': 584 horizontalScrollBar()->triggerAction( 585 QScrollBar::SliderSingleStepSub); 586 break; 587 case 'l': 588 horizontalScrollBar()->triggerAction( 589 QScrollBar::SliderSingleStepAdd); 590 break; 591 case '0': 592 jumpToStartOfLine(); 593 break; 594 case '$': 595 jumpToEndOfLine(); 596 break; 597 case 'g': 598 { 599 int newLine = qMax( 0, digitsBuffer_.content() - 1 ); 600 if ( newLine >= logData->getNbLine() ) 601 newLine = logData->getNbLine() - 1; 602 emit followDisabled(); 603 selectAndDisplayLine( newLine ); 604 emit updateLineNumber( newLine ); 605 break; 606 } 607 case 'G': 608 emit followDisabled(); 609 selection_.selectLine( logData->getNbLine() - 1 ); 610 emit updateLineNumber( logData->getNbLine() - 1 ); 611 jumpToBottom(); 612 break; 613 case 'n': 614 emit searchNext(); 615 break; 616 case 'N': 617 emit searchPrevious(); 618 break; 619 case '*': 620 // Use the selected 'word' and search forward 621 findNextSelected(); 622 break; 623 case '#': 624 // Use the selected 'word' and search backward 625 findPreviousSelected(); 626 break; 627 default: 628 keyEvent->ignore(); 629 } 630 } 631 } 632 633 if ( keyEvent->isAccepted() ) { 634 emit activity(); 635 } 636 else { 637 QAbstractScrollArea::keyPressEvent( keyEvent ); 638 } 639 } 640 641 void AbstractLogView::wheelEvent( QWheelEvent* wheelEvent ) 642 { 643 emit followDisabled(); 644 emit activity(); 645 646 // LOG(logDEBUG) << "wheelEvent"; 647 648 if ( verticalScrollBar()->value() == verticalScrollBar()->maximum() ) { 649 // LOG(logDEBUG) << "Elastic " << wheelEvent->pixelDelta().y(); 650 followElasticHook_.move( - wheelEvent->pixelDelta().y() ); 651 } 652 653 // LOG(logDEBUG) << "Length = " << followElasticHook_.length(); 654 if ( followElasticHook_.length() == 0 ) { 655 QAbstractScrollArea::wheelEvent( wheelEvent ); 656 } 657 } 658 659 void AbstractLogView::resizeEvent( QResizeEvent* ) 660 { 661 if ( logData == NULL ) 662 return; 663 664 LOG(logDEBUG) << "resizeEvent received"; 665 666 updateDisplaySize(); 667 } 668 669 void AbstractLogView::scrollContentsBy( int dx, int dy ) 670 { 671 LOG(logDEBUG) << "scrollContentsBy received " << dy; 672 673 /* 674 LineNumber visible_lines = std::max( 675 static_cast<LineNumber>( logData->getNbLine() ), 676 static_cast<LineNumber>( getNbVisibleLines() ) ); 677 firstLine = std::min( std::max( firstLine - dy, 0LL ), 678 logData->getNbLine() - visible_lines ); 679 680 LOG(logDEBUG) << "scrollContentsBy " << visible_lines << " " << firstLine; 681 */ 682 683 firstLine = std::max( firstLine - dy, 0LL ); 684 firstCol = (firstCol - dx) > 0 ? firstCol - dx : 0; 685 lastLine = firstLine + getNbVisibleLines(); 686 687 // Update the overview if we have one 688 if ( overview_ != NULL ) 689 overview_->updateCurrentPosition( firstLine, lastLine ); 690 691 // Are we hovering over a new line? 692 const QPoint mouse_pos = mapFromGlobal( QCursor::pos() ); 693 considerMouseHovering( mouse_pos.x(), mouse_pos.y() ); 694 695 // Redraw 696 update(); 697 } 698 699 void AbstractLogView::paintEvent( QPaintEvent* paintEvent ) 700 { 701 QRect invalidRect = paintEvent->rect(); 702 if ( (invalidRect.isEmpty()) || (logData == NULL) ) 703 return; 704 705 LOG(logDEBUG4) << "paintEvent received, firstLine=" << firstLine 706 << " lastLine=" << lastLine << 707 " rect: " << invalidRect.topLeft().x() << 708 ", " << invalidRect.topLeft().y() << 709 ", " << invalidRect.bottomRight().x() << 710 ", " << invalidRect.bottomRight().y(); 711 712 #ifdef GLOGG_PERF_MEASURE_FPS 713 static uint32_t maxline = logData->getNbLine(); 714 if ( ! perfCounter_.addEvent() && logData->getNbLine() > maxline ) { 715 LOG(logWARNING) << "Redraw per second: " << perfCounter_.readAndReset() 716 << " lines: " << logData->getNbLine(); 717 perfCounter_.addEvent(); 718 maxline = logData->getNbLine(); 719 } 720 #endif 721 722 auto start = std::chrono::system_clock::now(); 723 724 // Can we use our cache? 725 if ( ( ! textAreaCache_.invalid_ ) 726 && ( textAreaCache_.first_line_ == firstLine ) 727 && ( textAreaCache_.last_line_ == lastLine ) ) { 728 } 729 else { 730 // Full redraw 731 drawTextArea( &textAreaCache_.pixmap_ ); 732 733 textAreaCache_.invalid_ = false; 734 textAreaCache_.first_line_ = firstLine; 735 textAreaCache_.last_line_ = lastLine; 736 737 LOG(logDEBUG) << "End of writing " << 738 std::chrono::duration_cast<std::chrono::microseconds> 739 ( std::chrono::system_clock::now() - start ).count(); 740 } 741 742 // Height in pixels of the "pull to follow" bottom bar. 743 const int pullToFollowHeight = mapPullToFollowLength( followElasticHook_.length() ); 744 745 QPainter devicePainter( viewport() ); 746 devicePainter.drawPixmap( 0, - pullToFollowHeight, textAreaCache_.pixmap_ ); 747 748 // Draw the "pull to follow" zone if needed 749 if ( pullToFollowHeight ) { 750 static constexpr int barWidth = 40; 751 const int nbBars = viewport()->width() / (barWidth * 2) + 1; 752 const int bottomOfTextPx = viewport()->height() - pullToFollowHeight; 753 754 devicePainter.setPen( QPen( QColor( 0, 0, 0, 0 ) ) ); 755 devicePainter.setBrush( QBrush( QColor( "lightyellow" ) ) ); 756 757 for ( int i = 0; i < nbBars; ++i ) { 758 QPoint points[4] = { 759 { (i*2+1)*barWidth, bottomOfTextPx }, 760 { 0, bottomOfTextPx + (i*2+1)*barWidth }, 761 { 0, bottomOfTextPx + (i+1)*2*barWidth }, 762 { (i+1)*2*barWidth, bottomOfTextPx } 763 }; 764 devicePainter.drawConvexPolygon( points, 4 ); 765 } 766 } 767 768 LOG(logDEBUG) << "End of repaint " << 769 std::chrono::duration_cast<std::chrono::microseconds> 770 ( std::chrono::system_clock::now() - start ).count(); 771 } 772 773 // These two functions are virtual and this implementation is clearly 774 // only valid for a non-filtered display. 775 // We count on the 'filtered' derived classes to override them. 776 qint64 AbstractLogView::displayLineNumber( int lineNumber ) const 777 { 778 return lineNumber + 1; // show a 1-based index 779 } 780 781 qint64 AbstractLogView::maxDisplayLineNumber() const 782 { 783 return logData->getNbLine(); 784 } 785 786 void AbstractLogView::setOverview( Overview* overview, 787 OverviewWidget* overview_widget ) 788 { 789 overview_ = overview; 790 overviewWidget_ = overview_widget; 791 792 if ( overviewWidget_ ) { 793 connect( overviewWidget_, SIGNAL( lineClicked ( int ) ), 794 this, SIGNAL( followDisabled() ) ); 795 connect( overviewWidget_, SIGNAL( lineClicked ( int ) ), 796 this, SLOT( jumpToLine( int ) ) ); 797 } 798 refreshOverview(); 799 } 800 801 void AbstractLogView::searchUsingFunction( 802 qint64 (QuickFind::*search_function)() ) 803 { 804 emit followDisabled(); 805 806 int line = (quickFind_.*search_function)(); 807 if ( line >= 0 ) { 808 LOG(logDEBUG) << "search " << line; 809 displayLine( line ); 810 emit updateLineNumber( line ); 811 } 812 } 813 814 void AbstractLogView::searchForward() 815 { 816 searchUsingFunction( &QuickFind::searchForward ); 817 } 818 819 void AbstractLogView::searchBackward() 820 { 821 searchUsingFunction( &QuickFind::searchBackward ); 822 } 823 824 void AbstractLogView::incrementallySearchForward() 825 { 826 searchUsingFunction( &QuickFind::incrementallySearchForward ); 827 } 828 829 void AbstractLogView::incrementallySearchBackward() 830 { 831 searchUsingFunction( &QuickFind::incrementallySearchBackward ); 832 } 833 834 void AbstractLogView::incrementalSearchAbort() 835 { 836 quickFind_.incrementalSearchAbort(); 837 emit changeQuickFind( 838 "", 839 QuickFindMux::Forward ); 840 } 841 842 void AbstractLogView::incrementalSearchStop() 843 { 844 quickFind_.incrementalSearchStop(); 845 } 846 847 void AbstractLogView::followSet( bool checked ) 848 { 849 followMode_ = checked; 850 if ( checked ) 851 jumpToBottom(); 852 } 853 854 void AbstractLogView::refreshOverview() 855 { 856 assert( overviewWidget_ ); 857 858 // Create space for the Overview if needed 859 if ( ( getOverview() != NULL ) && getOverview()->isVisible() ) { 860 setViewportMargins( 0, 0, OVERVIEW_WIDTH, 0 ); 861 overviewWidget_->show(); 862 } 863 else { 864 setViewportMargins( 0, 0, 0, 0 ); 865 overviewWidget_->hide(); 866 } 867 } 868 869 // Reset the QuickFind when the pattern is changed. 870 void AbstractLogView::handlePatternUpdated() 871 { 872 LOG(logDEBUG) << "AbstractLogView::handlePatternUpdated()"; 873 874 quickFind_.resetLimits(); 875 update(); 876 } 877 878 // OR the current with the current search expression 879 void AbstractLogView::addToSearch() 880 { 881 if ( selection_.isPortion() ) { 882 LOG(logDEBUG) << "AbstractLogView::addToSearch()"; 883 emit addToSearch( selection_.getSelectedText( logData ) ); 884 } 885 else { 886 LOG(logERROR) << "AbstractLogView::addToSearch called for a wrong type of selection"; 887 } 888 } 889 890 // Find next occurence of the selected text (*) 891 void AbstractLogView::findNextSelected() 892 { 893 // Use the selected 'word' and search forward 894 if ( selection_.isPortion() ) { 895 emit changeQuickFind( 896 selection_.getSelectedText( logData ), 897 QuickFindMux::Forward ); 898 emit searchNext(); 899 } 900 } 901 902 // Find next previous of the selected text (#) 903 void AbstractLogView::findPreviousSelected() 904 { 905 if ( selection_.isPortion() ) { 906 emit changeQuickFind( 907 selection_.getSelectedText( logData ), 908 QuickFindMux::Backward ); 909 emit searchNext(); 910 } 911 } 912 913 // Copy the selection to the clipboard 914 void AbstractLogView::copy() 915 { 916 static QClipboard* clipboard = QApplication::clipboard(); 917 918 clipboard->setText( selection_.getSelectedText( logData ) ); 919 } 920 921 // 922 // Public functions 923 // 924 925 void AbstractLogView::updateData() 926 { 927 LOG(logDEBUG) << "AbstractLogView::updateData"; 928 929 // Check the top Line is within range 930 if ( firstLine >= logData->getNbLine() ) { 931 firstLine = 0; 932 firstCol = 0; 933 verticalScrollBar()->setValue( 0 ); 934 horizontalScrollBar()->setValue( 0 ); 935 } 936 937 // Crop selection if it become out of range 938 selection_.crop( logData->getNbLine() - 1 ); 939 940 // Adapt the scroll bars to the new content 941 updateScrollBars(); 942 943 lastLine = qMin( logData->getNbLine(), firstLine + getNbVisibleLines() ); 944 945 // Reset the QuickFind in case we have new stuff to search into 946 quickFind_.resetLimits(); 947 948 if ( followMode_ ) 949 jumpToBottom(); 950 951 // Update the overview if we have one 952 if ( overview_ != NULL ) 953 overview_->updateCurrentPosition( firstLine, lastLine ); 954 955 // Update the length of line numbers 956 nbDigitsInLineNumber_ = countDigits( maxDisplayLineNumber() ); 957 958 // Repaint! 959 update(); 960 } 961 962 void AbstractLogView::updateDisplaySize() 963 { 964 // Font is assumed to be mono-space (is restricted by options dialog) 965 QFontMetrics fm = fontMetrics(); 966 charHeight_ = fm.height(); 967 // For some reason on Qt 4.8.2 for Win, maxWidth() is wrong but the 968 // following give the right result, not sure why: 969 charWidth_ = fm.width( QChar('a') ); 970 971 // Calculate the index of the last line shown 972 lastLine = qMin( logData->getNbLine(), firstLine + getNbVisibleLines() ); 973 974 // Update the scroll bars 975 updateScrollBars(); 976 verticalScrollBar()->setPageStep( getNbVisibleLines() ); 977 978 LOG(logDEBUG) << "viewport.width()=" << viewport()->width(); 979 LOG(logDEBUG) << "viewport.height()=" << viewport()->height(); 980 LOG(logDEBUG) << "width()=" << width(); 981 LOG(logDEBUG) << "height()=" << height(); 982 983 if ( overviewWidget_ ) 984 overviewWidget_->setGeometry( viewport()->width() + 2, 1, 985 OVERVIEW_WIDTH - 1, viewport()->height() ); 986 987 // Our text area cache is now invalid 988 textAreaCache_.invalid_ = true; 989 textAreaCache_.pixmap_ = QPixmap { 990 viewport()->size() * viewport()->devicePixelRatio() }; 991 textAreaCache_.pixmap_.setDevicePixelRatio( viewport()->devicePixelRatio() ); 992 } 993 994 int AbstractLogView::getTopLine() const 995 { 996 return firstLine; 997 } 998 999 QString AbstractLogView::getSelection() const 1000 { 1001 return selection_.getSelectedText( logData ); 1002 } 1003 1004 void AbstractLogView::selectAll() 1005 { 1006 selection_.selectRange( 0, logData->getNbLine() - 1 ); 1007 update(); 1008 } 1009 1010 void AbstractLogView::selectAndDisplayLine( int line ) 1011 { 1012 emit followDisabled(); 1013 selection_.selectLine( line ); 1014 displayLine( line ); 1015 emit updateLineNumber( line ); 1016 } 1017 1018 // The difference between this function and displayLine() is quite 1019 // subtle: this one always jump, even if the line passed is visible. 1020 void AbstractLogView::jumpToLine( int line ) 1021 { 1022 // Put the selected line in the middle if possible 1023 int newTopLine = line - ( getNbVisibleLines() / 2 ); 1024 if ( newTopLine < 0 ) 1025 newTopLine = 0; 1026 1027 // This will also trigger a scrollContents event 1028 verticalScrollBar()->setValue( newTopLine ); 1029 } 1030 1031 void AbstractLogView::setLineNumbersVisible( bool lineNumbersVisible ) 1032 { 1033 lineNumbersVisible_ = lineNumbersVisible; 1034 } 1035 1036 // 1037 // Private functions 1038 // 1039 1040 // Returns the number of lines visible in the viewport 1041 int AbstractLogView::getNbVisibleLines() const 1042 { 1043 return viewport()->height() / charHeight_ + 1; 1044 } 1045 1046 // Returns the number of columns visible in the viewport 1047 int AbstractLogView::getNbVisibleCols() const 1048 { 1049 return ( viewport()->width() - leftMarginPx_ ) / charWidth_ + 1; 1050 } 1051 1052 // Converts the mouse x, y coordinates to the line number in the file 1053 int AbstractLogView::convertCoordToLine(int yPos) const 1054 { 1055 int line = firstLine + yPos / charHeight_; 1056 1057 return line; 1058 } 1059 1060 // Converts the mouse x, y coordinates to the char coordinates (in the file) 1061 // This function ensure the pos exists in the file. 1062 QPoint AbstractLogView::convertCoordToFilePos( const QPoint& pos ) const 1063 { 1064 int line = firstLine + pos.y() / charHeight_; 1065 if ( line >= logData->getNbLine() ) 1066 line = logData->getNbLine() - 1; 1067 if ( line < 0 ) 1068 line = 0; 1069 1070 // Determine column in screen space and convert it to file space 1071 int column = firstCol + ( pos.x() - leftMarginPx_ ) / charWidth_; 1072 1073 QString this_line = logData->getExpandedLineString( line ); 1074 const int length = this_line.length(); 1075 1076 if ( column >= length ) 1077 column = length - 1; 1078 if ( column < 0 ) 1079 column = 0; 1080 1081 LOG(logDEBUG4) << "AbstractLogView::convertCoordToFilePos col=" 1082 << column << " line=" << line; 1083 QPoint point( column, line ); 1084 1085 return point; 1086 } 1087 1088 // Makes the widget adjust itself to display the passed line. 1089 // Doing so, it will throw itself a scrollContents event. 1090 void AbstractLogView::displayLine( int line ) 1091 { 1092 // If the line is already the screen 1093 if ( ( line >= firstLine ) && 1094 ( line < ( firstLine + getNbVisibleLines() ) ) ) { 1095 // ... don't scroll and just repaint 1096 update(); 1097 } else { 1098 jumpToLine( line ); 1099 } 1100 } 1101 1102 // Move the selection up and down by the passed number of lines 1103 void AbstractLogView::moveSelection( int delta ) 1104 { 1105 LOG(logDEBUG) << "AbstractLogView::moveSelection delta=" << delta; 1106 1107 QList<int> selection = selection_.getLines(); 1108 int new_line; 1109 1110 // If nothing is selected, do as if line -1 was. 1111 if ( selection.isEmpty() ) 1112 selection.append( -1 ); 1113 1114 if ( delta < 0 ) 1115 new_line = selection.first() + delta; 1116 else 1117 new_line = selection.last() + delta; 1118 1119 if ( new_line < 0 ) 1120 new_line = 0; 1121 else if ( new_line >= logData->getNbLine() ) 1122 new_line = logData->getNbLine() - 1; 1123 1124 // Select and display the new line 1125 selection_.selectLine( new_line ); 1126 displayLine( new_line ); 1127 emit updateLineNumber( new_line ); 1128 } 1129 1130 // Make the start of the lines visible 1131 void AbstractLogView::jumpToStartOfLine() 1132 { 1133 horizontalScrollBar()->setValue( 0 ); 1134 } 1135 1136 // Make the end of the lines in the selection visible 1137 void AbstractLogView::jumpToEndOfLine() 1138 { 1139 QList<int> selection = selection_.getLines(); 1140 1141 // Search the longest line in the selection 1142 int max_length = 0; 1143 foreach ( int line, selection ) { 1144 int length = logData->getLineLength( line ); 1145 if ( length > max_length ) 1146 max_length = length; 1147 } 1148 1149 horizontalScrollBar()->setValue( max_length - getNbVisibleCols() ); 1150 } 1151 1152 // Make the end of the lines on the screen visible 1153 void AbstractLogView::jumpToRightOfScreen() 1154 { 1155 QList<int> selection = selection_.getLines(); 1156 1157 // Search the longest line on screen 1158 int max_length = 0; 1159 for ( int i = firstLine; i <= ( firstLine + getNbVisibleLines() ); i++ ) { 1160 int length = logData->getLineLength( i ); 1161 if ( length > max_length ) 1162 max_length = length; 1163 } 1164 1165 horizontalScrollBar()->setValue( max_length - getNbVisibleCols() ); 1166 } 1167 1168 // Jump to the first line 1169 void AbstractLogView::jumpToTop() 1170 { 1171 // This will also trigger a scrollContents event 1172 verticalScrollBar()->setValue( 0 ); 1173 update(); // in case the screen hasn't moved 1174 } 1175 1176 // Jump to the last line 1177 void AbstractLogView::jumpToBottom() 1178 { 1179 const int new_top_line = 1180 qMax( logData->getNbLine() - getNbVisibleLines() + 1, 0LL ); 1181 1182 // This will also trigger a scrollContents event 1183 verticalScrollBar()->setValue( new_top_line ); 1184 update(); // in case the screen hasn't moved 1185 } 1186 1187 // Returns whether the character passed is a 'word' character 1188 inline bool AbstractLogView::isCharWord( char c ) 1189 { 1190 if ( ( ( c >= 'A' ) && ( c <= 'Z' ) ) || 1191 ( ( c >= 'a' ) && ( c <= 'z' ) ) || 1192 ( ( c >= '0' ) && ( c <= '9' ) ) || 1193 ( ( c == '_' ) ) ) 1194 return true; 1195 else 1196 return false; 1197 } 1198 1199 // Select the word under the given position 1200 void AbstractLogView::selectWordAtPosition( const QPoint& pos ) 1201 { 1202 const int x = pos.x(); 1203 const QString line = logData->getExpandedLineString( pos.y() ); 1204 1205 if ( isCharWord( line[x].toLatin1() ) ) { 1206 // Search backward for the first character in the word 1207 int currentPos = x; 1208 for ( ; currentPos > 0; currentPos-- ) 1209 if ( ! isCharWord( line[currentPos].toLatin1() ) ) 1210 break; 1211 // Exclude the first char of the line if needed 1212 if ( ! isCharWord( line[currentPos].toLatin1() ) ) 1213 currentPos++; 1214 int start = currentPos; 1215 1216 // Now search for the end 1217 currentPos = x; 1218 for ( ; currentPos < line.length() - 1; currentPos++ ) 1219 if ( ! isCharWord( line[currentPos].toLatin1() ) ) 1220 break; 1221 // Exclude the last char of the line if needed 1222 if ( ! isCharWord( line[currentPos].toLatin1() ) ) 1223 currentPos--; 1224 int end = currentPos; 1225 1226 selection_.selectPortion( pos.y(), start, end ); 1227 updateGlobalSelection(); 1228 update(); 1229 } 1230 } 1231 1232 // Update the system global (middle click) selection (X11 only) 1233 void AbstractLogView::updateGlobalSelection() 1234 { 1235 static QClipboard* const clipboard = QApplication::clipboard(); 1236 1237 // Updating it only for "non-trivial" (range or portion) selections 1238 if ( ! selection_.isSingleLine() ) 1239 clipboard->setText( selection_.getSelectedText( logData ), 1240 QClipboard::Selection ); 1241 } 1242 1243 // Create the pop-up menu 1244 void AbstractLogView::createMenu() 1245 { 1246 copyAction_ = new QAction( tr("&Copy"), this ); 1247 // No text as this action title depends on the type of selection 1248 connect( copyAction_, SIGNAL(triggered()), this, SLOT(copy()) ); 1249 1250 // For '#' and '*', shortcuts doesn't seem to work but 1251 // at least it displays them in the menu, we manually handle those keys 1252 // as keys event anyway (in keyPressEvent). 1253 findNextAction_ = new QAction(tr("Find &next"), this); 1254 findNextAction_->setShortcut( Qt::Key_Asterisk ); 1255 findNextAction_->setStatusTip( tr("Find the next occurence") ); 1256 connect( findNextAction_, SIGNAL(triggered()), 1257 this, SLOT( findNextSelected() ) ); 1258 1259 findPreviousAction_ = new QAction( tr("Find &previous"), this ); 1260 findPreviousAction_->setShortcut( tr("#") ); 1261 findPreviousAction_->setStatusTip( tr("Find the previous occurence") ); 1262 connect( findPreviousAction_, SIGNAL(triggered()), 1263 this, SLOT( findPreviousSelected() ) ); 1264 1265 addToSearchAction_ = new QAction( tr("&Add to search"), this ); 1266 addToSearchAction_->setStatusTip( 1267 tr("Add the selection to the current search") ); 1268 connect( addToSearchAction_, SIGNAL( triggered() ), 1269 this, SLOT( addToSearch() ) ); 1270 1271 popupMenu_ = new QMenu( this ); 1272 popupMenu_->addAction( copyAction_ ); 1273 popupMenu_->addSeparator(); 1274 popupMenu_->addAction( findNextAction_ ); 1275 popupMenu_->addAction( findPreviousAction_ ); 1276 popupMenu_->addAction( addToSearchAction_ ); 1277 } 1278 1279 void AbstractLogView::considerMouseHovering( int x_pos, int y_pos ) 1280 { 1281 int line = convertCoordToLine( y_pos ); 1282 if ( ( x_pos < leftMarginPx_ ) 1283 && ( line >= 0 ) 1284 && ( line < logData->getNbLine() ) ) { 1285 // Mouse moved in the margin, send event up 1286 // (possibly to highlight the overview) 1287 if ( line != lastHoveredLine_ ) { 1288 LOG(logDEBUG) << "Mouse moved in margin line: " << line; 1289 emit mouseHoveredOverLine( line ); 1290 lastHoveredLine_ = line; 1291 } 1292 } 1293 else { 1294 if ( lastHoveredLine_ != -1 ) { 1295 emit mouseLeftHoveringZone(); 1296 lastHoveredLine_ = -1; 1297 } 1298 } 1299 } 1300 1301 void AbstractLogView::updateScrollBars() 1302 { 1303 verticalScrollBar()->setRange( 0, std::max( 0LL, 1304 logData->getNbLine() - getNbVisibleLines() ) ); 1305 1306 const int hScrollMaxValue = ( logData->getMaxLength() - getNbVisibleCols() + 1 ) > 0 ? 1307 ( logData->getMaxLength() - getNbVisibleCols() + 1 ) : 0; 1308 horizontalScrollBar()->setRange( 0, hScrollMaxValue ); 1309 } 1310 1311 void AbstractLogView::drawTextArea( QPaintDevice* paint_device ) 1312 { 1313 // LOG( logDEBUG ) << "devicePixelRatio: " << viewport()->devicePixelRatio(); 1314 // LOG( logDEBUG ) << "viewport size: " << viewport()->size().width(); 1315 // LOG( logDEBUG ) << "pixmap size: " << textPixmap.width(); 1316 // Repaint the viewport 1317 QPainter painter( paint_device ); 1318 // LOG( logDEBUG ) << "font: " << viewport()->font().family().toStdString(); 1319 // LOG( logDEBUG ) << "font painter: " << painter.font().family().toStdString(); 1320 1321 // For pretty circles 1322 painter.setRenderHint( QPainter::Antialiasing ); 1323 painter.setFont( this->font() ); 1324 1325 const int fontHeight = charHeight_; 1326 const int fontAscent = painter.fontMetrics().ascent(); 1327 const int nbCols = getNbVisibleCols(); 1328 const QPalette& palette = viewport()->palette(); 1329 std::shared_ptr<const FilterSet> filterSet = 1330 Persistent<FilterSet>( "filterSet" ); 1331 QColor foreColor, backColor; 1332 1333 static const QBrush normalBulletBrush = QBrush( Qt::white ); 1334 static const QBrush matchBulletBrush = QBrush( Qt::red ); 1335 static const QBrush markBrush = QBrush( "dodgerblue" ); 1336 1337 static const int SEPARATOR_WIDTH = 1; 1338 static const int BULLET_AREA_WIDTH = 11; 1339 static const int CONTENT_MARGIN_WIDTH = 1; 1340 static const int LINE_NUMBER_PADDING = 3; 1341 1342 const int bottomOfTextPx = 1343 ( lastLine - firstLine + 1 ) * fontHeight; 1344 1345 // First check the lines to be drawn are within range (might not be the case if 1346 // the file has just changed) 1347 const int nbLines = logData->getNbLine(); 1348 if ( nbLines == 0 ) { 1349 return; 1350 } 1351 else { 1352 if ( firstLine >= nbLines ) 1353 firstLine = nbLines - 1; 1354 if ( lastLine >= nbLines ) 1355 lastLine = nbLines - 1; 1356 } 1357 1358 // Lines to write 1359 const QStringList lines = logData->getExpandedLines( firstLine, lastLine - firstLine + 1 ); 1360 1361 // First draw the bullet left margin 1362 painter.setPen(palette.color(QPalette::Text)); 1363 painter.drawLine( BULLET_AREA_WIDTH, 0, 1364 BULLET_AREA_WIDTH, bottomOfTextPx - 1 ); 1365 painter.fillRect( 0, 0, 1366 BULLET_AREA_WIDTH, bottomOfTextPx, 1367 Qt::darkGray ); 1368 1369 // Column at which the content should start (pixels) 1370 int contentStartPosX = BULLET_AREA_WIDTH + SEPARATOR_WIDTH; 1371 1372 // This is also the bullet zone width, used for marking clicks 1373 bulletZoneWidthPx_ = contentStartPosX; 1374 1375 // Draw the line numbers area 1376 int lineNumberAreaStartX = 0; 1377 if ( lineNumbersVisible_ ) { 1378 int lineNumberWidth = charWidth_ * nbDigitsInLineNumber_; 1379 int lineNumberAreaWidth = 1380 2 * LINE_NUMBER_PADDING + lineNumberWidth; 1381 lineNumberAreaStartX = contentStartPosX; 1382 1383 painter.setPen(palette.color(QPalette::Text)); 1384 /* Not sure if it looks good... 1385 painter.drawLine( contentStartPosX + lineNumberAreaWidth, 1386 0, 1387 contentStartPosX + lineNumberAreaWidth, 1388 viewport()->height() ); 1389 */ 1390 painter.fillRect( contentStartPosX, 0, 1391 lineNumberAreaWidth, bottomOfTextPx, 1392 Qt::lightGray ); 1393 1394 // Update for drawing the actual text 1395 contentStartPosX += lineNumberAreaWidth; 1396 } 1397 else { 1398 contentStartPosX += SEPARATOR_WIDTH; 1399 } 1400 1401 // This is the total width of the 'margin' (including line number if any) 1402 // used for mouse calculation etc... 1403 leftMarginPx_ = contentStartPosX; 1404 1405 // Then draw each line 1406 for (int i = firstLine; i <= lastLine; i++) { 1407 // Position in pixel of the base line of the line to print 1408 const int yPos = (i-firstLine) * fontHeight; 1409 const int xPos = contentStartPosX + CONTENT_MARGIN_WIDTH; 1410 1411 // string to print, cut to fit the length and position of the view 1412 const QString line = lines[i - firstLine]; 1413 const QString cutLine = line.mid( firstCol, nbCols ); 1414 1415 if ( selection_.isLineSelected( i ) ) { 1416 // Reverse the selected line 1417 foreColor = palette.color( QPalette::HighlightedText ); 1418 backColor = palette.color( QPalette::Highlight ); 1419 painter.setPen(palette.color(QPalette::Text)); 1420 } 1421 else if ( filterSet->matchLine( logData->getLineString( i ), 1422 &foreColor, &backColor ) ) { 1423 // Apply a filter to the line 1424 } 1425 else { 1426 // Use the default colors 1427 foreColor = palette.color( QPalette::Text ); 1428 backColor = palette.color( QPalette::Base ); 1429 } 1430 1431 // Is there something selected in the line? 1432 int sel_start, sel_end; 1433 bool isSelection = 1434 selection_.getPortionForLine( i, &sel_start, &sel_end ); 1435 // Has the line got elements to be highlighted 1436 QList<QuickFindMatch> qfMatchList; 1437 bool isMatch = 1438 quickFindPattern_->matchLine( line, qfMatchList ); 1439 1440 if ( isSelection || isMatch ) { 1441 // We use the LineDrawer and its chunks because the 1442 // line has to be somehow highlighted 1443 LineDrawer lineDrawer( backColor ); 1444 1445 // First we create a list of chunks with the highlights 1446 QList<LineChunk> chunkList; 1447 int column = 0; // Current column in line space 1448 foreach( const QuickFindMatch match, qfMatchList ) { 1449 int start = match.startColumn() - firstCol; 1450 int end = start + match.length(); 1451 // Ignore matches that are *completely* outside view area 1452 if ( ( start < 0 && end < 0 ) || start >= nbCols ) 1453 continue; 1454 if ( start > column ) 1455 chunkList << LineChunk( column, start - 1, LineChunk::Normal ); 1456 column = qMin( start + match.length() - 1, nbCols ); 1457 chunkList << LineChunk( qMax( start, 0 ), column, 1458 LineChunk::Highlighted ); 1459 column++; 1460 } 1461 if ( column <= cutLine.length() - 1 ) 1462 chunkList << LineChunk( column, cutLine.length() - 1, LineChunk::Normal ); 1463 1464 // Then we add the selection if needed 1465 QList<LineChunk> newChunkList; 1466 if ( isSelection ) { 1467 sel_start -= firstCol; // coord in line space 1468 sel_end -= firstCol; 1469 1470 foreach ( const LineChunk chunk, chunkList ) { 1471 newChunkList << chunk.select( sel_start, sel_end ); 1472 } 1473 } 1474 else 1475 newChunkList = chunkList; 1476 1477 foreach ( const LineChunk chunk, newChunkList ) { 1478 // Select the colours 1479 QColor fore; 1480 QColor back; 1481 switch ( chunk.type() ) { 1482 case LineChunk::Normal: 1483 fore = foreColor; 1484 back = backColor; 1485 break; 1486 case LineChunk::Highlighted: 1487 fore = QColor( "black" ); 1488 back = QColor( "yellow" ); 1489 // fore = highlightForeColor; 1490 // back = highlightBackColor; 1491 break; 1492 case LineChunk::Selected: 1493 fore = palette.color( QPalette::HighlightedText ), 1494 back = palette.color( QPalette::Highlight ); 1495 break; 1496 } 1497 lineDrawer.addChunk ( chunk, fore, back ); 1498 } 1499 1500 lineDrawer.draw( painter, xPos, yPos, 1501 viewport()->width(), cutLine, 1502 CONTENT_MARGIN_WIDTH ); 1503 } 1504 else { 1505 // Nothing to be highlighted, we print the whole line! 1506 painter.fillRect( xPos - CONTENT_MARGIN_WIDTH, yPos, 1507 viewport()->width(), fontHeight, backColor ); 1508 // (the rectangle is extended on the left to cover the small 1509 // margin, it looks better (LineDrawer does the same) ) 1510 painter.setPen( foreColor ); 1511 painter.drawText( xPos, yPos + fontAscent, cutLine ); 1512 } 1513 1514 // Then draw the bullet 1515 painter.setPen( palette.color( QPalette::Text ) ); 1516 const int circleSize = 3; 1517 const int arrowHeight = 4; 1518 const int middleXLine = BULLET_AREA_WIDTH / 2; 1519 const int middleYLine = yPos + (fontHeight / 2); 1520 1521 const LineType line_type = lineType( i ); 1522 if ( line_type == Marked ) { 1523 // A pretty arrow if the line is marked 1524 const QPoint points[7] = { 1525 QPoint(1, middleYLine - 2), 1526 QPoint(middleXLine, middleYLine - 2), 1527 QPoint(middleXLine, middleYLine - arrowHeight), 1528 QPoint(BULLET_AREA_WIDTH - 2, middleYLine), 1529 QPoint(middleXLine, middleYLine + arrowHeight), 1530 QPoint(middleXLine, middleYLine + 2), 1531 QPoint(1, middleYLine + 2 ), 1532 }; 1533 1534 painter.setBrush( markBrush ); 1535 painter.drawPolygon( points, 7 ); 1536 } 1537 else { 1538 if ( lineType( i ) == Match ) 1539 painter.setBrush( matchBulletBrush ); 1540 else 1541 painter.setBrush( normalBulletBrush ); 1542 painter.drawEllipse( middleXLine - circleSize, 1543 middleYLine - circleSize, 1544 circleSize * 2, circleSize * 2 ); 1545 } 1546 1547 // Draw the line number 1548 if ( lineNumbersVisible_ ) { 1549 static const QString lineNumberFormat( "%1" ); 1550 const QString& lineNumberStr = 1551 lineNumberFormat.arg( displayLineNumber( i ), 1552 nbDigitsInLineNumber_ ); 1553 painter.setPen( palette.color( QPalette::Text ) ); 1554 painter.drawText( lineNumberAreaStartX + LINE_NUMBER_PADDING, 1555 yPos + fontAscent, lineNumberStr ); 1556 } 1557 } // For each line 1558 1559 } 1560 1561 namespace { 1562 1563 // Convert the length of the pull to follow bar to pixels 1564 int mapPullToFollowLength( int length ) 1565 { 1566 return length / 14; 1567 } 1568 1569 }; 1570