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