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