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