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; 2619f0249b2SNicolas Bonnefon lastLineAligned = false; 262bb02e0acSNicolas Bonnefon firstCol = 0; 263bb02e0acSNicolas Bonnefon 264bb02e0acSNicolas Bonnefon overview_ = NULL; 265bb02e0acSNicolas Bonnefon overviewWidget_ = NULL; 266bb02e0acSNicolas Bonnefon 267bb02e0acSNicolas Bonnefon // Display 268bb02e0acSNicolas Bonnefon leftMarginPx_ = 0; 269bb02e0acSNicolas Bonnefon 270bc929c39SNicolas Bonnefon // Fonts (sensible default for overview widget) 271bb02e0acSNicolas Bonnefon charWidth_ = 1; 272bc929c39SNicolas Bonnefon charHeight_ = 10; 273bb02e0acSNicolas Bonnefon 274bb02e0acSNicolas Bonnefon // Create the viewport QWidget 275bb02e0acSNicolas Bonnefon setViewport( 0 ); 276bb02e0acSNicolas Bonnefon 277bb02e0acSNicolas Bonnefon // Hovering 278bb02e0acSNicolas Bonnefon setMouseTracking( true ); 279bb02e0acSNicolas Bonnefon lastHoveredLine_ = -1; 280bb02e0acSNicolas Bonnefon 281bb02e0acSNicolas Bonnefon // Init the popup menu 282bb02e0acSNicolas Bonnefon createMenu(); 283bb02e0acSNicolas Bonnefon 284bb02e0acSNicolas Bonnefon // Signals 285bb02e0acSNicolas Bonnefon connect( quickFindPattern_, SIGNAL( patternUpdated() ), 286bb02e0acSNicolas Bonnefon this, SLOT ( handlePatternUpdated() ) ); 287bb02e0acSNicolas Bonnefon connect( &quickFind_, SIGNAL( notify( const QFNotification& ) ), 288bb02e0acSNicolas Bonnefon this, SIGNAL( notifyQuickFind( const QFNotification& ) ) ); 289bb02e0acSNicolas Bonnefon connect( &quickFind_, SIGNAL( clearNotification() ), 290bb02e0acSNicolas Bonnefon this, SIGNAL( clearQuickFindNotification() ) ); 2910b05c6eaSNicolas Bonnefon connect( &followElasticHook_, SIGNAL( lengthChanged() ), 2920b05c6eaSNicolas Bonnefon this, SLOT( repaint() ) ); 293b297d2f4SNicolas Bonnefon connect( &followElasticHook_, SIGNAL( hooked( bool ) ), 294b297d2f4SNicolas Bonnefon this, SIGNAL( followModeChanged( bool ) ) ); 295bb02e0acSNicolas Bonnefon } 296bb02e0acSNicolas Bonnefon 297bb02e0acSNicolas Bonnefon AbstractLogView::~AbstractLogView() 298bb02e0acSNicolas Bonnefon { 299bb02e0acSNicolas Bonnefon } 300bb02e0acSNicolas Bonnefon 301bb02e0acSNicolas Bonnefon 302bb02e0acSNicolas Bonnefon // 303bb02e0acSNicolas Bonnefon // Received events 304bb02e0acSNicolas Bonnefon // 305bb02e0acSNicolas Bonnefon 306bb02e0acSNicolas Bonnefon void AbstractLogView::changeEvent( QEvent* changeEvent ) 307bb02e0acSNicolas Bonnefon { 308bb02e0acSNicolas Bonnefon QAbstractScrollArea::changeEvent( changeEvent ); 309bb02e0acSNicolas Bonnefon 310bb02e0acSNicolas Bonnefon // Stop the timer if the widget becomes inactive 311bb02e0acSNicolas Bonnefon if ( changeEvent->type() == QEvent::ActivationChange ) { 312bb02e0acSNicolas Bonnefon if ( ! isActiveWindow() ) 313bb02e0acSNicolas Bonnefon autoScrollTimer_.stop(); 314bb02e0acSNicolas Bonnefon } 315bb02e0acSNicolas Bonnefon viewport()->update(); 316bb02e0acSNicolas Bonnefon } 317bb02e0acSNicolas Bonnefon 318bb02e0acSNicolas Bonnefon void AbstractLogView::mousePressEvent( QMouseEvent* mouseEvent ) 319bb02e0acSNicolas Bonnefon { 32011582726SNicolas Bonnefon static std::shared_ptr<Configuration> config = 32111582726SNicolas Bonnefon Persistent<Configuration>( "settings" ); 322bb02e0acSNicolas Bonnefon 323bb02e0acSNicolas Bonnefon if ( mouseEvent->button() == Qt::LeftButton ) 324bb02e0acSNicolas Bonnefon { 325bb02e0acSNicolas Bonnefon int line = convertCoordToLine( mouseEvent->y() ); 326bb02e0acSNicolas Bonnefon 327bb02e0acSNicolas Bonnefon if ( mouseEvent->modifiers() & Qt::ShiftModifier ) 328bb02e0acSNicolas Bonnefon { 329bb02e0acSNicolas Bonnefon selection_.selectRangeFromPrevious( line ); 330bb02e0acSNicolas Bonnefon emit updateLineNumber( line ); 331bb02e0acSNicolas Bonnefon update(); 332bb02e0acSNicolas Bonnefon } 333bb02e0acSNicolas Bonnefon else 334bb02e0acSNicolas Bonnefon { 335bb02e0acSNicolas Bonnefon if ( mouseEvent->x() < bulletZoneWidthPx_ ) { 336bb02e0acSNicolas Bonnefon // Mark a line if it is clicked in the left margin 337bb02e0acSNicolas Bonnefon // (only if click and release in the same area) 338bb02e0acSNicolas Bonnefon markingClickInitiated_ = true; 339bb02e0acSNicolas Bonnefon markingClickLine_ = line; 340bb02e0acSNicolas Bonnefon } 341bb02e0acSNicolas Bonnefon else { 342bb02e0acSNicolas Bonnefon // Select the line, and start a selection 343bb02e0acSNicolas Bonnefon if ( line < logData->getNbLine() ) { 344bb02e0acSNicolas Bonnefon selection_.selectLine( line ); 345bb02e0acSNicolas Bonnefon emit updateLineNumber( line ); 346bb02e0acSNicolas Bonnefon emit newSelection( line ); 347bb02e0acSNicolas Bonnefon } 348bb02e0acSNicolas Bonnefon 349bb02e0acSNicolas Bonnefon // Remember the click in case we're starting a selection 350bb02e0acSNicolas Bonnefon selectionStarted_ = true; 351bb02e0acSNicolas Bonnefon selectionStartPos_ = convertCoordToFilePos( mouseEvent->pos() ); 352bb02e0acSNicolas Bonnefon selectionCurrentEndPos_ = selectionStartPos_; 353bb02e0acSNicolas Bonnefon } 354bb02e0acSNicolas Bonnefon } 355a9448ba0SNicolas Bonnefon 356a9448ba0SNicolas Bonnefon // Invalidate our cache 357a9448ba0SNicolas Bonnefon textAreaCache_.invalid_ = true; 358bb02e0acSNicolas Bonnefon } 359bb02e0acSNicolas Bonnefon else if ( mouseEvent->button() == Qt::RightButton ) 360bb02e0acSNicolas Bonnefon { 361bb02e0acSNicolas Bonnefon // Prepare the popup depending on selection type 362bb02e0acSNicolas Bonnefon if ( selection_.isSingleLine() ) { 363bb02e0acSNicolas Bonnefon copyAction_->setText( "&Copy this line" ); 364bb02e0acSNicolas Bonnefon } 365bb02e0acSNicolas Bonnefon else { 366bb02e0acSNicolas Bonnefon copyAction_->setText( "&Copy" ); 367bb02e0acSNicolas Bonnefon copyAction_->setStatusTip( tr("Copy the selection") ); 368bb02e0acSNicolas Bonnefon } 369bb02e0acSNicolas Bonnefon 370bb02e0acSNicolas Bonnefon if ( selection_.isPortion() ) { 371bb02e0acSNicolas Bonnefon findNextAction_->setEnabled( true ); 372bb02e0acSNicolas Bonnefon findPreviousAction_->setEnabled( true ); 373bb02e0acSNicolas Bonnefon addToSearchAction_->setEnabled( true ); 374bb02e0acSNicolas Bonnefon } 375bb02e0acSNicolas Bonnefon else { 376bb02e0acSNicolas Bonnefon findNextAction_->setEnabled( false ); 377bb02e0acSNicolas Bonnefon findPreviousAction_->setEnabled( false ); 378bb02e0acSNicolas Bonnefon addToSearchAction_->setEnabled( false ); 379bb02e0acSNicolas Bonnefon } 380bb02e0acSNicolas Bonnefon 381bb02e0acSNicolas Bonnefon // "Add to search" only makes sense in regexp mode 38211582726SNicolas Bonnefon if ( config->mainRegexpType() != ExtendedRegexp ) 383bb02e0acSNicolas Bonnefon addToSearchAction_->setEnabled( false ); 384bb02e0acSNicolas Bonnefon 385bb02e0acSNicolas Bonnefon // Display the popup (blocking) 386bb02e0acSNicolas Bonnefon popupMenu_->exec( QCursor::pos() ); 387bb02e0acSNicolas Bonnefon } 38845ef183cSNicolas Bonnefon 38945ef183cSNicolas Bonnefon emit activity(); 390bb02e0acSNicolas Bonnefon } 391bb02e0acSNicolas Bonnefon 392bb02e0acSNicolas Bonnefon void AbstractLogView::mouseMoveEvent( QMouseEvent* mouseEvent ) 393bb02e0acSNicolas Bonnefon { 394bb02e0acSNicolas Bonnefon // Selection implementation 395bb02e0acSNicolas Bonnefon if ( selectionStarted_ ) 396bb02e0acSNicolas Bonnefon { 397a9448ba0SNicolas Bonnefon // Invalidate our cache 398a9448ba0SNicolas Bonnefon textAreaCache_.invalid_ = true; 399a9448ba0SNicolas Bonnefon 400bb02e0acSNicolas Bonnefon QPoint thisEndPos = convertCoordToFilePos( mouseEvent->pos() ); 401bb02e0acSNicolas Bonnefon if ( thisEndPos != selectionCurrentEndPos_ ) 402bb02e0acSNicolas Bonnefon { 403bb02e0acSNicolas Bonnefon // Are we on a different line? 404bb02e0acSNicolas Bonnefon if ( selectionStartPos_.y() != thisEndPos.y() ) 405bb02e0acSNicolas Bonnefon { 406bb02e0acSNicolas Bonnefon if ( thisEndPos.y() != selectionCurrentEndPos_.y() ) 407bb02e0acSNicolas Bonnefon { 408bb02e0acSNicolas Bonnefon // This is a 'range' selection 409bb02e0acSNicolas Bonnefon selection_.selectRange( selectionStartPos_.y(), 410bb02e0acSNicolas Bonnefon thisEndPos.y() ); 411bb02e0acSNicolas Bonnefon emit updateLineNumber( thisEndPos.y() ); 412bb02e0acSNicolas Bonnefon update(); 413bb02e0acSNicolas Bonnefon } 414bb02e0acSNicolas Bonnefon } 415bb02e0acSNicolas Bonnefon // So we are on the same line. Are we moving horizontaly? 416bb02e0acSNicolas Bonnefon else if ( thisEndPos.x() != selectionCurrentEndPos_.x() ) 417bb02e0acSNicolas Bonnefon { 418bb02e0acSNicolas Bonnefon // This is a 'portion' selection 419bb02e0acSNicolas Bonnefon selection_.selectPortion( thisEndPos.y(), 420bb02e0acSNicolas Bonnefon selectionStartPos_.x(), thisEndPos.x() ); 421bb02e0acSNicolas Bonnefon update(); 422bb02e0acSNicolas Bonnefon } 423bb02e0acSNicolas Bonnefon // On the same line, and moving vertically then 424bb02e0acSNicolas Bonnefon else 425bb02e0acSNicolas Bonnefon { 426bb02e0acSNicolas Bonnefon // This is a 'line' selection 427bb02e0acSNicolas Bonnefon selection_.selectLine( thisEndPos.y() ); 428bb02e0acSNicolas Bonnefon emit updateLineNumber( thisEndPos.y() ); 429bb02e0acSNicolas Bonnefon update(); 430bb02e0acSNicolas Bonnefon } 431bb02e0acSNicolas Bonnefon selectionCurrentEndPos_ = thisEndPos; 432bb02e0acSNicolas Bonnefon 433bb02e0acSNicolas Bonnefon // Do we need to scroll while extending the selection? 434bb02e0acSNicolas Bonnefon QRect visible = viewport()->rect(); 435bb02e0acSNicolas Bonnefon if ( visible.contains( mouseEvent->pos() ) ) 436bb02e0acSNicolas Bonnefon autoScrollTimer_.stop(); 437bb02e0acSNicolas Bonnefon else if ( ! autoScrollTimer_.isActive() ) 438bb02e0acSNicolas Bonnefon autoScrollTimer_.start( 100, this ); 439bb02e0acSNicolas Bonnefon } 440bb02e0acSNicolas Bonnefon } 441bb02e0acSNicolas Bonnefon else { 442bb02e0acSNicolas Bonnefon considerMouseHovering( mouseEvent->x(), mouseEvent->y() ); 443bb02e0acSNicolas Bonnefon } 444bb02e0acSNicolas Bonnefon } 445bb02e0acSNicolas Bonnefon 446bb02e0acSNicolas Bonnefon void AbstractLogView::mouseReleaseEvent( QMouseEvent* mouseEvent ) 447bb02e0acSNicolas Bonnefon { 448bb02e0acSNicolas Bonnefon if ( markingClickInitiated_ ) { 449bb02e0acSNicolas Bonnefon markingClickInitiated_ = false; 450bb02e0acSNicolas Bonnefon int line = convertCoordToLine( mouseEvent->y() ); 451a9448ba0SNicolas Bonnefon if ( line == markingClickLine_ ) { 452a9448ba0SNicolas Bonnefon // Invalidate our cache 453a9448ba0SNicolas Bonnefon textAreaCache_.invalid_ = true; 454a9448ba0SNicolas Bonnefon 455bb02e0acSNicolas Bonnefon emit markLine( line ); 456bb02e0acSNicolas Bonnefon } 457a9448ba0SNicolas Bonnefon } 458bb02e0acSNicolas Bonnefon else { 459bb02e0acSNicolas Bonnefon selectionStarted_ = false; 460bb02e0acSNicolas Bonnefon if ( autoScrollTimer_.isActive() ) 461bb02e0acSNicolas Bonnefon autoScrollTimer_.stop(); 462bb02e0acSNicolas Bonnefon updateGlobalSelection(); 463bb02e0acSNicolas Bonnefon } 464bb02e0acSNicolas Bonnefon } 465bb02e0acSNicolas Bonnefon 466bb02e0acSNicolas Bonnefon void AbstractLogView::mouseDoubleClickEvent( QMouseEvent* mouseEvent ) 467bb02e0acSNicolas Bonnefon { 468bb02e0acSNicolas Bonnefon if ( mouseEvent->button() == Qt::LeftButton ) 469bb02e0acSNicolas Bonnefon { 470a9448ba0SNicolas Bonnefon // Invalidate our cache 471a9448ba0SNicolas Bonnefon textAreaCache_.invalid_ = true; 472a9448ba0SNicolas Bonnefon 473bb02e0acSNicolas Bonnefon const QPoint pos = convertCoordToFilePos( mouseEvent->pos() ); 474bb02e0acSNicolas Bonnefon selectWordAtPosition( pos ); 475bb02e0acSNicolas Bonnefon } 47645ef183cSNicolas Bonnefon 47745ef183cSNicolas Bonnefon emit activity(); 478bb02e0acSNicolas Bonnefon } 479bb02e0acSNicolas Bonnefon 480bb02e0acSNicolas Bonnefon void AbstractLogView::timerEvent( QTimerEvent* timerEvent ) 481bb02e0acSNicolas Bonnefon { 482bb02e0acSNicolas Bonnefon if ( timerEvent->timerId() == autoScrollTimer_.timerId() ) { 483bb02e0acSNicolas Bonnefon QRect visible = viewport()->rect(); 484bb02e0acSNicolas Bonnefon const QPoint globalPos = QCursor::pos(); 485bb02e0acSNicolas Bonnefon const QPoint pos = viewport()->mapFromGlobal( globalPos ); 486bb02e0acSNicolas Bonnefon QMouseEvent ev( QEvent::MouseMove, pos, globalPos, Qt::LeftButton, 487bb02e0acSNicolas Bonnefon Qt::LeftButton, Qt::NoModifier ); 488bb02e0acSNicolas Bonnefon mouseMoveEvent( &ev ); 489bb02e0acSNicolas Bonnefon int deltaX = qMax( pos.x() - visible.left(), 490bb02e0acSNicolas Bonnefon visible.right() - pos.x() ) - visible.width(); 491bb02e0acSNicolas Bonnefon int deltaY = qMax( pos.y() - visible.top(), 492bb02e0acSNicolas Bonnefon visible.bottom() - pos.y() ) - visible.height(); 493bb02e0acSNicolas Bonnefon int delta = qMax( deltaX, deltaY ); 494bb02e0acSNicolas Bonnefon 495bb02e0acSNicolas Bonnefon if ( delta >= 0 ) { 496bb02e0acSNicolas Bonnefon if ( delta < 7 ) 497bb02e0acSNicolas Bonnefon delta = 7; 498bb02e0acSNicolas Bonnefon int timeout = 4900 / ( delta * delta ); 499bb02e0acSNicolas Bonnefon autoScrollTimer_.start( timeout, this ); 500bb02e0acSNicolas Bonnefon 501bb02e0acSNicolas Bonnefon if ( deltaX > 0 ) 502bb02e0acSNicolas Bonnefon horizontalScrollBar()->triggerAction( 503bb02e0acSNicolas Bonnefon pos.x() <visible.center().x() ? 504bb02e0acSNicolas Bonnefon QAbstractSlider::SliderSingleStepSub : 505bb02e0acSNicolas Bonnefon QAbstractSlider::SliderSingleStepAdd ); 506bb02e0acSNicolas Bonnefon 507bb02e0acSNicolas Bonnefon if ( deltaY > 0 ) 508bb02e0acSNicolas Bonnefon verticalScrollBar()->triggerAction( 509bb02e0acSNicolas Bonnefon pos.y() <visible.center().y() ? 510bb02e0acSNicolas Bonnefon QAbstractSlider::SliderSingleStepSub : 511bb02e0acSNicolas Bonnefon QAbstractSlider::SliderSingleStepAdd ); 512bb02e0acSNicolas Bonnefon } 513bb02e0acSNicolas Bonnefon } 514bb02e0acSNicolas Bonnefon QAbstractScrollArea::timerEvent( timerEvent ); 515bb02e0acSNicolas Bonnefon } 516bb02e0acSNicolas Bonnefon 517bb02e0acSNicolas Bonnefon void AbstractLogView::keyPressEvent( QKeyEvent* keyEvent ) 518bb02e0acSNicolas Bonnefon { 519bb02e0acSNicolas Bonnefon LOG(logDEBUG4) << "keyPressEvent received"; 520a9518197SNicolas Bonnefon 521bb02e0acSNicolas Bonnefon bool controlModifier = (keyEvent->modifiers() & Qt::ControlModifier) == Qt::ControlModifier; 522bb02e0acSNicolas Bonnefon bool shiftModifier = (keyEvent->modifiers() & Qt::ShiftModifier) == Qt::ShiftModifier; 523a9518197SNicolas Bonnefon bool noModifier = keyEvent->modifiers() == Qt::NoModifier; 524bb02e0acSNicolas Bonnefon 525a9518197SNicolas Bonnefon if ( keyEvent->key() == Qt::Key_Left && noModifier ) 526bb02e0acSNicolas Bonnefon horizontalScrollBar()->triggerAction(QScrollBar::SliderPageStepSub); 527a9518197SNicolas Bonnefon else if ( keyEvent->key() == Qt::Key_Right && noModifier ) 528bb02e0acSNicolas Bonnefon horizontalScrollBar()->triggerAction(QScrollBar::SliderPageStepAdd); 529bb02e0acSNicolas Bonnefon else if ( keyEvent->key() == Qt::Key_Home && !controlModifier) 530bb02e0acSNicolas Bonnefon jumpToStartOfLine(); 531bb02e0acSNicolas Bonnefon else if ( keyEvent->key() == Qt::Key_End && !controlModifier) 532bb02e0acSNicolas Bonnefon jumpToRightOfScreen(); 533bb02e0acSNicolas Bonnefon else if ( (keyEvent->key() == Qt::Key_PageDown && controlModifier) 534bb02e0acSNicolas Bonnefon || (keyEvent->key() == Qt::Key_End && controlModifier) ) 535bb02e0acSNicolas Bonnefon { 536b297d2f4SNicolas Bonnefon disableFollow(); // duplicate of 'G' action. 537bb02e0acSNicolas Bonnefon selection_.selectLine( logData->getNbLine() - 1 ); 538bb02e0acSNicolas Bonnefon emit updateLineNumber( logData->getNbLine() - 1 ); 539bb02e0acSNicolas Bonnefon jumpToBottom(); 540bb02e0acSNicolas Bonnefon } 541bb02e0acSNicolas Bonnefon else if ( (keyEvent->key() == Qt::Key_PageUp && controlModifier) 542bb02e0acSNicolas Bonnefon || (keyEvent->key() == Qt::Key_Home && controlModifier) ) 543bb02e0acSNicolas Bonnefon selectAndDisplayLine( 0 ); 544bb02e0acSNicolas Bonnefon else if ( keyEvent->key() == Qt::Key_F3 && !shiftModifier ) 545bb02e0acSNicolas Bonnefon searchNext(); // duplicate of 'n' action. 546bb02e0acSNicolas Bonnefon else if ( keyEvent->key() == Qt::Key_F3 && shiftModifier ) 547bb02e0acSNicolas Bonnefon searchPrevious(); // duplicate of 'N' action. 5487552c461SNicolas Bonnefon else if ( keyEvent->key() == Qt::Key_Space && noModifier ) 5497552c461SNicolas Bonnefon emit exitView(); 550bb02e0acSNicolas Bonnefon else { 551eb6a8f99SNicolas Bonnefon const char character = (keyEvent->text())[0].toLatin1(); 552bb02e0acSNicolas Bonnefon 553f88e59a4SNicolas Bonnefon if ( keyEvent->modifiers() == Qt::NoModifier && 554f88e59a4SNicolas Bonnefon ( character >= '0' ) && ( character <= '9' ) ) { 555bb02e0acSNicolas Bonnefon // Adds the digit to the timed buffer 556bb02e0acSNicolas Bonnefon digitsBuffer_.add( character ); 557bb02e0acSNicolas Bonnefon } 558bb02e0acSNicolas Bonnefon else { 559eb6a8f99SNicolas Bonnefon switch ( (keyEvent->text())[0].toLatin1() ) { 560bb02e0acSNicolas Bonnefon case 'j': 561bb02e0acSNicolas Bonnefon { 562bb02e0acSNicolas Bonnefon int delta = qMax( 1, digitsBuffer_.content() ); 563b297d2f4SNicolas Bonnefon disableFollow(); 564bb02e0acSNicolas Bonnefon //verticalScrollBar()->triggerAction( 565bb02e0acSNicolas Bonnefon //QScrollBar::SliderSingleStepAdd); 566bb02e0acSNicolas Bonnefon moveSelection( delta ); 567bb02e0acSNicolas Bonnefon break; 568bb02e0acSNicolas Bonnefon } 569bb02e0acSNicolas Bonnefon case 'k': 570bb02e0acSNicolas Bonnefon { 571bb02e0acSNicolas Bonnefon int delta = qMin( -1, - digitsBuffer_.content() ); 572b297d2f4SNicolas Bonnefon disableFollow(); 573bb02e0acSNicolas Bonnefon //verticalScrollBar()->triggerAction( 574bb02e0acSNicolas Bonnefon //QScrollBar::SliderSingleStepSub); 575bb02e0acSNicolas Bonnefon moveSelection( delta ); 576bb02e0acSNicolas Bonnefon break; 577bb02e0acSNicolas Bonnefon } 578bb02e0acSNicolas Bonnefon case 'h': 579bb02e0acSNicolas Bonnefon horizontalScrollBar()->triggerAction( 580bb02e0acSNicolas Bonnefon QScrollBar::SliderSingleStepSub); 581bb02e0acSNicolas Bonnefon break; 582bb02e0acSNicolas Bonnefon case 'l': 583bb02e0acSNicolas Bonnefon horizontalScrollBar()->triggerAction( 584bb02e0acSNicolas Bonnefon QScrollBar::SliderSingleStepAdd); 585bb02e0acSNicolas Bonnefon break; 586bb02e0acSNicolas Bonnefon case '0': 587bb02e0acSNicolas Bonnefon jumpToStartOfLine(); 588bb02e0acSNicolas Bonnefon break; 589bb02e0acSNicolas Bonnefon case '$': 590bb02e0acSNicolas Bonnefon jumpToEndOfLine(); 591bb02e0acSNicolas Bonnefon break; 592bb02e0acSNicolas Bonnefon case 'g': 593bb02e0acSNicolas Bonnefon { 594bb02e0acSNicolas Bonnefon int newLine = qMax( 0, digitsBuffer_.content() - 1 ); 595bb02e0acSNicolas Bonnefon if ( newLine >= logData->getNbLine() ) 596bb02e0acSNicolas Bonnefon newLine = logData->getNbLine() - 1; 597bb02e0acSNicolas Bonnefon selectAndDisplayLine( newLine ); 598bb02e0acSNicolas Bonnefon break; 599bb02e0acSNicolas Bonnefon } 600bb02e0acSNicolas Bonnefon case 'G': 601b297d2f4SNicolas Bonnefon disableFollow(); 602bb02e0acSNicolas Bonnefon selection_.selectLine( logData->getNbLine() - 1 ); 603bb02e0acSNicolas Bonnefon emit updateLineNumber( logData->getNbLine() - 1 ); 6048e788202SNicolas Bonnefon emit newSelection( 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; 6217552c461SNicolas Bonnefon case 'm': 6227552c461SNicolas Bonnefon { 6237552c461SNicolas Bonnefon qint64 line = selection_.selectedLine(); 6247552c461SNicolas Bonnefon if ( line >= 0 ) 6257552c461SNicolas Bonnefon emit markLine( line ); 6267552c461SNicolas Bonnefon break; 6277552c461SNicolas Bonnefon } 628bb02e0acSNicolas Bonnefon default: 629bb02e0acSNicolas Bonnefon keyEvent->ignore(); 630bb02e0acSNicolas Bonnefon } 631bb02e0acSNicolas Bonnefon } 632bb02e0acSNicolas Bonnefon } 633bb02e0acSNicolas Bonnefon 63445ef183cSNicolas Bonnefon if ( keyEvent->isAccepted() ) { 63545ef183cSNicolas Bonnefon emit activity(); 63645ef183cSNicolas Bonnefon } 63745ef183cSNicolas Bonnefon else { 638a9518197SNicolas Bonnefon // Only pass bare keys to the superclass this is so that 639a9518197SNicolas Bonnefon // shortcuts such as Ctrl+Alt+Arrow are handled by the parent. 640a9518197SNicolas Bonnefon LOG(logDEBUG) << std::hex << keyEvent->modifiers(); 641a9518197SNicolas Bonnefon if ( keyEvent->modifiers() == Qt::NoModifier || 642a9518197SNicolas Bonnefon keyEvent->modifiers() == Qt::KeypadModifier ) { 643bb02e0acSNicolas Bonnefon QAbstractScrollArea::keyPressEvent( keyEvent ); 644bb02e0acSNicolas Bonnefon } 64545ef183cSNicolas Bonnefon } 646a9518197SNicolas Bonnefon } 647bb02e0acSNicolas Bonnefon 648bb02e0acSNicolas Bonnefon void AbstractLogView::wheelEvent( QWheelEvent* wheelEvent ) 649bb02e0acSNicolas Bonnefon { 65045ef183cSNicolas Bonnefon emit activity(); 651bb02e0acSNicolas Bonnefon 652a9448ba0SNicolas Bonnefon // LOG(logDEBUG) << "wheelEvent"; 6530b05c6eaSNicolas Bonnefon 6542493b508SNicolas Bonnefon // This is to handle the case where follow mode is on, but the user 6552493b508SNicolas Bonnefon // has moved using the scroll bar. We take them back to the bottom. 6562493b508SNicolas Bonnefon if ( followMode_ ) 6572493b508SNicolas Bonnefon jumpToBottom(); 6582493b508SNicolas Bonnefon 65968d95677SNicolas Bonnefon int y_delta = 0; 6600b05c6eaSNicolas Bonnefon if ( verticalScrollBar()->value() == verticalScrollBar()->maximum() ) { 661819e4a32SNicolas Bonnefon // First see if we need to block the elastic (on Mac) 662819e4a32SNicolas Bonnefon if ( wheelEvent->phase() == Qt::ScrollBegin ) 663819e4a32SNicolas Bonnefon followElasticHook_.hold(); 664819e4a32SNicolas Bonnefon else if ( wheelEvent->phase() == Qt::ScrollEnd ) 665819e4a32SNicolas Bonnefon followElasticHook_.release(); 666819e4a32SNicolas Bonnefon 66768d95677SNicolas Bonnefon auto pixel_delta = wheelEvent->pixelDelta(); 66868d95677SNicolas Bonnefon 66968d95677SNicolas Bonnefon if ( pixel_delta.isNull() ) { 67006d5d36cSNicolas Bonnefon y_delta = wheelEvent->angleDelta().y() / 0.7; 67168d95677SNicolas Bonnefon } 67268d95677SNicolas Bonnefon else { 67368d95677SNicolas Bonnefon y_delta = pixel_delta.y(); 67468d95677SNicolas Bonnefon } 67568d95677SNicolas Bonnefon 67668d95677SNicolas Bonnefon // LOG(logDEBUG) << "Elastic " << y_delta; 67768d95677SNicolas Bonnefon followElasticHook_.move( - y_delta ); 6780b05c6eaSNicolas Bonnefon } 6790b05c6eaSNicolas Bonnefon 680a9448ba0SNicolas Bonnefon // LOG(logDEBUG) << "Length = " << followElasticHook_.length(); 681ac6602a5SNicolas Bonnefon if ( followElasticHook_.length() == 0 && !followElasticHook_.isHooked() ) { 682bb02e0acSNicolas Bonnefon QAbstractScrollArea::wheelEvent( wheelEvent ); 683bb02e0acSNicolas Bonnefon } 6840b05c6eaSNicolas Bonnefon } 685bb02e0acSNicolas Bonnefon 686bb02e0acSNicolas Bonnefon void AbstractLogView::resizeEvent( QResizeEvent* ) 687bb02e0acSNicolas Bonnefon { 688bb02e0acSNicolas Bonnefon if ( logData == NULL ) 689bb02e0acSNicolas Bonnefon return; 690bb02e0acSNicolas Bonnefon 691bb02e0acSNicolas Bonnefon LOG(logDEBUG) << "resizeEvent received"; 692bb02e0acSNicolas Bonnefon 693bb02e0acSNicolas Bonnefon updateDisplaySize(); 694bb02e0acSNicolas Bonnefon } 695bb02e0acSNicolas Bonnefon 6961853a1fdSNicolas Bonnefon bool AbstractLogView::event( QEvent* e ) 6971853a1fdSNicolas Bonnefon { 6981853a1fdSNicolas Bonnefon LOG(logDEBUG4) << "Event! Type: " << e->type(); 6991853a1fdSNicolas Bonnefon 7001853a1fdSNicolas Bonnefon // Make sure we ignore the gesture events as 7011853a1fdSNicolas Bonnefon // they seem to be accepted by default. 7021853a1fdSNicolas Bonnefon if ( e->type() == QEvent::Gesture ) { 7031853a1fdSNicolas Bonnefon auto gesture_event = dynamic_cast<QGestureEvent*>( e ); 7041853a1fdSNicolas Bonnefon if ( gesture_event ) { 7051853a1fdSNicolas Bonnefon foreach( QGesture* gesture, gesture_event->gestures() ) { 7062493b508SNicolas Bonnefon LOG(logDEBUG4) << "Gesture: " << gesture->gestureType(); 7071853a1fdSNicolas Bonnefon gesture_event->ignore( gesture ); 7081853a1fdSNicolas Bonnefon } 7091853a1fdSNicolas Bonnefon 7101853a1fdSNicolas Bonnefon // Ensure the event is sent up to parents who might care 7111853a1fdSNicolas Bonnefon return false; 7121853a1fdSNicolas Bonnefon } 7131853a1fdSNicolas Bonnefon } 7141853a1fdSNicolas Bonnefon 7151853a1fdSNicolas Bonnefon return QAbstractScrollArea::event( e ); 7161853a1fdSNicolas Bonnefon } 7171853a1fdSNicolas Bonnefon 718bb02e0acSNicolas Bonnefon void AbstractLogView::scrollContentsBy( int dx, int dy ) 719bb02e0acSNicolas Bonnefon { 7209f0249b2SNicolas Bonnefon LOG(logDEBUG) << "scrollContentsBy received " << dy 7219f0249b2SNicolas Bonnefon << "position " << verticalScrollBar()->value(); 722bb02e0acSNicolas Bonnefon 7239f0249b2SNicolas Bonnefon int32_t last_top_line = ( logData->getNbLine() - getNbVisibleLines() ); 7249f0249b2SNicolas Bonnefon if ( ( last_top_line > 0 ) && verticalScrollBar()->value() > last_top_line ) { 7259f0249b2SNicolas Bonnefon // The user is going further than the last line, we need to lock the last line at the bottom 7269f0249b2SNicolas Bonnefon LOG(logDEBUG) << "scrollContentsBy beyond!"; 7279f0249b2SNicolas Bonnefon firstLine = last_top_line; 7289f0249b2SNicolas Bonnefon lastLineAligned = true; 7299f0249b2SNicolas Bonnefon } 7309f0249b2SNicolas Bonnefon else { 7319f0249b2SNicolas Bonnefon firstLine = verticalScrollBar()->value(); 7329f0249b2SNicolas Bonnefon lastLineAligned = false; 7339f0249b2SNicolas Bonnefon } 7349f0249b2SNicolas Bonnefon 735bb02e0acSNicolas Bonnefon firstCol = (firstCol - dx) > 0 ? firstCol - dx : 0; 7362493b508SNicolas Bonnefon LineNumber last_line = firstLine + getNbVisibleLines(); 737bb02e0acSNicolas Bonnefon 738bb02e0acSNicolas Bonnefon // Update the overview if we have one 739bb02e0acSNicolas Bonnefon if ( overview_ != NULL ) 7402493b508SNicolas Bonnefon overview_->updateCurrentPosition( firstLine, last_line ); 741bb02e0acSNicolas Bonnefon 742bb02e0acSNicolas Bonnefon // Are we hovering over a new line? 743bb02e0acSNicolas Bonnefon const QPoint mouse_pos = mapFromGlobal( QCursor::pos() ); 744bb02e0acSNicolas Bonnefon considerMouseHovering( mouse_pos.x(), mouse_pos.y() ); 745bb02e0acSNicolas Bonnefon 746bb02e0acSNicolas Bonnefon // Redraw 747bb02e0acSNicolas Bonnefon update(); 748bb02e0acSNicolas Bonnefon } 749bb02e0acSNicolas Bonnefon 750bb02e0acSNicolas Bonnefon void AbstractLogView::paintEvent( QPaintEvent* paintEvent ) 751bb02e0acSNicolas Bonnefon { 7522493b508SNicolas Bonnefon const QRect invalidRect = paintEvent->rect(); 753bb02e0acSNicolas Bonnefon if ( (invalidRect.isEmpty()) || (logData == NULL) ) 754bb02e0acSNicolas Bonnefon return; 755bb02e0acSNicolas Bonnefon 756bb02e0acSNicolas Bonnefon LOG(logDEBUG4) << "paintEvent received, firstLine=" << firstLine 7579f0249b2SNicolas Bonnefon << " lastLineAligned=" << lastLineAligned 7582493b508SNicolas Bonnefon << " rect: " << invalidRect.topLeft().x() << 759bb02e0acSNicolas Bonnefon ", " << invalidRect.topLeft().y() << 760bb02e0acSNicolas Bonnefon ", " << invalidRect.bottomRight().x() << 761bb02e0acSNicolas Bonnefon ", " << invalidRect.bottomRight().y(); 762bb02e0acSNicolas Bonnefon 7638bda3285SNicolas Bonnefon #ifdef GLOGG_PERF_MEASURE_FPS 7648bda3285SNicolas Bonnefon static uint32_t maxline = logData->getNbLine(); 7658bda3285SNicolas Bonnefon if ( ! perfCounter_.addEvent() && logData->getNbLine() > maxline ) { 766045703daSNicolas Bonnefon LOG(logWARNING) << "Redraw per second: " << perfCounter_.readAndReset() 767045703daSNicolas Bonnefon << " lines: " << logData->getNbLine(); 768045703daSNicolas Bonnefon perfCounter_.addEvent(); 7698bda3285SNicolas Bonnefon maxline = logData->getNbLine(); 770045703daSNicolas Bonnefon } 7718bda3285SNicolas Bonnefon #endif 772045703daSNicolas Bonnefon 773a9448ba0SNicolas Bonnefon auto start = std::chrono::system_clock::now(); 774a9448ba0SNicolas Bonnefon 775a9448ba0SNicolas Bonnefon // Can we use our cache? 776ac6602a5SNicolas Bonnefon int32_t delta_y = textAreaCache_.first_line_ - firstLine; 777ac6602a5SNicolas Bonnefon 778ac6602a5SNicolas Bonnefon if ( textAreaCache_.invalid_ || ( textAreaCache_.first_column_ != firstCol ) ) { 779ac6602a5SNicolas Bonnefon // Force a full redraw 780ac6602a5SNicolas Bonnefon delta_y = INT32_MAX; 781a9448ba0SNicolas Bonnefon } 782ac6602a5SNicolas Bonnefon 783ac6602a5SNicolas Bonnefon if ( delta_y != 0 ) { 784ac6602a5SNicolas Bonnefon // Full or partial redraw 785ac6602a5SNicolas Bonnefon drawTextArea( &textAreaCache_.pixmap_, delta_y ); 786a9448ba0SNicolas Bonnefon 787a9448ba0SNicolas Bonnefon textAreaCache_.invalid_ = false; 788a9448ba0SNicolas Bonnefon textAreaCache_.first_line_ = firstLine; 789ac6602a5SNicolas Bonnefon textAreaCache_.first_column_ = firstCol; 790a9448ba0SNicolas Bonnefon 791a9448ba0SNicolas Bonnefon LOG(logDEBUG) << "End of writing " << 792a9448ba0SNicolas Bonnefon std::chrono::duration_cast<std::chrono::microseconds> 793a9448ba0SNicolas Bonnefon ( std::chrono::system_clock::now() - start ).count(); 794a9448ba0SNicolas Bonnefon } 795ac6602a5SNicolas Bonnefon else { 796ac6602a5SNicolas Bonnefon // Use the cache as is: nothing to do! 797ac6602a5SNicolas Bonnefon } 798a9448ba0SNicolas Bonnefon 799b297d2f4SNicolas Bonnefon // Height including the potentially invisible last line 8009f0249b2SNicolas Bonnefon const int whole_height = getNbVisibleLines() * charHeight_; 8010b05c6eaSNicolas Bonnefon // Height in pixels of the "pull to follow" bottom bar. 802257b1c50SNicolas Bonnefon int pullToFollowHeight = mapPullToFollowLength( followElasticHook_.length() ) 803b297d2f4SNicolas Bonnefon + ( followElasticHook_.isHooked() ? 8049f0249b2SNicolas Bonnefon ( whole_height - viewport()->height() ) + PULL_TO_FOLLOW_HOOKED_HEIGHT : 0 ); 805ac6602a5SNicolas Bonnefon 806257b1c50SNicolas Bonnefon if ( pullToFollowHeight 807ac6602a5SNicolas Bonnefon && ( pullToFollowCache_.nb_columns_ != getNbVisibleCols() ) ) { 808ac6602a5SNicolas Bonnefon LOG(logDEBUG) << "Drawing pull to follow bar"; 809ac6602a5SNicolas Bonnefon pullToFollowCache_.pixmap_ = drawPullToFollowBar( 810ac6602a5SNicolas Bonnefon viewport()->width(), viewport()->devicePixelRatio() ); 811ac6602a5SNicolas Bonnefon pullToFollowCache_.nb_columns_ = getNbVisibleCols(); 812ac6602a5SNicolas Bonnefon } 813bb02e0acSNicolas Bonnefon 814a9448ba0SNicolas Bonnefon QPainter devicePainter( viewport() ); 815257b1c50SNicolas Bonnefon int drawingTopPosition = - pullToFollowHeight; 8169f0249b2SNicolas Bonnefon int drawingPullToFollowTopPosition = drawingTopPosition + whole_height; 8172493b508SNicolas Bonnefon 8182493b508SNicolas Bonnefon // This is to cover the special case where there is less than a screenful 8192493b508SNicolas Bonnefon // worth of data, we want to see the document from the top, rather than 8202493b508SNicolas Bonnefon // pushing the first couple of lines above the viewport. 8219f0249b2SNicolas Bonnefon if ( followElasticHook_.isHooked() && ( logData->getNbLine() < getNbVisibleLines() ) ) { 822257b1c50SNicolas Bonnefon drawingTopOffset_ = 0; 8239f0249b2SNicolas Bonnefon drawingTopPosition += ( whole_height - viewport()->height() ) + PULL_TO_FOLLOW_HOOKED_HEIGHT; 8249f0249b2SNicolas Bonnefon drawingPullToFollowTopPosition = drawingTopPosition + viewport()->height() - PULL_TO_FOLLOW_HOOKED_HEIGHT; 8259f0249b2SNicolas Bonnefon } 8269f0249b2SNicolas Bonnefon // This is the case where the user is on the 'extra' slot at the end 8279f0249b2SNicolas Bonnefon // and is aligned on the last line (but no elastic shown) 828257b1c50SNicolas Bonnefon else if ( lastLineAligned && !followElasticHook_.isHooked() ) { 829257b1c50SNicolas Bonnefon drawingTopOffset_ = - ( whole_height - viewport()->height() ); 830257b1c50SNicolas Bonnefon drawingTopPosition += drawingTopOffset_; 8319f0249b2SNicolas Bonnefon drawingPullToFollowTopPosition = drawingTopPosition + whole_height; 8329f0249b2SNicolas Bonnefon } 833257b1c50SNicolas Bonnefon else { 834257b1c50SNicolas Bonnefon drawingTopOffset_ = - pullToFollowHeight; 835257b1c50SNicolas Bonnefon } 8362493b508SNicolas Bonnefon 8372493b508SNicolas Bonnefon devicePainter.drawPixmap( 0, drawingTopPosition, textAreaCache_.pixmap_ ); 8380b05c6eaSNicolas Bonnefon 8390b05c6eaSNicolas Bonnefon // Draw the "pull to follow" zone if needed 840257b1c50SNicolas Bonnefon if ( pullToFollowHeight ) { 841b297d2f4SNicolas Bonnefon devicePainter.drawPixmap( 0, 8429f0249b2SNicolas Bonnefon drawingPullToFollowTopPosition, 843ac6602a5SNicolas Bonnefon pullToFollowCache_.pixmap_ ); 8440b05c6eaSNicolas Bonnefon } 8450b05c6eaSNicolas Bonnefon 846a9448ba0SNicolas Bonnefon LOG(logDEBUG) << "End of repaint " << 847a9448ba0SNicolas Bonnefon std::chrono::duration_cast<std::chrono::microseconds> 848a9448ba0SNicolas Bonnefon ( std::chrono::system_clock::now() - start ).count(); 849bb02e0acSNicolas Bonnefon } 850bb02e0acSNicolas Bonnefon 851bb02e0acSNicolas Bonnefon // These two functions are virtual and this implementation is clearly 852bb02e0acSNicolas Bonnefon // only valid for a non-filtered display. 853bb02e0acSNicolas Bonnefon // We count on the 'filtered' derived classes to override them. 854bb02e0acSNicolas Bonnefon qint64 AbstractLogView::displayLineNumber( int lineNumber ) const 855bb02e0acSNicolas Bonnefon { 856bb02e0acSNicolas Bonnefon return lineNumber + 1; // show a 1-based index 857bb02e0acSNicolas Bonnefon } 858bb02e0acSNicolas Bonnefon 859bb02e0acSNicolas Bonnefon qint64 AbstractLogView::maxDisplayLineNumber() const 860bb02e0acSNicolas Bonnefon { 861bb02e0acSNicolas Bonnefon return logData->getNbLine(); 862bb02e0acSNicolas Bonnefon } 863bb02e0acSNicolas Bonnefon 864bb02e0acSNicolas Bonnefon void AbstractLogView::setOverview( Overview* overview, 865bb02e0acSNicolas Bonnefon OverviewWidget* overview_widget ) 866bb02e0acSNicolas Bonnefon { 867bb02e0acSNicolas Bonnefon overview_ = overview; 868bb02e0acSNicolas Bonnefon overviewWidget_ = overview_widget; 869bb02e0acSNicolas Bonnefon 870bb02e0acSNicolas Bonnefon if ( overviewWidget_ ) { 871bb02e0acSNicolas Bonnefon connect( overviewWidget_, SIGNAL( lineClicked ( int ) ), 872bb02e0acSNicolas Bonnefon this, SLOT( jumpToLine( int ) ) ); 873bb02e0acSNicolas Bonnefon } 874bb02e0acSNicolas Bonnefon refreshOverview(); 875bb02e0acSNicolas Bonnefon } 876bb02e0acSNicolas Bonnefon 8778e788202SNicolas Bonnefon LineNumber AbstractLogView::getViewPosition() const 8788e788202SNicolas Bonnefon { 8798e788202SNicolas Bonnefon LineNumber line; 8808e788202SNicolas Bonnefon 8818e788202SNicolas Bonnefon qint64 m_line = selection_.selectedLine(); 8828e788202SNicolas Bonnefon if ( m_line >= 0 ) { 8838e788202SNicolas Bonnefon line = m_line; 8848e788202SNicolas Bonnefon } 8858e788202SNicolas Bonnefon else { 8868e788202SNicolas Bonnefon // Middle of the view 8878e788202SNicolas Bonnefon line = firstLine + getNbVisibleLines() / 2; 8888e788202SNicolas Bonnefon } 8898e788202SNicolas Bonnefon 8908e788202SNicolas Bonnefon return line; 8918e788202SNicolas Bonnefon } 8928e788202SNicolas Bonnefon 893bb02e0acSNicolas Bonnefon void AbstractLogView::searchUsingFunction( 894bb02e0acSNicolas Bonnefon qint64 (QuickFind::*search_function)() ) 895bb02e0acSNicolas Bonnefon { 896b297d2f4SNicolas Bonnefon disableFollow(); 897bb02e0acSNicolas Bonnefon 898bb02e0acSNicolas Bonnefon int line = (quickFind_.*search_function)(); 899bb02e0acSNicolas Bonnefon if ( line >= 0 ) { 900bb02e0acSNicolas Bonnefon LOG(logDEBUG) << "search " << line; 901bb02e0acSNicolas Bonnefon displayLine( line ); 902bb02e0acSNicolas Bonnefon emit updateLineNumber( line ); 903bb02e0acSNicolas Bonnefon } 904bb02e0acSNicolas Bonnefon } 905bb02e0acSNicolas Bonnefon 906bb02e0acSNicolas Bonnefon void AbstractLogView::searchForward() 907bb02e0acSNicolas Bonnefon { 908bb02e0acSNicolas Bonnefon searchUsingFunction( &QuickFind::searchForward ); 909bb02e0acSNicolas Bonnefon } 910bb02e0acSNicolas Bonnefon 911bb02e0acSNicolas Bonnefon void AbstractLogView::searchBackward() 912bb02e0acSNicolas Bonnefon { 913bb02e0acSNicolas Bonnefon searchUsingFunction( &QuickFind::searchBackward ); 914bb02e0acSNicolas Bonnefon } 915bb02e0acSNicolas Bonnefon 916bb02e0acSNicolas Bonnefon void AbstractLogView::incrementallySearchForward() 917bb02e0acSNicolas Bonnefon { 918bb02e0acSNicolas Bonnefon searchUsingFunction( &QuickFind::incrementallySearchForward ); 919bb02e0acSNicolas Bonnefon } 920bb02e0acSNicolas Bonnefon 921bb02e0acSNicolas Bonnefon void AbstractLogView::incrementallySearchBackward() 922bb02e0acSNicolas Bonnefon { 923bb02e0acSNicolas Bonnefon searchUsingFunction( &QuickFind::incrementallySearchBackward ); 924bb02e0acSNicolas Bonnefon } 925bb02e0acSNicolas Bonnefon 926bb02e0acSNicolas Bonnefon void AbstractLogView::incrementalSearchAbort() 927bb02e0acSNicolas Bonnefon { 928bb02e0acSNicolas Bonnefon quickFind_.incrementalSearchAbort(); 929bb02e0acSNicolas Bonnefon emit changeQuickFind( 930bb02e0acSNicolas Bonnefon "", 931bb02e0acSNicolas Bonnefon QuickFindMux::Forward ); 932bb02e0acSNicolas Bonnefon } 933bb02e0acSNicolas Bonnefon 934bb02e0acSNicolas Bonnefon void AbstractLogView::incrementalSearchStop() 935bb02e0acSNicolas Bonnefon { 936bb02e0acSNicolas Bonnefon quickFind_.incrementalSearchStop(); 937bb02e0acSNicolas Bonnefon } 938bb02e0acSNicolas Bonnefon 939bb02e0acSNicolas Bonnefon void AbstractLogView::followSet( bool checked ) 940bb02e0acSNicolas Bonnefon { 941bb02e0acSNicolas Bonnefon followMode_ = checked; 942b297d2f4SNicolas Bonnefon followElasticHook_.hook( checked ); 943b297d2f4SNicolas Bonnefon update(); 944bb02e0acSNicolas Bonnefon if ( checked ) 945bb02e0acSNicolas Bonnefon jumpToBottom(); 946bb02e0acSNicolas Bonnefon } 947bb02e0acSNicolas Bonnefon 948bb02e0acSNicolas Bonnefon void AbstractLogView::refreshOverview() 949bb02e0acSNicolas Bonnefon { 950bb02e0acSNicolas Bonnefon assert( overviewWidget_ ); 951bb02e0acSNicolas Bonnefon 952bb02e0acSNicolas Bonnefon // Create space for the Overview if needed 953bb02e0acSNicolas Bonnefon if ( ( getOverview() != NULL ) && getOverview()->isVisible() ) { 954bb02e0acSNicolas Bonnefon setViewportMargins( 0, 0, OVERVIEW_WIDTH, 0 ); 955bb02e0acSNicolas Bonnefon overviewWidget_->show(); 956bb02e0acSNicolas Bonnefon } 957bb02e0acSNicolas Bonnefon else { 958bb02e0acSNicolas Bonnefon setViewportMargins( 0, 0, 0, 0 ); 959bb02e0acSNicolas Bonnefon overviewWidget_->hide(); 960bb02e0acSNicolas Bonnefon } 961bb02e0acSNicolas Bonnefon } 962bb02e0acSNicolas Bonnefon 963bb02e0acSNicolas Bonnefon // Reset the QuickFind when the pattern is changed. 964bb02e0acSNicolas Bonnefon void AbstractLogView::handlePatternUpdated() 965bb02e0acSNicolas Bonnefon { 966bb02e0acSNicolas Bonnefon LOG(logDEBUG) << "AbstractLogView::handlePatternUpdated()"; 967bb02e0acSNicolas Bonnefon 968bb02e0acSNicolas Bonnefon quickFind_.resetLimits(); 969bb02e0acSNicolas Bonnefon update(); 970bb02e0acSNicolas Bonnefon } 971bb02e0acSNicolas Bonnefon 972bb02e0acSNicolas Bonnefon // OR the current with the current search expression 973bb02e0acSNicolas Bonnefon void AbstractLogView::addToSearch() 974bb02e0acSNicolas Bonnefon { 975bb02e0acSNicolas Bonnefon if ( selection_.isPortion() ) { 976bb02e0acSNicolas Bonnefon LOG(logDEBUG) << "AbstractLogView::addToSearch()"; 977bb02e0acSNicolas Bonnefon emit addToSearch( selection_.getSelectedText( logData ) ); 978bb02e0acSNicolas Bonnefon } 979bb02e0acSNicolas Bonnefon else { 980bb02e0acSNicolas Bonnefon LOG(logERROR) << "AbstractLogView::addToSearch called for a wrong type of selection"; 981bb02e0acSNicolas Bonnefon } 982bb02e0acSNicolas Bonnefon } 983bb02e0acSNicolas Bonnefon 984bb02e0acSNicolas Bonnefon // Find next occurence of the selected text (*) 985bb02e0acSNicolas Bonnefon void AbstractLogView::findNextSelected() 986bb02e0acSNicolas Bonnefon { 987bb02e0acSNicolas Bonnefon // Use the selected 'word' and search forward 988bb02e0acSNicolas Bonnefon if ( selection_.isPortion() ) { 989bb02e0acSNicolas Bonnefon emit changeQuickFind( 990bb02e0acSNicolas Bonnefon selection_.getSelectedText( logData ), 991bb02e0acSNicolas Bonnefon QuickFindMux::Forward ); 992bb02e0acSNicolas Bonnefon emit searchNext(); 993bb02e0acSNicolas Bonnefon } 994bb02e0acSNicolas Bonnefon } 995bb02e0acSNicolas Bonnefon 996bb02e0acSNicolas Bonnefon // Find next previous of the selected text (#) 997bb02e0acSNicolas Bonnefon void AbstractLogView::findPreviousSelected() 998bb02e0acSNicolas Bonnefon { 999bb02e0acSNicolas Bonnefon if ( selection_.isPortion() ) { 1000bb02e0acSNicolas Bonnefon emit changeQuickFind( 1001bb02e0acSNicolas Bonnefon selection_.getSelectedText( logData ), 1002bb02e0acSNicolas Bonnefon QuickFindMux::Backward ); 1003bb02e0acSNicolas Bonnefon emit searchNext(); 1004bb02e0acSNicolas Bonnefon } 1005bb02e0acSNicolas Bonnefon } 1006bb02e0acSNicolas Bonnefon 1007bb02e0acSNicolas Bonnefon // Copy the selection to the clipboard 1008bb02e0acSNicolas Bonnefon void AbstractLogView::copy() 1009bb02e0acSNicolas Bonnefon { 1010bb02e0acSNicolas Bonnefon static QClipboard* clipboard = QApplication::clipboard(); 1011bb02e0acSNicolas Bonnefon 1012bb02e0acSNicolas Bonnefon clipboard->setText( selection_.getSelectedText( logData ) ); 1013bb02e0acSNicolas Bonnefon } 1014bb02e0acSNicolas Bonnefon 1015bb02e0acSNicolas Bonnefon // 1016bb02e0acSNicolas Bonnefon // Public functions 1017bb02e0acSNicolas Bonnefon // 1018bb02e0acSNicolas Bonnefon 1019bb02e0acSNicolas Bonnefon void AbstractLogView::updateData() 1020bb02e0acSNicolas Bonnefon { 1021bb02e0acSNicolas Bonnefon LOG(logDEBUG) << "AbstractLogView::updateData"; 1022bb02e0acSNicolas Bonnefon 1023bb02e0acSNicolas Bonnefon // Check the top Line is within range 1024bb02e0acSNicolas Bonnefon if ( firstLine >= logData->getNbLine() ) { 1025bb02e0acSNicolas Bonnefon firstLine = 0; 1026bb02e0acSNicolas Bonnefon firstCol = 0; 1027bb02e0acSNicolas Bonnefon verticalScrollBar()->setValue( 0 ); 1028bb02e0acSNicolas Bonnefon horizontalScrollBar()->setValue( 0 ); 1029bb02e0acSNicolas Bonnefon } 1030bb02e0acSNicolas Bonnefon 1031bb02e0acSNicolas Bonnefon // Crop selection if it become out of range 1032bb02e0acSNicolas Bonnefon selection_.crop( logData->getNbLine() - 1 ); 1033bb02e0acSNicolas Bonnefon 1034bb02e0acSNicolas Bonnefon // Adapt the scroll bars to the new content 10350b05c6eaSNicolas Bonnefon updateScrollBars(); 1036bb02e0acSNicolas Bonnefon 10372493b508SNicolas Bonnefon // Calculate the index of the last line shown 1038c7ef2b85SNicolas Bonnefon LineNumber last_line = std::min( static_cast<int64_t>( logData->getNbLine() ), 10392493b508SNicolas Bonnefon static_cast<int64_t>( firstLine + getNbVisibleLines() ) ); 1040bb02e0acSNicolas Bonnefon 1041bb02e0acSNicolas Bonnefon // Reset the QuickFind in case we have new stuff to search into 1042bb02e0acSNicolas Bonnefon quickFind_.resetLimits(); 1043bb02e0acSNicolas Bonnefon 1044bb02e0acSNicolas Bonnefon if ( followMode_ ) 1045bb02e0acSNicolas Bonnefon jumpToBottom(); 1046bb02e0acSNicolas Bonnefon 1047bb02e0acSNicolas Bonnefon // Update the overview if we have one 1048bb02e0acSNicolas Bonnefon if ( overview_ != NULL ) 10492493b508SNicolas Bonnefon overview_->updateCurrentPosition( firstLine, last_line ); 1050bb02e0acSNicolas Bonnefon 10512493b508SNicolas Bonnefon // Invalidate our cache 10522493b508SNicolas Bonnefon textAreaCache_.invalid_ = true; 1053bb02e0acSNicolas Bonnefon 1054bb02e0acSNicolas Bonnefon // Repaint! 1055bb02e0acSNicolas Bonnefon update(); 1056bb02e0acSNicolas Bonnefon } 1057bb02e0acSNicolas Bonnefon 1058bb02e0acSNicolas Bonnefon void AbstractLogView::updateDisplaySize() 1059bb02e0acSNicolas Bonnefon { 1060bb02e0acSNicolas Bonnefon // Font is assumed to be mono-space (is restricted by options dialog) 1061bb02e0acSNicolas Bonnefon QFontMetrics fm = fontMetrics(); 1062bb02e0acSNicolas Bonnefon charHeight_ = fm.height(); 1063bb02e0acSNicolas Bonnefon // For some reason on Qt 4.8.2 for Win, maxWidth() is wrong but the 1064bb02e0acSNicolas Bonnefon // following give the right result, not sure why: 1065bb02e0acSNicolas Bonnefon charWidth_ = fm.width( QChar('a') ); 1066bb02e0acSNicolas Bonnefon 1067bb02e0acSNicolas Bonnefon // Update the scroll bars 10680b05c6eaSNicolas Bonnefon updateScrollBars(); 1069bb02e0acSNicolas Bonnefon verticalScrollBar()->setPageStep( getNbVisibleLines() ); 1070bb02e0acSNicolas Bonnefon 1071b297d2f4SNicolas Bonnefon if ( followMode_ ) 1072b297d2f4SNicolas Bonnefon jumpToBottom(); 1073b297d2f4SNicolas Bonnefon 1074bb02e0acSNicolas Bonnefon LOG(logDEBUG) << "viewport.width()=" << viewport()->width(); 1075bb02e0acSNicolas Bonnefon LOG(logDEBUG) << "viewport.height()=" << viewport()->height(); 1076bb02e0acSNicolas Bonnefon LOG(logDEBUG) << "width()=" << width(); 1077bb02e0acSNicolas Bonnefon LOG(logDEBUG) << "height()=" << height(); 1078bb02e0acSNicolas Bonnefon 1079bb02e0acSNicolas Bonnefon if ( overviewWidget_ ) 1080bb02e0acSNicolas Bonnefon overviewWidget_->setGeometry( viewport()->width() + 2, 1, 1081bb02e0acSNicolas Bonnefon OVERVIEW_WIDTH - 1, viewport()->height() ); 1082a9448ba0SNicolas Bonnefon 1083a9448ba0SNicolas Bonnefon // Our text area cache is now invalid 1084a9448ba0SNicolas Bonnefon textAreaCache_.invalid_ = true; 1085a9448ba0SNicolas Bonnefon textAreaCache_.pixmap_ = QPixmap { 1086b297d2f4SNicolas Bonnefon viewport()->width() * viewport()->devicePixelRatio(), 10872493b508SNicolas Bonnefon static_cast<int32_t>( getNbVisibleLines() ) * charHeight_ * viewport()->devicePixelRatio() }; 1088a9448ba0SNicolas Bonnefon textAreaCache_.pixmap_.setDevicePixelRatio( viewport()->devicePixelRatio() ); 1089bb02e0acSNicolas Bonnefon } 1090bb02e0acSNicolas Bonnefon 1091bb02e0acSNicolas Bonnefon int AbstractLogView::getTopLine() const 1092bb02e0acSNicolas Bonnefon { 1093bb02e0acSNicolas Bonnefon return firstLine; 1094bb02e0acSNicolas Bonnefon } 1095bb02e0acSNicolas Bonnefon 1096bb02e0acSNicolas Bonnefon QString AbstractLogView::getSelection() const 1097bb02e0acSNicolas Bonnefon { 1098bb02e0acSNicolas Bonnefon return selection_.getSelectedText( logData ); 1099bb02e0acSNicolas Bonnefon } 1100bb02e0acSNicolas Bonnefon 1101bb02e0acSNicolas Bonnefon void AbstractLogView::selectAll() 1102bb02e0acSNicolas Bonnefon { 1103bb02e0acSNicolas Bonnefon selection_.selectRange( 0, logData->getNbLine() - 1 ); 11041cd16a46SAnton Filimonov textAreaCache_.invalid_ = true; 1105bb02e0acSNicolas Bonnefon update(); 1106bb02e0acSNicolas Bonnefon } 1107bb02e0acSNicolas Bonnefon 1108bb02e0acSNicolas Bonnefon void AbstractLogView::selectAndDisplayLine( int line ) 1109bb02e0acSNicolas Bonnefon { 1110b297d2f4SNicolas Bonnefon disableFollow(); 1111bb02e0acSNicolas Bonnefon selection_.selectLine( line ); 1112bb02e0acSNicolas Bonnefon displayLine( line ); 1113bb02e0acSNicolas Bonnefon emit updateLineNumber( line ); 11148e788202SNicolas Bonnefon emit newSelection( line ); 1115bb02e0acSNicolas Bonnefon } 1116bb02e0acSNicolas Bonnefon 1117bb02e0acSNicolas Bonnefon // The difference between this function and displayLine() is quite 1118bb02e0acSNicolas Bonnefon // subtle: this one always jump, even if the line passed is visible. 1119bb02e0acSNicolas Bonnefon void AbstractLogView::jumpToLine( int line ) 1120bb02e0acSNicolas Bonnefon { 1121bb02e0acSNicolas Bonnefon // Put the selected line in the middle if possible 1122bb02e0acSNicolas Bonnefon int newTopLine = line - ( getNbVisibleLines() / 2 ); 1123bb02e0acSNicolas Bonnefon if ( newTopLine < 0 ) 1124bb02e0acSNicolas Bonnefon newTopLine = 0; 1125bb02e0acSNicolas Bonnefon 1126bb02e0acSNicolas Bonnefon // This will also trigger a scrollContents event 1127bb02e0acSNicolas Bonnefon verticalScrollBar()->setValue( newTopLine ); 1128bb02e0acSNicolas Bonnefon } 1129bb02e0acSNicolas Bonnefon 1130bb02e0acSNicolas Bonnefon void AbstractLogView::setLineNumbersVisible( bool lineNumbersVisible ) 1131bb02e0acSNicolas Bonnefon { 1132bb02e0acSNicolas Bonnefon lineNumbersVisible_ = lineNumbersVisible; 1133bb02e0acSNicolas Bonnefon } 1134bb02e0acSNicolas Bonnefon 11355fa25391SNicolas Bonnefon void AbstractLogView::forceRefresh() 11365fa25391SNicolas Bonnefon { 11375fa25391SNicolas Bonnefon // Invalidate our cache 11385fa25391SNicolas Bonnefon textAreaCache_.invalid_ = true; 11395fa25391SNicolas Bonnefon } 11405fa25391SNicolas Bonnefon 1141bb02e0acSNicolas Bonnefon // 1142bb02e0acSNicolas Bonnefon // Private functions 1143bb02e0acSNicolas Bonnefon // 1144bb02e0acSNicolas Bonnefon 1145bb02e0acSNicolas Bonnefon // Returns the number of lines visible in the viewport 11462493b508SNicolas Bonnefon LineNumber AbstractLogView::getNbVisibleLines() const 1147bb02e0acSNicolas Bonnefon { 11482493b508SNicolas Bonnefon return static_cast<LineNumber>( viewport()->height() / charHeight_ + 1 ); 1149bb02e0acSNicolas Bonnefon } 1150bb02e0acSNicolas Bonnefon 1151bb02e0acSNicolas Bonnefon // Returns the number of columns visible in the viewport 1152bb02e0acSNicolas Bonnefon int AbstractLogView::getNbVisibleCols() const 1153bb02e0acSNicolas Bonnefon { 1154bb02e0acSNicolas Bonnefon return ( viewport()->width() - leftMarginPx_ ) / charWidth_ + 1; 1155bb02e0acSNicolas Bonnefon } 1156bb02e0acSNicolas Bonnefon 1157bb02e0acSNicolas Bonnefon // Converts the mouse x, y coordinates to the line number in the file 1158bb02e0acSNicolas Bonnefon int AbstractLogView::convertCoordToLine(int yPos) const 1159bb02e0acSNicolas Bonnefon { 1160257b1c50SNicolas Bonnefon int line = firstLine + ( yPos - drawingTopOffset_ ) / charHeight_; 1161bb02e0acSNicolas Bonnefon 1162bb02e0acSNicolas Bonnefon return line; 1163bb02e0acSNicolas Bonnefon } 1164bb02e0acSNicolas Bonnefon 1165bb02e0acSNicolas Bonnefon // Converts the mouse x, y coordinates to the char coordinates (in the file) 1166bb02e0acSNicolas Bonnefon // This function ensure the pos exists in the file. 1167bb02e0acSNicolas Bonnefon QPoint AbstractLogView::convertCoordToFilePos( const QPoint& pos ) const 1168bb02e0acSNicolas Bonnefon { 1169257b1c50SNicolas Bonnefon int line = convertCoordToLine( pos.y() ); 1170bb02e0acSNicolas Bonnefon if ( line >= logData->getNbLine() ) 1171bb02e0acSNicolas Bonnefon line = logData->getNbLine() - 1; 1172bb02e0acSNicolas Bonnefon if ( line < 0 ) 1173bb02e0acSNicolas Bonnefon line = 0; 1174bb02e0acSNicolas Bonnefon 1175bb02e0acSNicolas Bonnefon // Determine column in screen space and convert it to file space 1176bb02e0acSNicolas Bonnefon int column = firstCol + ( pos.x() - leftMarginPx_ ) / charWidth_; 1177bb02e0acSNicolas Bonnefon 1178bb02e0acSNicolas Bonnefon QString this_line = logData->getExpandedLineString( line ); 1179bb02e0acSNicolas Bonnefon const int length = this_line.length(); 1180bb02e0acSNicolas Bonnefon 1181bb02e0acSNicolas Bonnefon if ( column >= length ) 1182bb02e0acSNicolas Bonnefon column = length - 1; 1183bb02e0acSNicolas Bonnefon if ( column < 0 ) 1184bb02e0acSNicolas Bonnefon column = 0; 1185bb02e0acSNicolas Bonnefon 1186bb02e0acSNicolas Bonnefon LOG(logDEBUG4) << "AbstractLogView::convertCoordToFilePos col=" 1187bb02e0acSNicolas Bonnefon << column << " line=" << line; 1188bb02e0acSNicolas Bonnefon QPoint point( column, line ); 1189bb02e0acSNicolas Bonnefon 1190bb02e0acSNicolas Bonnefon return point; 1191bb02e0acSNicolas Bonnefon } 1192bb02e0acSNicolas Bonnefon 1193bb02e0acSNicolas Bonnefon // Makes the widget adjust itself to display the passed line. 1194bb02e0acSNicolas Bonnefon // Doing so, it will throw itself a scrollContents event. 11952493b508SNicolas Bonnefon void AbstractLogView::displayLine( LineNumber line ) 1196bb02e0acSNicolas Bonnefon { 1197bb02e0acSNicolas Bonnefon // If the line is already the screen 1198bb02e0acSNicolas Bonnefon if ( ( line >= firstLine ) && 1199bb02e0acSNicolas Bonnefon ( line < ( firstLine + getNbVisibleLines() ) ) ) { 120000a57d05SNicolas Bonnefon // Invalidate our cache 120100a57d05SNicolas Bonnefon textAreaCache_.invalid_ = true; 120200a57d05SNicolas Bonnefon 1203bb02e0acSNicolas Bonnefon // ... don't scroll and just repaint 1204bb02e0acSNicolas Bonnefon update(); 1205bb02e0acSNicolas Bonnefon } else { 1206bb02e0acSNicolas Bonnefon jumpToLine( line ); 1207bb02e0acSNicolas Bonnefon } 1208bb02e0acSNicolas Bonnefon } 1209bb02e0acSNicolas Bonnefon 1210bb02e0acSNicolas Bonnefon // Move the selection up and down by the passed number of lines 1211bb02e0acSNicolas Bonnefon void AbstractLogView::moveSelection( int delta ) 1212bb02e0acSNicolas Bonnefon { 1213bb02e0acSNicolas Bonnefon LOG(logDEBUG) << "AbstractLogView::moveSelection delta=" << delta; 1214bb02e0acSNicolas Bonnefon 1215bb02e0acSNicolas Bonnefon QList<int> selection = selection_.getLines(); 1216bb02e0acSNicolas Bonnefon int new_line; 1217bb02e0acSNicolas Bonnefon 1218bb02e0acSNicolas Bonnefon // If nothing is selected, do as if line -1 was. 1219bb02e0acSNicolas Bonnefon if ( selection.isEmpty() ) 1220bb02e0acSNicolas Bonnefon selection.append( -1 ); 1221bb02e0acSNicolas Bonnefon 1222bb02e0acSNicolas Bonnefon if ( delta < 0 ) 1223bb02e0acSNicolas Bonnefon new_line = selection.first() + delta; 1224bb02e0acSNicolas Bonnefon else 1225bb02e0acSNicolas Bonnefon new_line = selection.last() + delta; 1226bb02e0acSNicolas Bonnefon 1227bb02e0acSNicolas Bonnefon if ( new_line < 0 ) 1228bb02e0acSNicolas Bonnefon new_line = 0; 1229bb02e0acSNicolas Bonnefon else if ( new_line >= logData->getNbLine() ) 1230bb02e0acSNicolas Bonnefon new_line = logData->getNbLine() - 1; 1231bb02e0acSNicolas Bonnefon 1232bb02e0acSNicolas Bonnefon // Select and display the new line 1233bb02e0acSNicolas Bonnefon selection_.selectLine( new_line ); 1234bb02e0acSNicolas Bonnefon displayLine( new_line ); 1235bb02e0acSNicolas Bonnefon emit updateLineNumber( new_line ); 12365c2998ccSNicolas Bonnefon emit newSelection( new_line ); 1237bb02e0acSNicolas Bonnefon } 1238bb02e0acSNicolas Bonnefon 1239bb02e0acSNicolas Bonnefon // Make the start of the lines visible 1240bb02e0acSNicolas Bonnefon void AbstractLogView::jumpToStartOfLine() 1241bb02e0acSNicolas Bonnefon { 1242bb02e0acSNicolas Bonnefon horizontalScrollBar()->setValue( 0 ); 1243bb02e0acSNicolas Bonnefon } 1244bb02e0acSNicolas Bonnefon 1245bb02e0acSNicolas Bonnefon // Make the end of the lines in the selection visible 1246bb02e0acSNicolas Bonnefon void AbstractLogView::jumpToEndOfLine() 1247bb02e0acSNicolas Bonnefon { 1248bb02e0acSNicolas Bonnefon QList<int> selection = selection_.getLines(); 1249bb02e0acSNicolas Bonnefon 1250bb02e0acSNicolas Bonnefon // Search the longest line in the selection 1251bb02e0acSNicolas Bonnefon int max_length = 0; 1252bb02e0acSNicolas Bonnefon foreach ( int line, selection ) { 1253bb02e0acSNicolas Bonnefon int length = logData->getLineLength( line ); 1254bb02e0acSNicolas Bonnefon if ( length > max_length ) 1255bb02e0acSNicolas Bonnefon max_length = length; 1256bb02e0acSNicolas Bonnefon } 1257bb02e0acSNicolas Bonnefon 1258bb02e0acSNicolas Bonnefon horizontalScrollBar()->setValue( max_length - getNbVisibleCols() ); 1259bb02e0acSNicolas Bonnefon } 1260bb02e0acSNicolas Bonnefon 1261bb02e0acSNicolas Bonnefon // Make the end of the lines on the screen visible 1262bb02e0acSNicolas Bonnefon void AbstractLogView::jumpToRightOfScreen() 1263bb02e0acSNicolas Bonnefon { 1264bb02e0acSNicolas Bonnefon QList<int> selection = selection_.getLines(); 1265bb02e0acSNicolas Bonnefon 1266bb02e0acSNicolas Bonnefon // Search the longest line on screen 1267bb02e0acSNicolas Bonnefon int max_length = 0; 12682493b508SNicolas Bonnefon for ( auto i = firstLine; i <= ( firstLine + getNbVisibleLines() ); i++ ) { 1269bb02e0acSNicolas Bonnefon int length = logData->getLineLength( i ); 1270bb02e0acSNicolas Bonnefon if ( length > max_length ) 1271bb02e0acSNicolas Bonnefon max_length = length; 1272bb02e0acSNicolas Bonnefon } 1273bb02e0acSNicolas Bonnefon 1274bb02e0acSNicolas Bonnefon horizontalScrollBar()->setValue( max_length - getNbVisibleCols() ); 1275bb02e0acSNicolas Bonnefon } 1276bb02e0acSNicolas Bonnefon 1277bb02e0acSNicolas Bonnefon // Jump to the first line 1278bb02e0acSNicolas Bonnefon void AbstractLogView::jumpToTop() 1279bb02e0acSNicolas Bonnefon { 1280bb02e0acSNicolas Bonnefon // This will also trigger a scrollContents event 1281bb02e0acSNicolas Bonnefon verticalScrollBar()->setValue( 0 ); 1282bb02e0acSNicolas Bonnefon update(); // in case the screen hasn't moved 1283bb02e0acSNicolas Bonnefon } 1284bb02e0acSNicolas Bonnefon 1285bb02e0acSNicolas Bonnefon // Jump to the last line 1286bb02e0acSNicolas Bonnefon void AbstractLogView::jumpToBottom() 1287bb02e0acSNicolas Bonnefon { 1288bb02e0acSNicolas Bonnefon const int new_top_line = 1289bb02e0acSNicolas Bonnefon qMax( logData->getNbLine() - getNbVisibleLines() + 1, 0LL ); 1290bb02e0acSNicolas Bonnefon 1291bb02e0acSNicolas Bonnefon // This will also trigger a scrollContents event 1292bb02e0acSNicolas Bonnefon verticalScrollBar()->setValue( new_top_line ); 1293bb02e0acSNicolas Bonnefon update(); // in case the screen hasn't moved 1294bb02e0acSNicolas Bonnefon } 1295bb02e0acSNicolas Bonnefon 1296bb02e0acSNicolas Bonnefon // Returns whether the character passed is a 'word' character 1297bb02e0acSNicolas Bonnefon inline bool AbstractLogView::isCharWord( char c ) 1298bb02e0acSNicolas Bonnefon { 1299bb02e0acSNicolas Bonnefon if ( ( ( c >= 'A' ) && ( c <= 'Z' ) ) || 1300bb02e0acSNicolas Bonnefon ( ( c >= 'a' ) && ( c <= 'z' ) ) || 1301bb02e0acSNicolas Bonnefon ( ( c >= '0' ) && ( c <= '9' ) ) || 1302bb02e0acSNicolas Bonnefon ( ( c == '_' ) ) ) 1303bb02e0acSNicolas Bonnefon return true; 1304bb02e0acSNicolas Bonnefon else 1305bb02e0acSNicolas Bonnefon return false; 1306bb02e0acSNicolas Bonnefon } 1307bb02e0acSNicolas Bonnefon 1308bb02e0acSNicolas Bonnefon // Select the word under the given position 1309bb02e0acSNicolas Bonnefon void AbstractLogView::selectWordAtPosition( const QPoint& pos ) 1310bb02e0acSNicolas Bonnefon { 1311bb02e0acSNicolas Bonnefon const int x = pos.x(); 1312bb02e0acSNicolas Bonnefon const QString line = logData->getExpandedLineString( pos.y() ); 1313bb02e0acSNicolas Bonnefon 1314bb02e0acSNicolas Bonnefon if ( isCharWord( line[x].toLatin1() ) ) { 1315bb02e0acSNicolas Bonnefon // Search backward for the first character in the word 1316bb02e0acSNicolas Bonnefon int currentPos = x; 1317bb02e0acSNicolas Bonnefon for ( ; currentPos > 0; currentPos-- ) 1318bb02e0acSNicolas Bonnefon if ( ! isCharWord( line[currentPos].toLatin1() ) ) 1319bb02e0acSNicolas Bonnefon break; 1320bb02e0acSNicolas Bonnefon // Exclude the first char of the line if needed 1321bb02e0acSNicolas Bonnefon if ( ! isCharWord( line[currentPos].toLatin1() ) ) 1322bb02e0acSNicolas Bonnefon currentPos++; 1323bb02e0acSNicolas Bonnefon int start = currentPos; 1324bb02e0acSNicolas Bonnefon 1325bb02e0acSNicolas Bonnefon // Now search for the end 1326bb02e0acSNicolas Bonnefon currentPos = x; 1327bb02e0acSNicolas Bonnefon for ( ; currentPos < line.length() - 1; currentPos++ ) 1328bb02e0acSNicolas Bonnefon if ( ! isCharWord( line[currentPos].toLatin1() ) ) 1329bb02e0acSNicolas Bonnefon break; 1330bb02e0acSNicolas Bonnefon // Exclude the last char of the line if needed 1331bb02e0acSNicolas Bonnefon if ( ! isCharWord( line[currentPos].toLatin1() ) ) 1332bb02e0acSNicolas Bonnefon currentPos--; 1333bb02e0acSNicolas Bonnefon int end = currentPos; 1334bb02e0acSNicolas Bonnefon 1335bb02e0acSNicolas Bonnefon selection_.selectPortion( pos.y(), start, end ); 1336bb02e0acSNicolas Bonnefon updateGlobalSelection(); 1337bb02e0acSNicolas Bonnefon update(); 1338bb02e0acSNicolas Bonnefon } 1339bb02e0acSNicolas Bonnefon } 1340bb02e0acSNicolas Bonnefon 1341bb02e0acSNicolas Bonnefon // Update the system global (middle click) selection (X11 only) 1342bb02e0acSNicolas Bonnefon void AbstractLogView::updateGlobalSelection() 1343bb02e0acSNicolas Bonnefon { 1344bb02e0acSNicolas Bonnefon static QClipboard* const clipboard = QApplication::clipboard(); 1345bb02e0acSNicolas Bonnefon 1346bb02e0acSNicolas Bonnefon // Updating it only for "non-trivial" (range or portion) selections 1347bb02e0acSNicolas Bonnefon if ( ! selection_.isSingleLine() ) 1348bb02e0acSNicolas Bonnefon clipboard->setText( selection_.getSelectedText( logData ), 1349bb02e0acSNicolas Bonnefon QClipboard::Selection ); 1350bb02e0acSNicolas Bonnefon } 1351bb02e0acSNicolas Bonnefon 1352bb02e0acSNicolas Bonnefon // Create the pop-up menu 1353bb02e0acSNicolas Bonnefon void AbstractLogView::createMenu() 1354bb02e0acSNicolas Bonnefon { 1355bb02e0acSNicolas Bonnefon copyAction_ = new QAction( tr("&Copy"), this ); 1356bb02e0acSNicolas Bonnefon // No text as this action title depends on the type of selection 1357bb02e0acSNicolas Bonnefon connect( copyAction_, SIGNAL(triggered()), this, SLOT(copy()) ); 1358bb02e0acSNicolas Bonnefon 1359bb02e0acSNicolas Bonnefon // For '#' and '*', shortcuts doesn't seem to work but 1360bb02e0acSNicolas Bonnefon // at least it displays them in the menu, we manually handle those keys 1361bb02e0acSNicolas Bonnefon // as keys event anyway (in keyPressEvent). 1362bb02e0acSNicolas Bonnefon findNextAction_ = new QAction(tr("Find &next"), this); 1363bb02e0acSNicolas Bonnefon findNextAction_->setShortcut( Qt::Key_Asterisk ); 1364bb02e0acSNicolas Bonnefon findNextAction_->setStatusTip( tr("Find the next occurence") ); 1365bb02e0acSNicolas Bonnefon connect( findNextAction_, SIGNAL(triggered()), 1366bb02e0acSNicolas Bonnefon this, SLOT( findNextSelected() ) ); 1367bb02e0acSNicolas Bonnefon 1368bb02e0acSNicolas Bonnefon findPreviousAction_ = new QAction( tr("Find &previous"), this ); 1369bb02e0acSNicolas Bonnefon findPreviousAction_->setShortcut( tr("#") ); 1370bb02e0acSNicolas Bonnefon findPreviousAction_->setStatusTip( tr("Find the previous occurence") ); 1371bb02e0acSNicolas Bonnefon connect( findPreviousAction_, SIGNAL(triggered()), 1372bb02e0acSNicolas Bonnefon this, SLOT( findPreviousSelected() ) ); 1373bb02e0acSNicolas Bonnefon 1374bb02e0acSNicolas Bonnefon addToSearchAction_ = new QAction( tr("&Add to search"), this ); 1375bb02e0acSNicolas Bonnefon addToSearchAction_->setStatusTip( 1376bb02e0acSNicolas Bonnefon tr("Add the selection to the current search") ); 1377bb02e0acSNicolas Bonnefon connect( addToSearchAction_, SIGNAL( triggered() ), 1378bb02e0acSNicolas Bonnefon this, SLOT( addToSearch() ) ); 1379bb02e0acSNicolas Bonnefon 1380bb02e0acSNicolas Bonnefon popupMenu_ = new QMenu( this ); 1381bb02e0acSNicolas Bonnefon popupMenu_->addAction( copyAction_ ); 1382bb02e0acSNicolas Bonnefon popupMenu_->addSeparator(); 1383bb02e0acSNicolas Bonnefon popupMenu_->addAction( findNextAction_ ); 1384bb02e0acSNicolas Bonnefon popupMenu_->addAction( findPreviousAction_ ); 1385bb02e0acSNicolas Bonnefon popupMenu_->addAction( addToSearchAction_ ); 1386bb02e0acSNicolas Bonnefon } 1387bb02e0acSNicolas Bonnefon 1388bb02e0acSNicolas Bonnefon void AbstractLogView::considerMouseHovering( int x_pos, int y_pos ) 1389bb02e0acSNicolas Bonnefon { 1390bb02e0acSNicolas Bonnefon int line = convertCoordToLine( y_pos ); 1391bb02e0acSNicolas Bonnefon if ( ( x_pos < leftMarginPx_ ) 1392bb02e0acSNicolas Bonnefon && ( line >= 0 ) 1393bb02e0acSNicolas Bonnefon && ( line < logData->getNbLine() ) ) { 1394bb02e0acSNicolas Bonnefon // Mouse moved in the margin, send event up 1395bb02e0acSNicolas Bonnefon // (possibly to highlight the overview) 1396bb02e0acSNicolas Bonnefon if ( line != lastHoveredLine_ ) { 1397bb02e0acSNicolas Bonnefon LOG(logDEBUG) << "Mouse moved in margin line: " << line; 1398bb02e0acSNicolas Bonnefon emit mouseHoveredOverLine( line ); 1399bb02e0acSNicolas Bonnefon lastHoveredLine_ = line; 1400bb02e0acSNicolas Bonnefon } 1401bb02e0acSNicolas Bonnefon } 1402bb02e0acSNicolas Bonnefon else { 1403bb02e0acSNicolas Bonnefon if ( lastHoveredLine_ != -1 ) { 1404bb02e0acSNicolas Bonnefon emit mouseLeftHoveringZone(); 1405bb02e0acSNicolas Bonnefon lastHoveredLine_ = -1; 1406bb02e0acSNicolas Bonnefon } 1407bb02e0acSNicolas Bonnefon } 1408bb02e0acSNicolas Bonnefon } 14090b05c6eaSNicolas Bonnefon 14100b05c6eaSNicolas Bonnefon void AbstractLogView::updateScrollBars() 14110b05c6eaSNicolas Bonnefon { 14120b05c6eaSNicolas Bonnefon verticalScrollBar()->setRange( 0, std::max( 0LL, 14139f0249b2SNicolas Bonnefon logData->getNbLine() - getNbVisibleLines() + 1 ) ); 14140b05c6eaSNicolas Bonnefon 14152493b508SNicolas Bonnefon const int hScrollMaxValue = std::max( 0, 14162493b508SNicolas Bonnefon logData->getMaxLength() - getNbVisibleCols() + 1 ); 14170b05c6eaSNicolas Bonnefon horizontalScrollBar()->setRange( 0, hScrollMaxValue ); 14180b05c6eaSNicolas Bonnefon } 14190b05c6eaSNicolas Bonnefon 1420*481c483cSSergei Dyshel void AbstractLogView::drawTextArea( QPaintDevice* paint_device, int32_t ) 1421a9448ba0SNicolas Bonnefon { 1422a9448ba0SNicolas Bonnefon // LOG( logDEBUG ) << "devicePixelRatio: " << viewport()->devicePixelRatio(); 1423a9448ba0SNicolas Bonnefon // LOG( logDEBUG ) << "viewport size: " << viewport()->size().width(); 1424a9448ba0SNicolas Bonnefon // LOG( logDEBUG ) << "pixmap size: " << textPixmap.width(); 1425a9448ba0SNicolas Bonnefon // Repaint the viewport 1426a9448ba0SNicolas Bonnefon QPainter painter( paint_device ); 1427a9448ba0SNicolas Bonnefon // LOG( logDEBUG ) << "font: " << viewport()->font().family().toStdString(); 1428a9448ba0SNicolas Bonnefon // LOG( logDEBUG ) << "font painter: " << painter.font().family().toStdString(); 1429a9448ba0SNicolas Bonnefon 1430a9448ba0SNicolas Bonnefon painter.setFont( this->font() ); 1431a9448ba0SNicolas Bonnefon 1432a9448ba0SNicolas Bonnefon const int fontHeight = charHeight_; 1433a9448ba0SNicolas Bonnefon const int fontAscent = painter.fontMetrics().ascent(); 1434a9448ba0SNicolas Bonnefon const int nbCols = getNbVisibleCols(); 14352493b508SNicolas Bonnefon const int paintDeviceHeight = paint_device->height() / viewport()->devicePixelRatio(); 14362493b508SNicolas Bonnefon const int paintDeviceWidth = paint_device->width() / viewport()->devicePixelRatio(); 1437a9448ba0SNicolas Bonnefon const QPalette& palette = viewport()->palette(); 1438a9448ba0SNicolas Bonnefon std::shared_ptr<const FilterSet> filterSet = 1439a9448ba0SNicolas Bonnefon Persistent<FilterSet>( "filterSet" ); 1440a9448ba0SNicolas Bonnefon QColor foreColor, backColor; 1441a9448ba0SNicolas Bonnefon 1442a9448ba0SNicolas Bonnefon static const QBrush normalBulletBrush = QBrush( Qt::white ); 1443a9448ba0SNicolas Bonnefon static const QBrush matchBulletBrush = QBrush( Qt::red ); 1444a9448ba0SNicolas Bonnefon static const QBrush markBrush = QBrush( "dodgerblue" ); 1445a9448ba0SNicolas Bonnefon 1446a9448ba0SNicolas Bonnefon static const int SEPARATOR_WIDTH = 1; 1447f01b5ae6SNicolas Bonnefon static const qreal BULLET_AREA_WIDTH = 11; 1448a9448ba0SNicolas Bonnefon static const int CONTENT_MARGIN_WIDTH = 1; 1449a9448ba0SNicolas Bonnefon static const int LINE_NUMBER_PADDING = 3; 1450a9448ba0SNicolas Bonnefon 1451a9448ba0SNicolas Bonnefon // First check the lines to be drawn are within range (might not be the case if 1452a9448ba0SNicolas Bonnefon // the file has just changed) 14532493b508SNicolas Bonnefon const int64_t lines_in_file = logData->getNbLine(); 14542493b508SNicolas Bonnefon 14552493b508SNicolas Bonnefon if ( firstLine > lines_in_file ) 14568ea5bc4cSNicolas Bonnefon firstLine = lines_in_file ? lines_in_file - 1 : 0; 14572493b508SNicolas Bonnefon 14582493b508SNicolas Bonnefon const int64_t nbLines = std::min( 14592493b508SNicolas Bonnefon static_cast<int64_t>( getNbVisibleLines() ), lines_in_file - firstLine ); 14602493b508SNicolas Bonnefon 14612493b508SNicolas Bonnefon const int bottomOfTextPx = nbLines * fontHeight; 14622493b508SNicolas Bonnefon 14632493b508SNicolas Bonnefon LOG(logDEBUG) << "drawing lines from " << firstLine << " (" << nbLines << " lines)"; 14642493b508SNicolas Bonnefon LOG(logDEBUG) << "bottomOfTextPx: " << bottomOfTextPx; 14652493b508SNicolas Bonnefon LOG(logDEBUG) << "Height: " << paintDeviceHeight; 1466a9448ba0SNicolas Bonnefon 1467a9448ba0SNicolas Bonnefon // Lines to write 14682493b508SNicolas Bonnefon const QStringList lines = logData->getExpandedLines( firstLine, nbLines ); 1469a9448ba0SNicolas Bonnefon 1470a9448ba0SNicolas Bonnefon // First draw the bullet left margin 1471a9448ba0SNicolas Bonnefon painter.setPen(palette.color(QPalette::Text)); 1472a9448ba0SNicolas Bonnefon painter.fillRect( 0, 0, 14732493b508SNicolas Bonnefon BULLET_AREA_WIDTH, paintDeviceHeight, 1474a9448ba0SNicolas Bonnefon Qt::darkGray ); 1475a9448ba0SNicolas Bonnefon 1476a9448ba0SNicolas Bonnefon // Column at which the content should start (pixels) 1477f01b5ae6SNicolas Bonnefon qreal contentStartPosX = BULLET_AREA_WIDTH + SEPARATOR_WIDTH; 1478a9448ba0SNicolas Bonnefon 1479a9448ba0SNicolas Bonnefon // This is also the bullet zone width, used for marking clicks 1480a9448ba0SNicolas Bonnefon bulletZoneWidthPx_ = contentStartPosX; 1481a9448ba0SNicolas Bonnefon 14822493b508SNicolas Bonnefon // Update the length of line numbers 14832493b508SNicolas Bonnefon const int nbDigitsInLineNumber = countDigits( maxDisplayLineNumber() ); 14842493b508SNicolas Bonnefon 1485a9448ba0SNicolas Bonnefon // Draw the line numbers area 1486a9448ba0SNicolas Bonnefon int lineNumberAreaStartX = 0; 1487a9448ba0SNicolas Bonnefon if ( lineNumbersVisible_ ) { 14882493b508SNicolas Bonnefon int lineNumberWidth = charWidth_ * nbDigitsInLineNumber; 1489a9448ba0SNicolas Bonnefon int lineNumberAreaWidth = 1490a9448ba0SNicolas Bonnefon 2 * LINE_NUMBER_PADDING + lineNumberWidth; 1491a9448ba0SNicolas Bonnefon lineNumberAreaStartX = contentStartPosX; 1492a9448ba0SNicolas Bonnefon 1493a9448ba0SNicolas Bonnefon painter.setPen(palette.color(QPalette::Text)); 1494a9448ba0SNicolas Bonnefon /* Not sure if it looks good... 1495a9448ba0SNicolas Bonnefon painter.drawLine( contentStartPosX + lineNumberAreaWidth, 1496a9448ba0SNicolas Bonnefon 0, 1497a9448ba0SNicolas Bonnefon contentStartPosX + lineNumberAreaWidth, 1498a9448ba0SNicolas Bonnefon viewport()->height() ); 1499a9448ba0SNicolas Bonnefon */ 1500f01b5ae6SNicolas Bonnefon painter.fillRect( contentStartPosX - SEPARATOR_WIDTH, 0, 1501f01b5ae6SNicolas Bonnefon lineNumberAreaWidth + SEPARATOR_WIDTH, paintDeviceHeight, 1502a9448ba0SNicolas Bonnefon Qt::lightGray ); 1503a9448ba0SNicolas Bonnefon 1504a9448ba0SNicolas Bonnefon // Update for drawing the actual text 1505a9448ba0SNicolas Bonnefon contentStartPosX += lineNumberAreaWidth; 1506a9448ba0SNicolas Bonnefon } 1507a9448ba0SNicolas Bonnefon else { 1508f01b5ae6SNicolas Bonnefon painter.fillRect( contentStartPosX - SEPARATOR_WIDTH, 0, 1509f01b5ae6SNicolas Bonnefon SEPARATOR_WIDTH + 1, paintDeviceHeight, 1510f01b5ae6SNicolas Bonnefon Qt::lightGray ); 1511f01b5ae6SNicolas Bonnefon // contentStartPosX += SEPARATOR_WIDTH; 1512a9448ba0SNicolas Bonnefon } 1513a9448ba0SNicolas Bonnefon 1514f01b5ae6SNicolas Bonnefon painter.drawLine( BULLET_AREA_WIDTH, 0, 1515f01b5ae6SNicolas Bonnefon BULLET_AREA_WIDTH, paintDeviceHeight - 1 ); 1516f01b5ae6SNicolas Bonnefon 1517a9448ba0SNicolas Bonnefon // This is the total width of the 'margin' (including line number if any) 1518a9448ba0SNicolas Bonnefon // used for mouse calculation etc... 1519f01b5ae6SNicolas Bonnefon leftMarginPx_ = contentStartPosX + SEPARATOR_WIDTH; 1520a9448ba0SNicolas Bonnefon 1521a9448ba0SNicolas Bonnefon // Then draw each line 15222493b508SNicolas Bonnefon for (int i = 0; i < nbLines; i++) { 15232493b508SNicolas Bonnefon const LineNumber line_index = i + firstLine; 15242493b508SNicolas Bonnefon 1525a9448ba0SNicolas Bonnefon // Position in pixel of the base line of the line to print 15262493b508SNicolas Bonnefon const int yPos = i * fontHeight; 1527a9448ba0SNicolas Bonnefon const int xPos = contentStartPosX + CONTENT_MARGIN_WIDTH; 1528a9448ba0SNicolas Bonnefon 1529a9448ba0SNicolas Bonnefon // string to print, cut to fit the length and position of the view 15302493b508SNicolas Bonnefon const QString line = lines[i]; 1531a9448ba0SNicolas Bonnefon const QString cutLine = line.mid( firstCol, nbCols ); 1532a9448ba0SNicolas Bonnefon 15332493b508SNicolas Bonnefon if ( selection_.isLineSelected( line_index ) ) { 1534a9448ba0SNicolas Bonnefon // Reverse the selected line 1535a9448ba0SNicolas Bonnefon foreColor = palette.color( QPalette::HighlightedText ); 1536a9448ba0SNicolas Bonnefon backColor = palette.color( QPalette::Highlight ); 1537a9448ba0SNicolas Bonnefon painter.setPen(palette.color(QPalette::Text)); 1538a9448ba0SNicolas Bonnefon } 15392493b508SNicolas Bonnefon else if ( filterSet->matchLine( logData->getLineString( line_index ), 1540a9448ba0SNicolas Bonnefon &foreColor, &backColor ) ) { 1541a9448ba0SNicolas Bonnefon // Apply a filter to the line 1542a9448ba0SNicolas Bonnefon } 1543a9448ba0SNicolas Bonnefon else { 1544a9448ba0SNicolas Bonnefon // Use the default colors 1545a9448ba0SNicolas Bonnefon foreColor = palette.color( QPalette::Text ); 1546a9448ba0SNicolas Bonnefon backColor = palette.color( QPalette::Base ); 1547a9448ba0SNicolas Bonnefon } 1548a9448ba0SNicolas Bonnefon 1549a9448ba0SNicolas Bonnefon // Is there something selected in the line? 1550a9448ba0SNicolas Bonnefon int sel_start, sel_end; 1551a9448ba0SNicolas Bonnefon bool isSelection = 15522493b508SNicolas Bonnefon selection_.getPortionForLine( line_index, &sel_start, &sel_end ); 1553a9448ba0SNicolas Bonnefon // Has the line got elements to be highlighted 1554a9448ba0SNicolas Bonnefon QList<QuickFindMatch> qfMatchList; 1555a9448ba0SNicolas Bonnefon bool isMatch = 1556a9448ba0SNicolas Bonnefon quickFindPattern_->matchLine( line, qfMatchList ); 1557a9448ba0SNicolas Bonnefon 1558a9448ba0SNicolas Bonnefon if ( isSelection || isMatch ) { 1559a9448ba0SNicolas Bonnefon // We use the LineDrawer and its chunks because the 1560a9448ba0SNicolas Bonnefon // line has to be somehow highlighted 1561a9448ba0SNicolas Bonnefon LineDrawer lineDrawer( backColor ); 1562a9448ba0SNicolas Bonnefon 1563a9448ba0SNicolas Bonnefon // First we create a list of chunks with the highlights 1564a9448ba0SNicolas Bonnefon QList<LineChunk> chunkList; 1565a9448ba0SNicolas Bonnefon int column = 0; // Current column in line space 1566a9448ba0SNicolas Bonnefon foreach( const QuickFindMatch match, qfMatchList ) { 1567a9448ba0SNicolas Bonnefon int start = match.startColumn() - firstCol; 1568a9448ba0SNicolas Bonnefon int end = start + match.length(); 1569a9448ba0SNicolas Bonnefon // Ignore matches that are *completely* outside view area 1570a9448ba0SNicolas Bonnefon if ( ( start < 0 && end < 0 ) || start >= nbCols ) 1571a9448ba0SNicolas Bonnefon continue; 1572a9448ba0SNicolas Bonnefon if ( start > column ) 1573a9448ba0SNicolas Bonnefon chunkList << LineChunk( column, start - 1, LineChunk::Normal ); 1574a9448ba0SNicolas Bonnefon column = qMin( start + match.length() - 1, nbCols ); 1575a9448ba0SNicolas Bonnefon chunkList << LineChunk( qMax( start, 0 ), column, 1576a9448ba0SNicolas Bonnefon LineChunk::Highlighted ); 1577a9448ba0SNicolas Bonnefon column++; 1578a9448ba0SNicolas Bonnefon } 1579a9448ba0SNicolas Bonnefon if ( column <= cutLine.length() - 1 ) 1580a9448ba0SNicolas Bonnefon chunkList << LineChunk( column, cutLine.length() - 1, LineChunk::Normal ); 1581a9448ba0SNicolas Bonnefon 1582a9448ba0SNicolas Bonnefon // Then we add the selection if needed 1583a9448ba0SNicolas Bonnefon QList<LineChunk> newChunkList; 1584a9448ba0SNicolas Bonnefon if ( isSelection ) { 1585a9448ba0SNicolas Bonnefon sel_start -= firstCol; // coord in line space 1586a9448ba0SNicolas Bonnefon sel_end -= firstCol; 1587a9448ba0SNicolas Bonnefon 1588a9448ba0SNicolas Bonnefon foreach ( const LineChunk chunk, chunkList ) { 1589a9448ba0SNicolas Bonnefon newChunkList << chunk.select( sel_start, sel_end ); 1590a9448ba0SNicolas Bonnefon } 1591a9448ba0SNicolas Bonnefon } 1592a9448ba0SNicolas Bonnefon else 1593a9448ba0SNicolas Bonnefon newChunkList = chunkList; 1594a9448ba0SNicolas Bonnefon 1595a9448ba0SNicolas Bonnefon foreach ( const LineChunk chunk, newChunkList ) { 1596a9448ba0SNicolas Bonnefon // Select the colours 1597a9448ba0SNicolas Bonnefon QColor fore; 1598a9448ba0SNicolas Bonnefon QColor back; 1599a9448ba0SNicolas Bonnefon switch ( chunk.type() ) { 1600a9448ba0SNicolas Bonnefon case LineChunk::Normal: 1601a9448ba0SNicolas Bonnefon fore = foreColor; 1602a9448ba0SNicolas Bonnefon back = backColor; 1603a9448ba0SNicolas Bonnefon break; 1604a9448ba0SNicolas Bonnefon case LineChunk::Highlighted: 1605a9448ba0SNicolas Bonnefon fore = QColor( "black" ); 1606a9448ba0SNicolas Bonnefon back = QColor( "yellow" ); 1607a9448ba0SNicolas Bonnefon // fore = highlightForeColor; 1608a9448ba0SNicolas Bonnefon // back = highlightBackColor; 1609a9448ba0SNicolas Bonnefon break; 1610a9448ba0SNicolas Bonnefon case LineChunk::Selected: 1611a9448ba0SNicolas Bonnefon fore = palette.color( QPalette::HighlightedText ), 1612a9448ba0SNicolas Bonnefon back = palette.color( QPalette::Highlight ); 1613a9448ba0SNicolas Bonnefon break; 1614a9448ba0SNicolas Bonnefon } 1615a9448ba0SNicolas Bonnefon lineDrawer.addChunk ( chunk, fore, back ); 1616a9448ba0SNicolas Bonnefon } 1617a9448ba0SNicolas Bonnefon 1618a9448ba0SNicolas Bonnefon lineDrawer.draw( painter, xPos, yPos, 1619a9448ba0SNicolas Bonnefon viewport()->width(), cutLine, 1620a9448ba0SNicolas Bonnefon CONTENT_MARGIN_WIDTH ); 1621a9448ba0SNicolas Bonnefon } 1622a9448ba0SNicolas Bonnefon else { 1623a9448ba0SNicolas Bonnefon // Nothing to be highlighted, we print the whole line! 1624a9448ba0SNicolas Bonnefon painter.fillRect( xPos - CONTENT_MARGIN_WIDTH, yPos, 1625a9448ba0SNicolas Bonnefon viewport()->width(), fontHeight, backColor ); 1626a9448ba0SNicolas Bonnefon // (the rectangle is extended on the left to cover the small 1627a9448ba0SNicolas Bonnefon // margin, it looks better (LineDrawer does the same) ) 1628a9448ba0SNicolas Bonnefon painter.setPen( foreColor ); 1629a9448ba0SNicolas Bonnefon painter.drawText( xPos, yPos + fontAscent, cutLine ); 1630a9448ba0SNicolas Bonnefon } 1631a9448ba0SNicolas Bonnefon 1632a9448ba0SNicolas Bonnefon // Then draw the bullet 1633a9448ba0SNicolas Bonnefon painter.setPen( palette.color( QPalette::Text ) ); 1634739c6a66SNicolas Bonnefon const qreal circleSize = 3; 1635739c6a66SNicolas Bonnefon const qreal arrowHeight = 4; 1636739c6a66SNicolas Bonnefon const qreal middleXLine = BULLET_AREA_WIDTH / 2; 1637739c6a66SNicolas Bonnefon const qreal middleYLine = yPos + (fontHeight / 2); 1638a9448ba0SNicolas Bonnefon 16392493b508SNicolas Bonnefon const LineType line_type = lineType( line_index ); 1640a9448ba0SNicolas Bonnefon if ( line_type == Marked ) { 1641a9448ba0SNicolas Bonnefon // A pretty arrow if the line is marked 1642739c6a66SNicolas Bonnefon const QPointF points[7] = { 1643739c6a66SNicolas Bonnefon QPointF(1, middleYLine - 2), 1644739c6a66SNicolas Bonnefon QPointF(middleXLine, middleYLine - 2), 1645739c6a66SNicolas Bonnefon QPointF(middleXLine, middleYLine - arrowHeight), 1646739c6a66SNicolas Bonnefon QPointF(BULLET_AREA_WIDTH - 1, middleYLine), 1647739c6a66SNicolas Bonnefon QPointF(middleXLine, middleYLine + arrowHeight), 1648739c6a66SNicolas Bonnefon QPointF(middleXLine, middleYLine + 2), 1649739c6a66SNicolas Bonnefon QPointF(1, middleYLine + 2 ), 1650a9448ba0SNicolas Bonnefon }; 1651a9448ba0SNicolas Bonnefon 1652a9448ba0SNicolas Bonnefon painter.setBrush( markBrush ); 1653a9448ba0SNicolas Bonnefon painter.drawPolygon( points, 7 ); 1654a9448ba0SNicolas Bonnefon } 1655a9448ba0SNicolas Bonnefon else { 1656739c6a66SNicolas Bonnefon // For pretty circles 1657739c6a66SNicolas Bonnefon painter.setRenderHint( QPainter::Antialiasing ); 1658739c6a66SNicolas Bonnefon 16592493b508SNicolas Bonnefon if ( lineType( line_index ) == Match ) 1660a9448ba0SNicolas Bonnefon painter.setBrush( matchBulletBrush ); 1661a9448ba0SNicolas Bonnefon else 1662a9448ba0SNicolas Bonnefon painter.setBrush( normalBulletBrush ); 1663a9448ba0SNicolas Bonnefon painter.drawEllipse( middleXLine - circleSize, 1664a9448ba0SNicolas Bonnefon middleYLine - circleSize, 1665a9448ba0SNicolas Bonnefon circleSize * 2, circleSize * 2 ); 1666a9448ba0SNicolas Bonnefon } 1667a9448ba0SNicolas Bonnefon 1668a9448ba0SNicolas Bonnefon // Draw the line number 1669a9448ba0SNicolas Bonnefon if ( lineNumbersVisible_ ) { 1670a9448ba0SNicolas Bonnefon static const QString lineNumberFormat( "%1" ); 1671a9448ba0SNicolas Bonnefon const QString& lineNumberStr = 16722493b508SNicolas Bonnefon lineNumberFormat.arg( displayLineNumber( line_index ), 16732493b508SNicolas Bonnefon nbDigitsInLineNumber ); 1674a9448ba0SNicolas Bonnefon painter.setPen( palette.color( QPalette::Text ) ); 1675a9448ba0SNicolas Bonnefon painter.drawText( lineNumberAreaStartX + LINE_NUMBER_PADDING, 1676a9448ba0SNicolas Bonnefon yPos + fontAscent, lineNumberStr ); 1677a9448ba0SNicolas Bonnefon } 1678a9448ba0SNicolas Bonnefon } // For each line 16792493b508SNicolas Bonnefon 16802493b508SNicolas Bonnefon if ( bottomOfTextPx < paintDeviceHeight ) { 16812493b508SNicolas Bonnefon // The lines don't cover the whole device 16822493b508SNicolas Bonnefon painter.fillRect( contentStartPosX, bottomOfTextPx, 16832493b508SNicolas Bonnefon paintDeviceWidth - contentStartPosX, 16842493b508SNicolas Bonnefon paintDeviceHeight, palette.color( QPalette::Window ) ); 16852493b508SNicolas Bonnefon } 1686ac6602a5SNicolas Bonnefon } 1687a9448ba0SNicolas Bonnefon 1688ac6602a5SNicolas Bonnefon // Draw the "pull to follow" bar and return a pixmap. 1689ac6602a5SNicolas Bonnefon // The width is passed in "logic" pixels. 1690ac6602a5SNicolas Bonnefon QPixmap AbstractLogView::drawPullToFollowBar( int width, float pixel_ratio ) 1691ac6602a5SNicolas Bonnefon { 1692ac6602a5SNicolas Bonnefon static constexpr int barWidth = 40; 1693ac6602a5SNicolas Bonnefon QPixmap pixmap ( static_cast<float>( width ) * pixel_ratio, barWidth * 6.0 ); 1694ac6602a5SNicolas Bonnefon pixmap.setDevicePixelRatio( pixel_ratio ); 1695ac6602a5SNicolas Bonnefon pixmap.fill( this->palette().color( this->backgroundRole() ) ); 1696ac6602a5SNicolas Bonnefon const int nbBars = width / (barWidth * 2) + 1; 1697ac6602a5SNicolas Bonnefon 1698ac6602a5SNicolas Bonnefon QPainter painter( &pixmap ); 1699ac6602a5SNicolas Bonnefon painter.setPen( QPen( QColor( 0, 0, 0, 0 ) ) ); 1700ac6602a5SNicolas Bonnefon painter.setBrush( QBrush( QColor( "lightyellow" ) ) ); 1701ac6602a5SNicolas Bonnefon 1702ac6602a5SNicolas Bonnefon for ( int i = 0; i < nbBars; ++i ) { 1703ac6602a5SNicolas Bonnefon QPoint points[4] = { 1704ac6602a5SNicolas Bonnefon { (i*2+1)*barWidth, 0 }, 1705ac6602a5SNicolas Bonnefon { 0, (i*2+1)*barWidth }, 1706ac6602a5SNicolas Bonnefon { 0, (i+1)*2*barWidth }, 1707ac6602a5SNicolas Bonnefon { (i+1)*2*barWidth, 0 } 1708ac6602a5SNicolas Bonnefon }; 1709ac6602a5SNicolas Bonnefon painter.drawConvexPolygon( points, 4 ); 1710ac6602a5SNicolas Bonnefon } 1711ac6602a5SNicolas Bonnefon 1712ac6602a5SNicolas Bonnefon return pixmap; 1713a9448ba0SNicolas Bonnefon } 1714a9448ba0SNicolas Bonnefon 1715b297d2f4SNicolas Bonnefon void AbstractLogView::disableFollow() 1716b297d2f4SNicolas Bonnefon { 1717b297d2f4SNicolas Bonnefon emit followModeChanged( false ); 1718b297d2f4SNicolas Bonnefon followElasticHook_.hook( false ); 1719b297d2f4SNicolas Bonnefon } 1720b297d2f4SNicolas Bonnefon 17210b05c6eaSNicolas Bonnefon namespace { 17220b05c6eaSNicolas Bonnefon 17230b05c6eaSNicolas Bonnefon // Convert the length of the pull to follow bar to pixels 17240b05c6eaSNicolas Bonnefon int mapPullToFollowLength( int length ) 17250b05c6eaSNicolas Bonnefon { 17260b05c6eaSNicolas Bonnefon return length / 14; 17270b05c6eaSNicolas Bonnefon } 17280b05c6eaSNicolas Bonnefon 17290b05c6eaSNicolas Bonnefon }; 1730