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