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