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