xref: /glogg/src/abstractlogview.cpp (revision 11582726a85c08832d009bfe179074b8d1152d21)
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