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