xref: /glogg/src/abstractlogview.cpp (revision 8ea5bc4cf192a6c6287db6ac5c56058f9d88fd6f)
1bb02e0acSNicolas Bonnefon /*
2ac6602a5SNicolas Bonnefon  * Copyright (C) 2009, 2010, 2011, 2012, 2013, 2015 Nicolas Bonnefon
3ac6602a5SNicolas Bonnefon  * and other contributors
4bb02e0acSNicolas Bonnefon  *
5bb02e0acSNicolas Bonnefon  * This file is part of glogg.
6bb02e0acSNicolas Bonnefon  *
7bb02e0acSNicolas Bonnefon  * glogg is free software: you can redistribute it and/or modify
8bb02e0acSNicolas Bonnefon  * it under the terms of the GNU General Public License as published by
9bb02e0acSNicolas Bonnefon  * the Free Software Foundation, either version 3 of the License, or
10bb02e0acSNicolas Bonnefon  * (at your option) any later version.
11bb02e0acSNicolas Bonnefon  *
12bb02e0acSNicolas Bonnefon  * glogg is distributed in the hope that it will be useful,
13bb02e0acSNicolas Bonnefon  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14bb02e0acSNicolas Bonnefon  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15bb02e0acSNicolas Bonnefon  * GNU General Public License for more details.
16bb02e0acSNicolas Bonnefon  *
17bb02e0acSNicolas Bonnefon  * You should have received a copy of the GNU General Public License
18bb02e0acSNicolas Bonnefon  * along with glogg.  If not, see <http://www.gnu.org/licenses/>.
19bb02e0acSNicolas Bonnefon  */
20bb02e0acSNicolas Bonnefon 
21bb02e0acSNicolas Bonnefon // This file implements the AbstractLogView base class.
22bb02e0acSNicolas Bonnefon // Most of the actual drawing and event management common to the two views
23bb02e0acSNicolas Bonnefon // is implemented in this class.  The class only calls protected virtual
24bb02e0acSNicolas Bonnefon // functions when view specific behaviour is desired, using the template
25bb02e0acSNicolas Bonnefon // pattern.
26bb02e0acSNicolas Bonnefon 
27bb02e0acSNicolas Bonnefon #include <iostream>
28bb02e0acSNicolas Bonnefon #include <cassert>
29bb02e0acSNicolas Bonnefon 
30bb02e0acSNicolas Bonnefon #include <QApplication>
31bb02e0acSNicolas Bonnefon #include <QClipboard>
32bb02e0acSNicolas Bonnefon #include <QFile>
33bb02e0acSNicolas Bonnefon #include <QRect>
34bb02e0acSNicolas Bonnefon #include <QPaintEvent>
35bb02e0acSNicolas Bonnefon #include <QPainter>
36bb02e0acSNicolas Bonnefon #include <QFontMetrics>
37bb02e0acSNicolas Bonnefon #include <QScrollBar>
38bb02e0acSNicolas Bonnefon #include <QMenu>
39bb02e0acSNicolas Bonnefon #include <QAction>
40bb02e0acSNicolas Bonnefon #include <QtCore>
411853a1fdSNicolas Bonnefon #include <QGestureEvent>
42bb02e0acSNicolas Bonnefon 
43bb02e0acSNicolas Bonnefon #include "log.h"
44bb02e0acSNicolas Bonnefon 
45bb02e0acSNicolas Bonnefon #include "persistentinfo.h"
46bb02e0acSNicolas Bonnefon #include "filterset.h"
47bb02e0acSNicolas Bonnefon #include "logmainview.h"
48bb02e0acSNicolas Bonnefon #include "quickfind.h"
49bb02e0acSNicolas Bonnefon #include "quickfindpattern.h"
50bb02e0acSNicolas Bonnefon #include "overview.h"
51bb02e0acSNicolas Bonnefon #include "configuration.h"
52bb02e0acSNicolas Bonnefon 
53bb02e0acSNicolas Bonnefon namespace {
540b05c6eaSNicolas Bonnefon int mapPullToFollowLength( int length );
550b05c6eaSNicolas Bonnefon };
560b05c6eaSNicolas Bonnefon 
570b05c6eaSNicolas Bonnefon namespace {
58bb02e0acSNicolas Bonnefon 
59bb02e0acSNicolas Bonnefon int countDigits( quint64 n )
60bb02e0acSNicolas Bonnefon {
61bb02e0acSNicolas Bonnefon     if (n == 0)
62bb02e0acSNicolas Bonnefon         return 1;
63bb02e0acSNicolas Bonnefon 
64bb02e0acSNicolas Bonnefon     // We must force the compiler to not store intermediate results
65bb02e0acSNicolas Bonnefon     // in registers because this causes incorrect result on some
66bb02e0acSNicolas Bonnefon     // systems under optimizations level >0. For the skeptical:
67bb02e0acSNicolas Bonnefon     //
68bb02e0acSNicolas Bonnefon     // #include <math.h>
69bb02e0acSNicolas Bonnefon     // #include <stdlib.h>
70bb02e0acSNicolas Bonnefon     // int main(int argc, char **argv) {
71bb02e0acSNicolas Bonnefon     //     (void)argc;
72bb02e0acSNicolas Bonnefon     //     long long int n = atoll(argv[1]);
73bb02e0acSNicolas Bonnefon     //     return floor( log( n ) / log( 10 ) + 1 );
74bb02e0acSNicolas Bonnefon     // }
75bb02e0acSNicolas Bonnefon     //
76bb02e0acSNicolas Bonnefon     // This is on Thinkpad T60 (Genuine Intel(R) CPU T2300).
77bb02e0acSNicolas Bonnefon     // $ g++ --version
78bb02e0acSNicolas Bonnefon     // g++ (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
79bb02e0acSNicolas Bonnefon     // $ g++ -O0 -Wall -W -o math math.cpp -lm; ./math 10; echo $?
80bb02e0acSNicolas Bonnefon     // 2
81bb02e0acSNicolas Bonnefon     // $ g++ -O1 -Wall -W -o math math.cpp -lm; ./math 10; echo $?
82bb02e0acSNicolas Bonnefon     // 1
83bb02e0acSNicolas Bonnefon     //
84bb02e0acSNicolas Bonnefon     // A fix is to (1) explicitly place intermediate results in
85bb02e0acSNicolas Bonnefon     // variables *and* (2) [A] mark them as 'volatile', or [B] pass
86bb02e0acSNicolas Bonnefon     // -ffloat-store to g++ (note that approach [A] is more portable).
87bb02e0acSNicolas Bonnefon 
88bb02e0acSNicolas Bonnefon     volatile qreal ln_n  = qLn( n );
89bb02e0acSNicolas Bonnefon     volatile qreal ln_10 = qLn( 10 );
90bb02e0acSNicolas Bonnefon     volatile qreal lg_n = ln_n / ln_10;
91bb02e0acSNicolas Bonnefon     volatile qreal lg_n_1 = lg_n + 1;
92bb02e0acSNicolas Bonnefon     volatile qreal fl_lg_n_1 = qFloor( lg_n_1 );
93bb02e0acSNicolas Bonnefon 
94bb02e0acSNicolas Bonnefon     return fl_lg_n_1;
95bb02e0acSNicolas Bonnefon }
96bb02e0acSNicolas Bonnefon 
97bb02e0acSNicolas Bonnefon } // anon namespace
98bb02e0acSNicolas Bonnefon 
99bb02e0acSNicolas Bonnefon 
100bb02e0acSNicolas Bonnefon LineChunk::LineChunk( int first_col, int last_col, ChunkType type )
101bb02e0acSNicolas Bonnefon {
102bb02e0acSNicolas Bonnefon     // LOG(logDEBUG) << "new LineChunk: " << first_col << " " << last_col;
103bb02e0acSNicolas Bonnefon 
104bb02e0acSNicolas Bonnefon     start_ = first_col;
105bb02e0acSNicolas Bonnefon     end_   = last_col;
106bb02e0acSNicolas Bonnefon     type_  = type;
107bb02e0acSNicolas Bonnefon }
108bb02e0acSNicolas Bonnefon 
109bb02e0acSNicolas Bonnefon QList<LineChunk> LineChunk::select( int sel_start, int sel_end ) const
110bb02e0acSNicolas Bonnefon {
111bb02e0acSNicolas Bonnefon     QList<LineChunk> list;
112bb02e0acSNicolas Bonnefon 
113bb02e0acSNicolas Bonnefon     if ( ( sel_start < start_ ) && ( sel_end < start_ ) ) {
114bb02e0acSNicolas Bonnefon         // Selection BEFORE this chunk: no change
115bb02e0acSNicolas Bonnefon         list << LineChunk( *this );
116bb02e0acSNicolas Bonnefon     }
117bb02e0acSNicolas Bonnefon     else if ( sel_start > end_ ) {
118bb02e0acSNicolas Bonnefon         // Selection AFTER this chunk: no change
119bb02e0acSNicolas Bonnefon         list << LineChunk( *this );
120bb02e0acSNicolas Bonnefon     }
121bb02e0acSNicolas Bonnefon     else /* if ( ( sel_start >= start_ ) && ( sel_end <= end_ ) ) */
122bb02e0acSNicolas Bonnefon     {
123bb02e0acSNicolas Bonnefon         // We only want to consider what's inside THIS chunk
124bb02e0acSNicolas Bonnefon         sel_start = qMax( sel_start, start_ );
125bb02e0acSNicolas Bonnefon         sel_end   = qMin( sel_end, end_ );
126bb02e0acSNicolas Bonnefon 
127bb02e0acSNicolas Bonnefon         if ( sel_start > start_ )
128bb02e0acSNicolas Bonnefon             list << LineChunk( start_, sel_start - 1, type_ );
129bb02e0acSNicolas Bonnefon         list << LineChunk( sel_start, sel_end, Selected );
130bb02e0acSNicolas Bonnefon         if ( sel_end < end_ )
131bb02e0acSNicolas Bonnefon             list << LineChunk( sel_end + 1, end_, type_ );
132bb02e0acSNicolas Bonnefon     }
133bb02e0acSNicolas Bonnefon 
134bb02e0acSNicolas Bonnefon     return list;
135bb02e0acSNicolas Bonnefon }
136bb02e0acSNicolas Bonnefon 
137bb02e0acSNicolas Bonnefon inline void LineDrawer::addChunk( int first_col, int last_col,
138bb02e0acSNicolas Bonnefon         QColor fore, QColor back )
139bb02e0acSNicolas Bonnefon {
140bb02e0acSNicolas Bonnefon     if ( first_col < 0 )
141bb02e0acSNicolas Bonnefon         first_col = 0;
142bb02e0acSNicolas Bonnefon     int length = last_col - first_col + 1;
143bb02e0acSNicolas Bonnefon     if ( length > 0 ) {
144bb02e0acSNicolas Bonnefon         list << Chunk ( first_col, length, fore, back );
145bb02e0acSNicolas Bonnefon     }
146bb02e0acSNicolas Bonnefon }
147bb02e0acSNicolas Bonnefon 
148bb02e0acSNicolas Bonnefon inline void LineDrawer::addChunk( const LineChunk& chunk,
149bb02e0acSNicolas Bonnefon         QColor fore, QColor back )
150bb02e0acSNicolas Bonnefon {
151bb02e0acSNicolas Bonnefon     int first_col = chunk.start();
152bb02e0acSNicolas Bonnefon     int last_col  = chunk.end();
153bb02e0acSNicolas Bonnefon 
154bb02e0acSNicolas Bonnefon     addChunk( first_col, last_col, fore, back );
155bb02e0acSNicolas Bonnefon }
156bb02e0acSNicolas Bonnefon 
157bb02e0acSNicolas Bonnefon inline void LineDrawer::draw( QPainter& painter,
158bb02e0acSNicolas Bonnefon         int initialXPos, int initialYPos,
159bb02e0acSNicolas Bonnefon         int line_width, const QString& line,
160bb02e0acSNicolas Bonnefon         int leftExtraBackgroundPx )
161bb02e0acSNicolas Bonnefon {
162bb02e0acSNicolas Bonnefon     QFontMetrics fm = painter.fontMetrics();
163bb02e0acSNicolas Bonnefon     const int fontHeight = fm.height();
164bb02e0acSNicolas Bonnefon     const int fontAscent = fm.ascent();
165bb02e0acSNicolas Bonnefon     // For some reason on Qt 4.8.2 for Win, maxWidth() is wrong but the
166bb02e0acSNicolas Bonnefon     // following give the right result, not sure why:
167bb02e0acSNicolas Bonnefon     const int fontWidth = fm.width( QChar('a') );
168bb02e0acSNicolas Bonnefon 
169bb02e0acSNicolas Bonnefon     int xPos = initialXPos;
170bb02e0acSNicolas Bonnefon     int yPos = initialYPos;
171bb02e0acSNicolas Bonnefon 
172bb02e0acSNicolas Bonnefon     foreach ( Chunk chunk, list ) {
173bb02e0acSNicolas Bonnefon         // Draw each chunk
174bb02e0acSNicolas Bonnefon         // LOG(logDEBUG) << "Chunk: " << chunk.start() << " " << chunk.length();
175bb02e0acSNicolas Bonnefon         QString cutline = line.mid( chunk.start(), chunk.length() );
176bb02e0acSNicolas Bonnefon         const int chunk_width = cutline.length() * fontWidth;
177bb02e0acSNicolas Bonnefon         if ( xPos == initialXPos ) {
178bb02e0acSNicolas Bonnefon             // First chunk, we extend the left background a bit,
179bb02e0acSNicolas Bonnefon             // it looks prettier.
180bb02e0acSNicolas Bonnefon             painter.fillRect( xPos - leftExtraBackgroundPx, yPos,
181bb02e0acSNicolas Bonnefon                     chunk_width + leftExtraBackgroundPx,
182bb02e0acSNicolas Bonnefon                     fontHeight, chunk.backColor() );
183bb02e0acSNicolas Bonnefon         }
184bb02e0acSNicolas Bonnefon         else {
185bb02e0acSNicolas Bonnefon             // other chunks...
186bb02e0acSNicolas Bonnefon             painter.fillRect( xPos, yPos, chunk_width,
187bb02e0acSNicolas Bonnefon                     fontHeight, chunk.backColor() );
188bb02e0acSNicolas Bonnefon         }
189bb02e0acSNicolas Bonnefon         painter.setPen( chunk.foreColor() );
190bb02e0acSNicolas Bonnefon         painter.drawText( xPos, yPos + fontAscent, cutline );
191bb02e0acSNicolas Bonnefon         xPos += chunk_width;
192bb02e0acSNicolas Bonnefon     }
193bb02e0acSNicolas Bonnefon 
194bb02e0acSNicolas Bonnefon     // Draw the empty block at the end of the line
195bb02e0acSNicolas Bonnefon     int blank_width = line_width - xPos;
196bb02e0acSNicolas Bonnefon 
197bb02e0acSNicolas Bonnefon     if ( blank_width > 0 )
198bb02e0acSNicolas Bonnefon         painter.fillRect( xPos, yPos, blank_width, fontHeight, backColor_ );
199bb02e0acSNicolas Bonnefon }
200bb02e0acSNicolas Bonnefon 
201bb02e0acSNicolas Bonnefon const int DigitsBuffer::timeout_ = 2000;
202bb02e0acSNicolas Bonnefon 
203bb02e0acSNicolas Bonnefon DigitsBuffer::DigitsBuffer() : QObject()
204bb02e0acSNicolas Bonnefon {
205bb02e0acSNicolas Bonnefon }
206bb02e0acSNicolas Bonnefon 
207bb02e0acSNicolas Bonnefon void DigitsBuffer::reset()
208bb02e0acSNicolas Bonnefon {
209bb02e0acSNicolas Bonnefon     LOG(logDEBUG) << "DigitsBuffer::reset()";
210bb02e0acSNicolas Bonnefon 
211bb02e0acSNicolas Bonnefon     timer_.stop();
212bb02e0acSNicolas Bonnefon     digits_.clear();
213bb02e0acSNicolas Bonnefon }
214bb02e0acSNicolas Bonnefon 
215bb02e0acSNicolas Bonnefon void DigitsBuffer::add( char character )
216bb02e0acSNicolas Bonnefon {
217bb02e0acSNicolas Bonnefon     LOG(logDEBUG) << "DigitsBuffer::add()";
218bb02e0acSNicolas Bonnefon 
219bb02e0acSNicolas Bonnefon     digits_.append( QChar( character ) );
220bb02e0acSNicolas Bonnefon     timer_.start( timeout_ , this );
221bb02e0acSNicolas Bonnefon }
222bb02e0acSNicolas Bonnefon 
223bb02e0acSNicolas Bonnefon int DigitsBuffer::content()
224bb02e0acSNicolas Bonnefon {
225bb02e0acSNicolas Bonnefon     int result = digits_.toInt();
226bb02e0acSNicolas Bonnefon     reset();
227bb02e0acSNicolas Bonnefon 
228bb02e0acSNicolas Bonnefon     return result;
229bb02e0acSNicolas Bonnefon }
230bb02e0acSNicolas Bonnefon 
231bb02e0acSNicolas Bonnefon void DigitsBuffer::timerEvent( QTimerEvent* event )
232bb02e0acSNicolas Bonnefon {
233bb02e0acSNicolas Bonnefon     if ( event->timerId() == timer_.timerId() ) {
234bb02e0acSNicolas Bonnefon         reset();
235bb02e0acSNicolas Bonnefon     }
236bb02e0acSNicolas Bonnefon     else {
237bb02e0acSNicolas Bonnefon         QObject::timerEvent( event );
238bb02e0acSNicolas Bonnefon     }
239bb02e0acSNicolas Bonnefon }
240bb02e0acSNicolas Bonnefon 
241bb02e0acSNicolas Bonnefon AbstractLogView::AbstractLogView(const AbstractLogData* newLogData,
242bb02e0acSNicolas Bonnefon         const QuickFindPattern* const quickFindPattern, QWidget* parent) :
243bb02e0acSNicolas Bonnefon     QAbstractScrollArea( parent ),
2442493b508SNicolas Bonnefon     followElasticHook_( HOOK_THRESHOLD ),
245bb02e0acSNicolas Bonnefon     lineNumbersVisible_( false ),
246bb02e0acSNicolas Bonnefon     selectionStartPos_(),
247bb02e0acSNicolas Bonnefon     selectionCurrentEndPos_(),
248bb02e0acSNicolas Bonnefon     autoScrollTimer_(),
249bb02e0acSNicolas Bonnefon     selection_(),
250bb02e0acSNicolas Bonnefon     quickFindPattern_( quickFindPattern ),
2512493b508SNicolas Bonnefon     quickFind_( newLogData, &selection_, quickFindPattern )
252bb02e0acSNicolas Bonnefon {
253bb02e0acSNicolas Bonnefon     logData = newLogData;
254bb02e0acSNicolas Bonnefon 
255bb02e0acSNicolas Bonnefon     followMode_ = false;
256bb02e0acSNicolas Bonnefon 
257bb02e0acSNicolas Bonnefon     selectionStarted_ = false;
258bb02e0acSNicolas Bonnefon     markingClickInitiated_ = false;
259bb02e0acSNicolas Bonnefon 
260bb02e0acSNicolas Bonnefon     firstLine = 0;
261bb02e0acSNicolas Bonnefon     firstCol = 0;
262bb02e0acSNicolas Bonnefon 
263bb02e0acSNicolas Bonnefon     overview_ = NULL;
264bb02e0acSNicolas Bonnefon     overviewWidget_ = NULL;
265bb02e0acSNicolas Bonnefon 
266bb02e0acSNicolas Bonnefon     // Display
267bb02e0acSNicolas Bonnefon     leftMarginPx_ = 0;
268bb02e0acSNicolas Bonnefon 
269bb02e0acSNicolas Bonnefon     // Fonts
270bb02e0acSNicolas Bonnefon     charWidth_ = 1;
271bb02e0acSNicolas Bonnefon     charHeight_ = 1;
272bb02e0acSNicolas Bonnefon 
273bb02e0acSNicolas Bonnefon     // Create the viewport QWidget
274bb02e0acSNicolas Bonnefon     setViewport( 0 );
275bb02e0acSNicolas Bonnefon 
276bb02e0acSNicolas Bonnefon     // Hovering
277bb02e0acSNicolas Bonnefon     setMouseTracking( true );
278bb02e0acSNicolas Bonnefon     lastHoveredLine_ = -1;
279bb02e0acSNicolas Bonnefon 
280bb02e0acSNicolas Bonnefon     // Init the popup menu
281bb02e0acSNicolas Bonnefon     createMenu();
282bb02e0acSNicolas Bonnefon 
283bb02e0acSNicolas Bonnefon     // Signals
284bb02e0acSNicolas Bonnefon     connect( quickFindPattern_, SIGNAL( patternUpdated() ),
285bb02e0acSNicolas Bonnefon             this, SLOT ( handlePatternUpdated() ) );
286bb02e0acSNicolas Bonnefon     connect( &quickFind_, SIGNAL( notify( const QFNotification& ) ),
287bb02e0acSNicolas Bonnefon             this, SIGNAL( notifyQuickFind( const QFNotification& ) ) );
288bb02e0acSNicolas Bonnefon     connect( &quickFind_, SIGNAL( clearNotification() ),
289bb02e0acSNicolas Bonnefon             this, SIGNAL( clearQuickFindNotification() ) );
2900b05c6eaSNicolas Bonnefon     connect( &followElasticHook_, SIGNAL( lengthChanged() ),
2910b05c6eaSNicolas Bonnefon             this, SLOT( repaint() ) );
292b297d2f4SNicolas Bonnefon     connect( &followElasticHook_, SIGNAL( hooked( bool ) ),
293b297d2f4SNicolas Bonnefon             this, SIGNAL( followModeChanged( bool ) ) );
294bb02e0acSNicolas Bonnefon }
295bb02e0acSNicolas Bonnefon 
296bb02e0acSNicolas Bonnefon AbstractLogView::~AbstractLogView()
297bb02e0acSNicolas Bonnefon {
298bb02e0acSNicolas Bonnefon }
299bb02e0acSNicolas Bonnefon 
300bb02e0acSNicolas Bonnefon 
301bb02e0acSNicolas Bonnefon //
302bb02e0acSNicolas Bonnefon // Received events
303bb02e0acSNicolas Bonnefon //
304bb02e0acSNicolas Bonnefon 
305bb02e0acSNicolas Bonnefon void AbstractLogView::changeEvent( QEvent* changeEvent )
306bb02e0acSNicolas Bonnefon {
307bb02e0acSNicolas Bonnefon     QAbstractScrollArea::changeEvent( changeEvent );
308bb02e0acSNicolas Bonnefon 
309bb02e0acSNicolas Bonnefon     // Stop the timer if the widget becomes inactive
310bb02e0acSNicolas Bonnefon     if ( changeEvent->type() == QEvent::ActivationChange ) {
311bb02e0acSNicolas Bonnefon         if ( ! isActiveWindow() )
312bb02e0acSNicolas Bonnefon             autoScrollTimer_.stop();
313bb02e0acSNicolas Bonnefon     }
314bb02e0acSNicolas Bonnefon     viewport()->update();
315bb02e0acSNicolas Bonnefon }
316bb02e0acSNicolas Bonnefon 
317bb02e0acSNicolas Bonnefon void AbstractLogView::mousePressEvent( QMouseEvent* mouseEvent )
318bb02e0acSNicolas Bonnefon {
31911582726SNicolas Bonnefon     static std::shared_ptr<Configuration> config =
32011582726SNicolas Bonnefon         Persistent<Configuration>( "settings" );
321bb02e0acSNicolas Bonnefon 
322bb02e0acSNicolas Bonnefon     if ( mouseEvent->button() == Qt::LeftButton )
323bb02e0acSNicolas Bonnefon     {
324bb02e0acSNicolas Bonnefon         int line = convertCoordToLine( mouseEvent->y() );
325bb02e0acSNicolas Bonnefon 
326bb02e0acSNicolas Bonnefon         if ( mouseEvent->modifiers() & Qt::ShiftModifier )
327bb02e0acSNicolas Bonnefon         {
328bb02e0acSNicolas Bonnefon             selection_.selectRangeFromPrevious( line );
329bb02e0acSNicolas Bonnefon             emit updateLineNumber( line );
330bb02e0acSNicolas Bonnefon             update();
331bb02e0acSNicolas Bonnefon         }
332bb02e0acSNicolas Bonnefon         else
333bb02e0acSNicolas Bonnefon         {
334bb02e0acSNicolas Bonnefon             if ( mouseEvent->x() < bulletZoneWidthPx_ ) {
335bb02e0acSNicolas Bonnefon                 // Mark a line if it is clicked in the left margin
336bb02e0acSNicolas Bonnefon                 // (only if click and release in the same area)
337bb02e0acSNicolas Bonnefon                 markingClickInitiated_ = true;
338bb02e0acSNicolas Bonnefon                 markingClickLine_ = line;
339bb02e0acSNicolas Bonnefon             }
340bb02e0acSNicolas Bonnefon             else {
341bb02e0acSNicolas Bonnefon                 // Select the line, and start a selection
342bb02e0acSNicolas Bonnefon                 if ( line < logData->getNbLine() ) {
343bb02e0acSNicolas Bonnefon                     selection_.selectLine( line );
344bb02e0acSNicolas Bonnefon                     emit updateLineNumber( line );
345bb02e0acSNicolas Bonnefon                     emit newSelection( line );
346bb02e0acSNicolas Bonnefon                 }
347bb02e0acSNicolas Bonnefon 
348bb02e0acSNicolas Bonnefon                 // Remember the click in case we're starting a selection
349bb02e0acSNicolas Bonnefon                 selectionStarted_ = true;
350bb02e0acSNicolas Bonnefon                 selectionStartPos_ = convertCoordToFilePos( mouseEvent->pos() );
351bb02e0acSNicolas Bonnefon                 selectionCurrentEndPos_ = selectionStartPos_;
352bb02e0acSNicolas Bonnefon             }
353bb02e0acSNicolas Bonnefon         }
354a9448ba0SNicolas Bonnefon 
355a9448ba0SNicolas Bonnefon         // Invalidate our cache
356a9448ba0SNicolas Bonnefon         textAreaCache_.invalid_ = true;
357bb02e0acSNicolas Bonnefon     }
358bb02e0acSNicolas Bonnefon     else if ( mouseEvent->button() == Qt::RightButton )
359bb02e0acSNicolas Bonnefon     {
360bb02e0acSNicolas Bonnefon         // Prepare the popup depending on selection type
361bb02e0acSNicolas Bonnefon         if ( selection_.isSingleLine() ) {
362bb02e0acSNicolas Bonnefon             copyAction_->setText( "&Copy this line" );
363bb02e0acSNicolas Bonnefon         }
364bb02e0acSNicolas Bonnefon         else {
365bb02e0acSNicolas Bonnefon             copyAction_->setText( "&Copy" );
366bb02e0acSNicolas Bonnefon             copyAction_->setStatusTip( tr("Copy the selection") );
367bb02e0acSNicolas Bonnefon         }
368bb02e0acSNicolas Bonnefon 
369bb02e0acSNicolas Bonnefon         if ( selection_.isPortion() ) {
370bb02e0acSNicolas Bonnefon             findNextAction_->setEnabled( true );
371bb02e0acSNicolas Bonnefon             findPreviousAction_->setEnabled( true );
372bb02e0acSNicolas Bonnefon             addToSearchAction_->setEnabled( true );
373bb02e0acSNicolas Bonnefon         }
374bb02e0acSNicolas Bonnefon         else {
375bb02e0acSNicolas Bonnefon             findNextAction_->setEnabled( false );
376bb02e0acSNicolas Bonnefon             findPreviousAction_->setEnabled( false );
377bb02e0acSNicolas Bonnefon             addToSearchAction_->setEnabled( false );
378bb02e0acSNicolas Bonnefon         }
379bb02e0acSNicolas Bonnefon 
380bb02e0acSNicolas Bonnefon         // "Add to search" only makes sense in regexp mode
38111582726SNicolas Bonnefon         if ( config->mainRegexpType() != ExtendedRegexp )
382bb02e0acSNicolas Bonnefon             addToSearchAction_->setEnabled( false );
383bb02e0acSNicolas Bonnefon 
384bb02e0acSNicolas Bonnefon         // Display the popup (blocking)
385bb02e0acSNicolas Bonnefon         popupMenu_->exec( QCursor::pos() );
386bb02e0acSNicolas Bonnefon     }
38745ef183cSNicolas Bonnefon 
38845ef183cSNicolas Bonnefon     emit activity();
389bb02e0acSNicolas Bonnefon }
390bb02e0acSNicolas Bonnefon 
391bb02e0acSNicolas Bonnefon void AbstractLogView::mouseMoveEvent( QMouseEvent* mouseEvent )
392bb02e0acSNicolas Bonnefon {
393bb02e0acSNicolas Bonnefon     // Selection implementation
394bb02e0acSNicolas Bonnefon     if ( selectionStarted_ )
395bb02e0acSNicolas Bonnefon     {
396a9448ba0SNicolas Bonnefon         // Invalidate our cache
397a9448ba0SNicolas Bonnefon         textAreaCache_.invalid_ = true;
398a9448ba0SNicolas Bonnefon 
399bb02e0acSNicolas Bonnefon         QPoint thisEndPos = convertCoordToFilePos( mouseEvent->pos() );
400bb02e0acSNicolas Bonnefon         if ( thisEndPos != selectionCurrentEndPos_ )
401bb02e0acSNicolas Bonnefon         {
402bb02e0acSNicolas Bonnefon             // Are we on a different line?
403bb02e0acSNicolas Bonnefon             if ( selectionStartPos_.y() != thisEndPos.y() )
404bb02e0acSNicolas Bonnefon             {
405bb02e0acSNicolas Bonnefon                 if ( thisEndPos.y() != selectionCurrentEndPos_.y() )
406bb02e0acSNicolas Bonnefon                 {
407bb02e0acSNicolas Bonnefon                     // This is a 'range' selection
408bb02e0acSNicolas Bonnefon                     selection_.selectRange( selectionStartPos_.y(),
409bb02e0acSNicolas Bonnefon                             thisEndPos.y() );
410bb02e0acSNicolas Bonnefon                     emit updateLineNumber( thisEndPos.y() );
411bb02e0acSNicolas Bonnefon                     update();
412bb02e0acSNicolas Bonnefon                 }
413bb02e0acSNicolas Bonnefon             }
414bb02e0acSNicolas Bonnefon             // So we are on the same line. Are we moving horizontaly?
415bb02e0acSNicolas Bonnefon             else if ( thisEndPos.x() != selectionCurrentEndPos_.x() )
416bb02e0acSNicolas Bonnefon             {
417bb02e0acSNicolas Bonnefon                 // This is a 'portion' selection
418bb02e0acSNicolas Bonnefon                 selection_.selectPortion( thisEndPos.y(),
419bb02e0acSNicolas Bonnefon                         selectionStartPos_.x(), thisEndPos.x() );
420bb02e0acSNicolas Bonnefon                 update();
421bb02e0acSNicolas Bonnefon             }
422bb02e0acSNicolas Bonnefon             // On the same line, and moving vertically then
423bb02e0acSNicolas Bonnefon             else
424bb02e0acSNicolas Bonnefon             {
425bb02e0acSNicolas Bonnefon                 // This is a 'line' selection
426bb02e0acSNicolas Bonnefon                 selection_.selectLine( thisEndPos.y() );
427bb02e0acSNicolas Bonnefon                 emit updateLineNumber( thisEndPos.y() );
428bb02e0acSNicolas Bonnefon                 update();
429bb02e0acSNicolas Bonnefon             }
430bb02e0acSNicolas Bonnefon             selectionCurrentEndPos_ = thisEndPos;
431bb02e0acSNicolas Bonnefon 
432bb02e0acSNicolas Bonnefon             // Do we need to scroll while extending the selection?
433bb02e0acSNicolas Bonnefon             QRect visible = viewport()->rect();
434bb02e0acSNicolas Bonnefon             if ( visible.contains( mouseEvent->pos() ) )
435bb02e0acSNicolas Bonnefon                 autoScrollTimer_.stop();
436bb02e0acSNicolas Bonnefon             else if ( ! autoScrollTimer_.isActive() )
437bb02e0acSNicolas Bonnefon                 autoScrollTimer_.start( 100, this );
438bb02e0acSNicolas Bonnefon         }
439bb02e0acSNicolas Bonnefon     }
440bb02e0acSNicolas Bonnefon     else {
441bb02e0acSNicolas Bonnefon         considerMouseHovering( mouseEvent->x(), mouseEvent->y() );
442bb02e0acSNicolas Bonnefon     }
443bb02e0acSNicolas Bonnefon }
444bb02e0acSNicolas Bonnefon 
445bb02e0acSNicolas Bonnefon void AbstractLogView::mouseReleaseEvent( QMouseEvent* mouseEvent )
446bb02e0acSNicolas Bonnefon {
447bb02e0acSNicolas Bonnefon     if ( markingClickInitiated_ ) {
448bb02e0acSNicolas Bonnefon         markingClickInitiated_ = false;
449bb02e0acSNicolas Bonnefon         int line = convertCoordToLine( mouseEvent->y() );
450a9448ba0SNicolas Bonnefon         if ( line == markingClickLine_ ) {
451a9448ba0SNicolas Bonnefon             // Invalidate our cache
452a9448ba0SNicolas Bonnefon             textAreaCache_.invalid_ = true;
453a9448ba0SNicolas Bonnefon 
454bb02e0acSNicolas Bonnefon             emit markLine( line );
455bb02e0acSNicolas Bonnefon         }
456a9448ba0SNicolas Bonnefon     }
457bb02e0acSNicolas Bonnefon     else {
458bb02e0acSNicolas Bonnefon         selectionStarted_ = false;
459bb02e0acSNicolas Bonnefon         if ( autoScrollTimer_.isActive() )
460bb02e0acSNicolas Bonnefon             autoScrollTimer_.stop();
461bb02e0acSNicolas Bonnefon         updateGlobalSelection();
462bb02e0acSNicolas Bonnefon     }
463bb02e0acSNicolas Bonnefon }
464bb02e0acSNicolas Bonnefon 
465bb02e0acSNicolas Bonnefon void AbstractLogView::mouseDoubleClickEvent( QMouseEvent* mouseEvent )
466bb02e0acSNicolas Bonnefon {
467bb02e0acSNicolas Bonnefon     if ( mouseEvent->button() == Qt::LeftButton )
468bb02e0acSNicolas Bonnefon     {
469a9448ba0SNicolas Bonnefon         // Invalidate our cache
470a9448ba0SNicolas Bonnefon         textAreaCache_.invalid_ = true;
471a9448ba0SNicolas Bonnefon 
472bb02e0acSNicolas Bonnefon         const QPoint pos = convertCoordToFilePos( mouseEvent->pos() );
473bb02e0acSNicolas Bonnefon         selectWordAtPosition( pos );
474bb02e0acSNicolas Bonnefon     }
47545ef183cSNicolas Bonnefon 
47645ef183cSNicolas Bonnefon     emit activity();
477bb02e0acSNicolas Bonnefon }
478bb02e0acSNicolas Bonnefon 
479bb02e0acSNicolas Bonnefon void AbstractLogView::timerEvent( QTimerEvent* timerEvent )
480bb02e0acSNicolas Bonnefon {
481bb02e0acSNicolas Bonnefon     if ( timerEvent->timerId() == autoScrollTimer_.timerId() ) {
482bb02e0acSNicolas Bonnefon         QRect visible = viewport()->rect();
483bb02e0acSNicolas Bonnefon         const QPoint globalPos = QCursor::pos();
484bb02e0acSNicolas Bonnefon         const QPoint pos = viewport()->mapFromGlobal( globalPos );
485bb02e0acSNicolas Bonnefon         QMouseEvent ev( QEvent::MouseMove, pos, globalPos, Qt::LeftButton,
486bb02e0acSNicolas Bonnefon                 Qt::LeftButton, Qt::NoModifier );
487bb02e0acSNicolas Bonnefon         mouseMoveEvent( &ev );
488bb02e0acSNicolas Bonnefon         int deltaX = qMax( pos.x() - visible.left(),
489bb02e0acSNicolas Bonnefon                 visible.right() - pos.x() ) - visible.width();
490bb02e0acSNicolas Bonnefon         int deltaY = qMax( pos.y() - visible.top(),
491bb02e0acSNicolas Bonnefon                 visible.bottom() - pos.y() ) - visible.height();
492bb02e0acSNicolas Bonnefon         int delta = qMax( deltaX, deltaY );
493bb02e0acSNicolas Bonnefon 
494bb02e0acSNicolas Bonnefon         if ( delta >= 0 ) {
495bb02e0acSNicolas Bonnefon             if ( delta < 7 )
496bb02e0acSNicolas Bonnefon                 delta = 7;
497bb02e0acSNicolas Bonnefon             int timeout = 4900 / ( delta * delta );
498bb02e0acSNicolas Bonnefon             autoScrollTimer_.start( timeout, this );
499bb02e0acSNicolas Bonnefon 
500bb02e0acSNicolas Bonnefon             if ( deltaX > 0 )
501bb02e0acSNicolas Bonnefon                 horizontalScrollBar()->triggerAction(
502bb02e0acSNicolas Bonnefon                         pos.x() <visible.center().x() ?
503bb02e0acSNicolas Bonnefon                         QAbstractSlider::SliderSingleStepSub :
504bb02e0acSNicolas Bonnefon                         QAbstractSlider::SliderSingleStepAdd );
505bb02e0acSNicolas Bonnefon 
506bb02e0acSNicolas Bonnefon             if ( deltaY > 0 )
507bb02e0acSNicolas Bonnefon                 verticalScrollBar()->triggerAction(
508bb02e0acSNicolas Bonnefon                         pos.y() <visible.center().y() ?
509bb02e0acSNicolas Bonnefon                         QAbstractSlider::SliderSingleStepSub :
510bb02e0acSNicolas Bonnefon                         QAbstractSlider::SliderSingleStepAdd );
511bb02e0acSNicolas Bonnefon         }
512bb02e0acSNicolas Bonnefon     }
513bb02e0acSNicolas Bonnefon     QAbstractScrollArea::timerEvent( timerEvent );
514bb02e0acSNicolas Bonnefon }
515bb02e0acSNicolas Bonnefon 
516bb02e0acSNicolas Bonnefon void AbstractLogView::keyPressEvent( QKeyEvent* keyEvent )
517bb02e0acSNicolas Bonnefon {
518bb02e0acSNicolas Bonnefon     LOG(logDEBUG4) << "keyPressEvent received";
519bb02e0acSNicolas Bonnefon     bool controlModifier = (keyEvent->modifiers() & Qt::ControlModifier) == Qt::ControlModifier;
520bb02e0acSNicolas Bonnefon     bool shiftModifier = (keyEvent->modifiers() & Qt::ShiftModifier) == Qt::ShiftModifier;
521bb02e0acSNicolas Bonnefon 
522bb02e0acSNicolas Bonnefon     if ( keyEvent->key() == Qt::Key_Left )
523bb02e0acSNicolas Bonnefon         horizontalScrollBar()->triggerAction(QScrollBar::SliderPageStepSub);
524bb02e0acSNicolas Bonnefon     else if ( keyEvent->key() == Qt::Key_Right )
525bb02e0acSNicolas Bonnefon         horizontalScrollBar()->triggerAction(QScrollBar::SliderPageStepAdd);
526bb02e0acSNicolas Bonnefon     else if ( keyEvent->key() == Qt::Key_Home && !controlModifier)
527bb02e0acSNicolas Bonnefon         jumpToStartOfLine();
528bb02e0acSNicolas Bonnefon     else if ( keyEvent->key() == Qt::Key_End  && !controlModifier)
529bb02e0acSNicolas Bonnefon         jumpToRightOfScreen();
530bb02e0acSNicolas Bonnefon     else if ( (keyEvent->key() == Qt::Key_PageDown && controlModifier)
531bb02e0acSNicolas Bonnefon            || (keyEvent->key() == Qt::Key_End && controlModifier) )
532bb02e0acSNicolas Bonnefon     {
533b297d2f4SNicolas Bonnefon         disableFollow(); // duplicate of 'G' action.
534bb02e0acSNicolas Bonnefon         selection_.selectLine( logData->getNbLine() - 1 );
535bb02e0acSNicolas Bonnefon         emit updateLineNumber( logData->getNbLine() - 1 );
536bb02e0acSNicolas Bonnefon         jumpToBottom();
537bb02e0acSNicolas Bonnefon     }
538bb02e0acSNicolas Bonnefon     else if ( (keyEvent->key() == Qt::Key_PageUp && controlModifier)
539bb02e0acSNicolas Bonnefon            || (keyEvent->key() == Qt::Key_Home && controlModifier) )
540bb02e0acSNicolas Bonnefon     {
541b297d2f4SNicolas Bonnefon         disableFollow(); // like 'g' but 0 input first line action.
542bb02e0acSNicolas Bonnefon         selectAndDisplayLine( 0 );
543bb02e0acSNicolas Bonnefon         emit updateLineNumber( 0 );
544bb02e0acSNicolas Bonnefon     }
545bb02e0acSNicolas Bonnefon     else if ( keyEvent->key() == Qt::Key_F3 && !shiftModifier )
546bb02e0acSNicolas Bonnefon         searchNext(); // duplicate of 'n' action.
547bb02e0acSNicolas Bonnefon     else if ( keyEvent->key() == Qt::Key_F3 && shiftModifier )
548bb02e0acSNicolas Bonnefon         searchPrevious(); // duplicate of 'N' action.
549bb02e0acSNicolas Bonnefon     else {
550eb6a8f99SNicolas Bonnefon         const char character = (keyEvent->text())[0].toLatin1();
551bb02e0acSNicolas Bonnefon 
552f88e59a4SNicolas Bonnefon         if ( keyEvent->modifiers() == Qt::NoModifier &&
553f88e59a4SNicolas Bonnefon                 ( character >= '0' ) && ( character <= '9' ) ) {
554bb02e0acSNicolas Bonnefon             // Adds the digit to the timed buffer
555bb02e0acSNicolas Bonnefon             digitsBuffer_.add( character );
556bb02e0acSNicolas Bonnefon         }
557bb02e0acSNicolas Bonnefon         else {
558eb6a8f99SNicolas Bonnefon             switch ( (keyEvent->text())[0].toLatin1() ) {
559bb02e0acSNicolas Bonnefon                 case 'j':
560bb02e0acSNicolas Bonnefon                     {
561bb02e0acSNicolas Bonnefon                         int delta = qMax( 1, digitsBuffer_.content() );
562b297d2f4SNicolas Bonnefon                         disableFollow();
563bb02e0acSNicolas Bonnefon                         //verticalScrollBar()->triggerAction(
564bb02e0acSNicolas Bonnefon                         //QScrollBar::SliderSingleStepAdd);
565bb02e0acSNicolas Bonnefon                         moveSelection( delta );
566bb02e0acSNicolas Bonnefon                         break;
567bb02e0acSNicolas Bonnefon                     }
568bb02e0acSNicolas Bonnefon                 case 'k':
569bb02e0acSNicolas Bonnefon                     {
570bb02e0acSNicolas Bonnefon                         int delta = qMin( -1, - digitsBuffer_.content() );
571b297d2f4SNicolas Bonnefon                         disableFollow();
572bb02e0acSNicolas Bonnefon                         //verticalScrollBar()->triggerAction(
573bb02e0acSNicolas Bonnefon                         //QScrollBar::SliderSingleStepSub);
574bb02e0acSNicolas Bonnefon                         moveSelection( delta );
575bb02e0acSNicolas Bonnefon                         break;
576bb02e0acSNicolas Bonnefon                     }
577bb02e0acSNicolas Bonnefon                 case 'h':
578bb02e0acSNicolas Bonnefon                     horizontalScrollBar()->triggerAction(
579bb02e0acSNicolas Bonnefon                             QScrollBar::SliderSingleStepSub);
580bb02e0acSNicolas Bonnefon                     break;
581bb02e0acSNicolas Bonnefon                 case 'l':
582bb02e0acSNicolas Bonnefon                     horizontalScrollBar()->triggerAction(
583bb02e0acSNicolas Bonnefon                             QScrollBar::SliderSingleStepAdd);
584bb02e0acSNicolas Bonnefon                     break;
585bb02e0acSNicolas Bonnefon                 case '0':
586bb02e0acSNicolas Bonnefon                     jumpToStartOfLine();
587bb02e0acSNicolas Bonnefon                     break;
588bb02e0acSNicolas Bonnefon                 case '$':
589bb02e0acSNicolas Bonnefon                     jumpToEndOfLine();
590bb02e0acSNicolas Bonnefon                     break;
591bb02e0acSNicolas Bonnefon                 case 'g':
592bb02e0acSNicolas Bonnefon                     {
593bb02e0acSNicolas Bonnefon                         int newLine = qMax( 0, digitsBuffer_.content() - 1 );
594bb02e0acSNicolas Bonnefon                         if ( newLine >= logData->getNbLine() )
595bb02e0acSNicolas Bonnefon                             newLine = logData->getNbLine() - 1;
596b297d2f4SNicolas Bonnefon                         disableFollow();
597bb02e0acSNicolas Bonnefon                         selectAndDisplayLine( newLine );
598bb02e0acSNicolas Bonnefon                         emit updateLineNumber( newLine );
599bb02e0acSNicolas Bonnefon                         break;
600bb02e0acSNicolas Bonnefon                     }
601bb02e0acSNicolas Bonnefon                 case 'G':
602b297d2f4SNicolas Bonnefon                     disableFollow();
603bb02e0acSNicolas Bonnefon                     selection_.selectLine( logData->getNbLine() - 1 );
604bb02e0acSNicolas Bonnefon                     emit updateLineNumber( logData->getNbLine() - 1 );
605bb02e0acSNicolas Bonnefon                     jumpToBottom();
606bb02e0acSNicolas Bonnefon                     break;
607bb02e0acSNicolas Bonnefon                 case 'n':
608bb02e0acSNicolas Bonnefon                     emit searchNext();
609bb02e0acSNicolas Bonnefon                     break;
610bb02e0acSNicolas Bonnefon                 case 'N':
611bb02e0acSNicolas Bonnefon                     emit searchPrevious();
612bb02e0acSNicolas Bonnefon                     break;
613bb02e0acSNicolas Bonnefon                 case '*':
614bb02e0acSNicolas Bonnefon                     // Use the selected 'word' and search forward
615bb02e0acSNicolas Bonnefon                     findNextSelected();
616bb02e0acSNicolas Bonnefon                     break;
617bb02e0acSNicolas Bonnefon                 case '#':
618bb02e0acSNicolas Bonnefon                     // Use the selected 'word' and search backward
619bb02e0acSNicolas Bonnefon                     findPreviousSelected();
620bb02e0acSNicolas Bonnefon                     break;
621bb02e0acSNicolas Bonnefon                 default:
622bb02e0acSNicolas Bonnefon                     keyEvent->ignore();
623bb02e0acSNicolas Bonnefon             }
624bb02e0acSNicolas Bonnefon         }
625bb02e0acSNicolas Bonnefon     }
626bb02e0acSNicolas Bonnefon 
62745ef183cSNicolas Bonnefon     if ( keyEvent->isAccepted() ) {
62845ef183cSNicolas Bonnefon         emit activity();
62945ef183cSNicolas Bonnefon     }
63045ef183cSNicolas Bonnefon     else {
631bb02e0acSNicolas Bonnefon         QAbstractScrollArea::keyPressEvent( keyEvent );
632bb02e0acSNicolas Bonnefon     }
63345ef183cSNicolas Bonnefon }
634bb02e0acSNicolas Bonnefon 
635bb02e0acSNicolas Bonnefon void AbstractLogView::wheelEvent( QWheelEvent* wheelEvent )
636bb02e0acSNicolas Bonnefon {
63745ef183cSNicolas Bonnefon     emit activity();
638bb02e0acSNicolas Bonnefon 
639a9448ba0SNicolas Bonnefon     // LOG(logDEBUG) << "wheelEvent";
6400b05c6eaSNicolas Bonnefon 
6412493b508SNicolas Bonnefon     // This is to handle the case where follow mode is on, but the user
6422493b508SNicolas Bonnefon     // has moved using the scroll bar. We take them back to the bottom.
6432493b508SNicolas Bonnefon     if ( followMode_ )
6442493b508SNicolas Bonnefon         jumpToBottom();
6452493b508SNicolas Bonnefon 
64668d95677SNicolas Bonnefon     int y_delta = 0;
6470b05c6eaSNicolas Bonnefon     if ( verticalScrollBar()->value() == verticalScrollBar()->maximum() ) {
648819e4a32SNicolas Bonnefon         // First see if we need to block the elastic (on Mac)
649819e4a32SNicolas Bonnefon         if ( wheelEvent->phase() == Qt::ScrollBegin )
650819e4a32SNicolas Bonnefon             followElasticHook_.hold();
651819e4a32SNicolas Bonnefon         else if ( wheelEvent->phase() == Qt::ScrollEnd )
652819e4a32SNicolas Bonnefon             followElasticHook_.release();
653819e4a32SNicolas Bonnefon 
65468d95677SNicolas Bonnefon         auto pixel_delta = wheelEvent->pixelDelta();
65568d95677SNicolas Bonnefon 
65668d95677SNicolas Bonnefon         if ( pixel_delta.isNull() ) {
65706d5d36cSNicolas Bonnefon             y_delta = wheelEvent->angleDelta().y() / 0.7;
65868d95677SNicolas Bonnefon         }
65968d95677SNicolas Bonnefon         else {
66068d95677SNicolas Bonnefon             y_delta = pixel_delta.y();
66168d95677SNicolas Bonnefon         }
66268d95677SNicolas Bonnefon 
66368d95677SNicolas Bonnefon         // LOG(logDEBUG) << "Elastic " << y_delta;
66468d95677SNicolas Bonnefon         followElasticHook_.move( - y_delta );
6650b05c6eaSNicolas Bonnefon     }
6660b05c6eaSNicolas Bonnefon 
667a9448ba0SNicolas Bonnefon     // LOG(logDEBUG) << "Length = " << followElasticHook_.length();
668ac6602a5SNicolas Bonnefon     if ( followElasticHook_.length() == 0 && !followElasticHook_.isHooked() ) {
669bb02e0acSNicolas Bonnefon         QAbstractScrollArea::wheelEvent( wheelEvent );
670bb02e0acSNicolas Bonnefon     }
6710b05c6eaSNicolas Bonnefon }
672bb02e0acSNicolas Bonnefon 
673bb02e0acSNicolas Bonnefon void AbstractLogView::resizeEvent( QResizeEvent* )
674bb02e0acSNicolas Bonnefon {
675bb02e0acSNicolas Bonnefon     if ( logData == NULL )
676bb02e0acSNicolas Bonnefon         return;
677bb02e0acSNicolas Bonnefon 
678bb02e0acSNicolas Bonnefon     LOG(logDEBUG) << "resizeEvent received";
679bb02e0acSNicolas Bonnefon 
680bb02e0acSNicolas Bonnefon     updateDisplaySize();
681bb02e0acSNicolas Bonnefon }
682bb02e0acSNicolas Bonnefon 
6831853a1fdSNicolas Bonnefon bool AbstractLogView::event( QEvent* e )
6841853a1fdSNicolas Bonnefon {
6851853a1fdSNicolas Bonnefon     LOG(logDEBUG4) << "Event! Type: " << e->type();
6861853a1fdSNicolas Bonnefon 
6871853a1fdSNicolas Bonnefon     // Make sure we ignore the gesture events as
6881853a1fdSNicolas Bonnefon     // they seem to be accepted by default.
6891853a1fdSNicolas Bonnefon     if ( e->type() == QEvent::Gesture ) {
6901853a1fdSNicolas Bonnefon         auto gesture_event = dynamic_cast<QGestureEvent*>( e );
6911853a1fdSNicolas Bonnefon         if ( gesture_event ) {
6921853a1fdSNicolas Bonnefon             foreach( QGesture* gesture, gesture_event->gestures() ) {
6932493b508SNicolas Bonnefon                 LOG(logDEBUG4) << "Gesture: " << gesture->gestureType();
6941853a1fdSNicolas Bonnefon                 gesture_event->ignore( gesture );
6951853a1fdSNicolas Bonnefon             }
6961853a1fdSNicolas Bonnefon 
6971853a1fdSNicolas Bonnefon             // Ensure the event is sent up to parents who might care
6981853a1fdSNicolas Bonnefon             return false;
6991853a1fdSNicolas Bonnefon         }
7001853a1fdSNicolas Bonnefon     }
7011853a1fdSNicolas Bonnefon 
7021853a1fdSNicolas Bonnefon     return QAbstractScrollArea::event( e );
7031853a1fdSNicolas Bonnefon }
7041853a1fdSNicolas Bonnefon 
705bb02e0acSNicolas Bonnefon void AbstractLogView::scrollContentsBy( int dx, int dy )
706bb02e0acSNicolas Bonnefon {
7070b05c6eaSNicolas Bonnefon     LOG(logDEBUG) << "scrollContentsBy received " << dy;
708bb02e0acSNicolas Bonnefon 
7092493b508SNicolas Bonnefon     firstLine = std::max( static_cast<int32_t>( firstLine ) - dy, 0 );
710bb02e0acSNicolas Bonnefon     firstCol  = (firstCol - dx) > 0 ? firstCol - dx : 0;
7112493b508SNicolas Bonnefon     LineNumber last_line  = firstLine + getNbVisibleLines();
712bb02e0acSNicolas Bonnefon 
713bb02e0acSNicolas Bonnefon     // Update the overview if we have one
714bb02e0acSNicolas Bonnefon     if ( overview_ != NULL )
7152493b508SNicolas Bonnefon         overview_->updateCurrentPosition( firstLine, last_line );
716bb02e0acSNicolas Bonnefon 
717bb02e0acSNicolas Bonnefon     // Are we hovering over a new line?
718bb02e0acSNicolas Bonnefon     const QPoint mouse_pos = mapFromGlobal( QCursor::pos() );
719bb02e0acSNicolas Bonnefon     considerMouseHovering( mouse_pos.x(), mouse_pos.y() );
720bb02e0acSNicolas Bonnefon 
721bb02e0acSNicolas Bonnefon     // Redraw
722bb02e0acSNicolas Bonnefon     update();
723bb02e0acSNicolas Bonnefon }
724bb02e0acSNicolas Bonnefon 
725bb02e0acSNicolas Bonnefon void AbstractLogView::paintEvent( QPaintEvent* paintEvent )
726bb02e0acSNicolas Bonnefon {
7272493b508SNicolas Bonnefon     const QRect invalidRect = paintEvent->rect();
728bb02e0acSNicolas Bonnefon     if ( (invalidRect.isEmpty()) || (logData == NULL) )
729bb02e0acSNicolas Bonnefon         return;
730bb02e0acSNicolas Bonnefon 
731bb02e0acSNicolas Bonnefon     LOG(logDEBUG4) << "paintEvent received, firstLine=" << firstLine
7322493b508SNicolas Bonnefon         << " rect: " << invalidRect.topLeft().x() <<
733bb02e0acSNicolas Bonnefon         ", " << invalidRect.topLeft().y() <<
734bb02e0acSNicolas Bonnefon         ", " << invalidRect.bottomRight().x() <<
735bb02e0acSNicolas Bonnefon         ", " << invalidRect.bottomRight().y();
736bb02e0acSNicolas Bonnefon 
7378bda3285SNicolas Bonnefon #ifdef GLOGG_PERF_MEASURE_FPS
7388bda3285SNicolas Bonnefon     static uint32_t maxline = logData->getNbLine();
7398bda3285SNicolas Bonnefon     if ( ! perfCounter_.addEvent() && logData->getNbLine() > maxline ) {
740045703daSNicolas Bonnefon         LOG(logWARNING) << "Redraw per second: " << perfCounter_.readAndReset()
741045703daSNicolas Bonnefon             << " lines: " << logData->getNbLine();
742045703daSNicolas Bonnefon         perfCounter_.addEvent();
7438bda3285SNicolas Bonnefon         maxline = logData->getNbLine();
744045703daSNicolas Bonnefon     }
7458bda3285SNicolas Bonnefon #endif
746045703daSNicolas Bonnefon 
747a9448ba0SNicolas Bonnefon     auto start = std::chrono::system_clock::now();
748a9448ba0SNicolas Bonnefon 
749a9448ba0SNicolas Bonnefon     // Can we use our cache?
750ac6602a5SNicolas Bonnefon     int32_t delta_y = textAreaCache_.first_line_ - firstLine;
751ac6602a5SNicolas Bonnefon 
752ac6602a5SNicolas Bonnefon     if ( textAreaCache_.invalid_ || ( textAreaCache_.first_column_ != firstCol ) ) {
753ac6602a5SNicolas Bonnefon         // Force a full redraw
754ac6602a5SNicolas Bonnefon         delta_y = INT32_MAX;
755a9448ba0SNicolas Bonnefon     }
756ac6602a5SNicolas Bonnefon 
757ac6602a5SNicolas Bonnefon     if ( delta_y != 0 ) {
758ac6602a5SNicolas Bonnefon         // Full or partial redraw
759ac6602a5SNicolas Bonnefon         drawTextArea( &textAreaCache_.pixmap_, delta_y );
760a9448ba0SNicolas Bonnefon 
761a9448ba0SNicolas Bonnefon         textAreaCache_.invalid_      = false;
762a9448ba0SNicolas Bonnefon         textAreaCache_.first_line_   = firstLine;
763ac6602a5SNicolas Bonnefon         textAreaCache_.first_column_ = firstCol;
764a9448ba0SNicolas Bonnefon 
765a9448ba0SNicolas Bonnefon         LOG(logDEBUG) << "End of writing " <<
766a9448ba0SNicolas Bonnefon             std::chrono::duration_cast<std::chrono::microseconds>
767a9448ba0SNicolas Bonnefon             ( std::chrono::system_clock::now() - start ).count();
768a9448ba0SNicolas Bonnefon     }
769ac6602a5SNicolas Bonnefon     else {
770ac6602a5SNicolas Bonnefon         // Use the cache as is: nothing to do!
771ac6602a5SNicolas Bonnefon     }
772a9448ba0SNicolas Bonnefon 
773b297d2f4SNicolas Bonnefon     // Height including the potentially invisible last line
774b297d2f4SNicolas Bonnefon     const int wholeHeight = getNbVisibleLines() * charHeight_;
7750b05c6eaSNicolas Bonnefon     // Height in pixels of the "pull to follow" bottom bar.
7762493b508SNicolas Bonnefon     pullToFollowHeight_ = mapPullToFollowLength( followElasticHook_.length() )
777b297d2f4SNicolas Bonnefon         + ( followElasticHook_.isHooked() ?
7782493b508SNicolas Bonnefon                 ( wholeHeight - viewport()->height() ) + PULL_TO_FOLLOW_HOOKED_HEIGHT : 0 );
779ac6602a5SNicolas Bonnefon 
7802493b508SNicolas Bonnefon     if ( pullToFollowHeight_
781ac6602a5SNicolas Bonnefon             && ( pullToFollowCache_.nb_columns_ != getNbVisibleCols() ) ) {
782ac6602a5SNicolas Bonnefon         LOG(logDEBUG) << "Drawing pull to follow bar";
783ac6602a5SNicolas Bonnefon         pullToFollowCache_.pixmap_ = drawPullToFollowBar(
784ac6602a5SNicolas Bonnefon                 viewport()->width(), viewport()->devicePixelRatio() );
785ac6602a5SNicolas Bonnefon         pullToFollowCache_.nb_columns_ = getNbVisibleCols();
786ac6602a5SNicolas Bonnefon     }
787bb02e0acSNicolas Bonnefon 
788a9448ba0SNicolas Bonnefon     QPainter devicePainter( viewport() );
7892493b508SNicolas Bonnefon     int drawingTopPosition = - pullToFollowHeight_;
7902493b508SNicolas Bonnefon 
7912493b508SNicolas Bonnefon     // This is to cover the special case where there is less than a screenful
7922493b508SNicolas Bonnefon     // worth of data, we want to see the document from the top, rather than
7932493b508SNicolas Bonnefon     // pushing the first couple of lines above the viewport.
7942493b508SNicolas Bonnefon     if ( followElasticHook_.isHooked() && ( logData->getNbLine() < getNbVisibleLines() ) )
7952493b508SNicolas Bonnefon         drawingTopPosition += ( wholeHeight - viewport()->height() + PULL_TO_FOLLOW_HOOKED_HEIGHT );
7962493b508SNicolas Bonnefon 
7972493b508SNicolas Bonnefon     devicePainter.drawPixmap( 0, drawingTopPosition, textAreaCache_.pixmap_ );
7980b05c6eaSNicolas Bonnefon 
7990b05c6eaSNicolas Bonnefon     // Draw the "pull to follow" zone if needed
8002493b508SNicolas Bonnefon     if ( pullToFollowHeight_ ) {
801b297d2f4SNicolas Bonnefon         devicePainter.drawPixmap( 0,
8022493b508SNicolas Bonnefon                 getNbVisibleLines() * charHeight_ - pullToFollowHeight_,
803ac6602a5SNicolas Bonnefon                 pullToFollowCache_.pixmap_ );
8040b05c6eaSNicolas Bonnefon     }
8050b05c6eaSNicolas Bonnefon 
806a9448ba0SNicolas Bonnefon     LOG(logDEBUG) << "End of repaint " <<
807a9448ba0SNicolas Bonnefon         std::chrono::duration_cast<std::chrono::microseconds>
808a9448ba0SNicolas Bonnefon         ( std::chrono::system_clock::now() - start ).count();
809bb02e0acSNicolas Bonnefon }
810bb02e0acSNicolas Bonnefon 
811bb02e0acSNicolas Bonnefon // These two functions are virtual and this implementation is clearly
812bb02e0acSNicolas Bonnefon // only valid for a non-filtered display.
813bb02e0acSNicolas Bonnefon // We count on the 'filtered' derived classes to override them.
814bb02e0acSNicolas Bonnefon qint64 AbstractLogView::displayLineNumber( int lineNumber ) const
815bb02e0acSNicolas Bonnefon {
816bb02e0acSNicolas Bonnefon     return lineNumber + 1; // show a 1-based index
817bb02e0acSNicolas Bonnefon }
818bb02e0acSNicolas Bonnefon 
819bb02e0acSNicolas Bonnefon qint64 AbstractLogView::maxDisplayLineNumber() const
820bb02e0acSNicolas Bonnefon {
821bb02e0acSNicolas Bonnefon     return logData->getNbLine();
822bb02e0acSNicolas Bonnefon }
823bb02e0acSNicolas Bonnefon 
824bb02e0acSNicolas Bonnefon void AbstractLogView::setOverview( Overview* overview,
825bb02e0acSNicolas Bonnefon        OverviewWidget* overview_widget )
826bb02e0acSNicolas Bonnefon {
827bb02e0acSNicolas Bonnefon     overview_ = overview;
828bb02e0acSNicolas Bonnefon     overviewWidget_ = overview_widget;
829bb02e0acSNicolas Bonnefon 
830bb02e0acSNicolas Bonnefon     if ( overviewWidget_ ) {
831bb02e0acSNicolas Bonnefon         connect( overviewWidget_, SIGNAL( lineClicked ( int ) ),
832bb02e0acSNicolas Bonnefon                 this, SIGNAL( followDisabled() ) );
833bb02e0acSNicolas Bonnefon         connect( overviewWidget_, SIGNAL( lineClicked ( int ) ),
834bb02e0acSNicolas Bonnefon                 this, SLOT( jumpToLine( int ) ) );
835bb02e0acSNicolas Bonnefon     }
836bb02e0acSNicolas Bonnefon     refreshOverview();
837bb02e0acSNicolas Bonnefon }
838bb02e0acSNicolas Bonnefon 
839bb02e0acSNicolas Bonnefon void AbstractLogView::searchUsingFunction(
840bb02e0acSNicolas Bonnefon         qint64 (QuickFind::*search_function)() )
841bb02e0acSNicolas Bonnefon {
842b297d2f4SNicolas Bonnefon     disableFollow();
843bb02e0acSNicolas Bonnefon 
844bb02e0acSNicolas Bonnefon     int line = (quickFind_.*search_function)();
845bb02e0acSNicolas Bonnefon     if ( line >= 0 ) {
846bb02e0acSNicolas Bonnefon         LOG(logDEBUG) << "search " << line;
847bb02e0acSNicolas Bonnefon         displayLine( line );
848bb02e0acSNicolas Bonnefon         emit updateLineNumber( line );
849bb02e0acSNicolas Bonnefon     }
850bb02e0acSNicolas Bonnefon }
851bb02e0acSNicolas Bonnefon 
852bb02e0acSNicolas Bonnefon void AbstractLogView::searchForward()
853bb02e0acSNicolas Bonnefon {
854bb02e0acSNicolas Bonnefon     searchUsingFunction( &QuickFind::searchForward );
855bb02e0acSNicolas Bonnefon }
856bb02e0acSNicolas Bonnefon 
857bb02e0acSNicolas Bonnefon void AbstractLogView::searchBackward()
858bb02e0acSNicolas Bonnefon {
859bb02e0acSNicolas Bonnefon     searchUsingFunction( &QuickFind::searchBackward );
860bb02e0acSNicolas Bonnefon }
861bb02e0acSNicolas Bonnefon 
862bb02e0acSNicolas Bonnefon void AbstractLogView::incrementallySearchForward()
863bb02e0acSNicolas Bonnefon {
864bb02e0acSNicolas Bonnefon     searchUsingFunction( &QuickFind::incrementallySearchForward );
865bb02e0acSNicolas Bonnefon }
866bb02e0acSNicolas Bonnefon 
867bb02e0acSNicolas Bonnefon void AbstractLogView::incrementallySearchBackward()
868bb02e0acSNicolas Bonnefon {
869bb02e0acSNicolas Bonnefon     searchUsingFunction( &QuickFind::incrementallySearchBackward );
870bb02e0acSNicolas Bonnefon }
871bb02e0acSNicolas Bonnefon 
872bb02e0acSNicolas Bonnefon void AbstractLogView::incrementalSearchAbort()
873bb02e0acSNicolas Bonnefon {
874bb02e0acSNicolas Bonnefon     quickFind_.incrementalSearchAbort();
875bb02e0acSNicolas Bonnefon     emit changeQuickFind(
876bb02e0acSNicolas Bonnefon             "",
877bb02e0acSNicolas Bonnefon             QuickFindMux::Forward );
878bb02e0acSNicolas Bonnefon }
879bb02e0acSNicolas Bonnefon 
880bb02e0acSNicolas Bonnefon void AbstractLogView::incrementalSearchStop()
881bb02e0acSNicolas Bonnefon {
882bb02e0acSNicolas Bonnefon     quickFind_.incrementalSearchStop();
883bb02e0acSNicolas Bonnefon }
884bb02e0acSNicolas Bonnefon 
885bb02e0acSNicolas Bonnefon void AbstractLogView::followSet( bool checked )
886bb02e0acSNicolas Bonnefon {
887bb02e0acSNicolas Bonnefon     followMode_ = checked;
888b297d2f4SNicolas Bonnefon     followElasticHook_.hook( checked );
889b297d2f4SNicolas Bonnefon     update();
890bb02e0acSNicolas Bonnefon     if ( checked )
891bb02e0acSNicolas Bonnefon         jumpToBottom();
892bb02e0acSNicolas Bonnefon }
893bb02e0acSNicolas Bonnefon 
894bb02e0acSNicolas Bonnefon void AbstractLogView::refreshOverview()
895bb02e0acSNicolas Bonnefon {
896bb02e0acSNicolas Bonnefon     assert( overviewWidget_ );
897bb02e0acSNicolas Bonnefon 
898bb02e0acSNicolas Bonnefon     // Create space for the Overview if needed
899bb02e0acSNicolas Bonnefon     if ( ( getOverview() != NULL ) && getOverview()->isVisible() ) {
900bb02e0acSNicolas Bonnefon         setViewportMargins( 0, 0, OVERVIEW_WIDTH, 0 );
901bb02e0acSNicolas Bonnefon         overviewWidget_->show();
902bb02e0acSNicolas Bonnefon     }
903bb02e0acSNicolas Bonnefon     else {
904bb02e0acSNicolas Bonnefon         setViewportMargins( 0, 0, 0, 0 );
905bb02e0acSNicolas Bonnefon         overviewWidget_->hide();
906bb02e0acSNicolas Bonnefon     }
907bb02e0acSNicolas Bonnefon }
908bb02e0acSNicolas Bonnefon 
909bb02e0acSNicolas Bonnefon // Reset the QuickFind when the pattern is changed.
910bb02e0acSNicolas Bonnefon void AbstractLogView::handlePatternUpdated()
911bb02e0acSNicolas Bonnefon {
912bb02e0acSNicolas Bonnefon     LOG(logDEBUG) << "AbstractLogView::handlePatternUpdated()";
913bb02e0acSNicolas Bonnefon 
914bb02e0acSNicolas Bonnefon     quickFind_.resetLimits();
915bb02e0acSNicolas Bonnefon     update();
916bb02e0acSNicolas Bonnefon }
917bb02e0acSNicolas Bonnefon 
918bb02e0acSNicolas Bonnefon // OR the current with the current search expression
919bb02e0acSNicolas Bonnefon void AbstractLogView::addToSearch()
920bb02e0acSNicolas Bonnefon {
921bb02e0acSNicolas Bonnefon     if ( selection_.isPortion() ) {
922bb02e0acSNicolas Bonnefon         LOG(logDEBUG) << "AbstractLogView::addToSearch()";
923bb02e0acSNicolas Bonnefon         emit addToSearch( selection_.getSelectedText( logData ) );
924bb02e0acSNicolas Bonnefon     }
925bb02e0acSNicolas Bonnefon     else {
926bb02e0acSNicolas Bonnefon         LOG(logERROR) << "AbstractLogView::addToSearch called for a wrong type of selection";
927bb02e0acSNicolas Bonnefon     }
928bb02e0acSNicolas Bonnefon }
929bb02e0acSNicolas Bonnefon 
930bb02e0acSNicolas Bonnefon // Find next occurence of the selected text (*)
931bb02e0acSNicolas Bonnefon void AbstractLogView::findNextSelected()
932bb02e0acSNicolas Bonnefon {
933bb02e0acSNicolas Bonnefon     // Use the selected 'word' and search forward
934bb02e0acSNicolas Bonnefon     if ( selection_.isPortion() ) {
935bb02e0acSNicolas Bonnefon         emit changeQuickFind(
936bb02e0acSNicolas Bonnefon                 selection_.getSelectedText( logData ),
937bb02e0acSNicolas Bonnefon                 QuickFindMux::Forward );
938bb02e0acSNicolas Bonnefon         emit searchNext();
939bb02e0acSNicolas Bonnefon     }
940bb02e0acSNicolas Bonnefon }
941bb02e0acSNicolas Bonnefon 
942bb02e0acSNicolas Bonnefon // Find next previous of the selected text (#)
943bb02e0acSNicolas Bonnefon void AbstractLogView::findPreviousSelected()
944bb02e0acSNicolas Bonnefon {
945bb02e0acSNicolas Bonnefon     if ( selection_.isPortion() ) {
946bb02e0acSNicolas Bonnefon         emit changeQuickFind(
947bb02e0acSNicolas Bonnefon                 selection_.getSelectedText( logData ),
948bb02e0acSNicolas Bonnefon                 QuickFindMux::Backward );
949bb02e0acSNicolas Bonnefon         emit searchNext();
950bb02e0acSNicolas Bonnefon     }
951bb02e0acSNicolas Bonnefon }
952bb02e0acSNicolas Bonnefon 
953bb02e0acSNicolas Bonnefon // Copy the selection to the clipboard
954bb02e0acSNicolas Bonnefon void AbstractLogView::copy()
955bb02e0acSNicolas Bonnefon {
956bb02e0acSNicolas Bonnefon     static QClipboard* clipboard = QApplication::clipboard();
957bb02e0acSNicolas Bonnefon 
958bb02e0acSNicolas Bonnefon     clipboard->setText( selection_.getSelectedText( logData ) );
959bb02e0acSNicolas Bonnefon }
960bb02e0acSNicolas Bonnefon 
961bb02e0acSNicolas Bonnefon //
962bb02e0acSNicolas Bonnefon // Public functions
963bb02e0acSNicolas Bonnefon //
964bb02e0acSNicolas Bonnefon 
965bb02e0acSNicolas Bonnefon void AbstractLogView::updateData()
966bb02e0acSNicolas Bonnefon {
967bb02e0acSNicolas Bonnefon     LOG(logDEBUG) << "AbstractLogView::updateData";
968bb02e0acSNicolas Bonnefon 
969bb02e0acSNicolas Bonnefon     // Check the top Line is within range
970bb02e0acSNicolas Bonnefon     if ( firstLine >= logData->getNbLine() ) {
971bb02e0acSNicolas Bonnefon         firstLine = 0;
972bb02e0acSNicolas Bonnefon         firstCol = 0;
973bb02e0acSNicolas Bonnefon         verticalScrollBar()->setValue( 0 );
974bb02e0acSNicolas Bonnefon         horizontalScrollBar()->setValue( 0 );
975bb02e0acSNicolas Bonnefon     }
976bb02e0acSNicolas Bonnefon 
977bb02e0acSNicolas Bonnefon     // Crop selection if it become out of range
978bb02e0acSNicolas Bonnefon     selection_.crop( logData->getNbLine() - 1 );
979bb02e0acSNicolas Bonnefon 
980bb02e0acSNicolas Bonnefon     // Adapt the scroll bars to the new content
9810b05c6eaSNicolas Bonnefon     updateScrollBars();
982bb02e0acSNicolas Bonnefon 
9832493b508SNicolas Bonnefon     // Calculate the index of the last line shown
984c7ef2b85SNicolas Bonnefon     LineNumber last_line = std::min( static_cast<int64_t>( logData->getNbLine() ),
9852493b508SNicolas Bonnefon             static_cast<int64_t>( firstLine + getNbVisibleLines() ) );
986bb02e0acSNicolas Bonnefon 
987bb02e0acSNicolas Bonnefon     // Reset the QuickFind in case we have new stuff to search into
988bb02e0acSNicolas Bonnefon     quickFind_.resetLimits();
989bb02e0acSNicolas Bonnefon 
990bb02e0acSNicolas Bonnefon     if ( followMode_ )
991bb02e0acSNicolas Bonnefon         jumpToBottom();
992bb02e0acSNicolas Bonnefon 
993bb02e0acSNicolas Bonnefon     // Update the overview if we have one
994bb02e0acSNicolas Bonnefon     if ( overview_ != NULL )
9952493b508SNicolas Bonnefon         overview_->updateCurrentPosition( firstLine, last_line );
996bb02e0acSNicolas Bonnefon 
9972493b508SNicolas Bonnefon     // Invalidate our cache
9982493b508SNicolas Bonnefon     textAreaCache_.invalid_ = true;
999bb02e0acSNicolas Bonnefon 
1000bb02e0acSNicolas Bonnefon     // Repaint!
1001bb02e0acSNicolas Bonnefon     update();
1002bb02e0acSNicolas Bonnefon }
1003bb02e0acSNicolas Bonnefon 
1004bb02e0acSNicolas Bonnefon void AbstractLogView::updateDisplaySize()
1005bb02e0acSNicolas Bonnefon {
1006bb02e0acSNicolas Bonnefon     // Font is assumed to be mono-space (is restricted by options dialog)
1007bb02e0acSNicolas Bonnefon     QFontMetrics fm = fontMetrics();
1008bb02e0acSNicolas Bonnefon     charHeight_ = fm.height();
1009bb02e0acSNicolas Bonnefon     // For some reason on Qt 4.8.2 for Win, maxWidth() is wrong but the
1010bb02e0acSNicolas Bonnefon     // following give the right result, not sure why:
1011bb02e0acSNicolas Bonnefon     charWidth_ = fm.width( QChar('a') );
1012bb02e0acSNicolas Bonnefon 
1013bb02e0acSNicolas Bonnefon     // Update the scroll bars
10140b05c6eaSNicolas Bonnefon     updateScrollBars();
1015bb02e0acSNicolas Bonnefon     verticalScrollBar()->setPageStep( getNbVisibleLines() );
1016bb02e0acSNicolas Bonnefon 
1017b297d2f4SNicolas Bonnefon     if ( followMode_ )
1018b297d2f4SNicolas Bonnefon         jumpToBottom();
1019b297d2f4SNicolas Bonnefon 
1020bb02e0acSNicolas Bonnefon     LOG(logDEBUG) << "viewport.width()=" << viewport()->width();
1021bb02e0acSNicolas Bonnefon     LOG(logDEBUG) << "viewport.height()=" << viewport()->height();
1022bb02e0acSNicolas Bonnefon     LOG(logDEBUG) << "width()=" << width();
1023bb02e0acSNicolas Bonnefon     LOG(logDEBUG) << "height()=" << height();
1024bb02e0acSNicolas Bonnefon 
1025bb02e0acSNicolas Bonnefon     if ( overviewWidget_ )
1026bb02e0acSNicolas Bonnefon         overviewWidget_->setGeometry( viewport()->width() + 2, 1,
1027bb02e0acSNicolas Bonnefon                 OVERVIEW_WIDTH - 1, viewport()->height() );
1028a9448ba0SNicolas Bonnefon 
1029a9448ba0SNicolas Bonnefon     // Our text area cache is now invalid
1030a9448ba0SNicolas Bonnefon     textAreaCache_.invalid_ = true;
1031a9448ba0SNicolas Bonnefon     textAreaCache_.pixmap_  = QPixmap {
1032b297d2f4SNicolas Bonnefon         viewport()->width() * viewport()->devicePixelRatio(),
10332493b508SNicolas Bonnefon         static_cast<int32_t>( getNbVisibleLines() ) * charHeight_ * viewport()->devicePixelRatio() };
1034a9448ba0SNicolas Bonnefon     textAreaCache_.pixmap_.setDevicePixelRatio( viewport()->devicePixelRatio() );
1035bb02e0acSNicolas Bonnefon }
1036bb02e0acSNicolas Bonnefon 
1037bb02e0acSNicolas Bonnefon int AbstractLogView::getTopLine() const
1038bb02e0acSNicolas Bonnefon {
1039bb02e0acSNicolas Bonnefon     return firstLine;
1040bb02e0acSNicolas Bonnefon }
1041bb02e0acSNicolas Bonnefon 
1042bb02e0acSNicolas Bonnefon QString AbstractLogView::getSelection() const
1043bb02e0acSNicolas Bonnefon {
1044bb02e0acSNicolas Bonnefon     return selection_.getSelectedText( logData );
1045bb02e0acSNicolas Bonnefon }
1046bb02e0acSNicolas Bonnefon 
1047bb02e0acSNicolas Bonnefon void AbstractLogView::selectAll()
1048bb02e0acSNicolas Bonnefon {
1049bb02e0acSNicolas Bonnefon     selection_.selectRange( 0, logData->getNbLine() - 1 );
1050bb02e0acSNicolas Bonnefon     update();
1051bb02e0acSNicolas Bonnefon }
1052bb02e0acSNicolas Bonnefon 
1053bb02e0acSNicolas Bonnefon void AbstractLogView::selectAndDisplayLine( int line )
1054bb02e0acSNicolas Bonnefon {
1055b297d2f4SNicolas Bonnefon     disableFollow();
1056bb02e0acSNicolas Bonnefon     selection_.selectLine( line );
1057bb02e0acSNicolas Bonnefon     displayLine( line );
1058bb02e0acSNicolas Bonnefon     emit updateLineNumber( line );
1059bb02e0acSNicolas Bonnefon }
1060bb02e0acSNicolas Bonnefon 
1061bb02e0acSNicolas Bonnefon // The difference between this function and displayLine() is quite
1062bb02e0acSNicolas Bonnefon // subtle: this one always jump, even if the line passed is visible.
1063bb02e0acSNicolas Bonnefon void AbstractLogView::jumpToLine( int line )
1064bb02e0acSNicolas Bonnefon {
1065bb02e0acSNicolas Bonnefon     // Put the selected line in the middle if possible
1066bb02e0acSNicolas Bonnefon     int newTopLine = line - ( getNbVisibleLines() / 2 );
1067bb02e0acSNicolas Bonnefon     if ( newTopLine < 0 )
1068bb02e0acSNicolas Bonnefon         newTopLine = 0;
1069bb02e0acSNicolas Bonnefon 
1070bb02e0acSNicolas Bonnefon     // This will also trigger a scrollContents event
1071bb02e0acSNicolas Bonnefon     verticalScrollBar()->setValue( newTopLine );
1072bb02e0acSNicolas Bonnefon }
1073bb02e0acSNicolas Bonnefon 
1074bb02e0acSNicolas Bonnefon void AbstractLogView::setLineNumbersVisible( bool lineNumbersVisible )
1075bb02e0acSNicolas Bonnefon {
1076bb02e0acSNicolas Bonnefon     lineNumbersVisible_ = lineNumbersVisible;
1077bb02e0acSNicolas Bonnefon }
1078bb02e0acSNicolas Bonnefon 
10795fa25391SNicolas Bonnefon void AbstractLogView::forceRefresh()
10805fa25391SNicolas Bonnefon {
10815fa25391SNicolas Bonnefon     // Invalidate our cache
10825fa25391SNicolas Bonnefon     textAreaCache_.invalid_ = true;
10835fa25391SNicolas Bonnefon }
10845fa25391SNicolas Bonnefon 
1085bb02e0acSNicolas Bonnefon //
1086bb02e0acSNicolas Bonnefon // Private functions
1087bb02e0acSNicolas Bonnefon //
1088bb02e0acSNicolas Bonnefon 
1089bb02e0acSNicolas Bonnefon // Returns the number of lines visible in the viewport
10902493b508SNicolas Bonnefon LineNumber AbstractLogView::getNbVisibleLines() const
1091bb02e0acSNicolas Bonnefon {
10922493b508SNicolas Bonnefon     return static_cast<LineNumber>( viewport()->height() / charHeight_ + 1 );
1093bb02e0acSNicolas Bonnefon }
1094bb02e0acSNicolas Bonnefon 
1095bb02e0acSNicolas Bonnefon // Returns the number of columns visible in the viewport
1096bb02e0acSNicolas Bonnefon int AbstractLogView::getNbVisibleCols() const
1097bb02e0acSNicolas Bonnefon {
1098bb02e0acSNicolas Bonnefon     return ( viewport()->width() - leftMarginPx_ ) / charWidth_ + 1;
1099bb02e0acSNicolas Bonnefon }
1100bb02e0acSNicolas Bonnefon 
1101bb02e0acSNicolas Bonnefon // Converts the mouse x, y coordinates to the line number in the file
1102bb02e0acSNicolas Bonnefon int AbstractLogView::convertCoordToLine(int yPos) const
1103bb02e0acSNicolas Bonnefon {
11042493b508SNicolas Bonnefon     int line = firstLine + ( yPos + pullToFollowHeight_ ) / charHeight_;
1105bb02e0acSNicolas Bonnefon 
1106bb02e0acSNicolas Bonnefon     return line;
1107bb02e0acSNicolas Bonnefon }
1108bb02e0acSNicolas Bonnefon 
1109bb02e0acSNicolas Bonnefon // Converts the mouse x, y coordinates to the char coordinates (in the file)
1110bb02e0acSNicolas Bonnefon // This function ensure the pos exists in the file.
1111bb02e0acSNicolas Bonnefon QPoint AbstractLogView::convertCoordToFilePos( const QPoint& pos ) const
1112bb02e0acSNicolas Bonnefon {
11132493b508SNicolas Bonnefon     int line = firstLine + ( pos.y() + pullToFollowHeight_ ) / charHeight_;
1114bb02e0acSNicolas Bonnefon     if ( line >= logData->getNbLine() )
1115bb02e0acSNicolas Bonnefon         line = logData->getNbLine() - 1;
1116bb02e0acSNicolas Bonnefon     if ( line < 0 )
1117bb02e0acSNicolas Bonnefon         line = 0;
1118bb02e0acSNicolas Bonnefon 
1119bb02e0acSNicolas Bonnefon     // Determine column in screen space and convert it to file space
1120bb02e0acSNicolas Bonnefon     int column = firstCol + ( pos.x() - leftMarginPx_ ) / charWidth_;
1121bb02e0acSNicolas Bonnefon 
1122bb02e0acSNicolas Bonnefon     QString this_line = logData->getExpandedLineString( line );
1123bb02e0acSNicolas Bonnefon     const int length = this_line.length();
1124bb02e0acSNicolas Bonnefon 
1125bb02e0acSNicolas Bonnefon     if ( column >= length )
1126bb02e0acSNicolas Bonnefon         column = length - 1;
1127bb02e0acSNicolas Bonnefon     if ( column < 0 )
1128bb02e0acSNicolas Bonnefon         column = 0;
1129bb02e0acSNicolas Bonnefon 
1130bb02e0acSNicolas Bonnefon     LOG(logDEBUG4) << "AbstractLogView::convertCoordToFilePos col="
1131bb02e0acSNicolas Bonnefon         << column << " line=" << line;
1132bb02e0acSNicolas Bonnefon     QPoint point( column, line );
1133bb02e0acSNicolas Bonnefon 
1134bb02e0acSNicolas Bonnefon     return point;
1135bb02e0acSNicolas Bonnefon }
1136bb02e0acSNicolas Bonnefon 
1137bb02e0acSNicolas Bonnefon // Makes the widget adjust itself to display the passed line.
1138bb02e0acSNicolas Bonnefon // Doing so, it will throw itself a scrollContents event.
11392493b508SNicolas Bonnefon void AbstractLogView::displayLine( LineNumber line )
1140bb02e0acSNicolas Bonnefon {
1141bb02e0acSNicolas Bonnefon     // If the line is already the screen
1142bb02e0acSNicolas Bonnefon     if ( ( line >= firstLine ) &&
1143bb02e0acSNicolas Bonnefon          ( line < ( firstLine + getNbVisibleLines() ) ) ) {
114400a57d05SNicolas Bonnefon         // Invalidate our cache
114500a57d05SNicolas Bonnefon         textAreaCache_.invalid_ = true;
114600a57d05SNicolas Bonnefon 
1147bb02e0acSNicolas Bonnefon         // ... don't scroll and just repaint
1148bb02e0acSNicolas Bonnefon         update();
1149bb02e0acSNicolas Bonnefon     } else {
1150bb02e0acSNicolas Bonnefon         jumpToLine( line );
1151bb02e0acSNicolas Bonnefon     }
1152bb02e0acSNicolas Bonnefon }
1153bb02e0acSNicolas Bonnefon 
1154bb02e0acSNicolas Bonnefon // Move the selection up and down by the passed number of lines
1155bb02e0acSNicolas Bonnefon void AbstractLogView::moveSelection( int delta )
1156bb02e0acSNicolas Bonnefon {
1157bb02e0acSNicolas Bonnefon     LOG(logDEBUG) << "AbstractLogView::moveSelection delta=" << delta;
1158bb02e0acSNicolas Bonnefon 
1159bb02e0acSNicolas Bonnefon     QList<int> selection = selection_.getLines();
1160bb02e0acSNicolas Bonnefon     int new_line;
1161bb02e0acSNicolas Bonnefon 
1162bb02e0acSNicolas Bonnefon     // If nothing is selected, do as if line -1 was.
1163bb02e0acSNicolas Bonnefon     if ( selection.isEmpty() )
1164bb02e0acSNicolas Bonnefon         selection.append( -1 );
1165bb02e0acSNicolas Bonnefon 
1166bb02e0acSNicolas Bonnefon     if ( delta < 0 )
1167bb02e0acSNicolas Bonnefon         new_line = selection.first() + delta;
1168bb02e0acSNicolas Bonnefon     else
1169bb02e0acSNicolas Bonnefon         new_line = selection.last() + delta;
1170bb02e0acSNicolas Bonnefon 
1171bb02e0acSNicolas Bonnefon     if ( new_line < 0 )
1172bb02e0acSNicolas Bonnefon         new_line = 0;
1173bb02e0acSNicolas Bonnefon     else if ( new_line >= logData->getNbLine() )
1174bb02e0acSNicolas Bonnefon         new_line = logData->getNbLine() - 1;
1175bb02e0acSNicolas Bonnefon 
1176bb02e0acSNicolas Bonnefon     // Select and display the new line
1177bb02e0acSNicolas Bonnefon     selection_.selectLine( new_line );
1178bb02e0acSNicolas Bonnefon     displayLine( new_line );
1179bb02e0acSNicolas Bonnefon     emit updateLineNumber( new_line );
1180bb02e0acSNicolas Bonnefon }
1181bb02e0acSNicolas Bonnefon 
1182bb02e0acSNicolas Bonnefon // Make the start of the lines visible
1183bb02e0acSNicolas Bonnefon void AbstractLogView::jumpToStartOfLine()
1184bb02e0acSNicolas Bonnefon {
1185bb02e0acSNicolas Bonnefon     horizontalScrollBar()->setValue( 0 );
1186bb02e0acSNicolas Bonnefon }
1187bb02e0acSNicolas Bonnefon 
1188bb02e0acSNicolas Bonnefon // Make the end of the lines in the selection visible
1189bb02e0acSNicolas Bonnefon void AbstractLogView::jumpToEndOfLine()
1190bb02e0acSNicolas Bonnefon {
1191bb02e0acSNicolas Bonnefon     QList<int> selection = selection_.getLines();
1192bb02e0acSNicolas Bonnefon 
1193bb02e0acSNicolas Bonnefon     // Search the longest line in the selection
1194bb02e0acSNicolas Bonnefon     int max_length = 0;
1195bb02e0acSNicolas Bonnefon     foreach ( int line, selection ) {
1196bb02e0acSNicolas Bonnefon         int length = logData->getLineLength( line );
1197bb02e0acSNicolas Bonnefon         if ( length > max_length )
1198bb02e0acSNicolas Bonnefon             max_length = length;
1199bb02e0acSNicolas Bonnefon     }
1200bb02e0acSNicolas Bonnefon 
1201bb02e0acSNicolas Bonnefon     horizontalScrollBar()->setValue( max_length - getNbVisibleCols() );
1202bb02e0acSNicolas Bonnefon }
1203bb02e0acSNicolas Bonnefon 
1204bb02e0acSNicolas Bonnefon // Make the end of the lines on the screen visible
1205bb02e0acSNicolas Bonnefon void AbstractLogView::jumpToRightOfScreen()
1206bb02e0acSNicolas Bonnefon {
1207bb02e0acSNicolas Bonnefon     QList<int> selection = selection_.getLines();
1208bb02e0acSNicolas Bonnefon 
1209bb02e0acSNicolas Bonnefon     // Search the longest line on screen
1210bb02e0acSNicolas Bonnefon     int max_length = 0;
12112493b508SNicolas Bonnefon     for ( auto i = firstLine; i <= ( firstLine + getNbVisibleLines() ); i++ ) {
1212bb02e0acSNicolas Bonnefon         int length = logData->getLineLength( i );
1213bb02e0acSNicolas Bonnefon         if ( length > max_length )
1214bb02e0acSNicolas Bonnefon             max_length = length;
1215bb02e0acSNicolas Bonnefon     }
1216bb02e0acSNicolas Bonnefon 
1217bb02e0acSNicolas Bonnefon     horizontalScrollBar()->setValue( max_length - getNbVisibleCols() );
1218bb02e0acSNicolas Bonnefon }
1219bb02e0acSNicolas Bonnefon 
1220bb02e0acSNicolas Bonnefon // Jump to the first line
1221bb02e0acSNicolas Bonnefon void AbstractLogView::jumpToTop()
1222bb02e0acSNicolas Bonnefon {
1223bb02e0acSNicolas Bonnefon     // This will also trigger a scrollContents event
1224bb02e0acSNicolas Bonnefon     verticalScrollBar()->setValue( 0 );
1225bb02e0acSNicolas Bonnefon     update();       // in case the screen hasn't moved
1226bb02e0acSNicolas Bonnefon }
1227bb02e0acSNicolas Bonnefon 
1228bb02e0acSNicolas Bonnefon // Jump to the last line
1229bb02e0acSNicolas Bonnefon void AbstractLogView::jumpToBottom()
1230bb02e0acSNicolas Bonnefon {
1231bb02e0acSNicolas Bonnefon     const int new_top_line =
1232bb02e0acSNicolas Bonnefon         qMax( logData->getNbLine() - getNbVisibleLines() + 1, 0LL );
1233bb02e0acSNicolas Bonnefon 
1234bb02e0acSNicolas Bonnefon     // This will also trigger a scrollContents event
1235bb02e0acSNicolas Bonnefon     verticalScrollBar()->setValue( new_top_line );
1236bb02e0acSNicolas Bonnefon     update();       // in case the screen hasn't moved
1237bb02e0acSNicolas Bonnefon }
1238bb02e0acSNicolas Bonnefon 
1239bb02e0acSNicolas Bonnefon // Returns whether the character passed is a 'word' character
1240bb02e0acSNicolas Bonnefon inline bool AbstractLogView::isCharWord( char c )
1241bb02e0acSNicolas Bonnefon {
1242bb02e0acSNicolas Bonnefon     if ( ( ( c >= 'A' ) && ( c <= 'Z' ) ) ||
1243bb02e0acSNicolas Bonnefon          ( ( c >= 'a' ) && ( c <= 'z' ) ) ||
1244bb02e0acSNicolas Bonnefon          ( ( c >= '0' ) && ( c <= '9' ) ) ||
1245bb02e0acSNicolas Bonnefon          ( ( c == '_' ) ) )
1246bb02e0acSNicolas Bonnefon         return true;
1247bb02e0acSNicolas Bonnefon     else
1248bb02e0acSNicolas Bonnefon         return false;
1249bb02e0acSNicolas Bonnefon }
1250bb02e0acSNicolas Bonnefon 
1251bb02e0acSNicolas Bonnefon // Select the word under the given position
1252bb02e0acSNicolas Bonnefon void AbstractLogView::selectWordAtPosition( const QPoint& pos )
1253bb02e0acSNicolas Bonnefon {
1254bb02e0acSNicolas Bonnefon     const int x = pos.x();
1255bb02e0acSNicolas Bonnefon     const QString line = logData->getExpandedLineString( pos.y() );
1256bb02e0acSNicolas Bonnefon 
1257bb02e0acSNicolas Bonnefon     if ( isCharWord( line[x].toLatin1() ) ) {
1258bb02e0acSNicolas Bonnefon         // Search backward for the first character in the word
1259bb02e0acSNicolas Bonnefon         int currentPos = x;
1260bb02e0acSNicolas Bonnefon         for ( ; currentPos > 0; currentPos-- )
1261bb02e0acSNicolas Bonnefon             if ( ! isCharWord( line[currentPos].toLatin1() ) )
1262bb02e0acSNicolas Bonnefon                 break;
1263bb02e0acSNicolas Bonnefon         // Exclude the first char of the line if needed
1264bb02e0acSNicolas Bonnefon         if ( ! isCharWord( line[currentPos].toLatin1() ) )
1265bb02e0acSNicolas Bonnefon             currentPos++;
1266bb02e0acSNicolas Bonnefon         int start = currentPos;
1267bb02e0acSNicolas Bonnefon 
1268bb02e0acSNicolas Bonnefon         // Now search for the end
1269bb02e0acSNicolas Bonnefon         currentPos = x;
1270bb02e0acSNicolas Bonnefon         for ( ; currentPos < line.length() - 1; currentPos++ )
1271bb02e0acSNicolas Bonnefon             if ( ! isCharWord( line[currentPos].toLatin1() ) )
1272bb02e0acSNicolas Bonnefon                 break;
1273bb02e0acSNicolas Bonnefon         // Exclude the last char of the line if needed
1274bb02e0acSNicolas Bonnefon         if ( ! isCharWord( line[currentPos].toLatin1() ) )
1275bb02e0acSNicolas Bonnefon             currentPos--;
1276bb02e0acSNicolas Bonnefon         int end = currentPos;
1277bb02e0acSNicolas Bonnefon 
1278bb02e0acSNicolas Bonnefon         selection_.selectPortion( pos.y(), start, end );
1279bb02e0acSNicolas Bonnefon         updateGlobalSelection();
1280bb02e0acSNicolas Bonnefon         update();
1281bb02e0acSNicolas Bonnefon     }
1282bb02e0acSNicolas Bonnefon }
1283bb02e0acSNicolas Bonnefon 
1284bb02e0acSNicolas Bonnefon // Update the system global (middle click) selection (X11 only)
1285bb02e0acSNicolas Bonnefon void AbstractLogView::updateGlobalSelection()
1286bb02e0acSNicolas Bonnefon {
1287bb02e0acSNicolas Bonnefon     static QClipboard* const clipboard = QApplication::clipboard();
1288bb02e0acSNicolas Bonnefon 
1289bb02e0acSNicolas Bonnefon     // Updating it only for "non-trivial" (range or portion) selections
1290bb02e0acSNicolas Bonnefon     if ( ! selection_.isSingleLine() )
1291bb02e0acSNicolas Bonnefon         clipboard->setText( selection_.getSelectedText( logData ),
1292bb02e0acSNicolas Bonnefon                 QClipboard::Selection );
1293bb02e0acSNicolas Bonnefon }
1294bb02e0acSNicolas Bonnefon 
1295bb02e0acSNicolas Bonnefon // Create the pop-up menu
1296bb02e0acSNicolas Bonnefon void AbstractLogView::createMenu()
1297bb02e0acSNicolas Bonnefon {
1298bb02e0acSNicolas Bonnefon     copyAction_ = new QAction( tr("&Copy"), this );
1299bb02e0acSNicolas Bonnefon     // No text as this action title depends on the type of selection
1300bb02e0acSNicolas Bonnefon     connect( copyAction_, SIGNAL(triggered()), this, SLOT(copy()) );
1301bb02e0acSNicolas Bonnefon 
1302bb02e0acSNicolas Bonnefon     // For '#' and '*', shortcuts doesn't seem to work but
1303bb02e0acSNicolas Bonnefon     // at least it displays them in the menu, we manually handle those keys
1304bb02e0acSNicolas Bonnefon     // as keys event anyway (in keyPressEvent).
1305bb02e0acSNicolas Bonnefon     findNextAction_ = new QAction(tr("Find &next"), this);
1306bb02e0acSNicolas Bonnefon     findNextAction_->setShortcut( Qt::Key_Asterisk );
1307bb02e0acSNicolas Bonnefon     findNextAction_->setStatusTip( tr("Find the next occurence") );
1308bb02e0acSNicolas Bonnefon     connect( findNextAction_, SIGNAL(triggered()),
1309bb02e0acSNicolas Bonnefon             this, SLOT( findNextSelected() ) );
1310bb02e0acSNicolas Bonnefon 
1311bb02e0acSNicolas Bonnefon     findPreviousAction_ = new QAction( tr("Find &previous"), this );
1312bb02e0acSNicolas Bonnefon     findPreviousAction_->setShortcut( tr("#")  );
1313bb02e0acSNicolas Bonnefon     findPreviousAction_->setStatusTip( tr("Find the previous occurence") );
1314bb02e0acSNicolas Bonnefon     connect( findPreviousAction_, SIGNAL(triggered()),
1315bb02e0acSNicolas Bonnefon             this, SLOT( findPreviousSelected() ) );
1316bb02e0acSNicolas Bonnefon 
1317bb02e0acSNicolas Bonnefon     addToSearchAction_ = new QAction( tr("&Add to search"), this );
1318bb02e0acSNicolas Bonnefon     addToSearchAction_->setStatusTip(
1319bb02e0acSNicolas Bonnefon             tr("Add the selection to the current search") );
1320bb02e0acSNicolas Bonnefon     connect( addToSearchAction_, SIGNAL( triggered() ),
1321bb02e0acSNicolas Bonnefon             this, SLOT( addToSearch() ) );
1322bb02e0acSNicolas Bonnefon 
1323bb02e0acSNicolas Bonnefon     popupMenu_ = new QMenu( this );
1324bb02e0acSNicolas Bonnefon     popupMenu_->addAction( copyAction_ );
1325bb02e0acSNicolas Bonnefon     popupMenu_->addSeparator();
1326bb02e0acSNicolas Bonnefon     popupMenu_->addAction( findNextAction_ );
1327bb02e0acSNicolas Bonnefon     popupMenu_->addAction( findPreviousAction_ );
1328bb02e0acSNicolas Bonnefon     popupMenu_->addAction( addToSearchAction_ );
1329bb02e0acSNicolas Bonnefon }
1330bb02e0acSNicolas Bonnefon 
1331bb02e0acSNicolas Bonnefon void AbstractLogView::considerMouseHovering( int x_pos, int y_pos )
1332bb02e0acSNicolas Bonnefon {
1333bb02e0acSNicolas Bonnefon     int line = convertCoordToLine( y_pos );
1334bb02e0acSNicolas Bonnefon     if ( ( x_pos < leftMarginPx_ )
1335bb02e0acSNicolas Bonnefon             && ( line >= 0 )
1336bb02e0acSNicolas Bonnefon             && ( line < logData->getNbLine() ) ) {
1337bb02e0acSNicolas Bonnefon         // Mouse moved in the margin, send event up
1338bb02e0acSNicolas Bonnefon         // (possibly to highlight the overview)
1339bb02e0acSNicolas Bonnefon         if ( line != lastHoveredLine_ ) {
1340bb02e0acSNicolas Bonnefon             LOG(logDEBUG) << "Mouse moved in margin line: " << line;
1341bb02e0acSNicolas Bonnefon             emit mouseHoveredOverLine( line );
1342bb02e0acSNicolas Bonnefon             lastHoveredLine_ = line;
1343bb02e0acSNicolas Bonnefon         }
1344bb02e0acSNicolas Bonnefon     }
1345bb02e0acSNicolas Bonnefon     else {
1346bb02e0acSNicolas Bonnefon         if ( lastHoveredLine_ != -1 ) {
1347bb02e0acSNicolas Bonnefon             emit mouseLeftHoveringZone();
1348bb02e0acSNicolas Bonnefon             lastHoveredLine_ = -1;
1349bb02e0acSNicolas Bonnefon         }
1350bb02e0acSNicolas Bonnefon     }
1351bb02e0acSNicolas Bonnefon }
13520b05c6eaSNicolas Bonnefon 
13530b05c6eaSNicolas Bonnefon void AbstractLogView::updateScrollBars()
13540b05c6eaSNicolas Bonnefon {
13550b05c6eaSNicolas Bonnefon     verticalScrollBar()->setRange( 0, std::max( 0LL,
13560b05c6eaSNicolas Bonnefon             logData->getNbLine() - getNbVisibleLines() ) );
13570b05c6eaSNicolas Bonnefon 
13582493b508SNicolas Bonnefon     const int hScrollMaxValue = std::max( 0,
13592493b508SNicolas Bonnefon             logData->getMaxLength() - getNbVisibleCols() + 1 );
13600b05c6eaSNicolas Bonnefon     horizontalScrollBar()->setRange( 0, hScrollMaxValue );
13610b05c6eaSNicolas Bonnefon }
13620b05c6eaSNicolas Bonnefon 
1363ac6602a5SNicolas Bonnefon void AbstractLogView::drawTextArea( QPaintDevice* paint_device, int32_t delta_y )
1364a9448ba0SNicolas Bonnefon {
1365a9448ba0SNicolas Bonnefon     // LOG( logDEBUG ) << "devicePixelRatio: " << viewport()->devicePixelRatio();
1366a9448ba0SNicolas Bonnefon     // LOG( logDEBUG ) << "viewport size: " << viewport()->size().width();
1367a9448ba0SNicolas Bonnefon     // LOG( logDEBUG ) << "pixmap size: " << textPixmap.width();
1368a9448ba0SNicolas Bonnefon     // Repaint the viewport
1369a9448ba0SNicolas Bonnefon     QPainter painter( paint_device );
1370a9448ba0SNicolas Bonnefon     // LOG( logDEBUG ) << "font: " << viewport()->font().family().toStdString();
1371a9448ba0SNicolas Bonnefon     // LOG( logDEBUG ) << "font painter: " << painter.font().family().toStdString();
1372a9448ba0SNicolas Bonnefon 
1373a9448ba0SNicolas Bonnefon     painter.setFont( this->font() );
1374a9448ba0SNicolas Bonnefon 
1375a9448ba0SNicolas Bonnefon     const int fontHeight = charHeight_;
1376a9448ba0SNicolas Bonnefon     const int fontAscent = painter.fontMetrics().ascent();
1377a9448ba0SNicolas Bonnefon     const int nbCols = getNbVisibleCols();
13782493b508SNicolas Bonnefon     const int paintDeviceHeight = paint_device->height() / viewport()->devicePixelRatio();
13792493b508SNicolas Bonnefon     const int paintDeviceWidth = paint_device->width() / viewport()->devicePixelRatio();
1380a9448ba0SNicolas Bonnefon     const QPalette& palette = viewport()->palette();
1381a9448ba0SNicolas Bonnefon     std::shared_ptr<const FilterSet> filterSet =
1382a9448ba0SNicolas Bonnefon         Persistent<FilterSet>( "filterSet" );
1383a9448ba0SNicolas Bonnefon     QColor foreColor, backColor;
1384a9448ba0SNicolas Bonnefon 
1385a9448ba0SNicolas Bonnefon     static const QBrush normalBulletBrush = QBrush( Qt::white );
1386a9448ba0SNicolas Bonnefon     static const QBrush matchBulletBrush = QBrush( Qt::red );
1387a9448ba0SNicolas Bonnefon     static const QBrush markBrush = QBrush( "dodgerblue" );
1388a9448ba0SNicolas Bonnefon 
1389a9448ba0SNicolas Bonnefon     static const int SEPARATOR_WIDTH = 1;
1390f01b5ae6SNicolas Bonnefon     static const qreal BULLET_AREA_WIDTH = 11;
1391a9448ba0SNicolas Bonnefon     static const int CONTENT_MARGIN_WIDTH = 1;
1392a9448ba0SNicolas Bonnefon     static const int LINE_NUMBER_PADDING = 3;
1393a9448ba0SNicolas Bonnefon 
1394a9448ba0SNicolas Bonnefon     // First check the lines to be drawn are within range (might not be the case if
1395a9448ba0SNicolas Bonnefon     // the file has just changed)
13962493b508SNicolas Bonnefon     const int64_t lines_in_file = logData->getNbLine();
13972493b508SNicolas Bonnefon 
13982493b508SNicolas Bonnefon     if ( firstLine > lines_in_file )
1399*8ea5bc4cSNicolas Bonnefon         firstLine = lines_in_file ? lines_in_file - 1 : 0;
14002493b508SNicolas Bonnefon 
14012493b508SNicolas Bonnefon     const int64_t nbLines = std::min(
14022493b508SNicolas Bonnefon             static_cast<int64_t>( getNbVisibleLines() ), lines_in_file - firstLine );
14032493b508SNicolas Bonnefon 
14042493b508SNicolas Bonnefon     const int bottomOfTextPx = nbLines * fontHeight;
14052493b508SNicolas Bonnefon 
14062493b508SNicolas Bonnefon     LOG(logDEBUG) << "drawing lines from " << firstLine << " (" << nbLines << " lines)";
14072493b508SNicolas Bonnefon     LOG(logDEBUG) << "bottomOfTextPx: " << bottomOfTextPx;
14082493b508SNicolas Bonnefon     LOG(logDEBUG) << "Height: " << paintDeviceHeight;
1409a9448ba0SNicolas Bonnefon 
1410a9448ba0SNicolas Bonnefon     // Lines to write
14112493b508SNicolas Bonnefon     const QStringList lines = logData->getExpandedLines( firstLine, nbLines );
1412a9448ba0SNicolas Bonnefon 
1413a9448ba0SNicolas Bonnefon     // First draw the bullet left margin
1414a9448ba0SNicolas Bonnefon     painter.setPen(palette.color(QPalette::Text));
1415a9448ba0SNicolas Bonnefon     painter.fillRect( 0, 0,
14162493b508SNicolas Bonnefon                       BULLET_AREA_WIDTH, paintDeviceHeight,
1417a9448ba0SNicolas Bonnefon                       Qt::darkGray );
1418a9448ba0SNicolas Bonnefon 
1419a9448ba0SNicolas Bonnefon     // Column at which the content should start (pixels)
1420f01b5ae6SNicolas Bonnefon     qreal contentStartPosX = BULLET_AREA_WIDTH + SEPARATOR_WIDTH;
1421a9448ba0SNicolas Bonnefon 
1422a9448ba0SNicolas Bonnefon     // This is also the bullet zone width, used for marking clicks
1423a9448ba0SNicolas Bonnefon     bulletZoneWidthPx_ = contentStartPosX;
1424a9448ba0SNicolas Bonnefon 
14252493b508SNicolas Bonnefon     // Update the length of line numbers
14262493b508SNicolas Bonnefon     const int nbDigitsInLineNumber = countDigits( maxDisplayLineNumber() );
14272493b508SNicolas Bonnefon 
1428a9448ba0SNicolas Bonnefon     // Draw the line numbers area
1429a9448ba0SNicolas Bonnefon     int lineNumberAreaStartX = 0;
1430a9448ba0SNicolas Bonnefon     if ( lineNumbersVisible_ ) {
14312493b508SNicolas Bonnefon         int lineNumberWidth = charWidth_ * nbDigitsInLineNumber;
1432a9448ba0SNicolas Bonnefon         int lineNumberAreaWidth =
1433a9448ba0SNicolas Bonnefon             2 * LINE_NUMBER_PADDING + lineNumberWidth;
1434a9448ba0SNicolas Bonnefon         lineNumberAreaStartX = contentStartPosX;
1435a9448ba0SNicolas Bonnefon 
1436a9448ba0SNicolas Bonnefon         painter.setPen(palette.color(QPalette::Text));
1437a9448ba0SNicolas Bonnefon         /* Not sure if it looks good...
1438a9448ba0SNicolas Bonnefon         painter.drawLine( contentStartPosX + lineNumberAreaWidth,
1439a9448ba0SNicolas Bonnefon                           0,
1440a9448ba0SNicolas Bonnefon                           contentStartPosX + lineNumberAreaWidth,
1441a9448ba0SNicolas Bonnefon                           viewport()->height() );
1442a9448ba0SNicolas Bonnefon         */
1443f01b5ae6SNicolas Bonnefon         painter.fillRect( contentStartPosX - SEPARATOR_WIDTH, 0,
1444f01b5ae6SNicolas Bonnefon                           lineNumberAreaWidth + SEPARATOR_WIDTH, paintDeviceHeight,
1445a9448ba0SNicolas Bonnefon                           Qt::lightGray );
1446a9448ba0SNicolas Bonnefon 
1447a9448ba0SNicolas Bonnefon         // Update for drawing the actual text
1448a9448ba0SNicolas Bonnefon         contentStartPosX += lineNumberAreaWidth;
1449a9448ba0SNicolas Bonnefon     }
1450a9448ba0SNicolas Bonnefon     else {
1451f01b5ae6SNicolas Bonnefon         painter.fillRect( contentStartPosX - SEPARATOR_WIDTH, 0,
1452f01b5ae6SNicolas Bonnefon                           SEPARATOR_WIDTH + 1, paintDeviceHeight,
1453f01b5ae6SNicolas Bonnefon                           Qt::lightGray );
1454f01b5ae6SNicolas Bonnefon         // contentStartPosX += SEPARATOR_WIDTH;
1455a9448ba0SNicolas Bonnefon     }
1456a9448ba0SNicolas Bonnefon 
1457f01b5ae6SNicolas Bonnefon     painter.drawLine( BULLET_AREA_WIDTH, 0,
1458f01b5ae6SNicolas Bonnefon                       BULLET_AREA_WIDTH, paintDeviceHeight - 1 );
1459f01b5ae6SNicolas Bonnefon 
1460a9448ba0SNicolas Bonnefon     // This is the total width of the 'margin' (including line number if any)
1461a9448ba0SNicolas Bonnefon     // used for mouse calculation etc...
1462f01b5ae6SNicolas Bonnefon     leftMarginPx_ = contentStartPosX + SEPARATOR_WIDTH;
1463a9448ba0SNicolas Bonnefon 
1464a9448ba0SNicolas Bonnefon     // Then draw each line
14652493b508SNicolas Bonnefon     for (int i = 0; i < nbLines; i++) {
14662493b508SNicolas Bonnefon         const LineNumber line_index = i + firstLine;
14672493b508SNicolas Bonnefon 
1468a9448ba0SNicolas Bonnefon         // Position in pixel of the base line of the line to print
14692493b508SNicolas Bonnefon         const int yPos = i * fontHeight;
1470a9448ba0SNicolas Bonnefon         const int xPos = contentStartPosX + CONTENT_MARGIN_WIDTH;
1471a9448ba0SNicolas Bonnefon 
1472a9448ba0SNicolas Bonnefon         // string to print, cut to fit the length and position of the view
14732493b508SNicolas Bonnefon         const QString line = lines[i];
1474a9448ba0SNicolas Bonnefon         const QString cutLine = line.mid( firstCol, nbCols );
1475a9448ba0SNicolas Bonnefon 
14762493b508SNicolas Bonnefon         if ( selection_.isLineSelected( line_index ) ) {
1477a9448ba0SNicolas Bonnefon             // Reverse the selected line
1478a9448ba0SNicolas Bonnefon             foreColor = palette.color( QPalette::HighlightedText );
1479a9448ba0SNicolas Bonnefon             backColor = palette.color( QPalette::Highlight );
1480a9448ba0SNicolas Bonnefon             painter.setPen(palette.color(QPalette::Text));
1481a9448ba0SNicolas Bonnefon         }
14822493b508SNicolas Bonnefon         else if ( filterSet->matchLine( logData->getLineString( line_index ),
1483a9448ba0SNicolas Bonnefon                     &foreColor, &backColor ) ) {
1484a9448ba0SNicolas Bonnefon             // Apply a filter to the line
1485a9448ba0SNicolas Bonnefon         }
1486a9448ba0SNicolas Bonnefon         else {
1487a9448ba0SNicolas Bonnefon             // Use the default colors
1488a9448ba0SNicolas Bonnefon             foreColor = palette.color( QPalette::Text );
1489a9448ba0SNicolas Bonnefon             backColor = palette.color( QPalette::Base );
1490a9448ba0SNicolas Bonnefon         }
1491a9448ba0SNicolas Bonnefon 
1492a9448ba0SNicolas Bonnefon         // Is there something selected in the line?
1493a9448ba0SNicolas Bonnefon         int sel_start, sel_end;
1494a9448ba0SNicolas Bonnefon         bool isSelection =
14952493b508SNicolas Bonnefon             selection_.getPortionForLine( line_index, &sel_start, &sel_end );
1496a9448ba0SNicolas Bonnefon         // Has the line got elements to be highlighted
1497a9448ba0SNicolas Bonnefon         QList<QuickFindMatch> qfMatchList;
1498a9448ba0SNicolas Bonnefon         bool isMatch =
1499a9448ba0SNicolas Bonnefon             quickFindPattern_->matchLine( line, qfMatchList );
1500a9448ba0SNicolas Bonnefon 
1501a9448ba0SNicolas Bonnefon         if ( isSelection || isMatch ) {
1502a9448ba0SNicolas Bonnefon             // We use the LineDrawer and its chunks because the
1503a9448ba0SNicolas Bonnefon             // line has to be somehow highlighted
1504a9448ba0SNicolas Bonnefon             LineDrawer lineDrawer( backColor );
1505a9448ba0SNicolas Bonnefon 
1506a9448ba0SNicolas Bonnefon             // First we create a list of chunks with the highlights
1507a9448ba0SNicolas Bonnefon             QList<LineChunk> chunkList;
1508a9448ba0SNicolas Bonnefon             int column = 0; // Current column in line space
1509a9448ba0SNicolas Bonnefon             foreach( const QuickFindMatch match, qfMatchList ) {
1510a9448ba0SNicolas Bonnefon                 int start = match.startColumn() - firstCol;
1511a9448ba0SNicolas Bonnefon                 int end = start + match.length();
1512a9448ba0SNicolas Bonnefon                 // Ignore matches that are *completely* outside view area
1513a9448ba0SNicolas Bonnefon                 if ( ( start < 0 && end < 0 ) || start >= nbCols )
1514a9448ba0SNicolas Bonnefon                     continue;
1515a9448ba0SNicolas Bonnefon                 if ( start > column )
1516a9448ba0SNicolas Bonnefon                     chunkList << LineChunk( column, start - 1, LineChunk::Normal );
1517a9448ba0SNicolas Bonnefon                 column = qMin( start + match.length() - 1, nbCols );
1518a9448ba0SNicolas Bonnefon                 chunkList << LineChunk( qMax( start, 0 ), column,
1519a9448ba0SNicolas Bonnefon                         LineChunk::Highlighted );
1520a9448ba0SNicolas Bonnefon                 column++;
1521a9448ba0SNicolas Bonnefon             }
1522a9448ba0SNicolas Bonnefon             if ( column <= cutLine.length() - 1 )
1523a9448ba0SNicolas Bonnefon                 chunkList << LineChunk( column, cutLine.length() - 1, LineChunk::Normal );
1524a9448ba0SNicolas Bonnefon 
1525a9448ba0SNicolas Bonnefon             // Then we add the selection if needed
1526a9448ba0SNicolas Bonnefon             QList<LineChunk> newChunkList;
1527a9448ba0SNicolas Bonnefon             if ( isSelection ) {
1528a9448ba0SNicolas Bonnefon                 sel_start -= firstCol; // coord in line space
1529a9448ba0SNicolas Bonnefon                 sel_end   -= firstCol;
1530a9448ba0SNicolas Bonnefon 
1531a9448ba0SNicolas Bonnefon                 foreach ( const LineChunk chunk, chunkList ) {
1532a9448ba0SNicolas Bonnefon                     newChunkList << chunk.select( sel_start, sel_end );
1533a9448ba0SNicolas Bonnefon                 }
1534a9448ba0SNicolas Bonnefon             }
1535a9448ba0SNicolas Bonnefon             else
1536a9448ba0SNicolas Bonnefon                 newChunkList = chunkList;
1537a9448ba0SNicolas Bonnefon 
1538a9448ba0SNicolas Bonnefon             foreach ( const LineChunk chunk, newChunkList ) {
1539a9448ba0SNicolas Bonnefon                 // Select the colours
1540a9448ba0SNicolas Bonnefon                 QColor fore;
1541a9448ba0SNicolas Bonnefon                 QColor back;
1542a9448ba0SNicolas Bonnefon                 switch ( chunk.type() ) {
1543a9448ba0SNicolas Bonnefon                     case LineChunk::Normal:
1544a9448ba0SNicolas Bonnefon                         fore = foreColor;
1545a9448ba0SNicolas Bonnefon                         back = backColor;
1546a9448ba0SNicolas Bonnefon                         break;
1547a9448ba0SNicolas Bonnefon                     case LineChunk::Highlighted:
1548a9448ba0SNicolas Bonnefon                         fore = QColor( "black" );
1549a9448ba0SNicolas Bonnefon                         back = QColor( "yellow" );
1550a9448ba0SNicolas Bonnefon                         // fore = highlightForeColor;
1551a9448ba0SNicolas Bonnefon                         // back = highlightBackColor;
1552a9448ba0SNicolas Bonnefon                         break;
1553a9448ba0SNicolas Bonnefon                     case LineChunk::Selected:
1554a9448ba0SNicolas Bonnefon                         fore = palette.color( QPalette::HighlightedText ),
1555a9448ba0SNicolas Bonnefon                              back = palette.color( QPalette::Highlight );
1556a9448ba0SNicolas Bonnefon                         break;
1557a9448ba0SNicolas Bonnefon                 }
1558a9448ba0SNicolas Bonnefon                 lineDrawer.addChunk ( chunk, fore, back );
1559a9448ba0SNicolas Bonnefon             }
1560a9448ba0SNicolas Bonnefon 
1561a9448ba0SNicolas Bonnefon             lineDrawer.draw( painter, xPos, yPos,
1562a9448ba0SNicolas Bonnefon                     viewport()->width(), cutLine,
1563a9448ba0SNicolas Bonnefon                     CONTENT_MARGIN_WIDTH );
1564a9448ba0SNicolas Bonnefon         }
1565a9448ba0SNicolas Bonnefon         else {
1566a9448ba0SNicolas Bonnefon             // Nothing to be highlighted, we print the whole line!
1567a9448ba0SNicolas Bonnefon             painter.fillRect( xPos - CONTENT_MARGIN_WIDTH, yPos,
1568a9448ba0SNicolas Bonnefon                     viewport()->width(), fontHeight, backColor );
1569a9448ba0SNicolas Bonnefon             // (the rectangle is extended on the left to cover the small
1570a9448ba0SNicolas Bonnefon             // margin, it looks better (LineDrawer does the same) )
1571a9448ba0SNicolas Bonnefon             painter.setPen( foreColor );
1572a9448ba0SNicolas Bonnefon             painter.drawText( xPos, yPos + fontAscent, cutLine );
1573a9448ba0SNicolas Bonnefon         }
1574a9448ba0SNicolas Bonnefon 
1575a9448ba0SNicolas Bonnefon         // Then draw the bullet
1576a9448ba0SNicolas Bonnefon         painter.setPen( palette.color( QPalette::Text ) );
1577739c6a66SNicolas Bonnefon         const qreal circleSize = 3;
1578739c6a66SNicolas Bonnefon         const qreal arrowHeight = 4;
1579739c6a66SNicolas Bonnefon         const qreal middleXLine = BULLET_AREA_WIDTH / 2;
1580739c6a66SNicolas Bonnefon         const qreal middleYLine = yPos + (fontHeight / 2);
1581a9448ba0SNicolas Bonnefon 
15822493b508SNicolas Bonnefon         const LineType line_type = lineType( line_index );
1583a9448ba0SNicolas Bonnefon         if ( line_type == Marked ) {
1584a9448ba0SNicolas Bonnefon             // A pretty arrow if the line is marked
1585739c6a66SNicolas Bonnefon             const QPointF points[7] = {
1586739c6a66SNicolas Bonnefon                 QPointF(1, middleYLine - 2),
1587739c6a66SNicolas Bonnefon                 QPointF(middleXLine, middleYLine - 2),
1588739c6a66SNicolas Bonnefon                 QPointF(middleXLine, middleYLine - arrowHeight),
1589739c6a66SNicolas Bonnefon                 QPointF(BULLET_AREA_WIDTH - 1, middleYLine),
1590739c6a66SNicolas Bonnefon                 QPointF(middleXLine, middleYLine + arrowHeight),
1591739c6a66SNicolas Bonnefon                 QPointF(middleXLine, middleYLine + 2),
1592739c6a66SNicolas Bonnefon                 QPointF(1, middleYLine + 2 ),
1593a9448ba0SNicolas Bonnefon             };
1594a9448ba0SNicolas Bonnefon 
1595a9448ba0SNicolas Bonnefon             painter.setBrush( markBrush );
1596a9448ba0SNicolas Bonnefon             painter.drawPolygon( points, 7 );
1597a9448ba0SNicolas Bonnefon         }
1598a9448ba0SNicolas Bonnefon         else {
1599739c6a66SNicolas Bonnefon             // For pretty circles
1600739c6a66SNicolas Bonnefon             painter.setRenderHint( QPainter::Antialiasing );
1601739c6a66SNicolas Bonnefon 
16022493b508SNicolas Bonnefon             if ( lineType( line_index ) == Match )
1603a9448ba0SNicolas Bonnefon                 painter.setBrush( matchBulletBrush );
1604a9448ba0SNicolas Bonnefon             else
1605a9448ba0SNicolas Bonnefon                 painter.setBrush( normalBulletBrush );
1606a9448ba0SNicolas Bonnefon             painter.drawEllipse( middleXLine - circleSize,
1607a9448ba0SNicolas Bonnefon                     middleYLine - circleSize,
1608a9448ba0SNicolas Bonnefon                     circleSize * 2, circleSize * 2 );
1609a9448ba0SNicolas Bonnefon         }
1610a9448ba0SNicolas Bonnefon 
1611a9448ba0SNicolas Bonnefon         // Draw the line number
1612a9448ba0SNicolas Bonnefon         if ( lineNumbersVisible_ ) {
1613a9448ba0SNicolas Bonnefon             static const QString lineNumberFormat( "%1" );
1614a9448ba0SNicolas Bonnefon             const QString& lineNumberStr =
16152493b508SNicolas Bonnefon                 lineNumberFormat.arg( displayLineNumber( line_index ),
16162493b508SNicolas Bonnefon                         nbDigitsInLineNumber );
1617a9448ba0SNicolas Bonnefon             painter.setPen( palette.color( QPalette::Text ) );
1618a9448ba0SNicolas Bonnefon             painter.drawText( lineNumberAreaStartX + LINE_NUMBER_PADDING,
1619a9448ba0SNicolas Bonnefon                     yPos + fontAscent, lineNumberStr );
1620a9448ba0SNicolas Bonnefon         }
1621a9448ba0SNicolas Bonnefon     } // For each line
16222493b508SNicolas Bonnefon 
16232493b508SNicolas Bonnefon     if ( bottomOfTextPx < paintDeviceHeight ) {
16242493b508SNicolas Bonnefon         // The lines don't cover the whole device
16252493b508SNicolas Bonnefon         painter.fillRect( contentStartPosX, bottomOfTextPx,
16262493b508SNicolas Bonnefon                 paintDeviceWidth - contentStartPosX,
16272493b508SNicolas Bonnefon                 paintDeviceHeight, palette.color( QPalette::Window ) );
16282493b508SNicolas Bonnefon     }
1629ac6602a5SNicolas Bonnefon }
1630a9448ba0SNicolas Bonnefon 
1631ac6602a5SNicolas Bonnefon // Draw the "pull to follow" bar and return a pixmap.
1632ac6602a5SNicolas Bonnefon // The width is passed in "logic" pixels.
1633ac6602a5SNicolas Bonnefon QPixmap AbstractLogView::drawPullToFollowBar( int width, float pixel_ratio )
1634ac6602a5SNicolas Bonnefon {
1635ac6602a5SNicolas Bonnefon     static constexpr int barWidth = 40;
1636ac6602a5SNicolas Bonnefon     QPixmap pixmap ( static_cast<float>( width ) * pixel_ratio, barWidth * 6.0 );
1637ac6602a5SNicolas Bonnefon     pixmap.setDevicePixelRatio( pixel_ratio );
1638ac6602a5SNicolas Bonnefon     pixmap.fill( this->palette().color( this->backgroundRole() ) );
1639ac6602a5SNicolas Bonnefon     const int nbBars = width / (barWidth * 2) + 1;
1640ac6602a5SNicolas Bonnefon 
1641ac6602a5SNicolas Bonnefon     QPainter painter( &pixmap );
1642ac6602a5SNicolas Bonnefon     painter.setPen( QPen( QColor( 0, 0, 0, 0 ) ) );
1643ac6602a5SNicolas Bonnefon     painter.setBrush( QBrush( QColor( "lightyellow" ) ) );
1644ac6602a5SNicolas Bonnefon 
1645ac6602a5SNicolas Bonnefon     for ( int i = 0; i < nbBars; ++i ) {
1646ac6602a5SNicolas Bonnefon         QPoint points[4] = {
1647ac6602a5SNicolas Bonnefon             { (i*2+1)*barWidth, 0 },
1648ac6602a5SNicolas Bonnefon             { 0, (i*2+1)*barWidth },
1649ac6602a5SNicolas Bonnefon             { 0, (i+1)*2*barWidth },
1650ac6602a5SNicolas Bonnefon             { (i+1)*2*barWidth, 0 }
1651ac6602a5SNicolas Bonnefon         };
1652ac6602a5SNicolas Bonnefon         painter.drawConvexPolygon( points, 4 );
1653ac6602a5SNicolas Bonnefon     }
1654ac6602a5SNicolas Bonnefon 
1655ac6602a5SNicolas Bonnefon     return pixmap;
1656a9448ba0SNicolas Bonnefon }
1657a9448ba0SNicolas Bonnefon 
1658b297d2f4SNicolas Bonnefon void AbstractLogView::disableFollow()
1659b297d2f4SNicolas Bonnefon {
1660b297d2f4SNicolas Bonnefon     emit followModeChanged( false );
1661b297d2f4SNicolas Bonnefon     followElasticHook_.hook( false );
1662b297d2f4SNicolas Bonnefon }
1663b297d2f4SNicolas Bonnefon 
16640b05c6eaSNicolas Bonnefon namespace {
16650b05c6eaSNicolas Bonnefon 
16660b05c6eaSNicolas Bonnefon // Convert the length of the pull to follow bar to pixels
16670b05c6eaSNicolas Bonnefon int mapPullToFollowLength( int length )
16680b05c6eaSNicolas Bonnefon {
16690b05c6eaSNicolas Bonnefon     return length / 14;
16700b05c6eaSNicolas Bonnefon }
16710b05c6eaSNicolas Bonnefon 
16720b05c6eaSNicolas Bonnefon };
1673