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