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 else if ( mouseEvent->button() == Qt::RightButton ) 362 { 363 // Prepare the popup depending on selection type 364 if ( selection_.isSingleLine() ) { 365 copyAction_->setText( "&Copy this line" ); 366 } 367 else { 368 copyAction_->setText( "&Copy" ); 369 copyAction_->setStatusTip( tr("Copy the selection") ); 370 } 371 372 if ( selection_.isPortion() ) { 373 findNextAction_->setEnabled( true ); 374 findPreviousAction_->setEnabled( true ); 375 addToSearchAction_->setEnabled( true ); 376 } 377 else { 378 findNextAction_->setEnabled( false ); 379 findPreviousAction_->setEnabled( false ); 380 addToSearchAction_->setEnabled( false ); 381 } 382 383 // "Add to search" only makes sense in regexp mode 384 if ( config->mainRegexpType() != ExtendedRegexp ) 385 addToSearchAction_->setEnabled( false ); 386 387 // Display the popup (blocking) 388 popupMenu_->exec( QCursor::pos() ); 389 } 390 391 emit activity(); 392 } 393 394 void AbstractLogView::mouseMoveEvent( QMouseEvent* mouseEvent ) 395 { 396 // Selection implementation 397 if ( selectionStarted_ ) 398 { 399 QPoint thisEndPos = convertCoordToFilePos( mouseEvent->pos() ); 400 if ( thisEndPos != selectionCurrentEndPos_ ) 401 { 402 // Are we on a different line? 403 if ( selectionStartPos_.y() != thisEndPos.y() ) 404 { 405 if ( thisEndPos.y() != selectionCurrentEndPos_.y() ) 406 { 407 // This is a 'range' selection 408 selection_.selectRange( selectionStartPos_.y(), 409 thisEndPos.y() ); 410 emit updateLineNumber( thisEndPos.y() ); 411 update(); 412 } 413 } 414 // So we are on the same line. Are we moving horizontaly? 415 else if ( thisEndPos.x() != selectionCurrentEndPos_.x() ) 416 { 417 // This is a 'portion' selection 418 selection_.selectPortion( thisEndPos.y(), 419 selectionStartPos_.x(), thisEndPos.x() ); 420 update(); 421 } 422 // On the same line, and moving vertically then 423 else 424 { 425 // This is a 'line' selection 426 selection_.selectLine( thisEndPos.y() ); 427 emit updateLineNumber( thisEndPos.y() ); 428 update(); 429 } 430 selectionCurrentEndPos_ = thisEndPos; 431 432 // Do we need to scroll while extending the selection? 433 QRect visible = viewport()->rect(); 434 if ( visible.contains( mouseEvent->pos() ) ) 435 autoScrollTimer_.stop(); 436 else if ( ! autoScrollTimer_.isActive() ) 437 autoScrollTimer_.start( 100, this ); 438 } 439 } 440 else { 441 considerMouseHovering( mouseEvent->x(), mouseEvent->y() ); 442 } 443 } 444 445 void AbstractLogView::mouseReleaseEvent( QMouseEvent* mouseEvent ) 446 { 447 if ( markingClickInitiated_ ) { 448 markingClickInitiated_ = false; 449 int line = convertCoordToLine( mouseEvent->y() ); 450 if ( line == markingClickLine_ ) 451 emit markLine( line ); 452 } 453 else { 454 selectionStarted_ = false; 455 if ( autoScrollTimer_.isActive() ) 456 autoScrollTimer_.stop(); 457 updateGlobalSelection(); 458 } 459 } 460 461 void AbstractLogView::mouseDoubleClickEvent( QMouseEvent* mouseEvent ) 462 { 463 if ( mouseEvent->button() == Qt::LeftButton ) 464 { 465 const QPoint pos = convertCoordToFilePos( mouseEvent->pos() ); 466 selectWordAtPosition( pos ); 467 } 468 469 emit activity(); 470 } 471 472 void AbstractLogView::timerEvent( QTimerEvent* timerEvent ) 473 { 474 if ( timerEvent->timerId() == autoScrollTimer_.timerId() ) { 475 QRect visible = viewport()->rect(); 476 const QPoint globalPos = QCursor::pos(); 477 const QPoint pos = viewport()->mapFromGlobal( globalPos ); 478 QMouseEvent ev( QEvent::MouseMove, pos, globalPos, Qt::LeftButton, 479 Qt::LeftButton, Qt::NoModifier ); 480 mouseMoveEvent( &ev ); 481 int deltaX = qMax( pos.x() - visible.left(), 482 visible.right() - pos.x() ) - visible.width(); 483 int deltaY = qMax( pos.y() - visible.top(), 484 visible.bottom() - pos.y() ) - visible.height(); 485 int delta = qMax( deltaX, deltaY ); 486 487 if ( delta >= 0 ) { 488 if ( delta < 7 ) 489 delta = 7; 490 int timeout = 4900 / ( delta * delta ); 491 autoScrollTimer_.start( timeout, this ); 492 493 if ( deltaX > 0 ) 494 horizontalScrollBar()->triggerAction( 495 pos.x() <visible.center().x() ? 496 QAbstractSlider::SliderSingleStepSub : 497 QAbstractSlider::SliderSingleStepAdd ); 498 499 if ( deltaY > 0 ) 500 verticalScrollBar()->triggerAction( 501 pos.y() <visible.center().y() ? 502 QAbstractSlider::SliderSingleStepSub : 503 QAbstractSlider::SliderSingleStepAdd ); 504 } 505 } 506 QAbstractScrollArea::timerEvent( timerEvent ); 507 } 508 509 void AbstractLogView::keyPressEvent( QKeyEvent* keyEvent ) 510 { 511 LOG(logDEBUG4) << "keyPressEvent received"; 512 bool controlModifier = (keyEvent->modifiers() & Qt::ControlModifier) == Qt::ControlModifier; 513 bool shiftModifier = (keyEvent->modifiers() & Qt::ShiftModifier) == Qt::ShiftModifier; 514 515 if ( keyEvent->key() == Qt::Key_Left ) 516 horizontalScrollBar()->triggerAction(QScrollBar::SliderPageStepSub); 517 else if ( keyEvent->key() == Qt::Key_Right ) 518 horizontalScrollBar()->triggerAction(QScrollBar::SliderPageStepAdd); 519 else if ( keyEvent->key() == Qt::Key_Home && !controlModifier) 520 jumpToStartOfLine(); 521 else if ( keyEvent->key() == Qt::Key_End && !controlModifier) 522 jumpToRightOfScreen(); 523 else if ( (keyEvent->key() == Qt::Key_PageDown && controlModifier) 524 || (keyEvent->key() == Qt::Key_End && controlModifier) ) 525 { 526 emit followDisabled(); // duplicate of 'G' action. 527 selection_.selectLine( logData->getNbLine() - 1 ); 528 emit updateLineNumber( logData->getNbLine() - 1 ); 529 jumpToBottom(); 530 } 531 else if ( (keyEvent->key() == Qt::Key_PageUp && controlModifier) 532 || (keyEvent->key() == Qt::Key_Home && controlModifier) ) 533 { 534 emit followDisabled(); // like 'g' but 0 input first line action. 535 selectAndDisplayLine( 0 ); 536 emit updateLineNumber( 0 ); 537 } 538 else if ( keyEvent->key() == Qt::Key_F3 && !shiftModifier ) 539 searchNext(); // duplicate of 'n' action. 540 else if ( keyEvent->key() == Qt::Key_F3 && shiftModifier ) 541 searchPrevious(); // duplicate of 'N' action. 542 else { 543 const char character = (keyEvent->text())[0].toLatin1(); 544 545 if ( keyEvent->modifiers() == Qt::NoModifier && 546 ( character >= '0' ) && ( character <= '9' ) ) { 547 // Adds the digit to the timed buffer 548 digitsBuffer_.add( character ); 549 } 550 else { 551 switch ( (keyEvent->text())[0].toLatin1() ) { 552 case 'j': 553 { 554 int delta = qMax( 1, digitsBuffer_.content() ); 555 emit followDisabled(); 556 //verticalScrollBar()->triggerAction( 557 //QScrollBar::SliderSingleStepAdd); 558 moveSelection( delta ); 559 break; 560 } 561 case 'k': 562 { 563 int delta = qMin( -1, - digitsBuffer_.content() ); 564 emit followDisabled(); 565 //verticalScrollBar()->triggerAction( 566 //QScrollBar::SliderSingleStepSub); 567 moveSelection( delta ); 568 break; 569 } 570 case 'h': 571 horizontalScrollBar()->triggerAction( 572 QScrollBar::SliderSingleStepSub); 573 break; 574 case 'l': 575 horizontalScrollBar()->triggerAction( 576 QScrollBar::SliderSingleStepAdd); 577 break; 578 case '0': 579 jumpToStartOfLine(); 580 break; 581 case '$': 582 jumpToEndOfLine(); 583 break; 584 case 'g': 585 { 586 int newLine = qMax( 0, digitsBuffer_.content() - 1 ); 587 if ( newLine >= logData->getNbLine() ) 588 newLine = logData->getNbLine() - 1; 589 emit followDisabled(); 590 selectAndDisplayLine( newLine ); 591 emit updateLineNumber( newLine ); 592 break; 593 } 594 case 'G': 595 emit followDisabled(); 596 selection_.selectLine( logData->getNbLine() - 1 ); 597 emit updateLineNumber( logData->getNbLine() - 1 ); 598 jumpToBottom(); 599 break; 600 case 'n': 601 emit searchNext(); 602 break; 603 case 'N': 604 emit searchPrevious(); 605 break; 606 case '*': 607 // Use the selected 'word' and search forward 608 findNextSelected(); 609 break; 610 case '#': 611 // Use the selected 'word' and search backward 612 findPreviousSelected(); 613 break; 614 default: 615 keyEvent->ignore(); 616 } 617 } 618 } 619 620 if ( keyEvent->isAccepted() ) { 621 emit activity(); 622 } 623 else { 624 QAbstractScrollArea::keyPressEvent( keyEvent ); 625 } 626 } 627 628 void AbstractLogView::wheelEvent( QWheelEvent* wheelEvent ) 629 { 630 emit followDisabled(); 631 emit activity(); 632 633 LOG(logDEBUG) << "wheelEvent"; 634 635 if ( verticalScrollBar()->value() == verticalScrollBar()->maximum() ) { 636 LOG(logDEBUG) << "Elastic " << wheelEvent->pixelDelta().y(); 637 followElasticHook_.move( - wheelEvent->pixelDelta().y() ); 638 } 639 640 LOG(logDEBUG) << "Length = " << followElasticHook_.length(); 641 if ( followElasticHook_.length() == 0 ) { 642 LOG(logDEBUG) << "Up " << wheelEvent->pixelDelta().y(); 643 QAbstractScrollArea::wheelEvent( wheelEvent ); 644 } 645 } 646 647 void AbstractLogView::resizeEvent( QResizeEvent* ) 648 { 649 if ( logData == NULL ) 650 return; 651 652 LOG(logDEBUG) << "resizeEvent received"; 653 654 updateDisplaySize(); 655 } 656 657 void AbstractLogView::scrollContentsBy( int dx, int dy ) 658 { 659 LOG(logDEBUG) << "scrollContentsBy received " << dy; 660 661 /* 662 LineNumber visible_lines = std::max( 663 static_cast<LineNumber>( logData->getNbLine() ), 664 static_cast<LineNumber>( getNbVisibleLines() ) ); 665 firstLine = std::min( std::max( firstLine - dy, 0LL ), 666 logData->getNbLine() - visible_lines ); 667 668 LOG(logDEBUG) << "scrollContentsBy " << visible_lines << " " << firstLine; 669 */ 670 671 firstLine = std::max( firstLine - dy, 0LL ); 672 firstCol = (firstCol - dx) > 0 ? firstCol - dx : 0; 673 lastLine = firstLine + getNbVisibleLines(); 674 675 // Update the overview if we have one 676 if ( overview_ != NULL ) 677 overview_->updateCurrentPosition( firstLine, lastLine ); 678 679 // Are we hovering over a new line? 680 const QPoint mouse_pos = mapFromGlobal( QCursor::pos() ); 681 considerMouseHovering( mouse_pos.x(), mouse_pos.y() ); 682 683 // Redraw 684 update(); 685 } 686 687 void AbstractLogView::paintEvent( QPaintEvent* paintEvent ) 688 { 689 QRect invalidRect = paintEvent->rect(); 690 if ( (invalidRect.isEmpty()) || (logData == NULL) ) 691 return; 692 693 LOG(logDEBUG4) << "paintEvent received, firstLine=" << firstLine 694 << " lastLine=" << lastLine << 695 " rect: " << invalidRect.topLeft().x() << 696 ", " << invalidRect.topLeft().y() << 697 ", " << invalidRect.bottomRight().x() << 698 ", " << invalidRect.bottomRight().y(); 699 700 #ifdef GLOGG_PERF_MEASURE_FPS 701 static uint32_t maxline = logData->getNbLine(); 702 if ( ! perfCounter_.addEvent() && logData->getNbLine() > maxline ) { 703 LOG(logWARNING) << "Redraw per second: " << perfCounter_.readAndReset() 704 << " lines: " << logData->getNbLine(); 705 perfCounter_.addEvent(); 706 maxline = logData->getNbLine(); 707 } 708 #endif 709 710 { 711 // Repaint the viewport 712 QPainter painter( viewport() ); 713 const int fontHeight = charHeight_; 714 const int fontAscent = painter.fontMetrics().ascent(); 715 const int nbCols = getNbVisibleCols(); 716 const QPalette& palette = viewport()->palette(); 717 std::shared_ptr<const FilterSet> filterSet = 718 Persistent<FilterSet>( "filterSet" ); 719 QColor foreColor, backColor; 720 // Height in pixels of the "pull to follow" bottom bar. 721 const int pullToFollowHeight = mapPullToFollowLength( followElasticHook_.length() ); 722 723 static const QBrush normalBulletBrush = QBrush( Qt::white ); 724 static const QBrush matchBulletBrush = QBrush( Qt::red ); 725 static const QBrush markBrush = QBrush( "dodgerblue" ); 726 727 static const int SEPARATOR_WIDTH = 1; 728 static const int BULLET_AREA_WIDTH = 11; 729 static const int CONTENT_MARGIN_WIDTH = 1; 730 static const int LINE_NUMBER_PADDING = 3; 731 732 const int bottomOfTextPx = 733 ( lastLine - firstLine + 1 ) * fontHeight - pullToFollowHeight; 734 735 // First check the lines to be drawn are within range (might not be the case if 736 // the file has just changed) 737 const int nbLines = logData->getNbLine(); 738 if ( nbLines == 0 ) { 739 return; 740 } 741 else { 742 if ( firstLine >= nbLines ) 743 firstLine = nbLines - 1; 744 if ( lastLine >= nbLines ) 745 lastLine = nbLines - 1; 746 } 747 748 // Lines to write 749 const QStringList lines = logData->getExpandedLines( firstLine, lastLine - firstLine + 1 ); 750 751 // First draw the bullet left margin 752 painter.setPen(palette.color(QPalette::Text)); 753 painter.drawLine( BULLET_AREA_WIDTH, 0, 754 BULLET_AREA_WIDTH, bottomOfTextPx - 1 ); 755 painter.fillRect( 0, 0, 756 BULLET_AREA_WIDTH, bottomOfTextPx, 757 Qt::darkGray ); 758 759 // Column at which the content should start (pixels) 760 int contentStartPosX = BULLET_AREA_WIDTH + SEPARATOR_WIDTH; 761 762 // This is also the bullet zone width, used for marking clicks 763 bulletZoneWidthPx_ = contentStartPosX; 764 765 // Draw the line numbers area 766 int lineNumberAreaStartX = 0; 767 if ( lineNumbersVisible_ ) { 768 int lineNumberWidth = charWidth_ * nbDigitsInLineNumber_; 769 int lineNumberAreaWidth = 770 2 * LINE_NUMBER_PADDING + lineNumberWidth; 771 lineNumberAreaStartX = contentStartPosX; 772 773 painter.setPen(palette.color(QPalette::Text)); 774 /* Not sure if it looks good... 775 painter.drawLine( contentStartPosX + lineNumberAreaWidth, 776 0, 777 contentStartPosX + lineNumberAreaWidth, 778 viewport()->height() ); 779 */ 780 painter.fillRect( contentStartPosX, 0, 781 lineNumberAreaWidth, bottomOfTextPx, 782 Qt::lightGray ); 783 784 // Update for drawing the actual text 785 contentStartPosX += lineNumberAreaWidth; 786 } 787 else { 788 contentStartPosX += SEPARATOR_WIDTH; 789 } 790 791 // This is the total width of the 'margin' (including line number if any) 792 // used for mouse calculation etc... 793 leftMarginPx_ = contentStartPosX; 794 795 // Then draw each line 796 for (int i = firstLine; i <= lastLine; i++) { 797 // Position in pixel of the base line of the line to print 798 const int yPos = (i-firstLine) * fontHeight - pullToFollowHeight; 799 const int xPos = contentStartPosX + CONTENT_MARGIN_WIDTH; 800 801 // string to print, cut to fit the length and position of the view 802 const QString line = lines[i - firstLine]; 803 const QString cutLine = line.mid( firstCol, nbCols ); 804 805 if ( selection_.isLineSelected( i ) ) { 806 // Reverse the selected line 807 foreColor = palette.color( QPalette::HighlightedText ); 808 backColor = palette.color( QPalette::Highlight ); 809 painter.setPen(palette.color(QPalette::Text)); 810 } 811 else if ( filterSet->matchLine( logData->getLineString( i ), 812 &foreColor, &backColor ) ) { 813 // Apply a filter to the line 814 } 815 else { 816 // Use the default colors 817 foreColor = palette.color( QPalette::Text ); 818 backColor = palette.color( QPalette::Base ); 819 } 820 821 // Is there something selected in the line? 822 int sel_start, sel_end; 823 bool isSelection = 824 selection_.getPortionForLine( i, &sel_start, &sel_end ); 825 // Has the line got elements to be highlighted 826 QList<QuickFindMatch> qfMatchList; 827 bool isMatch = 828 quickFindPattern_->matchLine( line, qfMatchList ); 829 830 if ( isSelection || isMatch ) { 831 // We use the LineDrawer and its chunks because the 832 // line has to be somehow highlighted 833 LineDrawer lineDrawer( backColor ); 834 835 // First we create a list of chunks with the highlights 836 QList<LineChunk> chunkList; 837 int column = 0; // Current column in line space 838 foreach( const QuickFindMatch match, qfMatchList ) { 839 int start = match.startColumn() - firstCol; 840 int end = start + match.length(); 841 // Ignore matches that are *completely* outside view area 842 if ( ( start < 0 && end < 0 ) || start >= nbCols ) 843 continue; 844 if ( start > column ) 845 chunkList << LineChunk( column, start - 1, LineChunk::Normal ); 846 column = qMin( start + match.length() - 1, nbCols ); 847 chunkList << LineChunk( qMax( start, 0 ), column, 848 LineChunk::Highlighted ); 849 column++; 850 } 851 if ( column <= cutLine.length() - 1 ) 852 chunkList << LineChunk( column, cutLine.length() - 1, LineChunk::Normal ); 853 854 // Then we add the selection if needed 855 QList<LineChunk> newChunkList; 856 if ( isSelection ) { 857 sel_start -= firstCol; // coord in line space 858 sel_end -= firstCol; 859 860 foreach ( const LineChunk chunk, chunkList ) { 861 newChunkList << chunk.select( sel_start, sel_end ); 862 } 863 } 864 else 865 newChunkList = chunkList; 866 867 foreach ( const LineChunk chunk, newChunkList ) { 868 // Select the colours 869 QColor fore; 870 QColor back; 871 switch ( chunk.type() ) { 872 case LineChunk::Normal: 873 fore = foreColor; 874 back = backColor; 875 break; 876 case LineChunk::Highlighted: 877 fore = QColor( "black" ); 878 back = QColor( "yellow" ); 879 // fore = highlightForeColor; 880 // back = highlightBackColor; 881 break; 882 case LineChunk::Selected: 883 fore = palette.color( QPalette::HighlightedText ), 884 back = palette.color( QPalette::Highlight ); 885 break; 886 } 887 lineDrawer.addChunk ( chunk, fore, back ); 888 } 889 890 lineDrawer.draw( painter, xPos, yPos, 891 viewport()->width(), cutLine, 892 CONTENT_MARGIN_WIDTH ); 893 } 894 else { 895 // Nothing to be highlighted, we print the whole line! 896 painter.fillRect( xPos - CONTENT_MARGIN_WIDTH, yPos, 897 viewport()->width(), fontHeight, backColor ); 898 // (the rectangle is extended on the left to cover the small 899 // margin, it looks better (LineDrawer does the same) ) 900 painter.setPen( foreColor ); 901 painter.drawText( xPos, yPos + fontAscent, cutLine ); 902 } 903 904 // Then draw the bullet 905 painter.setPen( palette.color( QPalette::Text ) ); 906 const int circleSize = 3; 907 const int arrowHeight = 4; 908 const int middleXLine = BULLET_AREA_WIDTH / 2; 909 const int middleYLine = yPos + (fontHeight / 2); 910 911 const LineType line_type = lineType( i ); 912 if ( line_type == Marked ) { 913 // A pretty arrow if the line is marked 914 const QPoint points[7] = { 915 QPoint(1, middleYLine - 2), 916 QPoint(middleXLine, middleYLine - 2), 917 QPoint(middleXLine, middleYLine - arrowHeight), 918 QPoint(BULLET_AREA_WIDTH - 2, middleYLine), 919 QPoint(middleXLine, middleYLine + arrowHeight), 920 QPoint(middleXLine, middleYLine + 2), 921 QPoint(1, middleYLine + 2 ), 922 }; 923 924 painter.setBrush( markBrush ); 925 painter.drawPolygon( points, 7 ); 926 } 927 else { 928 if ( lineType( i ) == Match ) 929 painter.setBrush( matchBulletBrush ); 930 else 931 painter.setBrush( normalBulletBrush ); 932 painter.drawEllipse( middleXLine - circleSize, 933 middleYLine - circleSize, 934 circleSize * 2, circleSize * 2 ); 935 } 936 937 // Draw the line number 938 if ( lineNumbersVisible_ ) { 939 static const QString lineNumberFormat( "%1" ); 940 const QString& lineNumberStr = 941 lineNumberFormat.arg( displayLineNumber( i ), 942 nbDigitsInLineNumber_ ); 943 painter.setPen( palette.color( QPalette::Text ) ); 944 painter.drawText( lineNumberAreaStartX + LINE_NUMBER_PADDING, 945 yPos + fontAscent, lineNumberStr ); 946 } 947 } // For each line 948 949 // Draw the "pull to follow" zone if needed 950 if ( pullToFollowHeight ) { 951 static const int barWidth = 40; 952 const int nbBars = viewport()->width() / (barWidth * 2) + 1; 953 954 LOG(logDEBUG) << "Drawing pull to follow"; 955 956 for ( int i = 0; i < nbBars; ++i ) { 957 QPoint points[4] = { 958 { (i*2+1)*barWidth, bottomOfTextPx }, 959 { 0, bottomOfTextPx + (i*2+1)*barWidth }, 960 { 0, bottomOfTextPx + (i+1)*2*barWidth }, 961 { (i+1)*2*barWidth, bottomOfTextPx } 962 }; 963 painter.setPen( QPen( QColor( 0, 0, 0, 0 ) ) ); 964 painter.setBrush( QBrush( QColor( "lightyellow" ) ) ); 965 painter.drawConvexPolygon( points, 4 ); 966 } 967 968 } 969 } 970 LOG(logDEBUG4) << "End of repaint"; 971 } 972 973 // These two functions are virtual and this implementation is clearly 974 // only valid for a non-filtered display. 975 // We count on the 'filtered' derived classes to override them. 976 qint64 AbstractLogView::displayLineNumber( int lineNumber ) const 977 { 978 return lineNumber + 1; // show a 1-based index 979 } 980 981 qint64 AbstractLogView::maxDisplayLineNumber() const 982 { 983 return logData->getNbLine(); 984 } 985 986 void AbstractLogView::setOverview( Overview* overview, 987 OverviewWidget* overview_widget ) 988 { 989 overview_ = overview; 990 overviewWidget_ = overview_widget; 991 992 if ( overviewWidget_ ) { 993 connect( overviewWidget_, SIGNAL( lineClicked ( int ) ), 994 this, SIGNAL( followDisabled() ) ); 995 connect( overviewWidget_, SIGNAL( lineClicked ( int ) ), 996 this, SLOT( jumpToLine( int ) ) ); 997 } 998 refreshOverview(); 999 } 1000 1001 void AbstractLogView::searchUsingFunction( 1002 qint64 (QuickFind::*search_function)() ) 1003 { 1004 emit followDisabled(); 1005 1006 int line = (quickFind_.*search_function)(); 1007 if ( line >= 0 ) { 1008 LOG(logDEBUG) << "search " << line; 1009 displayLine( line ); 1010 emit updateLineNumber( line ); 1011 } 1012 } 1013 1014 void AbstractLogView::searchForward() 1015 { 1016 searchUsingFunction( &QuickFind::searchForward ); 1017 } 1018 1019 void AbstractLogView::searchBackward() 1020 { 1021 searchUsingFunction( &QuickFind::searchBackward ); 1022 } 1023 1024 void AbstractLogView::incrementallySearchForward() 1025 { 1026 searchUsingFunction( &QuickFind::incrementallySearchForward ); 1027 } 1028 1029 void AbstractLogView::incrementallySearchBackward() 1030 { 1031 searchUsingFunction( &QuickFind::incrementallySearchBackward ); 1032 } 1033 1034 void AbstractLogView::incrementalSearchAbort() 1035 { 1036 quickFind_.incrementalSearchAbort(); 1037 emit changeQuickFind( 1038 "", 1039 QuickFindMux::Forward ); 1040 } 1041 1042 void AbstractLogView::incrementalSearchStop() 1043 { 1044 quickFind_.incrementalSearchStop(); 1045 } 1046 1047 void AbstractLogView::followSet( bool checked ) 1048 { 1049 followMode_ = checked; 1050 if ( checked ) 1051 jumpToBottom(); 1052 } 1053 1054 void AbstractLogView::refreshOverview() 1055 { 1056 assert( overviewWidget_ ); 1057 1058 // Create space for the Overview if needed 1059 if ( ( getOverview() != NULL ) && getOverview()->isVisible() ) { 1060 setViewportMargins( 0, 0, OVERVIEW_WIDTH, 0 ); 1061 overviewWidget_->show(); 1062 } 1063 else { 1064 setViewportMargins( 0, 0, 0, 0 ); 1065 overviewWidget_->hide(); 1066 } 1067 } 1068 1069 // Reset the QuickFind when the pattern is changed. 1070 void AbstractLogView::handlePatternUpdated() 1071 { 1072 LOG(logDEBUG) << "AbstractLogView::handlePatternUpdated()"; 1073 1074 quickFind_.resetLimits(); 1075 update(); 1076 } 1077 1078 // OR the current with the current search expression 1079 void AbstractLogView::addToSearch() 1080 { 1081 if ( selection_.isPortion() ) { 1082 LOG(logDEBUG) << "AbstractLogView::addToSearch()"; 1083 emit addToSearch( selection_.getSelectedText( logData ) ); 1084 } 1085 else { 1086 LOG(logERROR) << "AbstractLogView::addToSearch called for a wrong type of selection"; 1087 } 1088 } 1089 1090 // Find next occurence of the selected text (*) 1091 void AbstractLogView::findNextSelected() 1092 { 1093 // Use the selected 'word' and search forward 1094 if ( selection_.isPortion() ) { 1095 emit changeQuickFind( 1096 selection_.getSelectedText( logData ), 1097 QuickFindMux::Forward ); 1098 emit searchNext(); 1099 } 1100 } 1101 1102 // Find next previous of the selected text (#) 1103 void AbstractLogView::findPreviousSelected() 1104 { 1105 if ( selection_.isPortion() ) { 1106 emit changeQuickFind( 1107 selection_.getSelectedText( logData ), 1108 QuickFindMux::Backward ); 1109 emit searchNext(); 1110 } 1111 } 1112 1113 // Copy the selection to the clipboard 1114 void AbstractLogView::copy() 1115 { 1116 static QClipboard* clipboard = QApplication::clipboard(); 1117 1118 clipboard->setText( selection_.getSelectedText( logData ) ); 1119 } 1120 1121 // 1122 // Public functions 1123 // 1124 1125 void AbstractLogView::updateData() 1126 { 1127 LOG(logDEBUG) << "AbstractLogView::updateData"; 1128 1129 // Check the top Line is within range 1130 if ( firstLine >= logData->getNbLine() ) { 1131 firstLine = 0; 1132 firstCol = 0; 1133 verticalScrollBar()->setValue( 0 ); 1134 horizontalScrollBar()->setValue( 0 ); 1135 } 1136 1137 // Crop selection if it become out of range 1138 selection_.crop( logData->getNbLine() - 1 ); 1139 1140 // Adapt the scroll bars to the new content 1141 updateScrollBars(); 1142 1143 lastLine = qMin( logData->getNbLine(), firstLine + getNbVisibleLines() ); 1144 1145 // Reset the QuickFind in case we have new stuff to search into 1146 quickFind_.resetLimits(); 1147 1148 if ( followMode_ ) 1149 jumpToBottom(); 1150 1151 // Update the overview if we have one 1152 if ( overview_ != NULL ) 1153 overview_->updateCurrentPosition( firstLine, lastLine ); 1154 1155 // Update the length of line numbers 1156 nbDigitsInLineNumber_ = countDigits( maxDisplayLineNumber() ); 1157 1158 // Repaint! 1159 update(); 1160 } 1161 1162 void AbstractLogView::updateDisplaySize() 1163 { 1164 // Font is assumed to be mono-space (is restricted by options dialog) 1165 QFontMetrics fm = fontMetrics(); 1166 charHeight_ = fm.height(); 1167 // For some reason on Qt 4.8.2 for Win, maxWidth() is wrong but the 1168 // following give the right result, not sure why: 1169 charWidth_ = fm.width( QChar('a') ); 1170 1171 // Calculate the index of the last line shown 1172 lastLine = qMin( logData->getNbLine(), firstLine + getNbVisibleLines() ); 1173 1174 // Update the scroll bars 1175 updateScrollBars(); 1176 verticalScrollBar()->setPageStep( getNbVisibleLines() ); 1177 1178 LOG(logDEBUG) << "viewport.width()=" << viewport()->width(); 1179 LOG(logDEBUG) << "viewport.height()=" << viewport()->height(); 1180 LOG(logDEBUG) << "width()=" << width(); 1181 LOG(logDEBUG) << "height()=" << height(); 1182 1183 if ( overviewWidget_ ) 1184 overviewWidget_->setGeometry( viewport()->width() + 2, 1, 1185 OVERVIEW_WIDTH - 1, viewport()->height() ); 1186 } 1187 1188 int AbstractLogView::getTopLine() const 1189 { 1190 return firstLine; 1191 } 1192 1193 QString AbstractLogView::getSelection() const 1194 { 1195 return selection_.getSelectedText( logData ); 1196 } 1197 1198 void AbstractLogView::selectAll() 1199 { 1200 selection_.selectRange( 0, logData->getNbLine() - 1 ); 1201 update(); 1202 } 1203 1204 void AbstractLogView::selectAndDisplayLine( int line ) 1205 { 1206 emit followDisabled(); 1207 selection_.selectLine( line ); 1208 displayLine( line ); 1209 emit updateLineNumber( line ); 1210 } 1211 1212 // The difference between this function and displayLine() is quite 1213 // subtle: this one always jump, even if the line passed is visible. 1214 void AbstractLogView::jumpToLine( int line ) 1215 { 1216 // Put the selected line in the middle if possible 1217 int newTopLine = line - ( getNbVisibleLines() / 2 ); 1218 if ( newTopLine < 0 ) 1219 newTopLine = 0; 1220 1221 // This will also trigger a scrollContents event 1222 verticalScrollBar()->setValue( newTopLine ); 1223 } 1224 1225 void AbstractLogView::setLineNumbersVisible( bool lineNumbersVisible ) 1226 { 1227 lineNumbersVisible_ = lineNumbersVisible; 1228 } 1229 1230 // 1231 // Private functions 1232 // 1233 1234 // Returns the number of lines visible in the viewport 1235 int AbstractLogView::getNbVisibleLines() const 1236 { 1237 return viewport()->height() / charHeight_ + 1; 1238 } 1239 1240 // Returns the number of columns visible in the viewport 1241 int AbstractLogView::getNbVisibleCols() const 1242 { 1243 return ( viewport()->width() - leftMarginPx_ ) / charWidth_ + 1; 1244 } 1245 1246 // Converts the mouse x, y coordinates to the line number in the file 1247 int AbstractLogView::convertCoordToLine(int yPos) const 1248 { 1249 int line = firstLine + yPos / charHeight_; 1250 1251 return line; 1252 } 1253 1254 // Converts the mouse x, y coordinates to the char coordinates (in the file) 1255 // This function ensure the pos exists in the file. 1256 QPoint AbstractLogView::convertCoordToFilePos( const QPoint& pos ) const 1257 { 1258 int line = firstLine + pos.y() / charHeight_; 1259 if ( line >= logData->getNbLine() ) 1260 line = logData->getNbLine() - 1; 1261 if ( line < 0 ) 1262 line = 0; 1263 1264 // Determine column in screen space and convert it to file space 1265 int column = firstCol + ( pos.x() - leftMarginPx_ ) / charWidth_; 1266 1267 QString this_line = logData->getExpandedLineString( line ); 1268 const int length = this_line.length(); 1269 1270 if ( column >= length ) 1271 column = length - 1; 1272 if ( column < 0 ) 1273 column = 0; 1274 1275 LOG(logDEBUG4) << "AbstractLogView::convertCoordToFilePos col=" 1276 << column << " line=" << line; 1277 QPoint point( column, line ); 1278 1279 return point; 1280 } 1281 1282 // Makes the widget adjust itself to display the passed line. 1283 // Doing so, it will throw itself a scrollContents event. 1284 void AbstractLogView::displayLine( int line ) 1285 { 1286 // If the line is already the screen 1287 if ( ( line >= firstLine ) && 1288 ( line < ( firstLine + getNbVisibleLines() ) ) ) { 1289 // ... don't scroll and just repaint 1290 update(); 1291 } else { 1292 jumpToLine( line ); 1293 } 1294 } 1295 1296 // Move the selection up and down by the passed number of lines 1297 void AbstractLogView::moveSelection( int delta ) 1298 { 1299 LOG(logDEBUG) << "AbstractLogView::moveSelection delta=" << delta; 1300 1301 QList<int> selection = selection_.getLines(); 1302 int new_line; 1303 1304 // If nothing is selected, do as if line -1 was. 1305 if ( selection.isEmpty() ) 1306 selection.append( -1 ); 1307 1308 if ( delta < 0 ) 1309 new_line = selection.first() + delta; 1310 else 1311 new_line = selection.last() + delta; 1312 1313 if ( new_line < 0 ) 1314 new_line = 0; 1315 else if ( new_line >= logData->getNbLine() ) 1316 new_line = logData->getNbLine() - 1; 1317 1318 // Select and display the new line 1319 selection_.selectLine( new_line ); 1320 displayLine( new_line ); 1321 emit updateLineNumber( new_line ); 1322 } 1323 1324 // Make the start of the lines visible 1325 void AbstractLogView::jumpToStartOfLine() 1326 { 1327 horizontalScrollBar()->setValue( 0 ); 1328 } 1329 1330 // Make the end of the lines in the selection visible 1331 void AbstractLogView::jumpToEndOfLine() 1332 { 1333 QList<int> selection = selection_.getLines(); 1334 1335 // Search the longest line in the selection 1336 int max_length = 0; 1337 foreach ( int line, selection ) { 1338 int length = logData->getLineLength( line ); 1339 if ( length > max_length ) 1340 max_length = length; 1341 } 1342 1343 horizontalScrollBar()->setValue( max_length - getNbVisibleCols() ); 1344 } 1345 1346 // Make the end of the lines on the screen visible 1347 void AbstractLogView::jumpToRightOfScreen() 1348 { 1349 QList<int> selection = selection_.getLines(); 1350 1351 // Search the longest line on screen 1352 int max_length = 0; 1353 for ( int i = firstLine; i <= ( firstLine + getNbVisibleLines() ); i++ ) { 1354 int length = logData->getLineLength( i ); 1355 if ( length > max_length ) 1356 max_length = length; 1357 } 1358 1359 horizontalScrollBar()->setValue( max_length - getNbVisibleCols() ); 1360 } 1361 1362 // Jump to the first line 1363 void AbstractLogView::jumpToTop() 1364 { 1365 // This will also trigger a scrollContents event 1366 verticalScrollBar()->setValue( 0 ); 1367 update(); // in case the screen hasn't moved 1368 } 1369 1370 // Jump to the last line 1371 void AbstractLogView::jumpToBottom() 1372 { 1373 const int new_top_line = 1374 qMax( logData->getNbLine() - getNbVisibleLines() + 1, 0LL ); 1375 1376 // This will also trigger a scrollContents event 1377 verticalScrollBar()->setValue( new_top_line ); 1378 update(); // in case the screen hasn't moved 1379 } 1380 1381 // Returns whether the character passed is a 'word' character 1382 inline bool AbstractLogView::isCharWord( char c ) 1383 { 1384 if ( ( ( c >= 'A' ) && ( c <= 'Z' ) ) || 1385 ( ( c >= 'a' ) && ( c <= 'z' ) ) || 1386 ( ( c >= '0' ) && ( c <= '9' ) ) || 1387 ( ( c == '_' ) ) ) 1388 return true; 1389 else 1390 return false; 1391 } 1392 1393 // Select the word under the given position 1394 void AbstractLogView::selectWordAtPosition( const QPoint& pos ) 1395 { 1396 const int x = pos.x(); 1397 const QString line = logData->getExpandedLineString( pos.y() ); 1398 1399 if ( isCharWord( line[x].toLatin1() ) ) { 1400 // Search backward for the first character in the word 1401 int currentPos = x; 1402 for ( ; currentPos > 0; currentPos-- ) 1403 if ( ! isCharWord( line[currentPos].toLatin1() ) ) 1404 break; 1405 // Exclude the first char of the line if needed 1406 if ( ! isCharWord( line[currentPos].toLatin1() ) ) 1407 currentPos++; 1408 int start = currentPos; 1409 1410 // Now search for the end 1411 currentPos = x; 1412 for ( ; currentPos < line.length() - 1; currentPos++ ) 1413 if ( ! isCharWord( line[currentPos].toLatin1() ) ) 1414 break; 1415 // Exclude the last char of the line if needed 1416 if ( ! isCharWord( line[currentPos].toLatin1() ) ) 1417 currentPos--; 1418 int end = currentPos; 1419 1420 selection_.selectPortion( pos.y(), start, end ); 1421 updateGlobalSelection(); 1422 update(); 1423 } 1424 } 1425 1426 // Update the system global (middle click) selection (X11 only) 1427 void AbstractLogView::updateGlobalSelection() 1428 { 1429 static QClipboard* const clipboard = QApplication::clipboard(); 1430 1431 // Updating it only for "non-trivial" (range or portion) selections 1432 if ( ! selection_.isSingleLine() ) 1433 clipboard->setText( selection_.getSelectedText( logData ), 1434 QClipboard::Selection ); 1435 } 1436 1437 // Create the pop-up menu 1438 void AbstractLogView::createMenu() 1439 { 1440 copyAction_ = new QAction( tr("&Copy"), this ); 1441 // No text as this action title depends on the type of selection 1442 connect( copyAction_, SIGNAL(triggered()), this, SLOT(copy()) ); 1443 1444 // For '#' and '*', shortcuts doesn't seem to work but 1445 // at least it displays them in the menu, we manually handle those keys 1446 // as keys event anyway (in keyPressEvent). 1447 findNextAction_ = new QAction(tr("Find &next"), this); 1448 findNextAction_->setShortcut( Qt::Key_Asterisk ); 1449 findNextAction_->setStatusTip( tr("Find the next occurence") ); 1450 connect( findNextAction_, SIGNAL(triggered()), 1451 this, SLOT( findNextSelected() ) ); 1452 1453 findPreviousAction_ = new QAction( tr("Find &previous"), this ); 1454 findPreviousAction_->setShortcut( tr("#") ); 1455 findPreviousAction_->setStatusTip( tr("Find the previous occurence") ); 1456 connect( findPreviousAction_, SIGNAL(triggered()), 1457 this, SLOT( findPreviousSelected() ) ); 1458 1459 addToSearchAction_ = new QAction( tr("&Add to search"), this ); 1460 addToSearchAction_->setStatusTip( 1461 tr("Add the selection to the current search") ); 1462 connect( addToSearchAction_, SIGNAL( triggered() ), 1463 this, SLOT( addToSearch() ) ); 1464 1465 popupMenu_ = new QMenu( this ); 1466 popupMenu_->addAction( copyAction_ ); 1467 popupMenu_->addSeparator(); 1468 popupMenu_->addAction( findNextAction_ ); 1469 popupMenu_->addAction( findPreviousAction_ ); 1470 popupMenu_->addAction( addToSearchAction_ ); 1471 } 1472 1473 void AbstractLogView::considerMouseHovering( int x_pos, int y_pos ) 1474 { 1475 int line = convertCoordToLine( y_pos ); 1476 if ( ( x_pos < leftMarginPx_ ) 1477 && ( line >= 0 ) 1478 && ( line < logData->getNbLine() ) ) { 1479 // Mouse moved in the margin, send event up 1480 // (possibly to highlight the overview) 1481 if ( line != lastHoveredLine_ ) { 1482 LOG(logDEBUG) << "Mouse moved in margin line: " << line; 1483 emit mouseHoveredOverLine( line ); 1484 lastHoveredLine_ = line; 1485 } 1486 } 1487 else { 1488 if ( lastHoveredLine_ != -1 ) { 1489 emit mouseLeftHoveringZone(); 1490 lastHoveredLine_ = -1; 1491 } 1492 } 1493 } 1494 1495 void AbstractLogView::updateScrollBars() 1496 { 1497 verticalScrollBar()->setRange( 0, std::max( 0LL, 1498 logData->getNbLine() - getNbVisibleLines() ) ); 1499 1500 const int hScrollMaxValue = ( logData->getMaxLength() - getNbVisibleCols() + 1 ) > 0 ? 1501 ( logData->getMaxLength() - getNbVisibleCols() + 1 ) : 0; 1502 horizontalScrollBar()->setRange( 0, hScrollMaxValue ); 1503 } 1504 1505 namespace { 1506 1507 // Convert the length of the pull to follow bar to pixels 1508 int mapPullToFollowLength( int length ) 1509 { 1510 return length / 14; 1511 } 1512 1513 }; 1514