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