glogg-1.1.0/0000775000175000017500000000000012657420705011612 5ustar nickonickoglogg-1.1.0/runtests.sh0000775000175000017500000000046212657420553014043 0ustar nickonicko#!/bin/sh ./tests/logcrawler_tests $* result=$? ICON_OK=gtk-apply ICON_ERROR=gtk-cancel if [ `which notify-send` ]; then if [ "$result" != "0" ]; then notify-send -i $ICON_ERROR "glogg" "Tests failed!" else notify-send -i $ICON_OK "glogg" "Tests passed!" fi fi exit $result glogg-1.1.0/glogg.qrc0000664000175000017500000000163712657420553013430 0ustar nickonicko images/hicolor/16x16/glogg.png images/hicolor/24x24/glogg.png images/hicolor/32x32/glogg.png images/hicolor/48x48/glogg.png images/open14.png images/open14@2x.png images/reload14.png images/reload14@2x.png images/olddata_icon.png images/olddata_icon@2x.png images/newdata_icon.png images/newdata_icon@2x.png images/newfiltered_icon.png images/newfiltered_icon@2x.png images/stop14.png images/plus.png images/minus.png images/down.png images/up.png images/arrowdown.png images/arrowup.png images/darkclosebutton.png glogg-1.1.0/src/0000775000175000017500000000000012657420553012402 5ustar nickonickoglogg-1.1.0/src/inotifywatchtowerdriver.h0000664000175000017500000001046712657420553017570 0ustar nickonicko/* * Copyright (C) 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef INOTIFYWATCHTOWERDRIVER_H #define INOTIFYWATCHTOWERDRIVER_H #include #include #include template class ObservedFile; template class ObservedFileList; class INotifyWatchTowerDriver { public: class FileId { public: friend class INotifyWatchTowerDriver; FileId() { wd_ = -1; } bool operator==( const FileId& other ) const { return wd_ == other.wd_; } private: FileId( int wd ) { wd_ = wd; } int wd_; }; class DirId { public: friend class INotifyWatchTowerDriver; DirId() { wd_ = -1; } bool operator==( const DirId& other ) const { return wd_ == other.wd_; } bool valid() const { return (wd_ != -1); } private: DirId( int wd ) { wd_ = wd; } int wd_; }; class SymlinkId { public: friend class INotifyWatchTowerDriver; SymlinkId() { wd_ = -1; } bool operator==( const SymlinkId& other ) const { return wd_ == other.wd_; } private: SymlinkId( int wd ) { wd_ = wd; } int wd_; }; // Dummy class for inotify class FileChangeToken { public: FileChangeToken() {} FileChangeToken( const std::string& ) {} void readFromFile( const std::string& ) {} bool operator!=( const FileChangeToken& ) { return true; } }; #ifdef HAS_TEMPLATE_ALIASES using INotifyObservedFile = ObservedFile; using INotifyObservedFileList = ObservedFileList; #else typedef ObservedFile INotifyObservedFile; typedef ObservedFileList INotifyObservedFileList; #endif // Default constructor INotifyWatchTowerDriver(); ~INotifyWatchTowerDriver(); // No copy/assign/move please INotifyWatchTowerDriver( const INotifyWatchTowerDriver& ) = delete; INotifyWatchTowerDriver& operator=( const INotifyWatchTowerDriver& ) = delete; INotifyWatchTowerDriver( const INotifyWatchTowerDriver&& ) = delete; INotifyWatchTowerDriver& operator=( const INotifyWatchTowerDriver&& ) = delete; FileId addFile( const std::string& file_name ); SymlinkId addSymlink( const std::string& file_name ); DirId addDir( const std::string& file_name ); void removeFile( const FileId& file_id ); void removeSymlink( const SymlinkId& symlink_id ); void removeDir( const DirId& dir_id ); // Wait for an event for the OS, treat it and // return a list of files to notify about. // This must be called with the lock on the list held, // the function will unlock it temporary whilst blocking. // Also returns a list of file that need readding // (because of renames/symlink...) std::vector waitAndProcessEvents( INotifyObservedFileList* list, std::unique_lock* list_mutex, std::vector* files_needing_readding, int timeout_ms ); // Interrupt waitAndProcessEvents if it is blocking. void interruptWait(); private: // Only written at initialisation so no protection needed. const int inotify_fd_; // Breaking pipe int breaking_pipe_read_fd_; int breaking_pipe_write_fd_; // Private member functions size_t processINotifyEvent( const struct inotify_event* event, INotifyObservedFileList* list, std::vector* files_to_notify, std::vector* files_needing_readding ); }; #endif glogg-1.1.0/src/qtfilewatcher.cpp0000664000175000017500000001135312657420553015753 0ustar nickonicko/* * Copyright (C) 2010 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include "log.h" #include "qtfilewatcher.h" #include #include QtFileWatcher::QtFileWatcher() : FileWatcher(), qtFileWatcher_( this ) { monitoringState_ = None; connect( &qtFileWatcher_, SIGNAL( fileChanged( const QString& ) ), this, SLOT( fileChangedOnDisk( const QString& ) ) ); connect( &qtFileWatcher_, SIGNAL( directoryChanged( const QString& ) ), this, SLOT( directoryChangedOnDisk( const QString& ) ) ); } QtFileWatcher::~QtFileWatcher() { disconnect( &qtFileWatcher_ ); } void QtFileWatcher::addFile( const QString& fileName ) { LOG(logDEBUG) << "QtFileWatcher::addFile " << fileName.toStdString(); QFileInfo fileInfo = QFileInfo( fileName ); if ( fileMonitored_.isEmpty() ) { fileMonitored_ = fileName; // Initialise the Qt file watcher qtFileWatcher_.addPath( fileInfo.path() ); if ( fileInfo.exists() ) { LOG(logDEBUG) << "QtFileWatcher::addFile: file exists."; qtFileWatcher_.addPath( fileName ); monitoringState_ = FileExists; } else { LOG(logDEBUG) << "QtFileWatcher::addFile: file doesn't exist."; monitoringState_ = FileRemoved; } } else { LOG(logWARNING) << "QtFileWatcher::addFile " << fileName.toStdString() << "- Already watching a file (" << fileMonitored_.toStdString() << ")!"; } } void QtFileWatcher::removeFile( const QString& fileName ) { LOG(logDEBUG) << "QtFileWatcher::removeFile " << fileName.toStdString(); QFileInfo fileInfo = QFileInfo( fileName ); if ( fileName == fileMonitored_ ) { if ( monitoringState_ == FileExists ) qtFileWatcher_.removePath( fileName ); qtFileWatcher_.removePath( fileInfo.path() ); fileMonitored_.clear(); monitoringState_ = None; } else { LOG(logWARNING) << "QtFileWatcher::removeFile - The file is not watched!"; } // For debug purpose: foreach (QString str, qtFileWatcher_.files()) { LOG(logERROR) << "File still watched: " << str.toStdString(); } foreach (QString str, qtFileWatcher_.directories()) { LOG(logERROR) << "Directories still watched: " << str.toStdString(); } } // // Slots // void QtFileWatcher::fileChangedOnDisk( const QString& filename ) { LOG(logDEBUG) << "QtFileWatcher::fileChangedOnDisk " << filename.toStdString(); if ( ( monitoringState_ == FileExists ) && ( filename == fileMonitored_ ) ) { emit fileChanged( filename ); // If the file has been removed... if ( !QFileInfo( filename ).exists() ) monitoringState_ = FileRemoved; } else LOG(logWARNING) << "QtFileWatcher::fileChangedOnDisk - call from Qt but no file monitored"; } void QtFileWatcher::directoryChangedOnDisk( const QString& filename ) { LOG(logDEBUG) << "QtFileWatcher::directoryChangedOnDisk " << filename.toStdString(); if ( monitoringState_ == FileRemoved ) { if ( QFileInfo( fileMonitored_ ).exists() ) { LOG(logDEBUG) << "QtFileWatcher::directoryChangedOnDisk - our file reappeared!"; // The file has been recreated, we have to watch it again. monitoringState_ = FileExists; // Restore the Qt file watcher (automatically cancelled // when the file is deleted) qtFileWatcher_.addPath( fileMonitored_ ); emit fileChanged( fileMonitored_ ); } else { LOG(logWARNING) << "QtFileWatcher::directoryChangedOnDisk - not the file we are watching"; } } else if ( monitoringState_ == FileExists ) { if ( ! QFileInfo( fileMonitored_ ).exists() ) { LOG(logDEBUG) << "QtFileWatcher::directoryChangedOnDisk - our file disappeared!"; monitoringState_ = FileRemoved; emit fileChanged( filename ); } else { LOG(logWARNING) << "QtFileWatcher::directoryChangedOnDisk - not the file we are watching"; } } } glogg-1.1.0/src/menuactiontooltipbehavior.cpp0000664000175000017500000000733112657420553020407 0ustar nickonicko/* * Copyright (C) 2009, 2010 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include #include #include #include #include "menuactiontooltipbehavior.h" // It would be nice to only need action, and have action be the parent, // however implementation needs the parent menu (see showToolTip), and // since neither action nor parent is guaranteed to die before the other, // for memory-management purposes the parent will have to be specified // explicity (probably, the window owning the action and the menu). MenuActionToolTipBehavior::MenuActionToolTipBehavior(QAction *action, QMenu *parentMenu, QObject *parent = 0) : QObject(parent), action(action), parentMenu(parentMenu), toolTipDelayMs(1000), timerId(0), hoverPoint() { connect(action, SIGNAL(hovered()), this, SLOT(onActionHovered())); } int MenuActionToolTipBehavior::toolTipDelay() { return toolTipDelayMs; } void MenuActionToolTipBehavior::setToolTipDelay(int delayMs) { toolTipDelayMs = delayMs; } void MenuActionToolTipBehavior::timerEvent(QTimerEvent *event) { // Not ours, don't touch if (event->timerId() != timerId) { QObject::timerEvent(event); return; } killTimer(timerId); // interested in a single shot timerId = 0; // Has the mouse waited unmoved in one location for 'delay' ms? const QPoint &mousePos = QCursor::pos(); if (hoverPoint == mousePos) showToolTip(hoverPoint); } void MenuActionToolTipBehavior::onActionHovered() { const QPoint &mousePos = QCursor::pos(); // Hover is fired on keyboard focus over action in menu, ignore it const QPoint &relativeMousePos = parentMenu->mapFromGlobal(mousePos); if (!parentMenu->actionGeometry(action).contains(relativeMousePos)) { if (timerId != 0) { // once timer expires its check will fail anyway killTimer(timerId); timerId = 0; } QToolTip::hideText(); // there might be one currently shown return; } // Record location hoverPoint = mousePos; // Restart timer if (timerId != 0) killTimer(timerId); timerId = startTimer(toolTipDelayMs); } void MenuActionToolTipBehavior::showToolTip(const QPoint &position) { const QString &toolTip = action->toolTip(); // Show tooltip until mouse moves at all // NOTE: using action->parentWidget() which is the MainWindow, // does not work (tooltip is not cleared when upon leaving the // region). This is the only reason we need parentMenu here. Just // a wild guess: maybe it isn't cleared because it would be // cleared on a mouse move over the designated widget, but mouse // move doesn't happen over MainWindow, since the mouse is over // the menu even when out of the activeRegion. QPoint relativePos = parentMenu->mapFromGlobal(position); QRect activeRegion(relativePos.x(), relativePos.y(), 1, 1); QToolTip::showText(position, toolTip, parentMenu, activeRegion); } glogg-1.1.0/src/filewatcher.cpp0000664000175000017500000000000012657420553015371 0ustar nickonickoglogg-1.1.0/src/loadingstatus.h0000664000175000017500000000214112657420553015432 0ustar nickonicko/* * Copyright (C) 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef LOADINGSTATUS_H #define LOADINGSTATUS_H #include // Loading status of a file enum class LoadingStatus { Successful, Interrupted, NoMemory }; // Data status (whether new, not seen, data is available) enum class DataStatus { OLD_DATA, NEW_DATA, NEW_FILTERED_DATA }; Q_DECLARE_METATYPE( DataStatus ) Q_DECLARE_METATYPE( LoadingStatus ) #endif glogg-1.1.0/src/externalcom.h0000664000175000017500000000335012657420553015075 0ustar nickonicko/* * Copyright (C) 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef EXTERNALCOM_H #define EXTERNALCOM_H #include class CantCreateExternalErr {}; /* * Virtual class representing another instance of glogg. * Sending messages to an object of this class will forward * them to the instance using the underlying IPC. */ class ExternalInstance { public: ExternalInstance() {} virtual ~ExternalInstance() {} virtual void loadFile( const QString& file_name ) const = 0; virtual uint32_t getVersion() const = 0; }; /* * Class receiving messages from another instance of glogg. * Messages are forwarded to the application by signals. */ class ExternalCommunicator : public QObject { Q_OBJECT public: ExternalCommunicator() : QObject() {} virtual ExternalInstance* otherInstance() const = 0; /* Instruct the communicator to start listening for * remote initiated operations */ virtual void startListening() = 0; signals: void loadFile( const QString& file_name ); public slots: virtual qint32 version() const = 0; }; #endif glogg-1.1.0/src/infoline.cpp0000664000175000017500000000416312657420553014715 0ustar nickonicko/* * Copyright (C) 2009, 2010 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include "log.h" #include "infoline.h" #include // This file implements InfoLine. It is responsible for decorating the // widget and managing the completion gauge. InfoLine::InfoLine() : QLabel(), origPalette_( palette() ), backgroundColor_( origPalette_.color( QPalette::Button ) ), darkBackgroundColor_( origPalette_.color( QPalette::Dark ) ) { setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); } void InfoLine::displayGauge( int completion ) { int changeoverX = width() * completion / 100; // Create a gradient for the progress bar QLinearGradient linearGrad( changeoverX - 1, 0, changeoverX + 1, 0 ); linearGrad.setColorAt( 0, darkBackgroundColor_ ); linearGrad.setColorAt( 1, backgroundColor_ ); // Apply the gradient to the current palette (background) QPalette newPalette = origPalette_; newPalette.setBrush( backgroundRole(), QBrush( linearGrad ) ); setPalette( newPalette ); } void InfoLine::hideGauge() { setPalette( origPalette_ ); } // Custom painter: draw the background then call QLabel's painter void InfoLine::paintEvent( QPaintEvent* paintEvent ) { // Fill the widget background { QPainter painter( this ); painter.fillRect( 0, 0, this->width(), this->height(), palette().brush( backgroundRole() ) ); } // Call the parent's painter QLabel::paintEvent( paintEvent ); } glogg-1.1.0/src/optionsdialog.h0000664000175000017500000000402312657420553015425 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2013 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef OPTIONSDIALOG_H #define OPTIONSDIALOG_H #include #include "configuration.h" #include "ui_optionsdialog.h" // Implements the main option dialog box class OptionsDialog : public QDialog, public Ui::OptionsDialog { Q_OBJECT public: OptionsDialog(QWidget* parent = 0); signals: // Is emitted when new settings must be used void optionsChanged(); private slots: // Clears and updates the font size box with the sizes allowed // by the passed font family. void updateFontSize(const QString& fontFamily); // Update the content of the global Config() using parameters // from the dialog box. void updateConfigFromDialog(); // Called when a ok/cancel/apply button is clicked. void onButtonBoxClicked( QAbstractButton* button ); // Called when the 'incremental' button is toggled. void onIncrementalChanged(); // Called when the 'polling' checkbox is toggled. void onPollingChanged(); private: void setupTabs(); void setupFontList(); void setupRegexp(); void setupIncremental(); void setupPolling(); int getRegexpIndex( SearchRegexpType syntax ) const; SearchRegexpType getRegexpTypeFromIndex( int index ) const; void updateDialogFromConfig(); QValidator* polling_interval_validator_; }; #endif glogg-1.1.0/src/signalmux.cpp0000664000175000017500000001155512657420553015124 0ustar nickonicko/* * Copyright (C) 2013 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include "signalmux.h" #include #include #include "log.h" SignalMux::SignalMux() : connectionList_() { currentDocument_ = nullptr; } void SignalMux::connect( QObject *sender, const char *signal, const char *slot ) { Connection new_connection = { sender, nullptr, signal, slot }; connectionList_.push_back( new_connection ); connect( new_connection ); } void SignalMux::disconnect( QObject *sender, const char *signal, const char *slot ) { // Find any signal that match our description auto connection = std::find_if( connectionList_.begin(), connectionList_.end(), [sender, signal, slot]( const Connection& c ) -> bool { return ((QObject*)c.source == sender) && (c.sink == nullptr) && (qstrcmp( c.signal, signal) == 0) && (qstrcmp( c.slot, slot) == 0); } ); if ( connection != connectionList_.end() ) { disconnect( *connection ); connectionList_.erase( connection ); } else { LOG( logERROR ) << "SignalMux disconnecting a non-existing signal"; } } // Upstream signals void SignalMux::connect( const char* signal, QObject* receiver, const char* slot ) { Connection new_connection = { nullptr, receiver, signal, slot }; connectionList_.push_back( new_connection ); connect( new_connection ); } void SignalMux::disconnect( const char *signal, QObject* receiver, const char *slot ) { // Find any signal that match our description auto connection = std::find_if( connectionList_.begin(), connectionList_.end(), [receiver, signal, slot]( const Connection& c ) -> bool { return ((QObject*)c.sink == receiver) && (c.source == nullptr) && (qstrcmp( c.signal, signal) == 0) && (qstrcmp( c.slot, slot) == 0); } ); if ( connection != connectionList_.end() ) { disconnect( *connection ); connectionList_.erase( connection ); } else { LOG( logERROR ) << "SignalMux disconnecting a non-existing signal"; } } void SignalMux::setCurrentDocument( QObject* current_document ) { // First disconnect everything to/from the old document for ( auto c: connectionList_ ) disconnect( c ); currentDocument_ = current_document; // And now connect to/from the new document for ( auto c: connectionList_ ) connect( c ); // And ask the doc to emit all state signals MuxableDocumentInterface* doc = dynamic_cast( current_document ); if ( doc ) doc->sendAllStateSignals(); } /* * Private functions */ void SignalMux::connect( const Connection& connection ) { if ( currentDocument_ ) { LOG( logDEBUG ) << "SignalMux::connect"; if ( connection.source && ( ! connection.sink ) ) { // Downstream signal QObject::connect( connection.source, connection.signal, currentDocument_, connection.slot ); } else if ( ( ! connection.source ) && connection.sink ) { // Upstream signal QObject::connect( currentDocument_, connection.signal, connection.sink, connection.slot ); } else { LOG( logERROR ) << "SignalMux has an unexpected signal"; } } } void SignalMux::disconnect( const Connection& connection ) { if ( currentDocument_ ) { LOG( logDEBUG ) << "SignalMux::disconnect"; if ( connection.source && ( ! connection.sink ) ) { // Downstream signal QObject::disconnect( connection.source, connection.signal, currentDocument_, connection.slot ); } else if ( ( ! connection.source ) && connection.sink ) { // Upstream signal QObject::disconnect( currentDocument_, connection.signal, connection.sink, connection.slot ); } else { LOG( logERROR ) << "SignalMux has an unexpected signal"; } } } glogg-1.1.0/src/quickfindpattern.cpp0000664000175000017500000000651512657420553016470 0ustar nickonicko/* * Copyright (C) 2010 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ // This file implements QuickFindPattern. // This class implements part of the Quick Find mechanism, it only stores the // current search pattern, once it has been confirmed (return pressed), // it can be asked to return the matches in a specific string. #include "quickfindpattern.h" #include "persistentinfo.h" #include "configuration.h" QuickFindPattern::QuickFindPattern() : QObject(), regexp_() { active_ = false; } void QuickFindPattern::changeSearchPattern( const QString& pattern ) { // Determine the type of regexp depending on the config QRegExp::PatternSyntax syntax; switch ( Persistent( "settings" )->quickfindRegexpType() ) { case Wildcard: syntax = QRegExp::Wildcard; break; case FixedString: syntax = QRegExp::FixedString; break; default: syntax = QRegExp::RegExp2; break; } regexp_.setPattern( pattern ); regexp_.setPatternSyntax( syntax ); if ( regexp_.isValid() && ( ! regexp_.isEmpty() ) ) active_ = true; else active_ = false; emit patternUpdated(); } void QuickFindPattern::changeSearchPattern( const QString& pattern, bool ignoreCase ) { regexp_.setCaseSensitivity( ignoreCase ? Qt::CaseInsensitive : Qt::CaseSensitive ); changeSearchPattern( pattern ); } bool QuickFindPattern::matchLine( const QString& line, QList& matches ) const { matches.clear(); if ( active_ ) { int pos = 0; while ( ( pos = regexp_.indexIn( line, pos ) ) != -1 ) { int length = regexp_.matchedLength(); matches << QuickFindMatch( pos, length ); pos += length; } } return ( matches.count() > 0 ); } bool QuickFindPattern::isLineMatching( const QString& line, int column ) const { int pos = 0; if ( ! active_ ) return false; if ( ( pos = regexp_.indexIn( line, column ) ) != -1 ) { lastMatchStart_ = pos; lastMatchEnd_ = pos + regexp_.matchedLength() - 1; return true; } else return false; } bool QuickFindPattern::isLineMatchingBackward( const QString& line, int column ) const { int pos = 0; if ( ! active_ ) return false; if ( ( pos = regexp_.lastIndexIn( line, column ) ) != -1 ) { lastMatchStart_ = pos; lastMatchEnd_ = pos + regexp_.matchedLength() - 1; return true; } else return false; } void QuickFindPattern::getLastMatch( int* start_col, int* end_col ) const { *start_col = lastMatchStart_; *end_col = lastMatchEnd_; } glogg-1.1.0/src/menuactiontooltipbehavior.h0000664000175000017500000000325112657420553020051 0ustar nickonicko/* * Copyright (C) 2009, 2010 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef MANUACTIONTOOLTIPBEHAVIOR_H #define MANUACTIONTOOLTIPBEHAVIOR_H #include #include class QAction; class QMenu; class QTimerEvent; // Provides a behavior to show an action's tooltip after mouse is unmoved for // a specified number of 'ms'. E.g. used for tooltips with full-path for recent // files in the file menu. Not thread-safe. class MenuActionToolTipBehavior : public QObject { Q_OBJECT; public: MenuActionToolTipBehavior(QAction *action, QMenu *parentMenu, QObject *parent); // Time in ms that mouse needs to stay unmoved for tooltip to be shown int toolTipDelay(); /* ms */ void setToolTipDelay(int ms); private: void timerEvent(QTimerEvent *event); void showToolTip(const QPoint &position); private slots: void onActionHovered(); private: QAction *action; QMenu *parentMenu; int toolTipDelayMs; int timerId; QPoint hoverPoint; }; #endif glogg-1.1.0/src/perfcounter.h0000664000175000017500000000354012657420553015111 0ustar nickonicko/* * Copyright (C) 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include // This class is a counter that remembers the number of events occuring within one second // it can be used for performance measurement (e.g. FPS) class PerfCounter { public: PerfCounter() {} // Count a new event, returns true if it has been counted. // If the function returns false, it indicates the current second is elapsed // and the user should read and reset the counter before re-adding the event. bool addEvent() { using namespace std::chrono; if ( counter_ == 0 ) first_event_date_ = steady_clock::now(); if ( duration_cast( steady_clock::now() - first_event_date_ ).count() < 1000000 ) { ++counter_; return true; } else { return false; } } // Read and reset the counter. Returns the number of events that occured in the // previous second. uint32_t readAndReset() { uint32_t value = counter_; counter_ = 0; return value; } private: uint32_t counter_ = 0; std::chrono::steady_clock::time_point first_event_date_; }; glogg-1.1.0/src/dbusexternalcom.h0000664000175000017500000000410312657420553015750 0ustar nickonicko/* * Copyright (C) 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef DBUSEXTERNALCOM_H #define DBUSEXTERNALCOM_H #include "externalcom.h" #include #include #include // An implementation of ExternalInstance using D-Bus via Qt class DBusExternalInstance : public ExternalInstance { public: DBusExternalInstance(); ~DBusExternalInstance() {} virtual void loadFile( const QString& file_name ) const; virtual uint32_t getVersion() const; private: std::shared_ptr dbusInterface_; }; class DBusInterfaceExternalCommunicator : public QObject { Q_OBJECT public: DBusInterfaceExternalCommunicator() : QObject() {} ~DBusInterfaceExternalCommunicator() {} public slots: void loadFile( const QString& file_name ); qint32 version() const; signals: void signalLoadFile( const QString& file_name ); }; // An implementation of ExternalCommunicator using D-Bus via Qt class DBusExternalCommunicator : public ExternalCommunicator { Q_OBJECT public: // Constructor: initialise the D-Bus connection, // can throw if D-Bus is not available DBusExternalCommunicator(); ~DBusExternalCommunicator() {} virtual void startListening(); virtual ExternalInstance* otherInstance() const; public slots: qint32 version() const; private: std::shared_ptr dbus_iface_object_; }; #endif glogg-1.1.0/src/winexternalcom.h0000664000175000017500000000513512657420553015616 0ustar nickonicko/* * Copyright (C) 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef WINEXTERNALCOM_H #define WINEXTERNALCOM_H #include "externalcom.h" #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include // An implementation of ExternalInstance using Windows IPC class WinExternalInstance : public ExternalInstance { public: WinExternalInstance(); ~WinExternalInstance() {} virtual void loadFile( const QString& file_name ) const; virtual uint32_t getVersion() const; private: static WINBOOL enumWindowsCallback( HWND hwnd, LPARAM lParam ); HWND window_handle_; }; enum class MessageId { LOAD_FILE }; // A hidden widget to listen for message via Windows' // WM_COPYDATA mechanism. // (this should be nested into WinExternalCommunicator really but Qt MOC // doesn't support nested classes) class WinMessageListener : public QWidget { Q_OBJECT public: WinMessageListener(); ~WinMessageListener() = default; signals: // A message has been received from Windows void messageReceived( MessageId message_id, const QString& data ); private: // Override the default event message bool winEvent( MSG* message, long* result ); }; // An implementation of ExternalCommunicator using Windows IPC class WinExternalCommunicator : public ExternalCommunicator { Q_OBJECT public: // Constructor: initialise the D-Bus connection, // can throw if D-Bus is not available WinExternalCommunicator(); ~WinExternalCommunicator() {} virtual void startListening(); virtual ExternalInstance* otherInstance() const; public slots: qint32 version() const; private slots: void handleMessageReceived( MessageId message_id, const QString& data ); private: static const QString WINDOW_TITLE; bool mutex_held_elsewhere_; std::shared_ptr message_listener_; }; #endif glogg-1.1.0/src/winwatchtowerdriver.h0000664000175000017500000001432712657420553016703 0ustar nickonicko/* * Copyright (C) 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef WINWATCHTOWERDRIVER_H #define WINWATCHTOWERDRIVER_H #include #include #include #include #include #include #define WIN32_LEAN_AND_MEAN #include // Utility class // Encapsulate a directory notification returned by Windows' // ReadDirectoryChangesW. class WinNotificationInfo { public: enum class Action { UNDEF, ADDED, REMOVED, MODIFIED, RENAMED_OLD_NAME, RENAMED_NEW_NAME }; WinNotificationInfo() { action_ = Action::UNDEF; } WinNotificationInfo( Action action, std::wstring file_name ) { action_ = action; file_name_ = file_name; } Action action() const { return action_; } std::wstring fileName() const { return file_name_; } private: Action action_; std::wstring file_name_; }; class WinNotificationInfoList { public: WinNotificationInfoList( const char* buffer, size_t buffer_size ); // Iterator class iterator : std::iterator { public: iterator( WinNotificationInfoList* list, const char* position ) { list_ = list; position_ = position; } const WinNotificationInfo& operator*() const { return list_->current_notification_; } const WinNotificationInfo* operator->() const { return &( list_->current_notification_ ); } const WinNotificationInfo& operator++() { position_ = list_->advanceToNext(); return list_->current_notification_; } WinNotificationInfo operator++( int ) { WinNotificationInfo tmp { list_->current_notification_ }; operator++(); return tmp; } bool operator!=( const iterator& other ) const { return ( list_ != other.list_ ) || ( position_ != other.position_ ); } private: WinNotificationInfoList* list_; const char* position_; }; iterator begin() { return iterator( this, pointer_ ); } iterator end() { return iterator( this, nullptr ); } private: const char* advanceToNext(); // Current notification (in the byte stream) const char* pointer_; // Next notification (in the byte stream) const char* next_; WinNotificationInfo current_notification_; const char* updateCurrentNotification( const char* new_position ); }; template class ObservedFile; template class ObservedFileList; class WinWatchTowerDriver { public: struct WinWatchedDirRecord { WinWatchedDirRecord( const std::string& file_name ) : path_( file_name ) { } static const int READ_DIR_CHANGE_BUFFER_SIZE = 4096; std::string path_; void* handle_ = nullptr; static const unsigned long buffer_length_ = READ_DIR_CHANGE_BUFFER_SIZE; char buffer_[buffer_length_]; }; class FileId { }; class SymlinkId { }; class DirId { public: friend class WinWatchTowerDriver; DirId() {} bool operator==( const DirId& other ) const { return dir_record_ == other.dir_record_; } bool valid() const { return ( dir_record_ != nullptr ); } private: std::shared_ptr dir_record_; }; // On Windows, the token is the "last write" time class FileChangeToken { public: FileChangeToken() {} FileChangeToken( const std::string& file_name ) { readFromFile( file_name ); } void readFromFile( const std::string& file_name ); bool operator==( const FileChangeToken& other ) { return ( low_date_time_ == other.low_date_time_ ) && ( high_date_time_ == other.high_date_time_ ); } bool operator!=( const FileChangeToken& other ) { return ! operator==( other ); } private: DWORD low_date_time_ = 0; DWORD high_date_time_ = 0; }; // Default constructor WinWatchTowerDriver(); ~WinWatchTowerDriver(); FileId addFile( const std::string& file_name ); SymlinkId addSymlink( const std::string& file_name ); DirId addDir( const std::string& file_name ); void removeFile( const FileId& file_id ); void removeSymlink( const SymlinkId& symlink_id ); void removeDir( const DirId& dir_id ); std::vector*> waitAndProcessEvents( ObservedFileList* list, std::unique_lock* lock, std::vector*>* files_needing_readding, int timout_ms ); // Interrupt waitAndProcessEvents void interruptWait(); private: // An action which will be picked up by the worker thread. class Action { public: Action( std::function function ) : function_ { function } {} ~Action() {} void operator()() { function_(); } private: std::function function_; }; // Action std::mutex action_mutex_; std::condition_variable action_done_cv_; std::unique_ptr scheduled_action_ = nullptr; // Win32 notification variables HANDLE hCompPort_; OVERLAPPED overlapped_; unsigned long buffer_length_; // List of directory records // Accessed exclusively in the worker thread context std::vector> dir_records_ { }; // Private member functions void serialisedAddDir( const std::string& file_name, DirId& dir_id ); }; #endif glogg-1.1.0/src/recentfiles.h0000664000175000017500000000301312657420553015053 0ustar nickonicko/* * Copyright (C) 2011 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef RECENTFILES_H #define RECENTFILES_H #include #include #include "persistable.h" // Manage the list of recently opened files class RecentFiles : public Persistable { public: // Creates an empty set of recent files RecentFiles(); // Adds the passed filename to the list of recently used searches void addRecent( const QString& text ); // Returns a list of recent files (latest loaded first) QStringList recentFiles() const; // Reads/writes the current config in the QSettings object passed virtual void saveToStorage( QSettings& settings ) const; virtual void retrieveFromStorage( QSettings& settings ); private: static const int RECENTFILES_VERSION; static const int MAX_NUMBER_OF_FILES; QStringList recentFiles_; }; #endif glogg-1.1.0/src/versionchecker.h0000664000175000017500000000454612657420553015576 0ustar nickonicko/* * Copyright (C) 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef VERSIONCHECKER_H #define VERSIONCHECKER_H #include #include #include #include "persistable.h" // This class holds the configuration options and persistent // data for the version checker class VersionCheckerConfig : public Persistable { public: VersionCheckerConfig(); // Accessors bool versionCheckingEnabled() const { return enabled_; } void setVersionCheckingEnabled( bool enabled ) { enabled_ = enabled; } std::time_t nextDeadline() const { return next_deadline_; } void setNextDeadline( std::time_t deadline ) { next_deadline_ = deadline; } // Reads/writes the current config in the QSettings object passed virtual void saveToStorage( QSettings& settings ) const; virtual void retrieveFromStorage( QSettings& settings ); private: bool enabled_; std::time_t next_deadline_; }; // This class compares the current version number with the latest // stored on a central server class VersionChecker : public QObject { Q_OBJECT public: VersionChecker(); ~VersionChecker(); // Starts an asynchronous check for a newer version if it is needed. // A newVersionFound signal is sent if one is found. // In case of error or if no new version is found, no signal is emitted. void startCheck(); signals: // New version "version" is available void newVersionFound( const QString& version ); private slots: // Called when download is finished void downloadFinished( QNetworkReply* ); private: static const char* VERSION_URL; static const uint64_t CHECK_INTERVAL_S; QNetworkAccessManager manager_; }; #endif glogg-1.1.0/src/persistentinfo.h0000664000175000017500000000530412657420553015631 0ustar nickonicko/* * Copyright (C) 2011 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef PERSISTENTINFO_H #define PERSISTENTINFO_H #include #include #include class Persistable; // Singleton class managing the saving of persistent data to permanent storage // Clients must implement Persistable and register with this object, they can // then be saved/loaded. class PersistentInfo { public: // Initialise the storage backend for the Persistable, migrating the settings // if needed. Must be called before any other function. void migrateAndInit(); // Register a Persistable void registerPersistable( std::shared_ptr object, const QString& name ); // Get a Persistable (or NULL if it doesn't exist) std::shared_ptr getPersistable( const QString& name ); // Save a persistable to its permanent storage void save( const QString& name ); // Retrieve a persistable from permanent storage void retrieve( const QString& name ); private: // Can't be constructed or copied (singleton) PersistentInfo(); PersistentInfo( const PersistentInfo& ); ~PersistentInfo(); // Has migrateAndInit() been called? bool initialised_; // List of persistables QHash> objectList_; // Qt setting object QSettings* settings_; // allow this function to create one instance friend PersistentInfo& GetPersistentInfo(); }; PersistentInfo& GetPersistentInfo(); // Global function used to get a reference to an object // from the PersistentInfo store template std::shared_ptr Persistent( const char* name ) { std::shared_ptr p = GetPersistentInfo().getPersistable( QString( name ) ); return std::dynamic_pointer_cast(p); } template std::shared_ptr PersistentCopy( const char* name ) { std::shared_ptr p = GetPersistentInfo().getPersistable( QString( name ) ); return std::make_shared( *( std::dynamic_pointer_cast(p) ) ); } #endif glogg-1.1.0/src/main.cpp0000664000175000017500000002073212657420553014036 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2011, 2013, 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include #include #include #include namespace po = boost::program_options; #include #include using namespace std; #ifdef _WIN32 #include "unistd.h" #endif #include "persistentinfo.h" #include "sessioninfo.h" #include "configuration.h" #include "filterset.h" #include "recentfiles.h" #include "session.h" #include "mainwindow.h" #include "savedsearches.h" #include "loadingstatus.h" #include "externalcom.h" #ifdef GLOGG_SUPPORTS_DBUS #include "dbusexternalcom.h" #elif GLOGG_SUPPORTS_WINIPC #include "winexternalcom.h" #endif #include "log.h" static void print_version(); int main(int argc, char *argv[]) { QApplication app(argc, argv); string filename = ""; // Configuration bool new_session = false; bool load_session = false; bool multi_instance = false; #ifdef _WIN32 bool log_to_file = false; #endif TLogLevel logLevel = logWARNING; try { po::options_description desc("Usage: glogg [options] [file]"); desc.add_options() ("help,h", "print out program usage (this message)") ("version,v", "print glogg's version information") ("multi,m", "allow multiple instance of glogg to run simultaneously (use together with -s)") ("load-session,s", "load the previous session (default when no file is passed)") ("new-session,n", "do not load the previous session (default when a file is passed)") #ifdef _WIN32 ("log,l", "save the log to a file (Windows only)") #endif ("debug,d", "output more debug (include multiple times for more verbosity e.g. -dddd)") ; po::options_description desc_hidden("Hidden options"); // For -dd, -ddd... for ( string s = "dd"; s.length() <= 10; s.append("d") ) desc_hidden.add_options()(s.c_str(), "debug"); desc_hidden.add_options() ("input-file", po::value(), "input file") ; po::options_description all_options("all options"); all_options.add(desc).add(desc_hidden); po::positional_options_description positional; positional.add("input-file", 1); int command_line_style = (((po::command_line_style::unix_style ^ po::command_line_style::allow_guessing) | po::command_line_style::allow_long_disguise) ^ po::command_line_style::allow_sticky); po::variables_map vm; po::store(po::command_line_parser(argc, argv). options(all_options). positional(positional). style(command_line_style).run(), vm); po::notify(vm); if ( vm.count("help") ) { desc.print(cout); return 0; } if ( vm.count("version") ) { print_version(); return 0; } if ( vm.count( "debug" ) ) logLevel = logINFO; if ( vm.count( "multi" ) ) multi_instance = true; if ( vm.count( "new-session" ) ) new_session = true; if ( vm.count( "load-session" ) ) load_session = true; #ifdef _WIN32 if ( vm.count( "log" ) ) log_to_file = true; #endif for ( string s = "dd"; s.length() <= 10; s.append("d") ) if ( vm.count( s ) ) logLevel = (TLogLevel) (logWARNING + s.length()); if ( vm.count("input-file") ) filename = vm["input-file"].as(); } catch(exception& e) { cerr << "Option processing error: " << e.what() << endl; return 1; } catch(...) { cerr << "Exception of unknown type!\n"; } #ifdef _WIN32 if ( log_to_file ) { char file_name[255]; snprintf( file_name, sizeof file_name, "glogg_%d.log", getpid() ); FILE* file = fopen(file_name, "w"); Output2FILE::Stream() = file; } #endif FILELog::setReportingLevel( logLevel ); if ( ! filename.empty() ) { // Convert to absolute path QFileInfo file( QString::fromStdString( filename ) ); filename = file.absoluteFilePath().toStdString(); } // External communicator shared_ptr externalCommunicator = nullptr; shared_ptr externalInstance = nullptr; try { #ifdef GLOGG_SUPPORTS_DBUS externalCommunicator = make_shared(); externalInstance = shared_ptr( externalCommunicator->otherInstance() ); #elif GLOGG_SUPPORTS_WINIPC externalCommunicator = make_shared(); externalInstance = shared_ptr( externalCommunicator->otherInstance() ); #endif } catch(CantCreateExternalErr& e) { LOG(logWARNING) << "Cannot initialise external communication."; } LOG(logDEBUG) << "externalInstance = " << externalInstance; if ( ( ! multi_instance ) && externalInstance ) { uint32_t version = externalInstance->getVersion(); LOG(logINFO) << "Found another glogg (version = " << std::setbase(16) << version << ")"; externalInstance->loadFile( QString::fromStdString( filename ) ); return 0; } else { // FIXME: there is a race condition here. One glogg could start // between the declaration of externalInstance and here, // is it a problem? if ( externalCommunicator ) externalCommunicator->startListening(); } // Register types for Qt qRegisterMetaType("LoadingStatus"); // Register the configuration items GetPersistentInfo().migrateAndInit(); GetPersistentInfo().registerPersistable( std::make_shared(), QString( "session" ) ); GetPersistentInfo().registerPersistable( std::make_shared(), QString( "settings" ) ); GetPersistentInfo().registerPersistable( std::make_shared(), QString( "filterSet" ) ); GetPersistentInfo().registerPersistable( std::make_shared(), QString( "savedSearches" ) ); GetPersistentInfo().registerPersistable( std::make_shared(), QString( "recentFiles" ) ); #ifdef GLOGG_SUPPORTS_VERSION_CHECKING GetPersistentInfo().registerPersistable( std::make_shared(), QString( "versionChecker" ) ); #endif #ifdef _WIN32 // Allow the app to raise it's own windows (in case an external // glogg send us a file to open) AllowSetForegroundWindow(ASFW_ANY); #endif // We support high-dpi (aka Retina) displays app.setAttribute(Qt::AA_UseHighDpiPixmaps); // FIXME: should be replaced by a two staged init of MainWindow GetPersistentInfo().retrieve( QString( "settings" ) ); std::unique_ptr session( new Session() ); MainWindow mw( std::move( session ), externalCommunicator ); LOG(logDEBUG) << "MainWindow created."; mw.show(); // Load the existing session if needed if ( load_session || ( filename.empty() && !new_session ) ) mw.reloadSession(); mw.loadInitialFile( QString::fromStdString( filename ) ); mw.startBackgroundTasks(); return app.exec(); } static void print_version() { cout << "glogg " GLOGG_VERSION "\n"; #ifdef GLOGG_COMMIT cout << "Built " GLOGG_DATE " from " GLOGG_COMMIT "\n"; #endif cout << "Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicolas Bonnefon and other contributors\n"; cout << "This is free software. You may redistribute copies of it under the terms of\n"; cout << "the GNU General Public License .\n"; cout << "There is NO WARRANTY, to the extent permitted by law.\n"; } glogg-1.1.0/src/filtersdialog.ui0000664000175000017500000001273512657420553015601 0ustar nickonicko FiltersDialog 0 0 526 290 Filters QLayout::SetNoConstraint 6 New Filter :/images/plus.png:/images/plus.png Delete Filter :/images/minus.png:/images/minus.png Qt::Horizontal 40 20 Move Filter Up :/images/up.png:/images/up.png Move Filter Down :/images/down.png:/images/down.png Matching Pattern: false Fore Color: false false Back Color: Qt::Horizontal QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok glogg-1.1.0/src/quickfindmux.h0000664000175000017500000001077612657420553015275 0ustar nickonicko/* * Copyright (C) 2013, 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef QUICKFINDMUX_H #define QUICKFINDMUX_H #include #include #include #include #include "quickfindpattern.h" // Interface representing a widget searchable in both direction. class SearchableWidgetInterface { public: virtual void searchForward() = 0; virtual void searchBackward() = 0; virtual void incrementallySearchForward() = 0; virtual void incrementallySearchBackward() = 0; virtual void incrementalSearchStop() = 0; virtual void incrementalSearchAbort() = 0; }; // Interface representing the selector. It will be called and asked // who the search have to be forwarded to. class QuickFindMuxSelectorInterface { public: // Return the searchable widget to use. SearchableWidgetInterface* getActiveSearchable() const { return doGetActiveSearchable(); } // Return the list of all possible searchables, this // is done on registration in order to establish // listeners on all searchables. std::vector getAllSearchables() const { return doGetAllSearchables(); } protected: virtual SearchableWidgetInterface* doGetActiveSearchable() const = 0; virtual std::vector doGetAllSearchables() const = 0; }; class QFNotification; // Represents a multiplexer (unique application wise) dispatching the // Quick Find search from the UI to the relevant view. // It is also its responsability to determine if an incremental search // must be performed and to react accordingly. class QuickFindMux : public QObject { Q_OBJECT public: enum QFDirection { Forward, Backward, }; // Construct the multiplexer, taking a reference to the pattern QuickFindMux( std::shared_ptr pattern ); // Register a new selector, which will be called and asked // who the search have to be forwarded to. // The selector is called immediately when registering to get the list of // searchables. // The previous selector and its associated views are automatically // deregistered. // A null selector is accepted, in this case QFM functionalities are // disabled until a valid selector is registered. void registerSelector( const QuickFindMuxSelectorInterface* selector ); // Set the direction that will be used by the search when searching // forward. void setDirection( QFDirection direction ); signals: void patternChanged( const QString& ); void notify( const QFNotification& ); void clearNotification(); public slots: // Signal the current pattern must be altered (will start an incremental // search if the options are configured in such a way). void setNewPattern( const QString& new_pattern, bool ignore_case ); // Signal the current pattern must be altered and is confirmed // (will stop an incremental search if needed) void confirmPattern( const QString& new_pattern, bool ignore_case ); // Signal the user cancelled the search // (used for incremental only) void cancelSearch(); // Starts a search in the specified direction void searchNext(); void searchPrevious(); // Idem but ignore the direction and always search in the // specified direction void searchForward(); void searchBackward(); private slots: void changeQuickFind( const QString& new_pattern, QuickFindMux::QFDirection new_direction ); void notifyPatternChanged(); private: const QuickFindMuxSelectorInterface* selector_; // The (application wide) quick find pattern std::shared_ptr pattern_; QFDirection currentDirection_; std::vector registeredSearchables_; SearchableWidgetInterface* getSearchableWidget() const; void registerSearchable( QObject* searchable ); void unregisterAllSearchables(); }; #endif glogg-1.1.0/src/tabbedcrawlerwidget.h0000664000175000017500000000340012657420553016555 0ustar nickonicko/* * Copyright (C) 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef TABBEDCRAWLERWIDGET_H #define TABBEDCRAWLERWIDGET_H #include #include #include "loadingstatus.h" // This class represents glogg's main widget, a tabbed // group of CrawlerWidgets. // This is a very slightly customised QTabWidget, with // a particular style. class TabbedCrawlerWidget : public QTabWidget { Q_OBJECT public: TabbedCrawlerWidget(); virtual ~TabbedCrawlerWidget() {} // "Overridden" addTab/removeTab that automatically // show/hide the tab bar // The tab is created with the 'old data' icon. int addTab( QWidget* page, const QString& label ); void removeTab( int index ); // Set the data status (icon) for the tab number 'index' void setTabDataStatus( int index, DataStatus status ); protected: void keyPressEvent( QKeyEvent* event ); void mouseReleaseEvent( QMouseEvent *event); private: const QIcon olddata_icon_; const QIcon newdata_icon_; const QIcon newfiltered_icon_; QTabBar myTabBar_; }; #endif glogg-1.1.0/src/filewatcher.h0000664000175000017500000000311612657420553015051 0ustar nickonicko/* * Copyright (C) 2010, 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef FILEWATCHER_H #define FILEWATCHER_H #include // This abstract class defines a way to watch a group of (potentially // absent) files for update. class FileWatcher : public QObject { Q_OBJECT public: // Create an empty object FileWatcher() {} // Destroy the object virtual ~FileWatcher() {} // Adds the file to the list of file to watch // (do nothing if a file is already monitored) virtual void addFile( const QString& fileName ) = 0; // Removes the file to the list of file to watch // (do nothing if said file is not monitored) virtual void removeFile( const QString& fileName ) = 0; // Set the polling interval (0 means disabled) virtual void setPollingInterval( uint32_t ) {} signals: // Sent when the file on disk has changed in any way. void fileChanged( const QString& ); }; #endif glogg-1.1.0/src/watchtower.h0000664000175000017500000002523612657420553014752 0ustar nickonicko/* * Copyright (C) 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef WATCHTOWER_H #define WATCHTOWER_H #include "config.h" #include #include #include #include #include "watchtowerlist.h" // Allow the client to register for notification on an arbitrary number // of files. It will be notified to any change (creation/deletion/modification) // on those files. // It is passed a platform specific driver. // FIXME: Where to put that so it is not dependant // Registration object to implement RAII #ifdef HAS_TEMPLATE_ALIASES using Registration = std::shared_ptr; #else typedef std::shared_ptr Registration; #endif template class WatchTower { public: // Create an empty watchtower WatchTower(); // Destroy the object ~WatchTower(); // Set the polling interval (in ms) // 0 disables polling and is the default void setPollingInterval( int interval_ms ); // Add a file to the notification list. notification will be called when // the file is modified, moved or deleted. // Lifetime of the notification is tied to the Registration object returned. // Note the notification function is called with a mutex held and in a // third party thread, beware of races! Registration addFile( const std::string& file_name, std::function notification ); // Number of watched directories (for tests) unsigned int numberWatchedDirectories() const; private: // The driver (parametrised) Driver driver_; // List of files/dirs observed ObservedFileList observed_file_list_; // Protects the observed_file_list_ std::mutex observers_mutex_; // Polling interval (0 disables polling) uint32_t polling_interval_ms_ = 0; // Thread std::atomic_bool running_; std::thread thread_; // Exist as long as the onject exists, to ensure observers won't try to // call us if we are dead. std::shared_ptr heartBeat_; // Private member functions std::tuple addFileToDriver( const std::string& ); static void removeNotification( WatchTower* watch_tower, std::shared_ptr notification ); void run(); }; // Class template implementation #include #include #include #include #include "log.h" namespace { bool isSymLink( const std::string& file_name ); std::string directory_path( const std::string& path ); }; template WatchTower::WatchTower() : driver_(), thread_(), heartBeat_(std::shared_ptr((void*) 0xDEADC0DE, [] (void*) {})) { running_ = true; thread_ = std::thread( &WatchTower::run, this ); } template WatchTower::~WatchTower() { running_ = false; driver_.interruptWait(); thread_.join(); } template void WatchTower::setPollingInterval( int interval_ms ) { if ( polling_interval_ms_ != interval_ms ) { polling_interval_ms_ = interval_ms; // Break out of the wait if ( polling_interval_ms_ > 0 ) driver_.interruptWait(); } } template Registration WatchTower::addFile( const std::string& file_name, std::function notification ) { // LOG(logDEBUG) << "WatchTower::addFile " << file_name; std::weak_ptr weakHeartBeat(heartBeat_); std::lock_guard lock( observers_mutex_ ); auto existing_observed_file = observed_file_list_.searchByName( file_name ); std::shared_ptr> ptr( new std::function(std::move( notification )) ); if ( ! existing_observed_file ) { typename Driver::FileId file_id; typename Driver::SymlinkId symlink_id; std::tie( file_id, symlink_id ) = addFileToDriver( file_name ); auto new_file = observed_file_list_.addNewObservedFile( ObservedFile( file_name, ptr, file_id, symlink_id ) ); auto dir = observed_file_list_.watchedDirectoryForFile( file_name ); if ( ! dir ) { LOG(logDEBUG) << "WatchTower::addFile dir for " << file_name << " not watched, adding..."; dir = observed_file_list_.addWatchedDirectoryForFile( file_name, [this, weakHeartBeat] (ObservedDir* dir) { if ( auto heart_beat = weakHeartBeat.lock() ) { driver_.removeDir( dir->dir_id_ ); } } ); dir->dir_id_ = driver_.addDir( dir->path ); if ( ! dir->dir_id_.valid() ) { LOG(logWARNING) << "WatchTower::addFile driver failed to add dir"; dir = nullptr; } } else { LOG(logDEBUG) << "WatchTower::addFile Found exisiting watch for dir " << file_name; } // Associate the dir to the file if ( dir ) new_file->dir_ = dir; } else { existing_observed_file->addCallback( ptr ); } // Returns a shared pointer that removes its own entry // from the list of watched stuff when it goes out of scope! // Uses a custom deleter to do the work. return std::shared_ptr( 0x0, [this, ptr, weakHeartBeat] (void*) { if ( auto heart_beat = weakHeartBeat.lock() ) WatchTower::removeNotification( this, ptr ); } ); } template unsigned int WatchTower::numberWatchedDirectories() const { return observed_file_list_.numberWatchedDirectories(); } // // Private functions // // Add the passed file name to the driver, returning the file and symlink id template std::tuple WatchTower::addFileToDriver( const std::string& file_name ) { typename Driver::SymlinkId symlink_id; auto file_id = driver_.addFile( file_name ); if ( isSymLink( file_name ) ) { // We want to follow the name (as opposed to the inode) // so we watch the symlink as well. symlink_id = driver_.addSymlink( file_name ); } return std::make_tuple( file_id, symlink_id ); } // Called by the dtor for a registration object template void WatchTower::removeNotification( WatchTower* watch_tower, std::shared_ptr notification ) { LOG(logDEBUG) << "WatchTower::removeNotification"; std::lock_guard lock( watch_tower->observers_mutex_ ); auto file = watch_tower->observed_file_list_.removeCallback( notification ); if ( file ) { watch_tower->driver_.removeFile( file->file_id_ ); watch_tower->driver_.removeSymlink( file->symlink_id_ ); } } // Run in its own thread template void WatchTower::run() { while ( running_ ) { std::unique_lock lock( observers_mutex_ ); std::vector*> files_needing_readding; auto files = driver_.waitAndProcessEvents( &observed_file_list_, &lock, &files_needing_readding, polling_interval_ms_ ); LOG(logDEBUG) << "WatchTower::run: waitAndProcessEvents returned " << files.size() << " files, " << files_needing_readding.size() << " needing re-adding"; for ( auto file: files_needing_readding ) { // A file 'needing readding' has the same name, // but probably a different inode, so it needs // to be readded for some drivers that rely on the // inode (e.g. inotify) driver_.removeFile( file->file_id_ ); driver_.removeSymlink( file->symlink_id_ ); std::tie( file->file_id_, file->symlink_id_ ) = addFileToDriver( file->file_name_ ); } for ( auto file: files ) { for ( auto observer: file->callbacks ) { LOG(logDEBUG) << "WatchTower::run: notifying the client!"; // Here we have to cast our generic pointer back to // the function pointer in order to perform the call const std::shared_ptr> fptr = std::static_pointer_cast>( observer ); // The observer is called with the mutex held, // Let's hope it doesn't do anything too funky. (*fptr)(); file->markAsChanged(); } } if ( polling_interval_ms_ > 0 ) { // Also call files that have not been called for a while for ( auto file: observed_file_list_ ) { uint32_t ms_since_last_check = std::chrono::duration_cast( std::chrono::steady_clock::now() - file->timeForLastCheck() ).count(); if ( ( ms_since_last_check > polling_interval_ms_ ) && file->hasChanged() ) { LOG(logDEBUG) << "WatchTower::run: " << file->file_name_; for ( auto observer: file->callbacks ) { LOG(logDEBUG) << "WatchTower::run: notifying the client because of a timeout!"; // Here we have to cast our generic pointer back to // the function pointer in order to perform the call const std::shared_ptr> fptr = std::static_pointer_cast>( observer ); // The observer is called with the mutex held, // Let's hope it doesn't do anything too funky. (*fptr)(); } file->markAsChanged(); } } } } } namespace { bool isSymLink( const std::string& file_name ) { #ifdef HAVE_SYMLINK struct stat buf; lstat( file_name.c_str(), &buf ); return ( S_ISLNK(buf.st_mode) ); #else return false; #endif } }; #endif glogg-1.1.0/src/quickfind.cpp0000664000175000017500000002622712657420553015074 0ustar nickonicko/* * Copyright (C) 2010, 2013 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ // This file implements QuickFind. // This class implements the Quick Find mechanism using references // to the logData, the QFP and the selection passed. // Search is started just after the selection and the selection is updated // if a match is found. #include #include "log.h" #include "quickfindpattern.h" #include "selection.h" #include "data/abstractlogdata.h" #include "quickfind.h" void SearchingNotifier::reset() { dotToDisplay_ = 0; startTime_ = QTime::currentTime(); } void SearchingNotifier::sendNotification( qint64 current_line, qint64 nb_lines ) { LOG( logDEBUG ) << "Emitting Searching...."; qint64 progress; if ( current_line < 0 ) progress = ( nb_lines + current_line ) * 100 / nb_lines; else progress = current_line * 100 / nb_lines; emit notify( QFNotificationProgress( progress ) ); QApplication::processEvents( QEventLoop::ExcludeUserInputEvents ); startTime_ = QTime::currentTime().addMSecs( -800 ); } void QuickFind::LastMatchPosition::set( int line, int column ) { if ( ( line_ == -1 ) || ( ( line <= line_ ) && ( column < column_ ) ) ) { line_ = line; column_ = column; } } void QuickFind::LastMatchPosition::set( const FilePosition &position ) { set( position.line(), position.column() ); } bool QuickFind::LastMatchPosition::isLater( int line, int column ) const { if ( line_ == -1 ) return false; else if ( ( line == line_ ) && ( column >= column_ ) ) return true; else if ( line > line_ ) return true; else return false; } bool QuickFind::LastMatchPosition::isLater( const FilePosition &position ) const { return isLater( position.line(), position.column() ); } bool QuickFind::LastMatchPosition::isSooner( int line, int column ) const { if ( line_ == -1 ) return false; else if ( ( line == line_ ) && ( column <= column_ ) ) return true; else if ( line < line_ ) return true; else return false; } bool QuickFind::LastMatchPosition::isSooner( const FilePosition &position ) const { return isSooner( position.line(), position.column() ); } QuickFind::QuickFind( const AbstractLogData* const logData, Selection* selection, const QuickFindPattern* const quickFindPattern ) : logData_( logData ), selection_( selection ), quickFindPattern_( quickFindPattern ), lastMatch_(), firstMatch_(), searchingNotifier_(), incrementalSearchStatus_() { connect( &searchingNotifier_, SIGNAL( notify( const QFNotification& ) ), this, SIGNAL( notify( const QFNotification& ) ) ); } void QuickFind::incrementalSearchStop() { if ( incrementalSearchStatus_.isOngoing() ) { if ( selection_->isEmpty() ) { // Nothing found? // We reset the selection to what it was *selection_ = incrementalSearchStatus_.initialSelection(); } incrementalSearchStatus_ = IncrementalSearchStatus(); } } void QuickFind::incrementalSearchAbort() { if ( incrementalSearchStatus_.isOngoing() ) { // We reset the selection to what it was *selection_ = incrementalSearchStatus_.initialSelection(); incrementalSearchStatus_ = IncrementalSearchStatus(); } } qint64 QuickFind::incrementallySearchForward() { LOG( logDEBUG ) << "QuickFind::incrementallySearchForward"; // Position where we start the search from FilePosition start_position = selection_->getNextPosition(); if ( incrementalSearchStatus_.direction() == Forward ) { // An incremental search is active, we restart the search // from the initial point LOG( logDEBUG ) << "Restart search from initial point"; start_position = incrementalSearchStatus_.position(); } else { // It's a new search so we search from the selection incrementalSearchStatus_ = IncrementalSearchStatus( Forward, start_position, *selection_ ); } qint64 line_found = doSearchForward( start_position ); if ( line_found >= 0 ) { // We have found a result... // ... the caller will jump to this line. return line_found; } else { // No result... // ... we want the client to show the initial line. selection_->clear(); return incrementalSearchStatus_.position().line(); } } qint64 QuickFind::incrementallySearchBackward() { LOG( logDEBUG ) << "QuickFind::incrementallySearchBackward"; // Position where we start the search from FilePosition start_position = selection_->getPreviousPosition(); if ( incrementalSearchStatus_.direction() == Backward ) { // An incremental search is active, we restart the search // from the initial point LOG( logDEBUG ) << "Restart search from initial point"; start_position = incrementalSearchStatus_.position(); } else { // It's a new search so we search from the selection incrementalSearchStatus_ = IncrementalSearchStatus( Backward, start_position, *selection_ ); } qint64 line_found = doSearchBackward( start_position ); if ( line_found >= 0 ) { // We have found a result... // ... the caller will jump to this line. return line_found; } else { // No result... // ... we want the client to show the initial line. selection_->clear(); return incrementalSearchStatus_.position().line(); } } qint64 QuickFind::searchForward() { incrementalSearchStatus_ = IncrementalSearchStatus(); // Position where we start the search from FilePosition start_position = selection_->getNextPosition(); return doSearchForward( start_position ); } qint64 QuickFind::searchBackward() { incrementalSearchStatus_ = IncrementalSearchStatus(); // Position where we start the search from FilePosition start_position = selection_->getPreviousPosition(); return doSearchBackward( start_position ); } // Internal implementation of forward search, // returns the line where the pattern is found or -1 if not found. // Parameters are the position the search shall start qint64 QuickFind::doSearchForward( const FilePosition &start_position ) { bool found = false; int found_start_col; int found_end_col; if ( ! quickFindPattern_->isActive() ) return -1; // Optimisation: if we are already after the last match, // we don't do any search at all. if ( lastMatch_.isLater( start_position ) ) { // Send a notification emit notify( QFNotificationReachedEndOfFile() ); return -1; } qint64 line = start_position.line(); LOG( logDEBUG ) << "Start searching at line " << line; // We look at the rest of the first line if ( quickFindPattern_->isLineMatching( logData_->getExpandedLineString( line ), start_position.column() ) ) { quickFindPattern_->getLastMatch( &found_start_col, &found_end_col ); found = true; } else { searchingNotifier_.reset(); // And then the rest of the file qint64 nb_lines = logData_->getNbLine(); line++; while ( line < nb_lines ) { if ( quickFindPattern_->isLineMatching( logData_->getExpandedLineString( line ) ) ) { quickFindPattern_->getLastMatch( &found_start_col, &found_end_col ); found = true; break; } line++; // See if we need to notify of the ongoing search searchingNotifier_.ping( line, nb_lines ); } } if ( found ) { selection_->selectPortion( line, found_start_col, found_end_col ); // Clear any notification emit clearNotification(); return line; } else { // Update the position of the last match FilePosition last_match_position = selection_->getPreviousPosition(); lastMatch_.set( last_match_position ); // Send a notification emit notify( QFNotificationReachedEndOfFile() ); return -1; } } // Internal implementation of backward search, // returns the line where the pattern is found or -1 if not found. // Parameters are the position the search shall start qint64 QuickFind::doSearchBackward( const FilePosition &start_position ) { bool found = false; int start_col; int end_col; if ( ! quickFindPattern_->isActive() ) return -1; // Optimisation: if we are already before the first match, // we don't do any search at all. if ( firstMatch_.isSooner( start_position ) ) { // Send a notification emit notify( QFNotificationReachedBegininningOfFile() ); return -1; } qint64 line = start_position.line(); LOG( logDEBUG ) << "Start searching at line " << line; // We look at the beginning of the first line if ( ( start_position.column() > 0 ) && ( quickFindPattern_->isLineMatchingBackward( logData_->getExpandedLineString( line ), start_position.column() ) ) ) { quickFindPattern_->getLastMatch( &start_col, &end_col ); found = true; } else { searchingNotifier_.reset(); // And then the rest of the file qint64 nb_lines = logData_->getNbLine(); line--; while ( line >= 0 ) { if ( quickFindPattern_->isLineMatchingBackward( logData_->getExpandedLineString( line ) ) ) { quickFindPattern_->getLastMatch( &start_col, &end_col ); found = true; break; } line--; // See if we need to notify of the ongoing search searchingNotifier_.ping( -line, nb_lines ); } } if ( found ) { selection_->selectPortion( line, start_col, end_col ); // Clear any notification emit clearNotification(); return line; } else { // Update the position of the first match FilePosition first_match_position = selection_->getNextPosition(); firstMatch_.set( first_match_position ); // Send a notification LOG( logDEBUG ) << "QF: Send BOF notification."; emit notify( QFNotificationReachedBegininningOfFile() ); return -1; } } void QuickFind::resetLimits() { lastMatch_.reset(); firstMatch_.reset(); } glogg-1.1.0/src/viewtools.h0000664000175000017500000000424612657420553014614 0ustar nickonicko/* * Copyright (C) 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef VIEWTOOLS_H #define VIEWTOOLS_H #include #include // This class is a controller for an elastic hook manipulated with // the mouse wheel or touchpad. // It is used for the "follow" line at the end of the file. class ElasticHook : public QObject { Q_OBJECT public: ElasticHook( int hook_threshold ) : hook_threshold_( hook_threshold ) {} // Instruct the elastic to move by the passed pixels // (a positive value increase the elastic tension) void move( int value ); // Hold the elastic and prevent automatic decrease. void hold() { held_ = true; } // Release the elastic. void release() { held_ = false; } // Programmatically force the hook hooked or not. void hook( bool hooked ) { hooked_ = hooked; } // Return the "length" of the elastic hook. int length() const { return position_; } bool isHooked() const { return hooked_; } protected: void timerEvent( QTimerEvent *event ); signals: // Sent when the length has changed void lengthChanged(); // Sent when the hooked status has changed void hooked( bool is_hooked ); private: void decreasePosition(); static constexpr int TIMER_PERIOD_MS = 10; static constexpr int DECREASE_RATE = 4; const int hook_threshold_; bool hooked_ = false; bool held_ = false; int position_ = 0; int timer_id_ = 0; std::chrono::time_point last_update_; }; #endif glogg-1.1.0/src/dbuscontrol.h0000664000175000017500000000023112657420553015105 0ustar nickonicko#include #include class DBusControl : public QObject { Q_OBJECT public slots: QString version(void) { return "1.0.0"; } }; glogg-1.1.0/src/configuration.cpp0000664000175000017500000001152012657420553015754 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2013, 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include #include "log.h" #include "configuration.h" Configuration::Configuration() { // Should have some sensible default values. mainFont_ = QFont("monaco", 10); mainFont_.setStyleHint( QFont::Courier, QFont::PreferOutline ); mainRegexpType_ = ExtendedRegexp; quickfindRegexpType_ = FixedString; quickfindIncremental_ = true; #ifdef GLOGG_SUPPORTS_POLLING pollingEnabled_ = true; #else pollingEnabled_ = false; #endif pollIntervalMs_ = 2000; overviewVisible_ = true; lineNumbersVisibleInMain_ = false; lineNumbersVisibleInFiltered_ = true; QFontInfo fi(mainFont_); LOG(logDEBUG) << "Default font is " << fi.family().toStdString(); searchAutoRefresh_ = false; searchIgnoreCase_ = false; } // Accessor functions QFont Configuration::mainFont() const { return mainFont_; } void Configuration::setMainFont( QFont newFont ) { LOG(logDEBUG) << "Configuration::setMainFont"; mainFont_ = newFont; } void Configuration::retrieveFromStorage( QSettings& settings ) { LOG(logDEBUG) << "Configuration::retrieveFromStorage"; // Fonts QString family = settings.value( "mainFont.family" ).toString(); int size = settings.value( "mainFont.size" ).toInt(); // If no config read, keep the default if ( !family.isNull() ) mainFont_ = QFont( family, size ); // Regexp types mainRegexpType_ = static_cast( settings.value( "regexpType.main", mainRegexpType_ ).toInt() ); quickfindRegexpType_ = static_cast( settings.value( "regexpType.quickfind", quickfindRegexpType_ ).toInt() ); if ( settings.contains( "quickfind.incremental" ) ) quickfindIncremental_ = settings.value( "quickfind.incremental" ).toBool(); // "Advanced" settings if ( settings.contains( "polling.enabled" ) ) pollingEnabled_ = settings.value( "polling.enabled" ).toBool(); if ( settings.contains( "polling.intervalMs" ) ) pollIntervalMs_ = settings.value( "polling.intervalMs" ).toInt(); // View settings if ( settings.contains( "view.overviewVisible" ) ) overviewVisible_ = settings.value( "view.overviewVisible" ).toBool(); if ( settings.contains( "view.lineNumbersVisibleInMain" ) ) lineNumbersVisibleInMain_ = settings.value( "view.lineNumbersVisibleInMain" ).toBool(); if ( settings.contains( "view.lineNumbersVisibleInFiltered" ) ) lineNumbersVisibleInFiltered_ = settings.value( "view.lineNumbersVisibleInFiltered" ).toBool(); // Some sanity check (mainly for people upgrading) if ( quickfindIncremental_ ) quickfindRegexpType_ = FixedString; // Default crawler settings if ( settings.contains( "defaultView.searchAutoRefresh" ) ) searchAutoRefresh_ = settings.value( "defaultView.searchAutoRefresh" ).toBool(); if ( settings.contains( "defaultView.searchIgnoreCase" ) ) searchIgnoreCase_ = settings.value( "defaultView.searchIgnoreCase" ).toBool(); } void Configuration::saveToStorage( QSettings& settings ) const { LOG(logDEBUG) << "Configuration::saveToStorage"; QFontInfo fi(mainFont_); settings.setValue( "mainFont.family", fi.family() ); settings.setValue( "mainFont.size", fi.pointSize() ); settings.setValue( "regexpType.main", static_cast( mainRegexpType_ ) ); settings.setValue( "regexpType.quickfind", static_cast( quickfindRegexpType_ ) ); settings.setValue( "quickfind.incremental", quickfindIncremental_ ); settings.setValue( "polling.enabled", pollingEnabled_ ); settings.setValue( "polling.intervalMs", pollIntervalMs_ ); settings.setValue( "view.overviewVisible", overviewVisible_ ); settings.setValue( "view.lineNumbersVisibleInMain", lineNumbersVisibleInMain_ ); settings.setValue( "view.lineNumbersVisibleInFiltered", lineNumbersVisibleInFiltered_ ); settings.setValue( "defaultView.searchAutoRefresh", searchAutoRefresh_ ); settings.setValue( "defaultView.searchIgnoreCase", searchIgnoreCase_ ); } glogg-1.1.0/src/quickfindpattern.h0000664000175000017500000000557712657420553016144 0ustar nickonicko/* * Copyright (C) 2010, 2013 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef QUICKFINDPATTERN_H #define QUICKFINDPATTERN_H #include #include #include #include // Represents a match result for QuickFind class QuickFindMatch { public: // Construct a match (must be initialised) QuickFindMatch( int start_column, int length ) { startColumn_ = start_column; length_ = length; } // Accessor functions int startColumn() const { return startColumn_; } int length() const { return length_; } private: int startColumn_; int length_; }; // Represents a search pattern for QuickFind (without its results) class QuickFindPattern : public QObject { Q_OBJECT public: // Construct an empty search QuickFindPattern(); // Set the search to a new pattern, using the current // case status void changeSearchPattern( const QString& pattern ); // Set the search to a new pattern, as well as the case status void changeSearchPattern( const QString& pattern, bool ignoreCase ); // Returns whether the search is active (i.e. valid and non empty regexp) bool isActive() const { return active_; } // Return the text of the regex QString getPattern() const { return regexp_.pattern(); } // Returns whether the passed line match the quick find search. // If so, it populate the passed list with the list of matches // within this particular line. bool matchLine( const QString& line, QList& matches ) const; // Returns whether there is a match in the passed line, starting at // the passed column. // Results are stored internally. bool isLineMatching( const QString& line, int column = 0 ) const; // Same as isLineMatching but search backward bool isLineMatchingBackward( const QString& line, int column = -1 ) const; // Must be called when isLineMatching returns 'true', returns // the position of the first match found. void getLastMatch( int* start_col, int* end_col ) const; signals: // Sent when the pattern is changed void patternUpdated(); private: bool active_; QRegExp regexp_; mutable int lastMatchStart_; mutable int lastMatchEnd_; }; #endif glogg-1.1.0/src/encodingspeculator.cpp0000664000175000017500000000663012657420553017003 0ustar nickonicko/* * Copyright (C) 2016 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include "encodingspeculator.h" #include void EncodingSpeculator::inject_byte( uint8_t byte ) { if ( ! ( byte & 0x80 ) ) { // 7-bit character, all fine } else { switch ( state_ ) { case State::ASCIIOnly: case State::ValidUTF8: if ( ( byte & 0xE0 ) == 0xC0 ) { state_ = State::UTF8LeadingByteSeen; code_point_ = ( byte & 0x1F ) << 6; continuation_left_ = 1; min_value_ = 0x80; // std::cout << "Lead: cp= " << std::hex << code_point_ << std::endl; } else if ( ( byte & 0xF0 ) == 0xE0 ) { state_ = State::UTF8LeadingByteSeen; code_point_ = ( byte & 0x0F ) << 12; continuation_left_ = 2; min_value_ = 0x800; // std::cout << "Lead 3: cp= " << std::hex << code_point_ << std::endl; } else if ( ( byte & 0xF8 ) == 0xF0 ) { state_ = State::UTF8LeadingByteSeen; code_point_ = ( byte & 0x07 ) << 18; continuation_left_ = 3; min_value_ = 0x800; // std::cout << "Lead 4: cp= " << std::hex << code_point_ << std::endl; } else { state_ = State::Unknown8Bit; } break; case State::UTF8LeadingByteSeen: if ( ( byte & 0xC0 ) == 0x80 ) { --continuation_left_; code_point_ |= ( byte & 0x3F ) << (continuation_left_ * 6); // std::cout << "Cont: cp= " << std::hex << code_point_ << std::endl; if ( continuation_left_ == 0 ) { if ( code_point_ >= min_value_ ) state_ = State::ValidUTF8; else state_ = State::Unknown8Bit; } } else { state_ = State::Unknown8Bit; } break; } // state_ = State::Unknown8Bit; } } EncodingSpeculator::Encoding EncodingSpeculator::guess() const { Encoding guess; switch ( state_ ) { case State::ASCIIOnly: guess = Encoding::ASCII7; break; case State::Unknown8Bit: case State::UTF8LeadingByteSeen: guess = Encoding::ASCII8; break; case State::ValidUTF8: guess = Encoding::UTF8; break; default: guess = Encoding::ASCII8; } return guess; } glogg-1.1.0/src/abstractlogview.cpp0000664000175000017500000015332412657420553016316 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2011, 2012, 2013, 2015 Nicolas Bonnefon * and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ // This file implements the AbstractLogView base class. // Most of the actual drawing and event management common to the two views // is implemented in this class. The class only calls protected virtual // functions when view specific behaviour is desired, using the template // pattern. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "log.h" #include "persistentinfo.h" #include "filterset.h" #include "logmainview.h" #include "quickfind.h" #include "quickfindpattern.h" #include "overview.h" #include "configuration.h" namespace { int mapPullToFollowLength( int length ); }; namespace { int countDigits( quint64 n ) { if (n == 0) return 1; // We must force the compiler to not store intermediate results // in registers because this causes incorrect result on some // systems under optimizations level >0. For the skeptical: // // #include // #include // int main(int argc, char **argv) { // (void)argc; // long long int n = atoll(argv[1]); // return floor( log( n ) / log( 10 ) + 1 ); // } // // This is on Thinkpad T60 (Genuine Intel(R) CPU T2300). // $ g++ --version // g++ (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3 // $ g++ -O0 -Wall -W -o math math.cpp -lm; ./math 10; echo $? // 2 // $ g++ -O1 -Wall -W -o math math.cpp -lm; ./math 10; echo $? // 1 // // A fix is to (1) explicitly place intermediate results in // variables *and* (2) [A] mark them as 'volatile', or [B] pass // -ffloat-store to g++ (note that approach [A] is more portable). volatile qreal ln_n = qLn( n ); volatile qreal ln_10 = qLn( 10 ); volatile qreal lg_n = ln_n / ln_10; volatile qreal lg_n_1 = lg_n + 1; volatile qreal fl_lg_n_1 = qFloor( lg_n_1 ); return fl_lg_n_1; } } // anon namespace LineChunk::LineChunk( int first_col, int last_col, ChunkType type ) { // LOG(logDEBUG) << "new LineChunk: " << first_col << " " << last_col; start_ = first_col; end_ = last_col; type_ = type; } QList LineChunk::select( int sel_start, int sel_end ) const { QList list; if ( ( sel_start < start_ ) && ( sel_end < start_ ) ) { // Selection BEFORE this chunk: no change list << LineChunk( *this ); } else if ( sel_start > end_ ) { // Selection AFTER this chunk: no change list << LineChunk( *this ); } else /* if ( ( sel_start >= start_ ) && ( sel_end <= end_ ) ) */ { // We only want to consider what's inside THIS chunk sel_start = qMax( sel_start, start_ ); sel_end = qMin( sel_end, end_ ); if ( sel_start > start_ ) list << LineChunk( start_, sel_start - 1, type_ ); list << LineChunk( sel_start, sel_end, Selected ); if ( sel_end < end_ ) list << LineChunk( sel_end + 1, end_, type_ ); } return list; } inline void LineDrawer::addChunk( int first_col, int last_col, QColor fore, QColor back ) { if ( first_col < 0 ) first_col = 0; int length = last_col - first_col + 1; if ( length > 0 ) { list << Chunk ( first_col, length, fore, back ); } } inline void LineDrawer::addChunk( const LineChunk& chunk, QColor fore, QColor back ) { int first_col = chunk.start(); int last_col = chunk.end(); addChunk( first_col, last_col, fore, back ); } inline void LineDrawer::draw( QPainter& painter, int initialXPos, int initialYPos, int line_width, const QString& line, int leftExtraBackgroundPx ) { QFontMetrics fm = painter.fontMetrics(); const int fontHeight = fm.height(); const int fontAscent = fm.ascent(); // For some reason on Qt 4.8.2 for Win, maxWidth() is wrong but the // following give the right result, not sure why: const int fontWidth = fm.width( QChar('a') ); int xPos = initialXPos; int yPos = initialYPos; foreach ( Chunk chunk, list ) { // Draw each chunk // LOG(logDEBUG) << "Chunk: " << chunk.start() << " " << chunk.length(); QString cutline = line.mid( chunk.start(), chunk.length() ); const int chunk_width = cutline.length() * fontWidth; if ( xPos == initialXPos ) { // First chunk, we extend the left background a bit, // it looks prettier. painter.fillRect( xPos - leftExtraBackgroundPx, yPos, chunk_width + leftExtraBackgroundPx, fontHeight, chunk.backColor() ); } else { // other chunks... painter.fillRect( xPos, yPos, chunk_width, fontHeight, chunk.backColor() ); } painter.setPen( chunk.foreColor() ); painter.drawText( xPos, yPos + fontAscent, cutline ); xPos += chunk_width; } // Draw the empty block at the end of the line int blank_width = line_width - xPos; if ( blank_width > 0 ) painter.fillRect( xPos, yPos, blank_width, fontHeight, backColor_ ); } const int DigitsBuffer::timeout_ = 2000; DigitsBuffer::DigitsBuffer() : QObject() { } void DigitsBuffer::reset() { LOG(logDEBUG) << "DigitsBuffer::reset()"; timer_.stop(); digits_.clear(); } void DigitsBuffer::add( char character ) { LOG(logDEBUG) << "DigitsBuffer::add()"; digits_.append( QChar( character ) ); timer_.start( timeout_ , this ); } int DigitsBuffer::content() { int result = digits_.toInt(); reset(); return result; } void DigitsBuffer::timerEvent( QTimerEvent* event ) { if ( event->timerId() == timer_.timerId() ) { reset(); } else { QObject::timerEvent( event ); } } AbstractLogView::AbstractLogView(const AbstractLogData* newLogData, const QuickFindPattern* const quickFindPattern, QWidget* parent) : QAbstractScrollArea( parent ), followElasticHook_( HOOK_THRESHOLD ), lineNumbersVisible_( false ), selectionStartPos_(), selectionCurrentEndPos_(), autoScrollTimer_(), selection_(), quickFindPattern_( quickFindPattern ), quickFind_( newLogData, &selection_, quickFindPattern ) { logData = newLogData; followMode_ = false; selectionStarted_ = false; markingClickInitiated_ = false; firstLine = 0; firstCol = 0; overview_ = NULL; overviewWidget_ = NULL; // Display leftMarginPx_ = 0; // Fonts charWidth_ = 1; charHeight_ = 1; // Create the viewport QWidget setViewport( 0 ); // Hovering setMouseTracking( true ); lastHoveredLine_ = -1; // Init the popup menu createMenu(); // Signals connect( quickFindPattern_, SIGNAL( patternUpdated() ), this, SLOT ( handlePatternUpdated() ) ); connect( &quickFind_, SIGNAL( notify( const QFNotification& ) ), this, SIGNAL( notifyQuickFind( const QFNotification& ) ) ); connect( &quickFind_, SIGNAL( clearNotification() ), this, SIGNAL( clearQuickFindNotification() ) ); connect( &followElasticHook_, SIGNAL( lengthChanged() ), this, SLOT( repaint() ) ); connect( &followElasticHook_, SIGNAL( hooked( bool ) ), this, SIGNAL( followModeChanged( bool ) ) ); } AbstractLogView::~AbstractLogView() { } // // Received events // void AbstractLogView::changeEvent( QEvent* changeEvent ) { QAbstractScrollArea::changeEvent( changeEvent ); // Stop the timer if the widget becomes inactive if ( changeEvent->type() == QEvent::ActivationChange ) { if ( ! isActiveWindow() ) autoScrollTimer_.stop(); } viewport()->update(); } void AbstractLogView::mousePressEvent( QMouseEvent* mouseEvent ) { static std::shared_ptr config = Persistent( "settings" ); if ( mouseEvent->button() == Qt::LeftButton ) { int line = convertCoordToLine( mouseEvent->y() ); if ( mouseEvent->modifiers() & Qt::ShiftModifier ) { selection_.selectRangeFromPrevious( line ); emit updateLineNumber( line ); update(); } else { if ( mouseEvent->x() < bulletZoneWidthPx_ ) { // Mark a line if it is clicked in the left margin // (only if click and release in the same area) markingClickInitiated_ = true; markingClickLine_ = line; } else { // Select the line, and start a selection if ( line < logData->getNbLine() ) { selection_.selectLine( line ); emit updateLineNumber( line ); emit newSelection( line ); } // Remember the click in case we're starting a selection selectionStarted_ = true; selectionStartPos_ = convertCoordToFilePos( mouseEvent->pos() ); selectionCurrentEndPos_ = selectionStartPos_; } } // Invalidate our cache textAreaCache_.invalid_ = true; } else if ( mouseEvent->button() == Qt::RightButton ) { // Prepare the popup depending on selection type if ( selection_.isSingleLine() ) { copyAction_->setText( "&Copy this line" ); } else { copyAction_->setText( "&Copy" ); copyAction_->setStatusTip( tr("Copy the selection") ); } if ( selection_.isPortion() ) { findNextAction_->setEnabled( true ); findPreviousAction_->setEnabled( true ); addToSearchAction_->setEnabled( true ); } else { findNextAction_->setEnabled( false ); findPreviousAction_->setEnabled( false ); addToSearchAction_->setEnabled( false ); } // "Add to search" only makes sense in regexp mode if ( config->mainRegexpType() != ExtendedRegexp ) addToSearchAction_->setEnabled( false ); // Display the popup (blocking) popupMenu_->exec( QCursor::pos() ); } emit activity(); } void AbstractLogView::mouseMoveEvent( QMouseEvent* mouseEvent ) { // Selection implementation if ( selectionStarted_ ) { // Invalidate our cache textAreaCache_.invalid_ = true; QPoint thisEndPos = convertCoordToFilePos( mouseEvent->pos() ); if ( thisEndPos != selectionCurrentEndPos_ ) { // Are we on a different line? if ( selectionStartPos_.y() != thisEndPos.y() ) { if ( thisEndPos.y() != selectionCurrentEndPos_.y() ) { // This is a 'range' selection selection_.selectRange( selectionStartPos_.y(), thisEndPos.y() ); emit updateLineNumber( thisEndPos.y() ); update(); } } // So we are on the same line. Are we moving horizontaly? else if ( thisEndPos.x() != selectionCurrentEndPos_.x() ) { // This is a 'portion' selection selection_.selectPortion( thisEndPos.y(), selectionStartPos_.x(), thisEndPos.x() ); update(); } // On the same line, and moving vertically then else { // This is a 'line' selection selection_.selectLine( thisEndPos.y() ); emit updateLineNumber( thisEndPos.y() ); update(); } selectionCurrentEndPos_ = thisEndPos; // Do we need to scroll while extending the selection? QRect visible = viewport()->rect(); if ( visible.contains( mouseEvent->pos() ) ) autoScrollTimer_.stop(); else if ( ! autoScrollTimer_.isActive() ) autoScrollTimer_.start( 100, this ); } } else { considerMouseHovering( mouseEvent->x(), mouseEvent->y() ); } } void AbstractLogView::mouseReleaseEvent( QMouseEvent* mouseEvent ) { if ( markingClickInitiated_ ) { markingClickInitiated_ = false; int line = convertCoordToLine( mouseEvent->y() ); if ( line == markingClickLine_ ) { // Invalidate our cache textAreaCache_.invalid_ = true; emit markLine( line ); } } else { selectionStarted_ = false; if ( autoScrollTimer_.isActive() ) autoScrollTimer_.stop(); updateGlobalSelection(); } } void AbstractLogView::mouseDoubleClickEvent( QMouseEvent* mouseEvent ) { if ( mouseEvent->button() == Qt::LeftButton ) { // Invalidate our cache textAreaCache_.invalid_ = true; const QPoint pos = convertCoordToFilePos( mouseEvent->pos() ); selectWordAtPosition( pos ); } emit activity(); } void AbstractLogView::timerEvent( QTimerEvent* timerEvent ) { if ( timerEvent->timerId() == autoScrollTimer_.timerId() ) { QRect visible = viewport()->rect(); const QPoint globalPos = QCursor::pos(); const QPoint pos = viewport()->mapFromGlobal( globalPos ); QMouseEvent ev( QEvent::MouseMove, pos, globalPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier ); mouseMoveEvent( &ev ); int deltaX = qMax( pos.x() - visible.left(), visible.right() - pos.x() ) - visible.width(); int deltaY = qMax( pos.y() - visible.top(), visible.bottom() - pos.y() ) - visible.height(); int delta = qMax( deltaX, deltaY ); if ( delta >= 0 ) { if ( delta < 7 ) delta = 7; int timeout = 4900 / ( delta * delta ); autoScrollTimer_.start( timeout, this ); if ( deltaX > 0 ) horizontalScrollBar()->triggerAction( pos.x() 0 ) verticalScrollBar()->triggerAction( pos.y() modifiers() & Qt::ControlModifier) == Qt::ControlModifier; bool shiftModifier = (keyEvent->modifiers() & Qt::ShiftModifier) == Qt::ShiftModifier; if ( keyEvent->key() == Qt::Key_Left ) horizontalScrollBar()->triggerAction(QScrollBar::SliderPageStepSub); else if ( keyEvent->key() == Qt::Key_Right ) horizontalScrollBar()->triggerAction(QScrollBar::SliderPageStepAdd); else if ( keyEvent->key() == Qt::Key_Home && !controlModifier) jumpToStartOfLine(); else if ( keyEvent->key() == Qt::Key_End && !controlModifier) jumpToRightOfScreen(); else if ( (keyEvent->key() == Qt::Key_PageDown && controlModifier) || (keyEvent->key() == Qt::Key_End && controlModifier) ) { disableFollow(); // duplicate of 'G' action. selection_.selectLine( logData->getNbLine() - 1 ); emit updateLineNumber( logData->getNbLine() - 1 ); jumpToBottom(); } else if ( (keyEvent->key() == Qt::Key_PageUp && controlModifier) || (keyEvent->key() == Qt::Key_Home && controlModifier) ) { disableFollow(); // like 'g' but 0 input first line action. selectAndDisplayLine( 0 ); emit updateLineNumber( 0 ); } else if ( keyEvent->key() == Qt::Key_F3 && !shiftModifier ) searchNext(); // duplicate of 'n' action. else if ( keyEvent->key() == Qt::Key_F3 && shiftModifier ) searchPrevious(); // duplicate of 'N' action. else { const char character = (keyEvent->text())[0].toLatin1(); if ( keyEvent->modifiers() == Qt::NoModifier && ( character >= '0' ) && ( character <= '9' ) ) { // Adds the digit to the timed buffer digitsBuffer_.add( character ); } else { switch ( (keyEvent->text())[0].toLatin1() ) { case 'j': { int delta = qMax( 1, digitsBuffer_.content() ); disableFollow(); //verticalScrollBar()->triggerAction( //QScrollBar::SliderSingleStepAdd); moveSelection( delta ); break; } case 'k': { int delta = qMin( -1, - digitsBuffer_.content() ); disableFollow(); //verticalScrollBar()->triggerAction( //QScrollBar::SliderSingleStepSub); moveSelection( delta ); break; } case 'h': horizontalScrollBar()->triggerAction( QScrollBar::SliderSingleStepSub); break; case 'l': horizontalScrollBar()->triggerAction( QScrollBar::SliderSingleStepAdd); break; case '0': jumpToStartOfLine(); break; case '$': jumpToEndOfLine(); break; case 'g': { int newLine = qMax( 0, digitsBuffer_.content() - 1 ); if ( newLine >= logData->getNbLine() ) newLine = logData->getNbLine() - 1; disableFollow(); selectAndDisplayLine( newLine ); emit updateLineNumber( newLine ); break; } case 'G': disableFollow(); selection_.selectLine( logData->getNbLine() - 1 ); emit updateLineNumber( logData->getNbLine() - 1 ); jumpToBottom(); break; case 'n': emit searchNext(); break; case 'N': emit searchPrevious(); break; case '*': // Use the selected 'word' and search forward findNextSelected(); break; case '#': // Use the selected 'word' and search backward findPreviousSelected(); break; default: keyEvent->ignore(); } } } if ( keyEvent->isAccepted() ) { emit activity(); } else { QAbstractScrollArea::keyPressEvent( keyEvent ); } } void AbstractLogView::wheelEvent( QWheelEvent* wheelEvent ) { emit activity(); // LOG(logDEBUG) << "wheelEvent"; // This is to handle the case where follow mode is on, but the user // has moved using the scroll bar. We take them back to the bottom. if ( followMode_ ) jumpToBottom(); int y_delta = 0; if ( verticalScrollBar()->value() == verticalScrollBar()->maximum() ) { // First see if we need to block the elastic (on Mac) if ( wheelEvent->phase() == Qt::ScrollBegin ) followElasticHook_.hold(); else if ( wheelEvent->phase() == Qt::ScrollEnd ) followElasticHook_.release(); auto pixel_delta = wheelEvent->pixelDelta(); if ( pixel_delta.isNull() ) { y_delta = wheelEvent->angleDelta().y() / 0.7; } else { y_delta = pixel_delta.y(); } // LOG(logDEBUG) << "Elastic " << y_delta; followElasticHook_.move( - y_delta ); } // LOG(logDEBUG) << "Length = " << followElasticHook_.length(); if ( followElasticHook_.length() == 0 && !followElasticHook_.isHooked() ) { QAbstractScrollArea::wheelEvent( wheelEvent ); } } void AbstractLogView::resizeEvent( QResizeEvent* ) { if ( logData == NULL ) return; LOG(logDEBUG) << "resizeEvent received"; updateDisplaySize(); } bool AbstractLogView::event( QEvent* e ) { LOG(logDEBUG4) << "Event! Type: " << e->type(); // Make sure we ignore the gesture events as // they seem to be accepted by default. if ( e->type() == QEvent::Gesture ) { auto gesture_event = dynamic_cast( e ); if ( gesture_event ) { foreach( QGesture* gesture, gesture_event->gestures() ) { LOG(logDEBUG4) << "Gesture: " << gesture->gestureType(); gesture_event->ignore( gesture ); } // Ensure the event is sent up to parents who might care return false; } } return QAbstractScrollArea::event( e ); } void AbstractLogView::scrollContentsBy( int dx, int dy ) { LOG(logDEBUG) << "scrollContentsBy received " << dy; firstLine = std::max( static_cast( firstLine ) - dy, 0 ); firstCol = (firstCol - dx) > 0 ? firstCol - dx : 0; LineNumber last_line = firstLine + getNbVisibleLines(); // Update the overview if we have one if ( overview_ != NULL ) overview_->updateCurrentPosition( firstLine, last_line ); // Are we hovering over a new line? const QPoint mouse_pos = mapFromGlobal( QCursor::pos() ); considerMouseHovering( mouse_pos.x(), mouse_pos.y() ); // Redraw update(); } void AbstractLogView::paintEvent( QPaintEvent* paintEvent ) { const QRect invalidRect = paintEvent->rect(); if ( (invalidRect.isEmpty()) || (logData == NULL) ) return; LOG(logDEBUG4) << "paintEvent received, firstLine=" << firstLine << " rect: " << invalidRect.topLeft().x() << ", " << invalidRect.topLeft().y() << ", " << invalidRect.bottomRight().x() << ", " << invalidRect.bottomRight().y(); #ifdef GLOGG_PERF_MEASURE_FPS static uint32_t maxline = logData->getNbLine(); if ( ! perfCounter_.addEvent() && logData->getNbLine() > maxline ) { LOG(logWARNING) << "Redraw per second: " << perfCounter_.readAndReset() << " lines: " << logData->getNbLine(); perfCounter_.addEvent(); maxline = logData->getNbLine(); } #endif auto start = std::chrono::system_clock::now(); // Can we use our cache? int32_t delta_y = textAreaCache_.first_line_ - firstLine; if ( textAreaCache_.invalid_ || ( textAreaCache_.first_column_ != firstCol ) ) { // Force a full redraw delta_y = INT32_MAX; } if ( delta_y != 0 ) { // Full or partial redraw drawTextArea( &textAreaCache_.pixmap_, delta_y ); textAreaCache_.invalid_ = false; textAreaCache_.first_line_ = firstLine; textAreaCache_.first_column_ = firstCol; LOG(logDEBUG) << "End of writing " << std::chrono::duration_cast ( std::chrono::system_clock::now() - start ).count(); } else { // Use the cache as is: nothing to do! } // Height including the potentially invisible last line const int wholeHeight = getNbVisibleLines() * charHeight_; // Height in pixels of the "pull to follow" bottom bar. pullToFollowHeight_ = mapPullToFollowLength( followElasticHook_.length() ) + ( followElasticHook_.isHooked() ? ( wholeHeight - viewport()->height() ) + PULL_TO_FOLLOW_HOOKED_HEIGHT : 0 ); if ( pullToFollowHeight_ && ( pullToFollowCache_.nb_columns_ != getNbVisibleCols() ) ) { LOG(logDEBUG) << "Drawing pull to follow bar"; pullToFollowCache_.pixmap_ = drawPullToFollowBar( viewport()->width(), viewport()->devicePixelRatio() ); pullToFollowCache_.nb_columns_ = getNbVisibleCols(); } QPainter devicePainter( viewport() ); int drawingTopPosition = - pullToFollowHeight_; // This is to cover the special case where there is less than a screenful // worth of data, we want to see the document from the top, rather than // pushing the first couple of lines above the viewport. if ( followElasticHook_.isHooked() && ( logData->getNbLine() < getNbVisibleLines() ) ) drawingTopPosition += ( wholeHeight - viewport()->height() + PULL_TO_FOLLOW_HOOKED_HEIGHT ); devicePainter.drawPixmap( 0, drawingTopPosition, textAreaCache_.pixmap_ ); // Draw the "pull to follow" zone if needed if ( pullToFollowHeight_ ) { devicePainter.drawPixmap( 0, getNbVisibleLines() * charHeight_ - pullToFollowHeight_, pullToFollowCache_.pixmap_ ); } LOG(logDEBUG) << "End of repaint " << std::chrono::duration_cast ( std::chrono::system_clock::now() - start ).count(); } // These two functions are virtual and this implementation is clearly // only valid for a non-filtered display. // We count on the 'filtered' derived classes to override them. qint64 AbstractLogView::displayLineNumber( int lineNumber ) const { return lineNumber + 1; // show a 1-based index } qint64 AbstractLogView::maxDisplayLineNumber() const { return logData->getNbLine(); } void AbstractLogView::setOverview( Overview* overview, OverviewWidget* overview_widget ) { overview_ = overview; overviewWidget_ = overview_widget; if ( overviewWidget_ ) { connect( overviewWidget_, SIGNAL( lineClicked ( int ) ), this, SIGNAL( followDisabled() ) ); connect( overviewWidget_, SIGNAL( lineClicked ( int ) ), this, SLOT( jumpToLine( int ) ) ); } refreshOverview(); } void AbstractLogView::searchUsingFunction( qint64 (QuickFind::*search_function)() ) { disableFollow(); int line = (quickFind_.*search_function)(); if ( line >= 0 ) { LOG(logDEBUG) << "search " << line; displayLine( line ); emit updateLineNumber( line ); } } void AbstractLogView::searchForward() { searchUsingFunction( &QuickFind::searchForward ); } void AbstractLogView::searchBackward() { searchUsingFunction( &QuickFind::searchBackward ); } void AbstractLogView::incrementallySearchForward() { searchUsingFunction( &QuickFind::incrementallySearchForward ); } void AbstractLogView::incrementallySearchBackward() { searchUsingFunction( &QuickFind::incrementallySearchBackward ); } void AbstractLogView::incrementalSearchAbort() { quickFind_.incrementalSearchAbort(); emit changeQuickFind( "", QuickFindMux::Forward ); } void AbstractLogView::incrementalSearchStop() { quickFind_.incrementalSearchStop(); } void AbstractLogView::followSet( bool checked ) { followMode_ = checked; followElasticHook_.hook( checked ); update(); if ( checked ) jumpToBottom(); } void AbstractLogView::refreshOverview() { assert( overviewWidget_ ); // Create space for the Overview if needed if ( ( getOverview() != NULL ) && getOverview()->isVisible() ) { setViewportMargins( 0, 0, OVERVIEW_WIDTH, 0 ); overviewWidget_->show(); } else { setViewportMargins( 0, 0, 0, 0 ); overviewWidget_->hide(); } } // Reset the QuickFind when the pattern is changed. void AbstractLogView::handlePatternUpdated() { LOG(logDEBUG) << "AbstractLogView::handlePatternUpdated()"; quickFind_.resetLimits(); update(); } // OR the current with the current search expression void AbstractLogView::addToSearch() { if ( selection_.isPortion() ) { LOG(logDEBUG) << "AbstractLogView::addToSearch()"; emit addToSearch( selection_.getSelectedText( logData ) ); } else { LOG(logERROR) << "AbstractLogView::addToSearch called for a wrong type of selection"; } } // Find next occurence of the selected text (*) void AbstractLogView::findNextSelected() { // Use the selected 'word' and search forward if ( selection_.isPortion() ) { emit changeQuickFind( selection_.getSelectedText( logData ), QuickFindMux::Forward ); emit searchNext(); } } // Find next previous of the selected text (#) void AbstractLogView::findPreviousSelected() { if ( selection_.isPortion() ) { emit changeQuickFind( selection_.getSelectedText( logData ), QuickFindMux::Backward ); emit searchNext(); } } // Copy the selection to the clipboard void AbstractLogView::copy() { static QClipboard* clipboard = QApplication::clipboard(); clipboard->setText( selection_.getSelectedText( logData ) ); } // // Public functions // void AbstractLogView::updateData() { LOG(logDEBUG) << "AbstractLogView::updateData"; // Check the top Line is within range if ( firstLine >= logData->getNbLine() ) { firstLine = 0; firstCol = 0; verticalScrollBar()->setValue( 0 ); horizontalScrollBar()->setValue( 0 ); } // Crop selection if it become out of range selection_.crop( logData->getNbLine() - 1 ); // Adapt the scroll bars to the new content updateScrollBars(); // Calculate the index of the last line shown LineNumber last_line = std::min( static_cast( logData->getNbLine() ), static_cast( firstLine + getNbVisibleLines() ) ); // Reset the QuickFind in case we have new stuff to search into quickFind_.resetLimits(); if ( followMode_ ) jumpToBottom(); // Update the overview if we have one if ( overview_ != NULL ) overview_->updateCurrentPosition( firstLine, last_line ); // Invalidate our cache textAreaCache_.invalid_ = true; // Repaint! update(); } void AbstractLogView::updateDisplaySize() { // Font is assumed to be mono-space (is restricted by options dialog) QFontMetrics fm = fontMetrics(); charHeight_ = fm.height(); // For some reason on Qt 4.8.2 for Win, maxWidth() is wrong but the // following give the right result, not sure why: charWidth_ = fm.width( QChar('a') ); // Update the scroll bars updateScrollBars(); verticalScrollBar()->setPageStep( getNbVisibleLines() ); if ( followMode_ ) jumpToBottom(); LOG(logDEBUG) << "viewport.width()=" << viewport()->width(); LOG(logDEBUG) << "viewport.height()=" << viewport()->height(); LOG(logDEBUG) << "width()=" << width(); LOG(logDEBUG) << "height()=" << height(); if ( overviewWidget_ ) overviewWidget_->setGeometry( viewport()->width() + 2, 1, OVERVIEW_WIDTH - 1, viewport()->height() ); // Our text area cache is now invalid textAreaCache_.invalid_ = true; textAreaCache_.pixmap_ = QPixmap { viewport()->width() * viewport()->devicePixelRatio(), static_cast( getNbVisibleLines() ) * charHeight_ * viewport()->devicePixelRatio() }; textAreaCache_.pixmap_.setDevicePixelRatio( viewport()->devicePixelRatio() ); } int AbstractLogView::getTopLine() const { return firstLine; } QString AbstractLogView::getSelection() const { return selection_.getSelectedText( logData ); } void AbstractLogView::selectAll() { selection_.selectRange( 0, logData->getNbLine() - 1 ); update(); } void AbstractLogView::selectAndDisplayLine( int line ) { disableFollow(); selection_.selectLine( line ); displayLine( line ); emit updateLineNumber( line ); } // The difference between this function and displayLine() is quite // subtle: this one always jump, even if the line passed is visible. void AbstractLogView::jumpToLine( int line ) { // Put the selected line in the middle if possible int newTopLine = line - ( getNbVisibleLines() / 2 ); if ( newTopLine < 0 ) newTopLine = 0; // This will also trigger a scrollContents event verticalScrollBar()->setValue( newTopLine ); } void AbstractLogView::setLineNumbersVisible( bool lineNumbersVisible ) { lineNumbersVisible_ = lineNumbersVisible; } void AbstractLogView::forceRefresh() { // Invalidate our cache textAreaCache_.invalid_ = true; } // // Private functions // // Returns the number of lines visible in the viewport LineNumber AbstractLogView::getNbVisibleLines() const { return static_cast( viewport()->height() / charHeight_ + 1 ); } // Returns the number of columns visible in the viewport int AbstractLogView::getNbVisibleCols() const { return ( viewport()->width() - leftMarginPx_ ) / charWidth_ + 1; } // Converts the mouse x, y coordinates to the line number in the file int AbstractLogView::convertCoordToLine(int yPos) const { int line = firstLine + ( yPos + pullToFollowHeight_ ) / charHeight_; return line; } // Converts the mouse x, y coordinates to the char coordinates (in the file) // This function ensure the pos exists in the file. QPoint AbstractLogView::convertCoordToFilePos( const QPoint& pos ) const { int line = firstLine + ( pos.y() + pullToFollowHeight_ ) / charHeight_; if ( line >= logData->getNbLine() ) line = logData->getNbLine() - 1; if ( line < 0 ) line = 0; // Determine column in screen space and convert it to file space int column = firstCol + ( pos.x() - leftMarginPx_ ) / charWidth_; QString this_line = logData->getExpandedLineString( line ); const int length = this_line.length(); if ( column >= length ) column = length - 1; if ( column < 0 ) column = 0; LOG(logDEBUG4) << "AbstractLogView::convertCoordToFilePos col=" << column << " line=" << line; QPoint point( column, line ); return point; } // Makes the widget adjust itself to display the passed line. // Doing so, it will throw itself a scrollContents event. void AbstractLogView::displayLine( LineNumber line ) { // If the line is already the screen if ( ( line >= firstLine ) && ( line < ( firstLine + getNbVisibleLines() ) ) ) { // Invalidate our cache textAreaCache_.invalid_ = true; // ... don't scroll and just repaint update(); } else { jumpToLine( line ); } } // Move the selection up and down by the passed number of lines void AbstractLogView::moveSelection( int delta ) { LOG(logDEBUG) << "AbstractLogView::moveSelection delta=" << delta; QList selection = selection_.getLines(); int new_line; // If nothing is selected, do as if line -1 was. if ( selection.isEmpty() ) selection.append( -1 ); if ( delta < 0 ) new_line = selection.first() + delta; else new_line = selection.last() + delta; if ( new_line < 0 ) new_line = 0; else if ( new_line >= logData->getNbLine() ) new_line = logData->getNbLine() - 1; // Select and display the new line selection_.selectLine( new_line ); displayLine( new_line ); emit updateLineNumber( new_line ); } // Make the start of the lines visible void AbstractLogView::jumpToStartOfLine() { horizontalScrollBar()->setValue( 0 ); } // Make the end of the lines in the selection visible void AbstractLogView::jumpToEndOfLine() { QList selection = selection_.getLines(); // Search the longest line in the selection int max_length = 0; foreach ( int line, selection ) { int length = logData->getLineLength( line ); if ( length > max_length ) max_length = length; } horizontalScrollBar()->setValue( max_length - getNbVisibleCols() ); } // Make the end of the lines on the screen visible void AbstractLogView::jumpToRightOfScreen() { QList selection = selection_.getLines(); // Search the longest line on screen int max_length = 0; for ( auto i = firstLine; i <= ( firstLine + getNbVisibleLines() ); i++ ) { int length = logData->getLineLength( i ); if ( length > max_length ) max_length = length; } horizontalScrollBar()->setValue( max_length - getNbVisibleCols() ); } // Jump to the first line void AbstractLogView::jumpToTop() { // This will also trigger a scrollContents event verticalScrollBar()->setValue( 0 ); update(); // in case the screen hasn't moved } // Jump to the last line void AbstractLogView::jumpToBottom() { const int new_top_line = qMax( logData->getNbLine() - getNbVisibleLines() + 1, 0LL ); // This will also trigger a scrollContents event verticalScrollBar()->setValue( new_top_line ); update(); // in case the screen hasn't moved } // Returns whether the character passed is a 'word' character inline bool AbstractLogView::isCharWord( char c ) { if ( ( ( c >= 'A' ) && ( c <= 'Z' ) ) || ( ( c >= 'a' ) && ( c <= 'z' ) ) || ( ( c >= '0' ) && ( c <= '9' ) ) || ( ( c == '_' ) ) ) return true; else return false; } // Select the word under the given position void AbstractLogView::selectWordAtPosition( const QPoint& pos ) { const int x = pos.x(); const QString line = logData->getExpandedLineString( pos.y() ); if ( isCharWord( line[x].toLatin1() ) ) { // Search backward for the first character in the word int currentPos = x; for ( ; currentPos > 0; currentPos-- ) if ( ! isCharWord( line[currentPos].toLatin1() ) ) break; // Exclude the first char of the line if needed if ( ! isCharWord( line[currentPos].toLatin1() ) ) currentPos++; int start = currentPos; // Now search for the end currentPos = x; for ( ; currentPos < line.length() - 1; currentPos++ ) if ( ! isCharWord( line[currentPos].toLatin1() ) ) break; // Exclude the last char of the line if needed if ( ! isCharWord( line[currentPos].toLatin1() ) ) currentPos--; int end = currentPos; selection_.selectPortion( pos.y(), start, end ); updateGlobalSelection(); update(); } } // Update the system global (middle click) selection (X11 only) void AbstractLogView::updateGlobalSelection() { static QClipboard* const clipboard = QApplication::clipboard(); // Updating it only for "non-trivial" (range or portion) selections if ( ! selection_.isSingleLine() ) clipboard->setText( selection_.getSelectedText( logData ), QClipboard::Selection ); } // Create the pop-up menu void AbstractLogView::createMenu() { copyAction_ = new QAction( tr("&Copy"), this ); // No text as this action title depends on the type of selection connect( copyAction_, SIGNAL(triggered()), this, SLOT(copy()) ); // For '#' and '*', shortcuts doesn't seem to work but // at least it displays them in the menu, we manually handle those keys // as keys event anyway (in keyPressEvent). findNextAction_ = new QAction(tr("Find &next"), this); findNextAction_->setShortcut( Qt::Key_Asterisk ); findNextAction_->setStatusTip( tr("Find the next occurence") ); connect( findNextAction_, SIGNAL(triggered()), this, SLOT( findNextSelected() ) ); findPreviousAction_ = new QAction( tr("Find &previous"), this ); findPreviousAction_->setShortcut( tr("#") ); findPreviousAction_->setStatusTip( tr("Find the previous occurence") ); connect( findPreviousAction_, SIGNAL(triggered()), this, SLOT( findPreviousSelected() ) ); addToSearchAction_ = new QAction( tr("&Add to search"), this ); addToSearchAction_->setStatusTip( tr("Add the selection to the current search") ); connect( addToSearchAction_, SIGNAL( triggered() ), this, SLOT( addToSearch() ) ); popupMenu_ = new QMenu( this ); popupMenu_->addAction( copyAction_ ); popupMenu_->addSeparator(); popupMenu_->addAction( findNextAction_ ); popupMenu_->addAction( findPreviousAction_ ); popupMenu_->addAction( addToSearchAction_ ); } void AbstractLogView::considerMouseHovering( int x_pos, int y_pos ) { int line = convertCoordToLine( y_pos ); if ( ( x_pos < leftMarginPx_ ) && ( line >= 0 ) && ( line < logData->getNbLine() ) ) { // Mouse moved in the margin, send event up // (possibly to highlight the overview) if ( line != lastHoveredLine_ ) { LOG(logDEBUG) << "Mouse moved in margin line: " << line; emit mouseHoveredOverLine( line ); lastHoveredLine_ = line; } } else { if ( lastHoveredLine_ != -1 ) { emit mouseLeftHoveringZone(); lastHoveredLine_ = -1; } } } void AbstractLogView::updateScrollBars() { verticalScrollBar()->setRange( 0, std::max( 0LL, logData->getNbLine() - getNbVisibleLines() ) ); const int hScrollMaxValue = std::max( 0, logData->getMaxLength() - getNbVisibleCols() + 1 ); horizontalScrollBar()->setRange( 0, hScrollMaxValue ); } void AbstractLogView::drawTextArea( QPaintDevice* paint_device, int32_t delta_y ) { // LOG( logDEBUG ) << "devicePixelRatio: " << viewport()->devicePixelRatio(); // LOG( logDEBUG ) << "viewport size: " << viewport()->size().width(); // LOG( logDEBUG ) << "pixmap size: " << textPixmap.width(); // Repaint the viewport QPainter painter( paint_device ); // LOG( logDEBUG ) << "font: " << viewport()->font().family().toStdString(); // LOG( logDEBUG ) << "font painter: " << painter.font().family().toStdString(); painter.setFont( this->font() ); const int fontHeight = charHeight_; const int fontAscent = painter.fontMetrics().ascent(); const int nbCols = getNbVisibleCols(); const int paintDeviceHeight = paint_device->height() / viewport()->devicePixelRatio(); const int paintDeviceWidth = paint_device->width() / viewport()->devicePixelRatio(); const QPalette& palette = viewport()->palette(); std::shared_ptr filterSet = Persistent( "filterSet" ); QColor foreColor, backColor; static const QBrush normalBulletBrush = QBrush( Qt::white ); static const QBrush matchBulletBrush = QBrush( Qt::red ); static const QBrush markBrush = QBrush( "dodgerblue" ); static const int SEPARATOR_WIDTH = 1; static const qreal BULLET_AREA_WIDTH = 11; static const int CONTENT_MARGIN_WIDTH = 1; static const int LINE_NUMBER_PADDING = 3; // First check the lines to be drawn are within range (might not be the case if // the file has just changed) const int64_t lines_in_file = logData->getNbLine(); if ( firstLine > lines_in_file ) firstLine = lines_in_file - 1; const int64_t nbLines = std::min( static_cast( getNbVisibleLines() ), lines_in_file - firstLine ); const int bottomOfTextPx = nbLines * fontHeight; LOG(logDEBUG) << "drawing lines from " << firstLine << " (" << nbLines << " lines)"; LOG(logDEBUG) << "bottomOfTextPx: " << bottomOfTextPx; LOG(logDEBUG) << "Height: " << paintDeviceHeight; // Lines to write const QStringList lines = logData->getExpandedLines( firstLine, nbLines ); // First draw the bullet left margin painter.setPen(palette.color(QPalette::Text)); painter.fillRect( 0, 0, BULLET_AREA_WIDTH, paintDeviceHeight, Qt::darkGray ); // Column at which the content should start (pixels) qreal contentStartPosX = BULLET_AREA_WIDTH + SEPARATOR_WIDTH; // This is also the bullet zone width, used for marking clicks bulletZoneWidthPx_ = contentStartPosX; // Update the length of line numbers const int nbDigitsInLineNumber = countDigits( maxDisplayLineNumber() ); // Draw the line numbers area int lineNumberAreaStartX = 0; if ( lineNumbersVisible_ ) { int lineNumberWidth = charWidth_ * nbDigitsInLineNumber; int lineNumberAreaWidth = 2 * LINE_NUMBER_PADDING + lineNumberWidth; lineNumberAreaStartX = contentStartPosX; painter.setPen(palette.color(QPalette::Text)); /* Not sure if it looks good... painter.drawLine( contentStartPosX + lineNumberAreaWidth, 0, contentStartPosX + lineNumberAreaWidth, viewport()->height() ); */ painter.fillRect( contentStartPosX - SEPARATOR_WIDTH, 0, lineNumberAreaWidth + SEPARATOR_WIDTH, paintDeviceHeight, Qt::lightGray ); // Update for drawing the actual text contentStartPosX += lineNumberAreaWidth; } else { painter.fillRect( contentStartPosX - SEPARATOR_WIDTH, 0, SEPARATOR_WIDTH + 1, paintDeviceHeight, Qt::lightGray ); // contentStartPosX += SEPARATOR_WIDTH; } painter.drawLine( BULLET_AREA_WIDTH, 0, BULLET_AREA_WIDTH, paintDeviceHeight - 1 ); // This is the total width of the 'margin' (including line number if any) // used for mouse calculation etc... leftMarginPx_ = contentStartPosX + SEPARATOR_WIDTH; // Then draw each line for (int i = 0; i < nbLines; i++) { const LineNumber line_index = i + firstLine; // Position in pixel of the base line of the line to print const int yPos = i * fontHeight; const int xPos = contentStartPosX + CONTENT_MARGIN_WIDTH; // string to print, cut to fit the length and position of the view const QString line = lines[i]; const QString cutLine = line.mid( firstCol, nbCols ); if ( selection_.isLineSelected( line_index ) ) { // Reverse the selected line foreColor = palette.color( QPalette::HighlightedText ); backColor = palette.color( QPalette::Highlight ); painter.setPen(palette.color(QPalette::Text)); } else if ( filterSet->matchLine( logData->getLineString( line_index ), &foreColor, &backColor ) ) { // Apply a filter to the line } else { // Use the default colors foreColor = palette.color( QPalette::Text ); backColor = palette.color( QPalette::Base ); } // Is there something selected in the line? int sel_start, sel_end; bool isSelection = selection_.getPortionForLine( line_index, &sel_start, &sel_end ); // Has the line got elements to be highlighted QList qfMatchList; bool isMatch = quickFindPattern_->matchLine( line, qfMatchList ); if ( isSelection || isMatch ) { // We use the LineDrawer and its chunks because the // line has to be somehow highlighted LineDrawer lineDrawer( backColor ); // First we create a list of chunks with the highlights QList chunkList; int column = 0; // Current column in line space foreach( const QuickFindMatch match, qfMatchList ) { int start = match.startColumn() - firstCol; int end = start + match.length(); // Ignore matches that are *completely* outside view area if ( ( start < 0 && end < 0 ) || start >= nbCols ) continue; if ( start > column ) chunkList << LineChunk( column, start - 1, LineChunk::Normal ); column = qMin( start + match.length() - 1, nbCols ); chunkList << LineChunk( qMax( start, 0 ), column, LineChunk::Highlighted ); column++; } if ( column <= cutLine.length() - 1 ) chunkList << LineChunk( column, cutLine.length() - 1, LineChunk::Normal ); // Then we add the selection if needed QList newChunkList; if ( isSelection ) { sel_start -= firstCol; // coord in line space sel_end -= firstCol; foreach ( const LineChunk chunk, chunkList ) { newChunkList << chunk.select( sel_start, sel_end ); } } else newChunkList = chunkList; foreach ( const LineChunk chunk, newChunkList ) { // Select the colours QColor fore; QColor back; switch ( chunk.type() ) { case LineChunk::Normal: fore = foreColor; back = backColor; break; case LineChunk::Highlighted: fore = QColor( "black" ); back = QColor( "yellow" ); // fore = highlightForeColor; // back = highlightBackColor; break; case LineChunk::Selected: fore = palette.color( QPalette::HighlightedText ), back = palette.color( QPalette::Highlight ); break; } lineDrawer.addChunk ( chunk, fore, back ); } lineDrawer.draw( painter, xPos, yPos, viewport()->width(), cutLine, CONTENT_MARGIN_WIDTH ); } else { // Nothing to be highlighted, we print the whole line! painter.fillRect( xPos - CONTENT_MARGIN_WIDTH, yPos, viewport()->width(), fontHeight, backColor ); // (the rectangle is extended on the left to cover the small // margin, it looks better (LineDrawer does the same) ) painter.setPen( foreColor ); painter.drawText( xPos, yPos + fontAscent, cutLine ); } // Then draw the bullet painter.setPen( palette.color( QPalette::Text ) ); const qreal circleSize = 3; const qreal arrowHeight = 4; const qreal middleXLine = BULLET_AREA_WIDTH / 2; const qreal middleYLine = yPos + (fontHeight / 2); const LineType line_type = lineType( line_index ); if ( line_type == Marked ) { // A pretty arrow if the line is marked const QPointF points[7] = { QPointF(1, middleYLine - 2), QPointF(middleXLine, middleYLine - 2), QPointF(middleXLine, middleYLine - arrowHeight), QPointF(BULLET_AREA_WIDTH - 1, middleYLine), QPointF(middleXLine, middleYLine + arrowHeight), QPointF(middleXLine, middleYLine + 2), QPointF(1, middleYLine + 2 ), }; painter.setBrush( markBrush ); painter.drawPolygon( points, 7 ); } else { // For pretty circles painter.setRenderHint( QPainter::Antialiasing ); if ( lineType( line_index ) == Match ) painter.setBrush( matchBulletBrush ); else painter.setBrush( normalBulletBrush ); painter.drawEllipse( middleXLine - circleSize, middleYLine - circleSize, circleSize * 2, circleSize * 2 ); } // Draw the line number if ( lineNumbersVisible_ ) { static const QString lineNumberFormat( "%1" ); const QString& lineNumberStr = lineNumberFormat.arg( displayLineNumber( line_index ), nbDigitsInLineNumber ); painter.setPen( palette.color( QPalette::Text ) ); painter.drawText( lineNumberAreaStartX + LINE_NUMBER_PADDING, yPos + fontAscent, lineNumberStr ); } } // For each line if ( bottomOfTextPx < paintDeviceHeight ) { // The lines don't cover the whole device painter.fillRect( contentStartPosX, bottomOfTextPx, paintDeviceWidth - contentStartPosX, paintDeviceHeight, palette.color( QPalette::Window ) ); } } // Draw the "pull to follow" bar and return a pixmap. // The width is passed in "logic" pixels. QPixmap AbstractLogView::drawPullToFollowBar( int width, float pixel_ratio ) { static constexpr int barWidth = 40; QPixmap pixmap ( static_cast( width ) * pixel_ratio, barWidth * 6.0 ); pixmap.setDevicePixelRatio( pixel_ratio ); pixmap.fill( this->palette().color( this->backgroundRole() ) ); const int nbBars = width / (barWidth * 2) + 1; QPainter painter( &pixmap ); painter.setPen( QPen( QColor( 0, 0, 0, 0 ) ) ); painter.setBrush( QBrush( QColor( "lightyellow" ) ) ); for ( int i = 0; i < nbBars; ++i ) { QPoint points[4] = { { (i*2+1)*barWidth, 0 }, { 0, (i*2+1)*barWidth }, { 0, (i+1)*2*barWidth }, { (i+1)*2*barWidth, 0 } }; painter.drawConvexPolygon( points, 4 ); } return pixmap; } void AbstractLogView::disableFollow() { emit followModeChanged( false ); followElasticHook_.hook( false ); } namespace { // Convert the length of the pull to follow bar to pixels int mapPullToFollowLength( int length ) { return length / 14; } }; glogg-1.1.0/src/winfilewatcher.h0000664000175000017500000000337512657420553015576 0ustar nickonicko/* * Copyright (C) 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef WINFILEWATCHER_H #define WINFILEWATCHER_H #include "filewatcher.h" #include // #include "inotifywatchtower.h" // An implementation of FileWatcher, as an adapter to INotifyWatchTower. // This is Linux only, and require a recent version of the kernel. // Please note that due to the implementation of the constructor // this class is not thread safe and shall always be used from the main UI thread. class WinFileWatcher : public FileWatcher { Q_OBJECT public: // Create the empty object WinFileWatcher(); // Destroy the object ~WinFileWatcher(); void addFile( const QString& fileName ) override; void removeFile( const QString& fileName ) override; signals: void fileChanged( const QString& ); private: // The following variables are protected by watched_files_mutex_ QString watched_file_name_; // Reference to the (unique) watchtower. // static std::shared_ptr watch_tower_; // std::shared_ptr notification_; }; #endif glogg-1.1.0/src/recentfiles.cpp0000664000175000017500000000562612657420553015422 0ustar nickonicko/* * Copyright (C) 2011 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ // This file implements class RecentFiles #include #include #include "log.h" #include "recentfiles.h" const int RecentFiles::RECENTFILES_VERSION = 1; const int RecentFiles::MAX_NUMBER_OF_FILES = 10; RecentFiles::RecentFiles() : recentFiles_() { } void RecentFiles::addRecent( const QString& text ) { // First prune non existent files QMutableStringListIterator i(recentFiles_); while ( i.hasNext() ) { if ( !QFile::exists(i.next()) ) i.remove(); } // Remove any copy of the about to be added filename recentFiles_.removeAll( text ); // Add at the front recentFiles_.push_front( text ); // Trim the list if it's too long while ( recentFiles_.size() > MAX_NUMBER_OF_FILES ) recentFiles_.pop_back(); } QStringList RecentFiles::recentFiles() const { return recentFiles_; } // // Persistable virtual functions implementation // void RecentFiles::saveToStorage( QSettings& settings ) const { LOG(logDEBUG) << "RecentFiles::saveToStorage"; settings.beginGroup( "RecentFiles" ); settings.setValue( "version", RECENTFILES_VERSION ); settings.beginWriteArray( "filesHistory" ); for (int i = 0; i < recentFiles_.size(); ++i) { settings.setArrayIndex( i ); settings.setValue( "name", recentFiles_.at( i ) ); } settings.endArray(); settings.endGroup(); } void RecentFiles::retrieveFromStorage( QSettings& settings ) { LOG(logDEBUG) << "RecentFiles::retrieveFromStorage"; recentFiles_.clear(); if ( settings.contains( "RecentFiles/version" ) ) { // Unserialise the "new style" stored history settings.beginGroup( "RecentFiles" ); if ( settings.value( "version" ) == RECENTFILES_VERSION ) { int size = settings.beginReadArray( "filesHistory" ); for (int i = 0; i < size; ++i) { settings.setArrayIndex(i); QString search = settings.value( "name" ).toString(); recentFiles_.append( search ); } settings.endArray(); } else { LOG(logERROR) << "Unknown version of FilterSet, ignoring it..."; } settings.endGroup(); } } glogg-1.1.0/src/sessioninfo.h0000664000175000017500000000416212657420553015115 0ustar nickonicko/* * Copyright (C) 2011, 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef SESSIONINFO_H #define SESSIONINFO_H #include #include #include #include #include "persistable.h" // Simple component class containing information related to the session // to be persisted and reloaded upon start class SessionInfo : public Persistable { public: SessionInfo() : openFiles_() { } // Geometry of the main window // (this is an opaque string which is interpreted by the // MainWindow implementation) QByteArray geometry() const { return geometry_; } void setGeometry( const QByteArray& geometry ) { geometry_ = geometry; } struct OpenFile { std::string fileName; uint64_t topLine; // The view context contains parameter specific to the view's // implementation (such as geometry...) std::string viewContext; }; // List of the loaded files std::vector openFiles() const { return openFiles_; } void setOpenFiles( const std::vector& loaded_files ) { openFiles_ = loaded_files; } // Reads/writes the current config in the QSettings object passed virtual void saveToStorage( QSettings& settings ) const; virtual void retrieveFromStorage( QSettings& settings ); private: static const int OPENFILES_VERSION; QByteArray geometry_; QByteArray crawlerState_; std::vector openFiles_; }; #endif glogg-1.1.0/src/selection.h0000664000175000017500000001016412657420553014542 0ustar nickonicko/* * Copyright (C) 2010, 2013 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef SELECTION_H #define SELECTION_H #include #include #include "utils.h" class AbstractLogData; class Portion { public: Portion() { line_ = -1; } Portion( int line, int start_column, int end_column ) { line_ = line; startColumn_ = start_column; endColumn_ = end_column; } int line() const { return line_; } int startColumn() const { return startColumn_; } int endColumn() const { return endColumn_; } bool isValid() const { return ( line_ != -1 ); } private: int line_; int startColumn_; int endColumn_; }; // Represents a selection in an AbstractLogView class Selection { public: // Construct an empty selection Selection(); // Clear the selection void clear() { selectedPartial_.line = -1; selectedLine_ = -1; }; // Select one line void selectLine( int line ) { selectedPartial_.line = -1; selectedRange_.startLine = -1; selectedLine_ = line; }; // Select a portion of line (both start and end included) void selectPortion( int line, int start_column, int end_column ); void selectPortion( Portion selection ) { selectPortion( selection.line(), selection.startColumn(), selection.endColumn() ); } // Select a range of lines (both start and end included) void selectRange( int start_line, int end_line ); // Select a range from the previously selected line or beginning // of range (shift+click behaviour) void selectRangeFromPrevious( int line ); // Crop selection so that in fit in the range ending with the line passed. void crop( int last_line ); // Returns whether the selection is empty bool isEmpty() const { return ( selectedPartial_.line == -1 ) && ( selectedLine_ == -1 ); } // Returns whether the selection is a single line bool isSingleLine() const { return ( selectedLine_ != -1 ); } // Returns whether the selection is a portion of line bool isPortion() const { return ( selectedPartial_.line != -1 ); } // Returns whether a portion is selected or not on the passed line. // If so, returns the portion position. bool getPortionForLine( int line, int* start_column, int* end_column ) const; // Get a list of selected line(s), in order. QList getLines() const; // Returns wether the line passed is selected (entirely). bool isLineSelected( int line ) const; // Returns the line selected or -1 if not a single line selection qint64 selectedLine() const; // Returns the text selected from the passed AbstractLogData QString getSelectedText( const AbstractLogData* logData ) const; // Return the position immediately after the current selection // (used for searches). // This is the next character or the start of the next line. FilePosition getNextPosition() const; // Idem from the position immediately before selection. FilePosition getPreviousPosition() const; private: // Line number currently selected, or -1 if none selected int selectedLine_; struct SelectedPartial { int line; int startColumn; int endColumn; }; struct SelectedRange { // The limits of the range, sorted int startLine; int endLine; // The line selected first, used for shift+click int firstLine; }; struct SelectedPartial selectedPartial_; struct SelectedRange selectedRange_; }; #endif glogg-1.1.0/src/winwatchtowerdriver.cpp0000664000175000017500000003027112657420553017232 0ustar nickonicko/* * Copyright (C) 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include "winwatchtowerdriver.h" #define WIN32_LEAN_AND_MEAN #include #include #include #include "watchtowerlist.h" #include "utils.h" #include "log.h" namespace { std::string shortstringize( const std::wstring& long_string ); std::wstring longstringize( const std::string& short_string ); }; // Utility classes WinNotificationInfoList::WinNotificationInfoList( const char* buffer, size_t buffer_size ) { pointer_ = buffer; next_ = updateCurrentNotification( pointer_ ); } const char* WinNotificationInfoList::updateCurrentNotification( const char* new_position ) { using Action = WinNotificationInfo::Action; static const std::map int_to_action = { { FILE_ACTION_ADDED, Action::ADDED }, { FILE_ACTION_REMOVED, Action::REMOVED }, { FILE_ACTION_MODIFIED, Action::MODIFIED }, { FILE_ACTION_RENAMED_OLD_NAME, Action::RENAMED_OLD_NAME }, { FILE_ACTION_RENAMED_NEW_NAME, Action::RENAMED_NEW_NAME }, }; uint32_t next_offset = *( reinterpret_cast( new_position ) ); uint32_t action = *( reinterpret_cast( new_position ) + 1 ); uint32_t length = *( reinterpret_cast( new_position ) + 2 ); const std::wstring file_name = { reinterpret_cast( new_position + 12 ), length / 2 }; LOG(logDEBUG) << "Next: " << next_offset; LOG(logDEBUG) << "Action: " << action; LOG(logDEBUG) << "Length: " << length; current_notification_ = WinNotificationInfo( int_to_action.at( action ), file_name ); return ( next_offset == 0 ) ? nullptr : new_position + next_offset; } const char* WinNotificationInfoList::advanceToNext() { pointer_ = next_; if ( pointer_ ) next_ = updateCurrentNotification( pointer_ ); return pointer_; } // WinWatchTowerDriver::FileChangeToken void WinWatchTowerDriver::FileChangeToken::readFromFile( const std::string& file_name ) { // On Windows, we open the file and get its last written date/time // That seems to work alright in my tests, but who knows for sure? HANDLE hFile = CreateFile( #ifdef UNICODE longstringize( file_name ).c_str(), #else ( file_name ).c_str(), #endif 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if ( hFile == (HANDLE)-1 ) { DWORD err = GetLastError(); LOG(logERROR) << "FileChangeToken::readFromFile: failed with " << err; low_date_time_ = 0; high_date_time_ = 0; return; } else { BY_HANDLE_FILE_INFORMATION file_info; if ( GetFileInformationByHandle( hFile, &file_info ) ) { low_date_time_ = file_info.ftLastWriteTime.dwLowDateTime; high_date_time_ = file_info.ftLastWriteTime.dwHighDateTime; LOG(logDEBUG) << "FileChangeToken::readFromFile: low_date_time_ " << low_date_time_; LOG(logDEBUG) << "FileChangeToken::readFromFile: high_date_time_ " << high_date_time_; } else { DWORD err = GetLastError(); LOG(logERROR) << "FileChangeToken::readFromFile: failed with " << err; low_date_time_ = 0; high_date_time_ = 0; } } CloseHandle(hFile); } // WinWatchTowerDriver WinWatchTowerDriver::WinWatchTowerDriver() { hCompPort_ = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0x0, 0); } WinWatchTowerDriver::~WinWatchTowerDriver() { } WinWatchTowerDriver::FileId WinWatchTowerDriver::addFile( const std::string& file_name ) { // Nothing for Windows return { }; } WinWatchTowerDriver::SymlinkId WinWatchTowerDriver::addSymlink( const std::string& file_name ) { // Nothing for Windows return { }; } // This implementation is blocking, i.e. it will wait until the file // is effectively loaded in the watchtower thread. WinWatchTowerDriver::DirId WinWatchTowerDriver::addDir( const std::string& file_name ) { DirId dir_id { }; // Add will be done in the watchtower thread { /* std::lock_guard lk( action_mutex_ ); scheduled_action_ = std::make_unique( [this, file_name, &dir_id] { serialisedAddDir( file_name, dir_id ); } ); */ serialisedAddDir( file_name, dir_id ); } // Poke the thread interruptWait(); // Wait for the add task to be completed { /* std::unique_lock lk( action_mutex_ ); action_done_cv_.wait( lk, [this]{ return ( scheduled_action_ == nullptr ); } ); */ } LOG(logDEBUG) << "addDir returned " << dir_id.dir_record_; return dir_id; } void WinWatchTowerDriver::removeFile( const WinWatchTowerDriver::FileId& ) { } void WinWatchTowerDriver::removeSymlink( const SymlinkId& ) { } void WinWatchTowerDriver::removeDir( const DirId& dir_id ) { LOG(logDEBUG) << "Entering driver::removeDir"; if ( dir_id.dir_record_ ) { void* handle = dir_id.dir_record_->handle_; LOG(logDEBUG) << "WinWatchTowerDriver::removeDir handle=" << std::hex << handle; CloseHandle( handle ); } else { /* Happens when an error occured when creating the dir_record_ */ } } // // Private functions // // Add a file (run in the context of the WatchTower thread) void WinWatchTowerDriver::serialisedAddDir( const std::string& dir_name, DirId& dir_id ) { bool inserted = false; auto dir_record = std::make_shared( dir_name ); // The index we will be inserting this record (if success), plus 1 (to avoid // 0 which is used as a magic value) unsigned int index_record = dir_records_.size() + 1; LOG(logDEBUG) << "Adding dir for: " << dir_name; // Open the directory HANDLE hDir = CreateFile( #ifdef UNICODE longstringize( dir_name ).c_str(), #else ( dir_name ).c_str(), #endif FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL ); if ( hDir == INVALID_HANDLE_VALUE ) { LOG(logERROR) << "CreateFile failed for dir " << dir_name; } else { dir_record->handle_ = hDir; //create a IO completion port/or associate this key with //the existing IO completion port hCompPort_ = CreateIoCompletionPort( hDir, hCompPort_, //if m_hCompPort is NULL, hDir is associated with a NEW completion port, //if m_hCompPort is NON-NULL, hDir is associated with the existing completion port that the handle m_hCompPort references // We use the index (plus 1) of the weak_ptr as a key index_record, 0 ); LOG(logDEBUG) << "Weak ptr address stored: " << index_record; memset( &overlapped_, 0, sizeof overlapped_ ); inserted = ReadDirectoryChangesW( hDir, dir_record->buffer_, dir_record->buffer_length_, false, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE, &buffer_length_, // not set when using asynchronous mechanisms... &overlapped_, NULL ); // no completion routine if ( ! inserted ) { LOG(logERROR) << "ReadDirectoryChangesW failed (" << GetLastError() << ")"; CloseHandle( hDir ); } else { dir_id.dir_record_ = dir_record; } } if ( inserted ) { dir_records_.push_back( std::weak_ptr( dir_record ) ); } } std::vector*> WinWatchTowerDriver::waitAndProcessEvents( ObservedFileList* list, std::unique_lock* lock, std::vector*>* /* not needed in WinWatchTowerDriver */, int timeout_ms ) { std::vector*> files_to_notify { }; ULONG_PTR key = 0; DWORD num_bytes = 0; LPOVERLAPPED lpOverlapped = 0; if ( timeout_ms == 0 ) timeout_ms = INFINITE; lock->unlock(); LOG(logDEBUG) << "waitAndProcessEvents now blocking..."; BOOL status = GetQueuedCompletionStatus( hCompPort_, &num_bytes, &key, &lpOverlapped, timeout_ms ); lock->lock(); LOG(logDEBUG) << "Event (" << status << ") key: " << std::hex << key; if ( key ) { // Extract the dir from the completion key auto dir_record_ptr = dir_records_[key - 1]; LOG(logDEBUG) << "use_count = " << dir_record_ptr.use_count(); if ( std::shared_ptr dir_record = dir_record_ptr.lock() ) { LOG(logDEBUG) << "Got event for dir " << dir_record.get(); WinNotificationInfoList notification_info( dir_record->buffer_, dir_record->buffer_length_ ); for ( auto notification : notification_info ) { std::string file_path = dir_record->path_ + shortstringize( notification.fileName() ); LOG(logDEBUG) << "File is " << file_path; auto file = list->searchByName( file_path ); if ( file ) { files_to_notify.push_back( file ); } } // Re-listen for changes status = ReadDirectoryChangesW( dir_record->handle_, dir_record->buffer_, dir_record->buffer_length_, false, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE, &buffer_length_,// not set when using asynchronous mechanisms... &overlapped_, NULL ); // no completion routine } else { LOG(logWARNING) << "Looks like our dir_record disappeared!"; } } else { LOG(logDEBUG) << "Signaled"; } { std::lock_guard lk( action_mutex_ ); if ( scheduled_action_ ) { (*scheduled_action_)(); scheduled_action_ = nullptr; action_done_cv_.notify_all(); } } /* // Just in case someone is waiting for an action to complete std::lock_guard lk( action_mutex_ ); scheduled_action_ = nullptr; action_done_cv_.notify_all(); */ return files_to_notify; } void WinWatchTowerDriver::interruptWait() { LOG(logDEBUG) << "Driver::interruptWait()"; PostQueuedCompletionStatus( hCompPort_, 0, 0, NULL ); } namespace { std::string shortstringize( const std::wstring& long_string ) { std::string short_result {}; for ( wchar_t c : long_string ) { // FIXME: that does not work for non ASCII char!! char short_c = static_cast( c & 0x00FF ); short_result += short_c; } return short_result; } std::wstring longstringize( const std::string& short_string ) { std::wstring long_result {}; for ( char c : short_string ) { wchar_t long_c = static_cast( c ); long_result += long_c; } return long_result; } }; glogg-1.1.0/src/filtersdialog.h0000664000175000017500000000350512657420553015406 0ustar nickonicko/* * Copyright (C) 2009, 2010 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef FILTERSDIALOG_H #define FILTERSDIALOG_H #include #include #include "filterset.h" #include "ui_filtersdialog.h" class FiltersDialog : public QDialog, public Ui::FiltersDialog { Q_OBJECT public: FiltersDialog( QWidget* parent = 0 ); signals: // Is emitted when new settings must be used void optionsChanged(); private slots: void on_addFilterButton_clicked(); void on_removeFilterButton_clicked(); void on_buttonBox_clicked( QAbstractButton* button ); void on_upFilterButton_clicked(); void on_downFilterButton_clicked(); // Update the property (pattern, color...) fields from the // selected Filter. void updatePropertyFields(); // Update the selected Filter from the values in the property fields. void updateFilterProperties(); private: // Temporary filterset modified by the dialog // it is copied from the one in Config() std::shared_ptr filterSet; // Index of the row currently selected or -1 if none. int selectedRow_; void populateColors(); void populateFilterList(); }; #endif glogg-1.1.0/src/quickfind.h0000664000175000017500000001426612657420553014541 0ustar nickonicko/* * Copyright (C) 2010, 2013 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef QUICKFIND_H #define QUICKFIND_H #include #include #include #include "utils.h" #include "qfnotifications.h" #include "selection.h" class QuickFindPattern; class AbstractLogData; class Portion; // Handle "long processing" notifications to the UI. // reset() shall be called at the beginning of the search // and then ping() should be called periodically during the processing. // The notify() signal should be forwarded to the UI. class SearchingNotifier : public QObject { Q_OBJECT public: SearchingNotifier() {}; // Reset internal timers at the beiginning of the processing void reset(); // Shall be called frequently during processing, send the notification // and call the event loop when appropriate. // Pass the current line number and total number of line so that // a progress percentage is calculated and displayed. // (line shall be negative if ging in reverse) inline void ping( qint64 line, qint64 nb_lines ) { if ( startTime_.msecsTo( QTime::currentTime() ) > 1000 ) sendNotification( line, nb_lines ); } signals: // Sent when the UI shall display a message to the user. void notify( const QFNotification& message ); private: void sendNotification( qint64 current_line, qint64 nb_lines ); QTime startTime_; int dotToDisplay_; }; // Represents a search made with Quick Find (without its results) // it keeps a pointer to a set of data and to a QuickFindPattern which // are used for the searches. (the caller retains ownership of both). class QuickFind : public QObject { Q_OBJECT public: // Construct a search QuickFind( const AbstractLogData* const logData, Selection* selection, const QuickFindPattern* const quickFindPattern ); // Set the starting point that will be used by the next search void setSearchStartPoint( QPoint startPoint ); // Used for incremental searches // Return the first occurence of the passed pattern from the starting // point. These searches don't use the QFP and don't change the // starting point. // TODO Update comment qint64 incrementallySearchForward(); qint64 incrementallySearchBackward(); // Stop the currently ongoing incremental search, leave the selection // where it is if a match has been found, restore the old one // if not. Also throw away the start point associated with // the search. void incrementalSearchStop(); // Throw away the current search and restore the initial // position/selection void incrementalSearchAbort(); // Used for 'repeated' (n/N) QF searches using the current direction // Return the line of the first occurence of the QFP and // update the selection. It returns -1 if nothing is found. /* int searchNext(); int searchPrevious(); */ // Idem but ignore the direction and always search in the // specified direction qint64 searchForward(); qint64 searchBackward(); // Make the object forget the 'no more match' flag. void resetLimits(); signals: // Sent when the UI shall display a message to the user. void notify( const QFNotification& message ); // Sent when the UI shall clear the notification. void clearNotification(); private: enum QFDirection { None, Forward, Backward, }; class LastMatchPosition { public: LastMatchPosition() : line_( -1 ), column_( -1 ) {} void set( int line, int column ); void set( const FilePosition& position ); void reset() { line_ = -1; column_ = -1; } // Does the passed position come after the recorded one bool isLater( int line, int column ) const; bool isLater( const FilePosition& position ) const; // Does the passed position come before the recorded one bool isSooner( int line, int column ) const; bool isSooner( const FilePosition& position ) const; private: int line_; int column_; }; class IncrementalSearchStatus { public: /* Constructors */ IncrementalSearchStatus() : ongoing_( None ), position_(), initialSelection_() {} IncrementalSearchStatus( QFDirection direction, const FilePosition& position, const Selection& initial_selection ) : ongoing_( direction ), position_( position ), initialSelection_( initial_selection ) {} bool isOngoing() const { return ( ongoing_ != None ); } QFDirection direction() const { return ongoing_; } FilePosition position() const { return position_; } Selection initialSelection() const { return initialSelection_; } private: QFDirection ongoing_; FilePosition position_; Selection initialSelection_; }; // Pointers to external objects const AbstractLogData* const logData_; Selection* selection_; const QuickFindPattern* const quickFindPattern_; // Owned objects // Position of the last match in the file // (to avoid searching multiple times where there is no result) LastMatchPosition lastMatch_; LastMatchPosition firstMatch_; SearchingNotifier searchingNotifier_; // Incremental search status IncrementalSearchStatus incrementalSearchStatus_; // Private functions qint64 doSearchForward( const FilePosition &start_position ); qint64 doSearchBackward( const FilePosition &start_position ); }; #endif glogg-1.1.0/src/filtersdialog.cpp0000664000175000017500000002211612657420553015740 0ustar nickonicko/* * Copyright (C) 2009, 2010 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include "log.h" #include "configuration.h" #include "persistentinfo.h" #include "filterset.h" #include "filtersdialog.h" static const QString DEFAULT_PATTERN = "New Filter"; static const QString DEFAULT_FORE_COLOUR = "black"; static const QString DEFAULT_BACK_COLOUR = "white"; // Construct the box, including a copy of the global FilterSet // to handle ok/cancel/apply FiltersDialog::FiltersDialog( QWidget* parent ) : QDialog( parent ) { setupUi( this ); // Reload the filter list from disk (in case it has been changed // by another glogg instance) and copy it to here. GetPersistentInfo().retrieve( "filterSet" ); filterSet = PersistentCopy( "filterSet" ); populateColors(); populateFilterList(); // Start with all buttons disabled except 'add' removeFilterButton->setEnabled(false); upFilterButton->setEnabled(false); downFilterButton->setEnabled(false); // Default to black on white int index = foreColorBox->findText( DEFAULT_FORE_COLOUR ); foreColorBox->setCurrentIndex( index ); index = backColorBox->findText( DEFAULT_BACK_COLOUR ); backColorBox->setCurrentIndex( index ); // No filter selected by default selectedRow_ = -1; connect( filterListWidget, SIGNAL( itemSelectionChanged() ), this, SLOT( updatePropertyFields() ) ); connect( patternEdit, SIGNAL( textEdited( const QString& ) ), this, SLOT( updateFilterProperties() ) ); connect( foreColorBox, SIGNAL( activated( int ) ), this, SLOT( updateFilterProperties() ) ); connect( backColorBox, SIGNAL( activated( int ) ), this, SLOT( updateFilterProperties() ) ); } // // Slots // void FiltersDialog::on_addFilterButton_clicked() { LOG(logDEBUG) << "on_addFilterButton_clicked()"; Filter newFilter = Filter( DEFAULT_PATTERN, DEFAULT_FORE_COLOUR, DEFAULT_BACK_COLOUR ); filterSet->filterList << newFilter; // Add and select the newly created filter filterListWidget->addItem( DEFAULT_PATTERN ); filterListWidget->setCurrentRow( filterListWidget->count() - 1 ); } void FiltersDialog::on_removeFilterButton_clicked() { int index = filterListWidget->currentRow(); LOG(logDEBUG) << "on_removeFilterButton_clicked() index " << index; if ( index >= 0 ) { filterSet->filterList.removeAt( index ); filterListWidget->setCurrentRow( -1 ); delete filterListWidget->takeItem( index ); // Select the new item at the same index filterListWidget->setCurrentRow( index ); } } void FiltersDialog::on_upFilterButton_clicked() { int index = filterListWidget->currentRow(); LOG(logDEBUG) << "on_upFilterButton_clicked() index " << index; if ( index > 0 ) { filterSet->filterList.move( index, index - 1 ); QListWidgetItem* item = filterListWidget->takeItem( index ); filterListWidget->insertItem( index - 1, item ); filterListWidget->setCurrentRow( index - 1 ); } } void FiltersDialog::on_downFilterButton_clicked() { int index = filterListWidget->currentRow(); LOG(logDEBUG) << "on_downFilterButton_clicked() index " << index; if ( ( index >= 0 ) && ( index < ( filterListWidget->count() - 1 ) ) ) { filterSet->filterList.move( index, index + 1 ); QListWidgetItem* item = filterListWidget->takeItem( index ); filterListWidget->insertItem( index + 1, item ); filterListWidget->setCurrentRow( index + 1 ); } } void FiltersDialog::on_buttonBox_clicked( QAbstractButton* button ) { LOG(logDEBUG) << "on_buttonBox_clicked()"; QDialogButtonBox::ButtonRole role = buttonBox->buttonRole( button ); if ( ( role == QDialogButtonBox::AcceptRole ) || ( role == QDialogButtonBox::ApplyRole ) ) { // Copy the filter set and persist it to disk *( Persistent( "filterSet" ) ) = *filterSet; GetPersistentInfo().save( "filterSet" ); emit optionsChanged(); } if ( role == QDialogButtonBox::AcceptRole ) accept(); else if ( role == QDialogButtonBox::RejectRole ) reject(); } void FiltersDialog::updatePropertyFields() { if ( filterListWidget->selectedItems().count() >= 1 ) selectedRow_ = filterListWidget->row( filterListWidget->selectedItems().at(0) ); else selectedRow_ = -1; LOG(logDEBUG) << "updatePropertyFields(), row = " << selectedRow_; if ( selectedRow_ >= 0 ) { const Filter& currentFilter = filterSet->filterList.at( selectedRow_ ); patternEdit->setText( currentFilter.pattern() ); patternEdit->setEnabled( true ); int index = foreColorBox->findText( currentFilter.foreColorName() ); if ( index != -1 ) { LOG(logDEBUG) << "fore index = " << index; foreColorBox->setCurrentIndex( index ); foreColorBox->setEnabled( true ); } index = backColorBox->findText( currentFilter.backColorName() ); if ( index != -1 ) { LOG(logDEBUG) << "back index = " << index; backColorBox->setCurrentIndex( index ); backColorBox->setEnabled( true ); } // Enable the buttons if needed removeFilterButton->setEnabled( true ); upFilterButton->setEnabled( ( selectedRow_ > 0 ) ? true : false ); downFilterButton->setEnabled( ( selectedRow_ < ( filterListWidget->count() - 1 ) ) ? true : false ); } else { // Nothing is selected, greys the buttons patternEdit->setEnabled( false ); foreColorBox->setEnabled( false ); backColorBox->setEnabled( false ); } } void FiltersDialog::updateFilterProperties() { LOG(logDEBUG) << "updateFilterProperties()"; // If a row is selected if ( selectedRow_ >= 0 ) { Filter& currentFilter = filterSet->filterList[selectedRow_]; // Update the internal data currentFilter.setPattern( patternEdit->text() ); currentFilter.setForeColor( foreColorBox->currentText() ); currentFilter.setBackColor( backColorBox->currentText() ); // Update the entry in the filterList widget filterListWidget->currentItem()->setText( patternEdit->text() ); filterListWidget->currentItem()->setForeground( QBrush( QColor( currentFilter.foreColorName() ) ) ); filterListWidget->currentItem()->setBackground( QBrush( QColor( currentFilter.backColorName() ) ) ); } } // // Private functions // // Fills the color selection combo boxes void FiltersDialog::populateColors() { const QStringList colorNames = QStringList() // Basic 16 HTML colors (minus greys): << "black" << "white" << "maroon" << "red" << "purple" << "fuchsia" << "green" << "lime" << "olive" << "yellow" << "navy" << "blue" << "teal" << "aqua" // Greys << "gainsboro" << "lightgrey" << "silver" << "darkgrey" << "grey" << "dimgrey" // Reds << "tomato" << "orangered" << "orange" << "crimson" << "darkred" // Greens << "greenyellow" << "lightgreen" << "darkgreen" << "lightseagreen" // Blues << "lightcyan" << "darkturquoise" << "steelblue" << "lightblue" << "royalblue" << "darkblue" << "midnightblue" // Browns << "bisque" << "tan" << "sandybrown" << "chocolate"; for ( QStringList::const_iterator i = colorNames.constBegin(); i != colorNames.constEnd(); ++i ) { QPixmap solidPixmap( 20, 10 ); solidPixmap.fill( QColor( *i ) ); QIcon* solidIcon = new QIcon( solidPixmap ); foreColorBox->addItem( *solidIcon, *i ); backColorBox->addItem( *solidIcon, *i ); } } void FiltersDialog::populateFilterList() { filterListWidget->clear(); foreach ( Filter filter, filterSet->filterList ) { QListWidgetItem* new_item = new QListWidgetItem( filter.pattern() ); // new_item->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled ); new_item->setForeground( QBrush( QColor( filter.foreColorName() ) ) ); new_item->setBackground( QBrush( QColor( filter.backColorName() ) ) ); filterListWidget->addItem( new_item ); } } glogg-1.1.0/src/overview.cpp0000664000175000017500000001042012657420553014751 0ustar nickonicko/* * Copyright (C) 2011, 2012 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ // This file implements the Overview class. // It provides support for drawing the match overview sidebar but // the actual drawing is done in AbstractLogView which uses this class. #include "log.h" #include "data/logfiltereddata.h" #include "overview.h" Overview::Overview() : matchLines_(), markLines_() { logFilteredData_ = NULL; linesInFile_ = 0; topLine_ = 0; nbLines_ = 0; height_ = 0; dirty_ = true; visible_ = false; } Overview::~Overview() { } void Overview::setFilteredData( const LogFilteredData* logFilteredData ) { logFilteredData_ = logFilteredData; } void Overview::updateData( int totalNbLine ) { LOG(logDEBUG) << "OverviewWidget::updateData " << totalNbLine; linesInFile_ = totalNbLine; dirty_ = true; } void Overview::updateView( int height ) { // We don't touch the cache if the height hasn't changed if ( ( height != height_ ) || ( dirty_ == true ) ) { height_ = height; recalculatesLines(); } } const QVector* Overview::getMatchLines() const { return &matchLines_; } const QVector* Overview::getMarkLines() const { return &markLines_; } std::pair Overview::getViewLines() const { int top = 0; int bottom = height_ - 1; if ( linesInFile_ > 0 ) { top = (int)((qint64)topLine_ * height_ / linesInFile_); bottom = (int)((qint64)top + nbLines_ * height_ / linesInFile_); } return std::pair(top, bottom); } int Overview::fileLineFromY( int position ) const { int line = (int)((qint64)position * linesInFile_ / height_); return line; } int Overview::yFromFileLine( int file_line ) const { int position = 0; if ( linesInFile_ > 0 ) position = (int)((qint64)file_line * height_ / linesInFile_); return position; } // Update the internal cache void Overview::recalculatesLines() { LOG(logDEBUG) << "OverviewWidget::recalculatesLines"; if ( logFilteredData_ != NULL ) { matchLines_.clear(); markLines_.clear(); if ( linesInFile_ > 0 ) { for ( int i = 0; i < logFilteredData_->getNbLine(); i++ ) { LogFilteredData::FilteredLineType line_type = logFilteredData_->filteredLineTypeByIndex( i ); int line = (int) logFilteredData_->getMatchingLineNumber( i ); int position = (int)( (qint64)line * height_ / linesInFile_ ); if ( line_type == LogFilteredData::Match ) { if ( ( ! matchLines_.isEmpty() ) && matchLines_.last().position() == position ) { // If the line is already there, we increase its weight matchLines_.last().load(); } else { // If not we just add it matchLines_.append( WeightedLine( position ) ); } } else { if ( ( ! markLines_.isEmpty() ) && markLines_.last().position() == position ) { // If the line is already there, we increase its weight markLines_.last().load(); } else { // If not we just add it markLines_.append( WeightedLine( position ) ); } } } } } else LOG(logDEBUG) << "Overview::recalculatesLines: logFilteredData_ == NULL"; dirty_ = false; } glogg-1.1.0/src/persistentinfo.cpp0000664000175000017500000000630612657420553016167 0ustar nickonicko/* * Copyright (C) 2011, 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ // Implements PersistentInfo, a singleton class which store/retrieve objects // to persistent storage. #include "persistentinfo.h" #include #include #include "log.h" #include "persistable.h" PersistentInfo::PersistentInfo() { settings_ = NULL; initialised_ = false; } PersistentInfo::~PersistentInfo() { if ( initialised_ ) delete settings_; } void PersistentInfo::migrateAndInit() { assert( initialised_ == false ); #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) // On Windows, we use .ini files and import from the registry if no // .ini file is found (glogg <= 0.9 used the registry). // This store the config file in %appdata% settings_ = new QSettings( QSettings::IniFormat, QSettings::UserScope, "glogg", "glogg" ); if ( settings_->childKeys().count() == 0 ) { LOG(logWARNING) << "INI file empty, trying to import from registry"; QSettings registry( "glogg", "glogg" ); foreach ( QString key, registry.allKeys() ) { settings_->setValue( key, registry.value( key ) ); } } #else // We use default Qt storage on proper OSes settings_ = new QSettings( "glogg", "glogg" ); #endif initialised_ = true; } void PersistentInfo::registerPersistable( std::shared_ptr object, const QString& name ) { assert( initialised_ ); objectList_.insert( name, object ); } std::shared_ptr PersistentInfo::getPersistable( const QString& name ) { assert( initialised_ ); std::shared_ptr object = objectList_.value( name, NULL ); return object; } void PersistentInfo::save( const QString& name ) { assert( initialised_ ); if ( objectList_.contains( name ) ) objectList_.value( name )->saveToStorage( *settings_ ); else LOG(logERROR) << "Unregistered persistable " << name.toStdString(); // Sync to ensure it is propagated to other processes settings_->sync(); } void PersistentInfo::retrieve( const QString& name ) { assert( initialised_ ); // Sync to ensure it has been propagated from other processes settings_->sync(); if ( objectList_.contains( name ) ) objectList_.value( name )->retrieveFromStorage( *settings_ ); else LOG(logERROR) << "Unregistered persistable " << name.toStdString(); } // Friend function to construct/get the singleton PersistentInfo& GetPersistentInfo() { static PersistentInfo pInfo; return pInfo; } glogg-1.1.0/src/dbusexternalcom.cpp0000664000175000017500000000702112657420553016305 0ustar nickonicko/* * Copyright (C) 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include "dbusexternalcom.h" #include #include "log.h" static const char* DBUS_SERVICE_NAME = "org.bonnefon.glogg"; DBusExternalCommunicator::DBusExternalCommunicator() { if (!QDBusConnection::sessionBus().isConnected()) { LOG(logERROR) << "Cannot connect to the D-Bus session bus.\n" << "To start it, run:\n" << "\teval `dbus-launch --auto-syntax`\n"; throw CantCreateExternalErr(); } dbus_iface_object_ = std::make_shared(); connect( dbus_iface_object_.get(), SIGNAL( signalLoadFile( const QString& ) ), this, SIGNAL( loadFile( const QString& ) ) ); } // If listening fails (e.g. another glogg is already listening, // the function will fail silently and no listening will be done. void DBusExternalCommunicator::startListening() { if (!QDBusConnection::sessionBus().registerService( DBUS_SERVICE_NAME )) { LOG(logERROR) << qPrintable(QDBusConnection::sessionBus().lastError().message()); } if ( !QDBusConnection::sessionBus().registerObject( "/", dbus_iface_object_.get(), QDBusConnection::ExportAllContents ) ) { LOG(logERROR) << qPrintable(QDBusConnection::sessionBus().lastError().message()); } } ExternalInstance* DBusExternalCommunicator::otherInstance() const { try { return static_cast( new DBusExternalInstance() ); } catch ( CantCreateExternalErr ) { LOG(logINFO) << "Cannot find external D-Bus correspondant, we are the only glogg out there."; return nullptr; } } qint32 DBusExternalCommunicator::version() const { return 3; } qint32 DBusInterfaceExternalCommunicator::version() const { return 0x010000; } void DBusInterfaceExternalCommunicator::loadFile( const QString& file_name ) { LOG(logDEBUG) << "DBusInterfaceExternalCommunicator::loadFile()"; emit signalLoadFile( file_name ); } DBusExternalInstance::DBusExternalInstance() { dbusInterface_ = std::make_shared( DBUS_SERVICE_NAME, "/", "", QDBusConnection::sessionBus() ); if ( ! dbusInterface_->isValid() ) { throw CantCreateExternalErr(); } } void DBusExternalInstance::loadFile( const QString& file_name ) const { QDBusReply reply = dbusInterface_->call( "loadFile", file_name ); if ( ! reply.isValid() ) { LOG( logWARNING ) << "Invalid reply from D-Bus call: " << qPrintable( reply.error().message() ); } } uint32_t DBusExternalInstance::getVersion() const { QDBusReply reply = dbusInterface_->call( "version" ); if ( ! reply.isValid() ) { LOG( logWARNING ) << "Invalid reply from D-Bus call: " << qPrintable( reply.error().message() ); return 0; } return (uint32_t) reply.value(); } glogg-1.1.0/src/mainwindow.h0000664000175000017500000001521712657420553014735 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2011, 2013, 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include "session.h" #include "crawlerwidget.h" #include "infoline.h" #include "signalmux.h" #include "tabbedcrawlerwidget.h" #include "quickfindwidget.h" #include "quickfindmux.h" #ifdef GLOGG_SUPPORTS_VERSION_CHECKING #include "versionchecker.h" #endif class QAction; class QActionGroup; class Session; class RecentFiles; class MenuActionToolTipBehavior; class ExternalCommunicator; // Main window of the application, creates menus, toolbar and // the CrawlerWidget class MainWindow : public QMainWindow { Q_OBJECT public: // Constructor // The ownership of the session is transferred to us MainWindow( std::unique_ptr session, std::shared_ptr external_communicator ); // Re-load the files from the previous session void reloadSession(); // Loads the initial file (parameter passed or from config file) void loadInitialFile( QString fileName ); // Starts the lower priority activities the MW controls such as // version checking etc... void startBackgroundTasks(); protected: void closeEvent( QCloseEvent* event ); // Drag and drop support void dragEnterEvent( QDragEnterEvent* event ); void dropEvent( QDropEvent* event ); void keyPressEvent( QKeyEvent* keyEvent ); private slots: void open(); void openRecentFile(); void closeTab(); void closeAll(); void selectAll(); void copy(); void find(); void filters(); void options(); void about(); void aboutQt(); void encodingChanged( QAction* action ); // Change the view settings void toggleOverviewVisibility( bool isVisible ); void toggleMainLineNumbersVisibility( bool isVisible ); void toggleFilteredLineNumbersVisibility( bool isVisible ); // Change the follow mode checkbox and send the followSet signal down void changeFollowMode( bool follow ); // Update the line number displayed in the status bar. // Must be passed as the internal (starts at 0) line number. void lineNumberHandler( int line ); // Instructs the widget to update the loading progress gauge void updateLoadingProgress( int progress ); // Instructs the widget to display the 'normal' status bar, // without the progress gauge and with file info // or an error recovery when loading is finished void handleLoadingFinished( LoadingStatus status ); // Save the new state as default setting when a crawler // is changing their view options. void handleSearchRefreshChanged( int state ); void handleIgnoreCaseChanged( int state ); // Close the tab with the passed index void closeTab( int index ); // Setup the tab with current index for view void currentTabChanged( int index ); // Instructs the widget to change the pattern in the QuickFind widget // and confirm it. void changeQFPattern( const QString& newPattern ); // Load a file in a new tab (non-interactive) // (for use from e.g. IPC) void loadFileNonInteractive( const QString& file_name ); // Notify the user a new version is available void newVersionNotification( const QString& new_version ); signals: // Is emitted when new settings must be used void optionsChanged(); // Is emitted when the 'follow' option is enabled/disabled void followSet( bool checked ); // Is emitted before the QuickFind box is activated, // to allow crawlers to get search in the right view. void enteringQuickFind(); // Emitted when the quickfind bar is closed. void exitingQuickFind(); private: void createActions(); void createMenus(); void createContextMenu(); void createToolBars(); void createStatusBar(); void createRecentFileToolTipTimer(); void readSettings(); void writeSettings(); bool loadFile( const QString& fileName ); void updateTitleBar( const QString& file_name ); void updateRecentFileActions(); QString strippedName( const QString& fullFileName ) const; CrawlerWidget* currentCrawlerWidget() const; void displayQuickFindBar( QuickFindMux::QFDirection direction ); void updateMenuBarFromDocument( const CrawlerWidget* crawler ); void updateInfoLine(); std::unique_ptr session_; std::shared_ptr externalCommunicator_; std::shared_ptr recentFiles_; QString loadingFileName; // Encoding struct EncodingList { const char* name; }; static const EncodingList encoding_list[]; enum { MaxRecentFiles = 5 }; QAction *recentFileActions[MaxRecentFiles]; MenuActionToolTipBehavior *recentFileActionBehaviors[MaxRecentFiles]; QAction *separatorAction; QMenu *fileMenu; QMenu *editMenu; QMenu *viewMenu; QMenu *toolsMenu; QMenu *encodingMenu; QMenu *helpMenu; InfoLine *infoLine; QLabel* lineNbField; QToolBar *toolBar; QAction *openAction; QAction *closeAction; QAction *closeAllAction; QAction *exitAction; QAction *copyAction; QAction *selectAllAction; QAction *findAction; QAction *overviewVisibleAction; QAction *lineNumbersVisibleInMainAction; QAction *lineNumbersVisibleInFilteredAction; QAction *followAction; QAction *reloadAction; QAction *stopAction; QAction *filtersAction; QAction *optionsAction; QAction *aboutAction; QAction *aboutQtAction; QActionGroup *encodingGroup; QAction *encodingAction[CrawlerWidget::ENCODING_MAX]; QIcon mainIcon_; // Multiplex signals to any of the CrawlerWidgets SignalMux signalMux_; // QuickFind widget QuickFindWidget quickFindWidget_; // Multiplex signals to/from the QuickFindWidget QuickFindMux quickFindMux_; // The main widget TabbedCrawlerWidget mainTabWidget_; // Version checker #ifdef GLOGG_SUPPORTS_VERSION_CHECKING VersionChecker versionChecker_; #endif }; #endif glogg-1.1.0/src/sessioninfo.cpp0000664000175000017500000000542412657420553015452 0ustar nickonicko/* * Copyright (C) 2011, 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include "sessioninfo.h" #include #include "log.h" const int SessionInfo::OPENFILES_VERSION = 1; void SessionInfo::retrieveFromStorage( QSettings& settings ) { LOG(logDEBUG) << "SessionInfo::retrieveFromStorage"; geometry_ = settings.value("geometry").toByteArray(); if ( settings.contains( "OpenFiles/version" ) ) { openFiles_.clear(); // Unserialise the "new style" stored history settings.beginGroup( "OpenFiles" ); if ( settings.value( "version" ) == OPENFILES_VERSION ) { int size = settings.beginReadArray( "openFiles" ); LOG(logDEBUG) << "SessionInfo: " << size << " files."; for (int i = 0; i < size; ++i) { settings.setArrayIndex(i); std::string file_name = settings.value( "fileName" ).toString().toStdString(); uint64_t top_line = settings.value( "topLine" ).toInt(); std::string view_context = settings.value( "viewContext" ).toString().toStdString(); openFiles_.push_back( { file_name, top_line, view_context } ); } settings.endArray(); } else { LOG(logERROR) << "Unknown version of OpenFiles, ignoring it..."; } settings.endGroup(); } } void SessionInfo::saveToStorage( QSettings& settings ) const { LOG(logDEBUG) << "SessionInfo::saveToStorage"; settings.setValue( "geometry", geometry_ ); settings.beginGroup( "OpenFiles" ); settings.setValue( "version", OPENFILES_VERSION ); settings.beginWriteArray( "openFiles" ); for ( unsigned i = 0; i < openFiles_.size(); ++i ) { settings.setArrayIndex( i ); const OpenFile* open_file = &(openFiles_.at( i )); settings.setValue( "fileName", QString( open_file->fileName.c_str() ) ); settings.setValue( "topLine", qint64( open_file->topLine ) ); settings.setValue( "viewContext", QString( open_file->viewContext.c_str() ) ); } settings.endArray(); settings.endGroup(); } glogg-1.1.0/src/configuration.h0000664000175000017500000000726412657420553015433 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2011, 2013, 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef CONFIGURATION_H #define CONFIGURATION_H #include #include #include "persistable.h" // Type of regexp to use for searches enum SearchRegexpType { ExtendedRegexp, Wildcard, FixedString, }; // Configuration class containing everything in the "Settings" dialog class Configuration : public Persistable { public: Configuration(); // Accesses the main font used for display QFont mainFont() const; void setMainFont( QFont newFont ); // Accesses the regexp types SearchRegexpType mainRegexpType() const { return mainRegexpType_; } SearchRegexpType quickfindRegexpType() const { return quickfindRegexpType_; } bool isQuickfindIncremental() const { return quickfindIncremental_; } void setMainRegexpType( SearchRegexpType type ) { mainRegexpType_ = type; } void setQuickfindRegexpType( SearchRegexpType type ) { quickfindRegexpType_ = type; } void setQuickfindIncremental( bool is_incremental ) { quickfindIncremental_ = is_incremental; } // "Advanced" settings bool pollingEnabled() const { return pollingEnabled_; } void setPollingEnabled( bool enabled ) { pollingEnabled_ = enabled; } uint32_t pollIntervalMs() const { return pollIntervalMs_; } void setPollIntervalMs( uint32_t interval ) { pollIntervalMs_ = interval; } // View settings bool isOverviewVisible() const { return overviewVisible_; } void setOverviewVisible( bool isVisible ) { overviewVisible_ = isVisible; } bool mainLineNumbersVisible() const { return lineNumbersVisibleInMain_; } bool filteredLineNumbersVisible() const { return lineNumbersVisibleInFiltered_; } void setMainLineNumbersVisible( bool lineNumbersVisible ) { lineNumbersVisibleInMain_ = lineNumbersVisible; } void setFilteredLineNumbersVisible( bool lineNumbersVisible ) { lineNumbersVisibleInFiltered_ = lineNumbersVisible; } // Default settings for new views bool isSearchAutoRefreshDefault() const { return searchAutoRefresh_; } void setSearchAutoRefreshDefault( bool auto_refresh ) { searchAutoRefresh_ = auto_refresh; } bool isSearchIgnoreCaseDefault() const { return searchIgnoreCase_; } void setSearchIgnoreCaseDefault( bool ignore_case ) { searchIgnoreCase_ = ignore_case; } // Reads/writes the current config in the QSettings object passed virtual void saveToStorage( QSettings& settings ) const; virtual void retrieveFromStorage( QSettings& settings ); private: // Configuration settings QFont mainFont_; SearchRegexpType mainRegexpType_; SearchRegexpType quickfindRegexpType_; bool quickfindIncremental_; bool pollingEnabled_; uint32_t pollIntervalMs_; // View settings bool overviewVisible_; bool lineNumbersVisibleInMain_; bool lineNumbersVisibleInFiltered_; // Default settings for new views bool searchAutoRefresh_; bool searchIgnoreCase_; }; #endif glogg-1.1.0/src/watchtowerlist.h0000664000175000017500000003134012657420553015637 0ustar nickonicko#ifndef WATCHTOWERLIST_H #define WATCHTOWERLIST_H // Utility classes for the WatchTower implementations #include #include #include #include #include #include #include #include #include "log.h" // Utility classes struct ProtocolInfo { // Win32 notification variables static const int READ_DIR_CHANGE_BUFFER_SIZE = 4096; void* handle_; static const unsigned long buffer_length_ = READ_DIR_CHANGE_BUFFER_SIZE; char buffer_[buffer_length_]; }; // List of files and observers template struct ObservedDir { ObservedDir( const std::string this_path ) : path { this_path } {} // Returns the address of the protocol specific informations ProtocolInfo* protocolInfo() { return &protocol_info_; } std::string path; typename Driver::DirId dir_id_; // Contains data specific to the protocol (inotify/Win32...) ProtocolInfo protocol_info_; }; template struct ObservedFile { ObservedFile( const std::string& file_name, std::shared_ptr callback, typename Driver::FileId file_id, typename Driver::SymlinkId symlink_id ) : file_name_( file_name ) { addCallback( callback ); file_id_ = file_id; symlink_id_ = symlink_id; dir_ = nullptr; markAsChanged(); } void addCallback( std::shared_ptr callback ) { callbacks.push_back( callback ); } // Records the file has changed void markAsChanged() { change_token_.readFromFile( file_name_ ); last_check_time_ = std::chrono::steady_clock::now(); } // Returns whether a file has changed // (for polling) bool hasChanged() { typename Driver::FileChangeToken new_token( file_name_ ); last_check_time_ = std::chrono::steady_clock::now(); return change_token_ != new_token; } std::chrono::steady_clock::time_point timeForLastCheck() { return last_check_time_; } std::string file_name_; // List of callbacks for this file std::vector> callbacks; // watch descriptor for the file itself typename Driver::FileId file_id_; // watch descriptor for the symlink (if file is a symlink) typename Driver::SymlinkId symlink_id_; // link to the dir containing the file std::shared_ptr> dir_; // token to identify modification // (the token change when the file is modified, this is used when polling) typename Driver::FileChangeToken change_token_; // Last time a check has been done std::chrono::steady_clock::time_point last_check_time_ = std::chrono::steady_clock::time_point::min(); }; // A list of the observed files and directories // This class is not thread safe template class ObservedFileList { public: ObservedFileList() : heartBeat_ { std::shared_ptr((void*) 0xDEADC0DE, [] (void*) {}) } { } ~ObservedFileList() = default; // The functions return a pointer to the existing file (if exists) // but keep ownership of the object. ObservedFile* searchByName( const std::string& file_name ); ObservedFile* searchByFileOrSymlinkWd( typename Driver::FileId file_id, typename Driver::SymlinkId symlink_id ); ObservedFile* searchByDirWdAndName( typename Driver::DirId id, const char* name ); // Add a new file, the list returns a pointer to the added file, // but has ownership of the file. ObservedFile* addNewObservedFile( ObservedFile new_observed ); // Remove a callback, remove and returns the file object if // it was the last callback on this object, nullptr if not. // The caller has ownership of the object. std::shared_ptr> removeCallback( std::shared_ptr callback ); // Return the watched directory if it is watched, or nullptr std::shared_ptr> watchedDirectory( const std::string& dir_name ); // Create a new watched directory for dir_name, the client is passed // shared ownership and have to keep the shared_ptr (the list only // maintain a weak link). // The remove notification is called just before the reference to // the directory is destroyed. std::shared_ptr> addWatchedDirectory( const std::string& dir_name, std::function* )> remove_notification ); // Similar to previous functions but extract the name of the // directory from the file name. std::shared_ptr> watchedDirectoryForFile( const std::string& file_name ); std::shared_ptr> addWatchedDirectoryForFile( const std::string& file_name, std::function* )> remove_notification ); // Removal of directories is done when there is no shared reference // left (RAII) // Number of watched directories (for tests) unsigned int numberWatchedDirectories() const; // Iterator template class iterator : std::iterator> { public: iterator( Container* list, const typename Container::iterator& iter ) { list_ = list; pos_ = iter; } iterator operator++() { ++pos_; return *this; } bool operator==( const iterator& other ) { return ( pos_ == other.pos_ ); } bool operator!=( const iterator& other ) { return ! operator==( other ); } typename Container::iterator operator*() { return pos_; } private: Container* list_; typename Container::iterator pos_; }; iterator>> begin() { return iterator>>( &observed_files_, observed_files_.begin() ); } iterator>> end() { return iterator>>( &observed_files_, observed_files_.end() ); } private: // List of observed files std::list> observed_files_; // List of observed dirs, key-ed by name std::map>> observed_dirs_; // Map the inotify file (including symlinks) wds to the observed file std::map*> by_file_wd_; // Map the inotify directory wds to the observed files std::map*> by_dir_wd_; // Heartbeat std::shared_ptr heartBeat_; // Clean all reference to any expired directory void cleanRefsToExpiredDirs(); }; namespace { std::string directory_path( const std::string& path ); }; // ObservedFileList class template ObservedFile* ObservedFileList::searchByName( const std::string& file_name ) { // Look for an existing observer on this file auto existing_observer = observed_files_.begin(); for ( ; existing_observer != observed_files_.end(); ++existing_observer ) { if ( existing_observer->file_name_ == file_name ) { LOG(logDEBUG) << "Found " << file_name; break; } } if ( existing_observer != observed_files_.end() ) return &( *existing_observer ); else return nullptr; } template ObservedFile* ObservedFileList::searchByFileOrSymlinkWd( typename Driver::FileId file_id, typename Driver::SymlinkId symlink_id ) { auto result = find_if( observed_files_.begin(), observed_files_.end(), [file_id, symlink_id] (ObservedFile file) -> bool { return ( file_id == file.file_id_ ) || ( symlink_id == file.symlink_id_ ); } ); if ( result != observed_files_.end() ) return &( *result ); else return nullptr; } template ObservedFile* ObservedFileList::searchByDirWdAndName( typename Driver::DirId id, const char* name ) { auto dir = find_if( observed_dirs_.begin(), observed_dirs_.end(), [id] (std::pair>> d) -> bool { if ( auto dir = d.second.lock() ) { return ( id == dir->dir_id_ ); } else { return false; } } ); if ( dir != observed_dirs_.end() ) { std::string path = dir->first + "/" + name; // LOG(logDEBUG) << "Testing path: " << path; // Looking for the path in the files we are watching return searchByName( path ); } else { return nullptr; } } template ObservedFile* ObservedFileList::addNewObservedFile( ObservedFile new_observed ) { auto new_file = observed_files_.insert( std::begin( observed_files_ ), new_observed ); return &( *new_file ); } template std::shared_ptr> ObservedFileList::removeCallback( std::shared_ptr callback ) { std::shared_ptr> returned_file = nullptr; for ( auto observer = std::begin( observed_files_ ); observer != std::end( observed_files_ ); ) { std::vector>& callbacks = observer->callbacks; callbacks.erase( std::remove( std::begin( callbacks ), std::end( callbacks ), callback ), std::end( callbacks ) ); /* See if all notifications have been deleted for this file */ if ( callbacks.empty() ) { LOG(logDEBUG) << "Empty notification list for " << observer->file_name_ << ", removing the watched file"; returned_file = std::make_shared>( *observer ); observer = observed_files_.erase( observer ); } else { ++observer; } } return returned_file; } template std::shared_ptr> ObservedFileList::watchedDirectory( const std::string& dir_name ) { std::shared_ptr> dir = nullptr; if ( observed_dirs_.find( dir_name ) != std::end( observed_dirs_ ) ) dir = observed_dirs_[ dir_name ].lock(); return dir; } template std::shared_ptr> ObservedFileList::addWatchedDirectory( const std::string& dir_name, std::function* )> remove_notification ) { std::weak_ptr weakHeartBeat(heartBeat_); std::shared_ptr> dir = { new ObservedDir( dir_name ), [this, remove_notification, weakHeartBeat] (ObservedDir* d) { if ( auto heart_beat = weakHeartBeat.lock() ) { remove_notification( d ); this->cleanRefsToExpiredDirs(); } delete d; } }; observed_dirs_[ dir_name ] = std::weak_ptr>( dir ); return dir; } template std::shared_ptr> ObservedFileList::watchedDirectoryForFile( const std::string& file_name ) { return watchedDirectory( directory_path( file_name ) ); } template std::shared_ptr> ObservedFileList::addWatchedDirectoryForFile( const std::string& file_name, std::function* )> remove_notification ) { return addWatchedDirectory( directory_path( file_name ), remove_notification ); } template unsigned int ObservedFileList::numberWatchedDirectories() const { return observed_dirs_.size(); } // Private functions template void ObservedFileList::cleanRefsToExpiredDirs() { for ( auto it = std::begin( observed_dirs_ ); it != std::end( observed_dirs_ ); ) { if ( it->second.expired() ) { it = observed_dirs_.erase( it ); } else { ++it; } } } namespace { std::string directory_path( const std::string& path ) { size_t slash_pos = path.rfind( '/' ); #ifdef _WIN32 if ( slash_pos == std::string::npos ) { slash_pos = path.rfind( '\\' ); } // We need to include the final slash on Windows ++slash_pos; #endif return std::string( path, 0, slash_pos ); } }; #endif glogg-1.1.0/src/utils.h0000664000175000017500000000724712657420553013725 0ustar nickonicko/* * Copyright (C) 2011, 2013 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef UTILS_H #define UTILS_H #include #include "config.h" // Line number are unsigned 32 bits for now. typedef uint32_t LineNumber; // Use a bisection method to find the given line number // in a sorted list. // The T type must be a container containing elements that // implement the lineNumber() member. // Returns true if the lineNumber is found, false if not // foundIndex is the index of the found number or the index // of the closest greater element. template bool lookupLineNumber( const T& list, qint64 lineNumber, int* foundIndex ) { int minIndex = 0; int maxIndex = list.size() - 1; // If the list is not empty if ( maxIndex - minIndex >= 0 ) { // First we test the ends if ( list[minIndex].lineNumber() == lineNumber ) { *foundIndex = minIndex; return true; } else if ( list[maxIndex].lineNumber() == lineNumber ) { *foundIndex = maxIndex; return true; } // Then we test the rest while ( (maxIndex - minIndex) > 1 ) { const int tryIndex = (minIndex + maxIndex) / 2; const qint64 currentMatchingNumber = list[tryIndex].lineNumber(); if ( currentMatchingNumber > lineNumber ) maxIndex = tryIndex; else if ( currentMatchingNumber < lineNumber ) minIndex = tryIndex; else if ( currentMatchingNumber == lineNumber ) { *foundIndex = tryIndex; return true; } } // If we haven't found anything... // ... end of the list or before the next if ( lineNumber > list[maxIndex].lineNumber() ) *foundIndex = maxIndex + 1; else if ( lineNumber > list[minIndex].lineNumber() ) *foundIndex = minIndex + 1; else *foundIndex = minIndex; } else { *foundIndex = 0; } return false; } // Represents a position in a file (line, column) class FilePosition { public: FilePosition() { line_ = -1; column_ = -1; } FilePosition( qint64 line, int column ) { line_ = line; column_ = column; } qint64 line() const { return line_; } int column() const { return column_; } private: qint64 line_; int column_; }; #ifndef HAVE_MAKE_UNIQUE #include namespace std { template std::unique_ptr make_unique(Args&&... args) { return std::unique_ptr(new T(std::forward(args)...)); } } #endif #ifndef HAVE_OVERRIDE #define override #endif #ifndef HAVE_HTONS inline uint16_t glogg_htons( uint16_t hostshort ) { static const uint16_t test_value = 0xABCD; if ( *( reinterpret_cast( &test_value ) ) == 0xCD ) { uint16_t result = ( hostshort & 0x00FF ) << 8; result |= ( hostshort & 0xFF00 ) >> 8; return result; } else { return hostshort; } } #endif #endif glogg-1.1.0/src/inotifywatchtowerdriver.cpp0000664000175000017500000001474312657420553020124 0ustar nickonicko/* * Copyright (C) 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include "inotifywatchtowerdriver.h" #include #include #include #include #include "log.h" #include "watchtowerlist.h" INotifyWatchTowerDriver::INotifyWatchTowerDriver() : inotify_fd_( inotify_init() ) { int pipefd[2]; pipe2( pipefd, O_NONBLOCK ); breaking_pipe_read_fd_ = pipefd[0]; breaking_pipe_write_fd_ = pipefd[1]; } INotifyWatchTowerDriver::~INotifyWatchTowerDriver() { close( breaking_pipe_read_fd_ ); close( breaking_pipe_write_fd_ ); } INotifyWatchTowerDriver::FileId INotifyWatchTowerDriver::addFile( const std::string& file_name ) { // Add a watch for the inode int wd = inotify_add_watch( inotify_fd_, file_name.c_str(), IN_DELETE_SELF | IN_MODIFY | IN_MOVE_SELF ); LOG(logDEBUG) << "INotifyWatchTower::addFile new inotify wd " << wd; return { wd }; } INotifyWatchTowerDriver::SymlinkId INotifyWatchTowerDriver::addSymlink( const std::string& file_name ) { int symlink_wd = inotify_add_watch( inotify_fd_, file_name.c_str(), IN_DONT_FOLLOW | IN_DELETE_SELF | IN_MODIFY | IN_MOVE_SELF ); LOG(logDEBUG) << "INotifyWatchTower::addFile new inotify symlink_wd " << symlink_wd; // (not sure a symlink can be modified but you never know) return { symlink_wd }; } INotifyWatchTowerDriver::DirId INotifyWatchTowerDriver::addDir( const std::string& file_name ) { int dir_wd = inotify_add_watch( inotify_fd_, file_name.c_str(), IN_CREATE | IN_MOVE | IN_ONLYDIR ); LOG(logDEBUG) << "INotifyWatchTower::addFile dir " << file_name << " watched wd " << dir_wd; return { dir_wd }; } void INotifyWatchTowerDriver::removeFile( const INotifyWatchTowerDriver::FileId& file_id ) { /* LOG(logDEBUG) << "INotifyWatchTower::removeNotification removing inotify wd " << file->file_wd_ << " symlink_wd " << file->symlink_wd_; */ if ( file_id.wd_ >= 0 ) inotify_rm_watch( inotify_fd_, file_id.wd_ ); } void INotifyWatchTowerDriver::removeSymlink( const SymlinkId& symlink_id ) { if ( symlink_id.wd_ >= 0 ) inotify_rm_watch( inotify_fd_, symlink_id.wd_ ); } void INotifyWatchTowerDriver::removeDir( const DirId& dir_id ) { LOG(logDEBUG) << "INotifyWatchTower::removeDir removing inotify wd " << dir_id.wd_; if ( dir_id.wd_ >= 0 ) inotify_rm_watch( inotify_fd_, dir_id.wd_ ); } static constexpr size_t INOTIFY_BUFFER_SIZE = 4096; std::vector INotifyWatchTowerDriver::waitAndProcessEvents( INotifyObservedFileList* list, std::unique_lock* list_lock, std::vector* files_needing_readding, int timeout_ms ) { std::vector files_to_notify; struct pollfd fds[2]; fds[0].fd = inotify_fd_; fds[0].events = POLLIN; fds[0].revents = 0; fds[1].fd = breaking_pipe_read_fd_; fds[1].events = POLLIN; fds[1].revents = 0; list_lock->unlock(); int poll_ret = poll( fds, 2, timeout_ms ? timeout_ms : -1 ); list_lock->lock(); if ( poll_ret > 0 ) { if ( fds[0].revents & POLLIN ) { LOG(logDEBUG) << "Pollin for inotify"; char buffer[ INOTIFY_BUFFER_SIZE ] __attribute__ ((aligned(__alignof__(struct inotify_event)))); ssize_t nb = read( inotify_fd_, buffer, sizeof( buffer ) ); if ( nb > 0 ) { ssize_t offset = 0; while ( offset < nb ) { const inotify_event* event = reinterpret_cast( buffer + offset ); offset += processINotifyEvent( event, list, &files_to_notify, files_needing_readding ); } } else { LOG(logWARNING) << "Error reading from inotify " << errno; } } if ( fds[1].revents & POLLIN ) { uint8_t byte; read( breaking_pipe_read_fd_, &byte, sizeof byte ); } } return files_to_notify; } // Treats the passed event and returns the number of bytes used size_t INotifyWatchTowerDriver::processINotifyEvent( const struct inotify_event* event, INotifyObservedFileList* list, std::vector* files_to_notify, std::vector* files_needing_readding ) { LOG(logDEBUG) << "Event received: " << std::hex << event->mask; INotifyObservedFile* file = nullptr; if ( event->mask & ( IN_MODIFY | IN_DELETE_SELF | IN_MOVE_SELF ) ) { LOG(logDEBUG) << "IN_MODIFY | IN_DELETE_SELF | IN_MOVE_SELF for wd " << event->wd; // Retrieve the file file = list->searchByFileOrSymlinkWd( { event->wd }, { event->wd } ); } else if ( event->mask & ( IN_CREATE | IN_MOVED_TO | IN_MOVED_FROM ) ) { LOG(logDEBUG) << "IN_CREATE | IN_MOVED_TO | IN_MOVED_FROM for wd " << event->wd << " name: " << event->name; // Retrieve the file file = list->searchByDirWdAndName( { event->wd }, event->name ); if ( file ) { LOG(logDEBUG) << "Dir change for watched file " << event->name; files_needing_readding->push_back( file ); } } else { LOG(logDEBUG) << "Unexpected event: " << event->mask << " wd " << event->wd; } if ( file ) { LOG(logDEBUG) << "Adding file: " << std::hex << file; files_to_notify->push_back( file ); } return sizeof( struct inotify_event ) + event->len; } void INotifyWatchTowerDriver::interruptWait() { char byte = 'X'; (void) write( breaking_pipe_write_fd_, (void*) &byte, sizeof byte ); } glogg-1.1.0/src/viewtools.cpp0000664000175000017500000000543012657420553015143 0ustar nickonicko/* * Copyright (C) 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include "viewtools.h" #include "log.h" /* ElasticHook */ void ElasticHook::move( int value ) { static constexpr int MAX_POSITION = 2000; if ( timer_id_ == 0 ) timer_id_ = startTimer( TIMER_PERIOD_MS ); int resistance = 0; if ( !held_ && ( position_ * value > 0 ) ) // value and resistance have the same sign resistance = position_ / 8; position_ = std::min( position_ + ( value - resistance ), MAX_POSITION ); if ( !held_ && ( std::chrono::duration_cast ( std::chrono::steady_clock::now() - last_update_ ).count() > TIMER_PERIOD_MS ) ) decreasePosition(); if ( ( ! hooked_ ) && position_ >= hook_threshold_ ) { position_ -= hook_threshold_; hooked_ = true; emit hooked( true ); } else if ( hooked_ && position_ <= - hook_threshold_ ) { position_ += hook_threshold_; hooked_ = false; emit hooked( false ); } if ( position_ < 0 && !isHooked() ) position_ = 0; last_update_ = std::chrono::steady_clock::now(); LOG( logDEBUG ) << "ElasticHook::move: new value " << position_; emit lengthChanged(); } void ElasticHook::timerEvent( QTimerEvent* event ) { if ( !held_ && ( std::chrono::duration_cast ( std::chrono::steady_clock::now() - last_update_ ).count() > TIMER_PERIOD_MS ) ) { decreasePosition(); last_update_ = std::chrono::steady_clock::now(); } } void ElasticHook::decreasePosition() { static constexpr int PROP_RATIO = 10; // position_ -= DECREASE_RATE + ( ( position_/SQUARE_RATIO ) * ( position_/SQUARE_RATIO ) ); if ( std::abs( position_ ) < DECREASE_RATE ) { position_ = 0; killTimer( timer_id_ ); timer_id_ = 0; } else if ( position_ > 0 ) position_ -= DECREASE_RATE + ( position_/PROP_RATIO ); else if ( position_ < 0 ) position_ += DECREASE_RATE - ( position_/PROP_RATIO ); LOG( logDEBUG ) << "ElasticHook::timerEvent: new value " << position_; emit lengthChanged(); } glogg-1.1.0/src/overviewwidget.cpp0000664000175000017500000002050012657420553016155 0ustar nickonicko/* * Copyright (C) 2011, 2012, 2013 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ // This file implements OverviewWidget. This class is responsable for // managing and painting the matches overview widget. #include #include #include #include "log.h" #include "overviewwidget.h" #include "overview.h" // Graphic parameters const int OverviewWidget::LINE_MARGIN = 4; const int OverviewWidget::STEP_DURATION_MS = 30; const int OverviewWidget::INITIAL_TTL_VALUE = 5; #define HIGHLIGHT_XPM_WIDTH 27 #define HIGHLIGHT_XPM_HEIGHT 9 #define S(x) #x #define SX(x) S(x) // width height colours char/pixel // Colours #define HIGHLIGHT_XPM_LEAD_LINE SX(HIGHLIGHT_XPM_WIDTH) " " SX(HIGHLIGHT_XPM_HEIGHT) " 2 1",\ " s mask c none",\ "x c #572F80" const char* const highlight_xpm[][14] = { { HIGHLIGHT_XPM_LEAD_LINE, " ", " ", " xxxxxxxxxxxxxxxxxxxxx ", " xxxxxxxxxxxxxxxxxxxxx ", " xx xx ", " xx xx ", " xx xx ", " xxxxxxxxxxxxxxxxxxxxx ", " xxxxxxxxxxxxxxxxxxxxx ", " ", " ", }, { HIGHLIGHT_XPM_LEAD_LINE, " ", " xxxxxxxxxxxxxxxxxxxxxxx ", " xxxxxxxxxxxxxxxxxxxxxxx ", " xxxxxxxxxxxxxxxxxxxxxxx ", " xxx xxx ", " xxx xxx ", " xxx xxx ", " xxxxxxxxxxxxxxxxxxxxxxx ", " xxxxxxxxxxxxxxxxxxxxxxx ", " xxxxxxxxxxxxxxxxxxxxxxx ", " ", }, { HIGHLIGHT_XPM_LEAD_LINE, " xxxxxxxxxxxxxxxxxxxxxxxxx ", " xxxxxxxxxxxxxxxxxxxxxxxxx ", " xxxxxxxxxxxxxxxxxxxxxxxxx ", " xxxxxxxxxxxxxxxxxxxxxxxxx ", " xxxx xxxx ", " xxxx xxxx ", " xxxx xxxx ", " xxxxxxxxxxxxxxxxxxxxxxxxx ", " xxxxxxxxxxxxxxxxxxxxxxxxx ", " xxxxxxxxxxxxxxxxxxxxxxxxx ", " xxxxxxxxxxxxxxxxxxxxxxxxx ", }, { HIGHLIGHT_XPM_LEAD_LINE, " ", " xxxxxxxxxxxxxxxxxxxxxxx ", " xxxxxxxxxxxxxxxxxxxxxxx ", " xxxxxxxxxxxxxxxxxxxxxxx ", " xxx xxx ", " xxx xxx ", " xxx xxx ", " xxxxxxxxxxxxxxxxxxxxxxx ", " xxxxxxxxxxxxxxxxxxxxxxx ", " xxxxxxxxxxxxxxxxxxxxxxx ", " ", }, { HIGHLIGHT_XPM_LEAD_LINE, " ", " ", " xxxxxxxxxxxxxxxxxxxxx ", " xxxxxxxxxxxxxxxxxxxxx ", " xx xx ", " xx xx ", " xx xx ", " xxxxxxxxxxxxxxxxxxxxx ", " xxxxxxxxxxxxxxxxxxxxx ", " ", " ", }, { HIGHLIGHT_XPM_LEAD_LINE, " ", " ", " ", " xxxxxxxxxxxxxxxxxxx ", " x x ", " x x ", " x x ", " xxxxxxxxxxxxxxxxxxx ", " ", " ", " ", }, }; OverviewWidget::OverviewWidget( QWidget* parent ) : QWidget( parent ), highlightTimer_() { overview_ = NULL; setBackgroundRole( QPalette::Window ); // Highlight highlightedLine_ = -1; highlightedTTL_ = 0; // We should be hidden by default (e.g. for the FilteredView) hide(); } void OverviewWidget::paintEvent( QPaintEvent* /* paintEvent */ ) { static const QColor match_color("red"); static const QColor mark_color("dodgerblue"); static const QPixmap highlight_pixmap[] = { QPixmap( highlight_xpm[0] ), QPixmap( highlight_xpm[1] ), QPixmap( highlight_xpm[2] ), QPixmap( highlight_xpm[3] ), QPixmap( highlight_xpm[4] ), QPixmap( highlight_xpm[5] ), }; // We must be hidden until we have an Overview assert( overview_ != NULL ); overview_->updateView( height() ); { QPainter painter( this ); painter.fillRect( painter.viewport(), painter.background() ); // The line separating from the main view painter.setPen( palette().color(QPalette::Text) ); painter.drawLine( 0, 0, 0, height() ); // The 'match' lines painter.setPen( match_color ); foreach (Overview::WeightedLine line, *(overview_->getMatchLines()) ) { painter.setOpacity( ( 1.0 / Overview::WeightedLine::WEIGHT_STEPS ) * ( line.weight() + 1 ) ); // (allow multiple matches to look 'darker' than a single one.) painter.drawLine( 1 + LINE_MARGIN, line.position(), width() - LINE_MARGIN - 1, line.position() ); } // The 'mark' lines painter.setPen( mark_color ); foreach (Overview::WeightedLine line, *(overview_->getMarkLines()) ) { painter.setOpacity( ( 1.0 / Overview::WeightedLine::WEIGHT_STEPS ) * ( line.weight() + 1 ) ); // (allow multiple matches to look 'darker' than a single one.) painter.drawLine( 1 + LINE_MARGIN, line.position(), width() - LINE_MARGIN - 1, line.position() ); } // The 'view' lines painter.setOpacity( 1 ); painter.setPen( palette().color(QPalette::Text) ); std::pair view_lines = overview_->getViewLines(); painter.drawLine( 1, view_lines.first, width(), view_lines.first ); painter.drawLine( 1, view_lines.second, width(), view_lines.second ); // The highlight if ( highlightedLine_ >= 0 ) { /* QPen highlight_pen( palette().color(QPalette::Text) ); highlight_pen.setWidth( 4 - highlightedTTL_ ); painter.setOpacity( 1 ); painter.setPen( highlight_pen ); painter.drawRect( 2, position - 2, width() - 2 - 2, 4 ); */ int position = overview_->yFromFileLine( highlightedLine_ ); painter.drawPixmap( ( width() - HIGHLIGHT_XPM_WIDTH ) / 2, position - ( HIGHLIGHT_XPM_HEIGHT / 2 ), highlight_pixmap[ INITIAL_TTL_VALUE - highlightedTTL_ ] ); } } } void OverviewWidget::mousePressEvent( QMouseEvent* mouseEvent ) { if ( mouseEvent->button() == Qt::LeftButton ) handleMousePress( mouseEvent->y() ); } void OverviewWidget::mouseMoveEvent( QMouseEvent* mouseEvent ) { if ( mouseEvent->buttons() |= Qt::LeftButton ) handleMousePress( mouseEvent->y() ); } void OverviewWidget::handleMousePress( int position ) { int line = overview_->fileLineFromY( position ); LOG(logDEBUG) << "OverviewWidget::handleMousePress y=" << position << " line=" << line; emit lineClicked( line ); } void OverviewWidget::highlightLine( qint64 line ) { highlightTimer_.stop(); highlightedLine_ = line; highlightedTTL_ = INITIAL_TTL_VALUE; update(); highlightTimer_.start( STEP_DURATION_MS, this ); } void OverviewWidget::removeHighlight() { highlightTimer_.stop(); highlightedLine_ = -1; update(); } void OverviewWidget::timerEvent( QTimerEvent* event ) { if ( event->timerId() == highlightTimer_.timerId() ) { LOG(logDEBUG) << "OverviewWidget::timerEvent"; if ( highlightedTTL_ > 0 ) { --highlightedTTL_; update(); } else { highlightTimer_.stop(); } } else { QObject::timerEvent( event ); } } glogg-1.1.0/src/persistable.h0000664000175000017500000000220512657420553015067 0ustar nickonicko/* * Copyright (C) 2011 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef PERSISTABLE_H #define PERSISTABLE_H class QSettings; // Must be implemented by classes which could be saved to persistent // storage by PersistentInfo. class Persistable { public: virtual ~Persistable() {} // Must be implemented to save/retrieve from Qt Settings virtual void saveToStorage( QSettings& settings ) const = 0; virtual void retrieveFromStorage( QSettings& settings ) = 0; }; #endif glogg-1.1.0/src/marks.h0000664000175000017500000000622712657420553013677 0ustar nickonicko/* * Copyright (C) 2011 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef MARKS_H #define MARKS_H #include #include // Class encapsulating a single mark // Contains the line number the mark is identifying. class Mark { public: Mark( int line ) { lineNumber_ = line; }; // Accessors int lineNumber() const { return lineNumber_; } private: int lineNumber_; }; // A list of marks, i.e. line numbers optionally associated to an // identifying character. class Marks { public: // Create an empty Marks Marks(); // Add a mark at the given line, optionally identified by the given char // If a mark for this char already exist, the previous one is replaced. // It will happily add marks anywhere, even at stupid indexes. void addMark( qint64 line, QChar mark = QChar() ); // Get the (unique) mark identified by the passed char. qint64 getMark( QChar mark ) const; // Returns wheither the passed line has a mark on it. bool isLineMarked( qint64 line ) const; // Delete the mark identified by the passed char. void deleteMark( QChar mark ); // Delete the mark present on the passed line or do nothing if there is // none. void deleteMark( qint64 line ); // Get the line marked identified by the index (in this list) passed. qint64 getLineMarkedByIndex( int index ) const { return marks_[index].lineNumber(); } // Return the total number of marks int size() const { return marks_.size(); } // Completely clear the marks list. void clear(); // Iterator // Provide a const_iterator for the client to iterate through the marks. class const_iterator { public: const_iterator( QList::const_iterator iter ) { internal_iter_ = iter; } const_iterator( const const_iterator& original ) { internal_iter_ = original.internal_iter_; } const Mark& operator*() { return *internal_iter_; } const Mark* operator->() { return &(*internal_iter_); } bool operator!=( const const_iterator& other ) const { return ( internal_iter_ != other.internal_iter_ ); } const_iterator& operator++() { ++internal_iter_ ; return *this; } private: QList::const_iterator internal_iter_; }; const_iterator begin() const { return const_iterator( marks_.begin() ); } const_iterator end() const { return const_iterator( marks_.end() ); } private: // List of marks. QList marks_; }; #endif glogg-1.1.0/src/platformfilewatcher.h0000664000175000017500000000426612657420553016625 0ustar nickonicko/* * Copyright (C) 2014, 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef PLATFORMFILEWATCHER_H #define PLATFORMFILEWATCHER_H #include "filewatcher.h" #include #ifdef _WIN32 # include "winwatchtowerdriver.h" #else # include "inotifywatchtowerdriver.h" #endif #include "watchtower.h" class INotifyWatchTower; // Please note that due to the implementation of the constructor // this class is not thread safe and shall always be used from the main UI thread. class PlatformFileWatcher : public FileWatcher { Q_OBJECT public: // Create the empty object PlatformFileWatcher(); // Destroy the object ~PlatformFileWatcher(); void addFile( const QString& fileName ); void removeFile( const QString& fileName ); // Set the polling interval (0 means disabled) void setPollingInterval( uint32_t interval_ms ); signals: void fileChanged( const QString& ); private: #ifdef _WIN32 # ifdef HAS_TEMPLATE_ALIASES using PlatformWatchTower = WatchTower; # else typedef WatchTower PlatformWatchTower; # endif #else # ifdef HAS_TEMPLATE_ALIASES using PlatformWatchTower = WatchTower; # else typedef WatchTower PlatformWatchTower; # endif #endif // The following variables are protected by watched_files_mutex_ QString watched_file_name_; // Reference to the (unique) watchtower. static std::shared_ptr watch_tower_; std::shared_ptr notification_; }; #endif glogg-1.1.0/src/filteredview.cpp0000664000175000017500000000473712657420553015612 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2012 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ // This file implements the FilteredView concrete class. // Most of the actual drawing and event management is done in AbstractLogView // Only behaviour specific to the filtered (bottom) view is implemented here. #include #include "filteredview.h" FilteredView::FilteredView( LogFilteredData* newLogData, const QuickFindPattern* const quickFindPattern, QWidget* parent ) : AbstractLogView( newLogData, quickFindPattern, parent ) { // We keep a copy of the filtered data for fast lookup of the line type logFilteredData_ = newLogData; } void FilteredView::setVisibility( Visibility visi ) { assert( logFilteredData_ ); LogFilteredData::Visibility data_visibility = LogFilteredData::MarksAndMatches; switch ( visi ) { case MarksOnly: data_visibility = LogFilteredData::MarksOnly; break; case MatchesOnly: data_visibility = LogFilteredData::MatchesOnly; break; case MarksAndMatches: data_visibility = LogFilteredData::MarksAndMatches; break; }; logFilteredData_->setVisibility( data_visibility ); updateData(); } // For the filtered view, a line is always matching! AbstractLogView::LineType FilteredView::lineType( int lineNumber ) const { LogFilteredData::FilteredLineType type = logFilteredData_->filteredLineTypeByIndex( lineNumber ); if ( type == LogFilteredData::Mark ) return Marked; else return Match; } qint64 FilteredView::displayLineNumber( int lineNumber ) const { // Display a 1-based index return logFilteredData_->getMatchingLineNumber( lineNumber ) + 1; } qint64 FilteredView::maxDisplayLineNumber() const { return logFilteredData_->getNbTotalLines(); } glogg-1.1.0/src/overviewwidget.h0000664000175000017500000000371712657420553015635 0ustar nickonicko/* * Copyright (C) 2011, 2012, 2013 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef OVERVIEWWIDGET_H #define OVERVIEWWIDGET_H #include #include class Overview; class OverviewWidget : public QWidget { Q_OBJECT public: OverviewWidget( QWidget* parent = 0 ); // Associate the widget with an Overview object. void setOverview( Overview* overview ) { overview_ = overview; } public slots: // Sent when a match at the line passed must be highlighted in // the overview void highlightLine( qint64 line ); void removeHighlight(); protected: void paintEvent( QPaintEvent* paintEvent ); void mousePressEvent( QMouseEvent* mouseEvent ); void mouseMoveEvent( QMouseEvent* mouseEvent ); void timerEvent( QTimerEvent* event ); signals: // Sent when the user click on a line in the Overview. void lineClicked( int line ); private: // Constants static const int LINE_MARGIN; static const int STEP_DURATION_MS; static const int INITIAL_TTL_VALUE; Overview* overview_; // Highlight: // Which line is higlighted, or -1 if none int highlightedLine_; // Number of step until the highlight become static int highlightedTTL_; QBasicTimer highlightTimer_; void handleMousePress( int position ); }; #endif glogg-1.1.0/src/log.h0000664000175000017500000001173512657420553013343 0ustar nickonicko/* * Copyright (C) 2009, 2010 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef __LOG_H__ #define __LOG_H__ #include #include #include // Modify here! //#define FILELOG_MAX_LEVEL logDEBUG inline std::string NowTime(); enum TLogLevel {logERROR, logWARNING, logINFO, logDEBUG, logDEBUG1, logDEBUG2, logDEBUG3, logDEBUG4}; template class Log { public: Log(); virtual ~Log(); std::ostringstream& Get(TLogLevel level = logINFO, const std::string& sourceFile = "", int lineNumber = 0); public: static TLogLevel ReportingLevel() { return reportingLevel; } static std::string ToString(TLogLevel level); static TLogLevel FromString(const std::string& level); static void setReportingLevel(TLogLevel level) { reportingLevel = level; } template friend class Log; protected: std::ostringstream os; private: static TLogLevel reportingLevel; Log(const Log&); Log& operator =(const Log&); }; template TLogLevel Log::reportingLevel = logDEBUG4; template Log::Log() { } template std::ostringstream& Log::Get(TLogLevel level, const std::string& sourceFile, int lineNumber) { os << "- " << NowTime(); os << " " << ToString(level); os << " " << sourceFile << ":" << lineNumber << ": "; os << std::string(level > logDEBUG ? level - logDEBUG : 0, '\t'); return os; } template Log::~Log() { os << std::endl; T::Output(os.str()); } template std::string Log::ToString(TLogLevel level) { static const char* const buffer[] = {"ERROR", "WARNING", "INFO", "DEBUG", "DEBUG1", "DEBUG2", "DEBUG3", "DEBUG4"}; return buffer[level]; } template TLogLevel Log::FromString(const std::string& level) { if (level == "DEBUG4") return logDEBUG4; if (level == "DEBUG3") return logDEBUG3; if (level == "DEBUG2") return logDEBUG2; if (level == "DEBUG1") return logDEBUG1; if (level == "DEBUG") return logDEBUG; if (level == "INFO") return logINFO; if (level == "WARNING") return logWARNING; if (level == "ERROR") return logERROR; Log().Get(logWARNING) << "Unknown logging level '" << level << "'. Using INFO level as default."; return logINFO; } class Output2FILE { public: static FILE*& Stream(); static void Output(const std::string& msg); }; inline FILE*& Output2FILE::Stream() { static FILE* pStream = stderr; return pStream; } inline void Output2FILE::Output(const std::string& msg) { FILE* pStream = Stream(); if (!pStream) return; fprintf(pStream, "%s", msg.c_str()); fflush(pStream); } #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) # if defined (BUILDING_FILELOG_DLL) # define FILELOG_DECLSPEC __declspec (dllexport) # elif defined (USING_FILELOG_DLL) # define FILELOG_DECLSPEC __declspec (dllimport) # else # define FILELOG_DECLSPEC # endif // BUILDING_DBSIMPLE_DLL #else # define FILELOG_DECLSPEC #endif // _WIN32 //class FILELOG_DECLSPEC FILELog : public Log {}; typedef Log FILELog; #ifndef FILELOG_MAX_LEVEL #define FILELOG_MAX_LEVEL logDEBUG #endif #define FILE_LOG(level) \ if (level > FILELOG_MAX_LEVEL) ;\ else if (level > FILELog::ReportingLevel() || !Output2FILE::Stream()) ; \ else FILELog().Get(level, __FILE__, __LINE__) #define LOG(level) FILE_LOG(level) #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) #include inline std::string NowTime() { const int MAX_LEN = 200; char buffer[MAX_LEN]; if (GetTimeFormatA(LOCALE_USER_DEFAULT, 0, 0, "HH':'mm':'ss", buffer, MAX_LEN) == 0) return "Error in NowTime()"; char result[100] = {0}; static DWORD first = GetTickCount(); std::sprintf(result, "%s.%03ld", buffer, (long)(GetTickCount() - first) % 1000); return result; } #else #include inline std::string NowTime() { char buffer[11]; time_t t; time(&t); tm r; strftime(buffer, sizeof(buffer), "%T", localtime_r(&t, &r)); struct timeval tv; gettimeofday(&tv, 0); char result[100] = {0}; std::sprintf(result, "%s.%03ld", buffer, (long)tv.tv_usec / 1000); return result; } #endif //WIN32 #endif //__LOG_H__ glogg-1.1.0/src/quickfindwidget.cpp0000664000175000017500000001373212657420553016275 0ustar nickonicko/* * Copyright (C) 2010, 2013 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include "log.h" #include #include #include #include #include #include "configuration.h" #include "qfnotifications.h" #include "quickfindwidget.h" const int QuickFindWidget::NOTIFICATION_TIMEOUT = 5000; const QString QFNotification::REACHED_EOF = "Reached end of file, no occurence found."; const QString QFNotification::REACHED_BOF = "Reached beginning of file, no occurence found."; QuickFindWidget::QuickFindWidget( QWidget* parent ) : QWidget( parent ) { // ui_.setupUi( this ); // setFocusProxy(ui_.findEdit); // setProperty("topBorder", true); QHBoxLayout *layout = new QHBoxLayout( this ); layout->setMargin( 0 ); layout->setSpacing( 6 ); closeButton_ = setupToolButton( QLatin1String(""), QLatin1String( ":/images/darkclosebutton.png" ) ); layout->addWidget( closeButton_ ); editQuickFind_ = new QLineEdit( this ); // FIXME: set MinimumSize might be to constraining editQuickFind_->setMinimumSize( QSize( 150, 0 ) ); layout->addWidget( editQuickFind_ ); ignoreCaseCheck_ = new QCheckBox( "Ignore &case" ); layout->addWidget( ignoreCaseCheck_ ); previousButton_ = setupToolButton( QLatin1String("Previous"), QLatin1String( ":/images/arrowup.png" ) ); layout->addWidget( previousButton_ ); nextButton_ = setupToolButton( QLatin1String("Next"), QLatin1String( ":/images/arrowdown.png" ) ); layout->addWidget( nextButton_ ); notificationText_ = new QLabel( "" ); // FIXME: set MinimumSize might be too constraining int width = QFNotification::maxWidth( notificationText_ ); notificationText_->setMinimumSize( width, 0 ); layout->addWidget( notificationText_ ); setMinimumWidth( minimumSizeHint().width() ); // Behaviour connect( closeButton_, SIGNAL( clicked() ), SLOT( closeHandler() ) ); connect( editQuickFind_, SIGNAL( textEdited( QString ) ), this, SLOT( textChanged() ) ); connect( ignoreCaseCheck_, SIGNAL( stateChanged( int ) ), this, SLOT( textChanged() ) ); /* connect( editQuickFind_. SIGNAL( textChanged( QString ) ), this, SLOT( updateButtons() ) ); */ connect( editQuickFind_, SIGNAL( returnPressed() ), this, SLOT( returnHandler() ) ); connect( previousButton_, SIGNAL( clicked() ), this, SLOT( doSearchBackward() ) ); connect( nextButton_, SIGNAL( clicked() ), this, SLOT( doSearchForward() ) ); // Notification timer: notificationTimer_ = new QTimer( this ); notificationTimer_->setSingleShot( true ); connect( notificationTimer_, SIGNAL( timeout() ), this, SLOT( notificationTimeout() ) ); } void QuickFindWidget::userActivate() { userRequested_ = true; QWidget::show(); editQuickFind_->setFocus( Qt::ShortcutFocusReason ); } // // SLOTS // void QuickFindWidget::changeDisplayedPattern( const QString& newPattern ) { editQuickFind_->setText( newPattern ); } void QuickFindWidget::notify( const QFNotification& message ) { LOG(logDEBUG) << "QuickFindWidget::notify()"; notificationText_->setText( message.message() ); QWidget::show(); notificationTimer_->start( NOTIFICATION_TIMEOUT ); } void QuickFindWidget::clearNotification() { LOG(logDEBUG) << "QuickFindWidget::clearNotification()"; notificationText_->setText( "" ); } // User clicks forward arrow void QuickFindWidget::doSearchForward() { LOG(logDEBUG) << "QuickFindWidget::doSearchForward()"; // The user has clicked on a button, so we assume she wants // the widget to stay visible. userRequested_ = true; emit patternConfirmed( editQuickFind_->text(), isIgnoreCase() ); emit searchForward(); } // User clicks backward arrow void QuickFindWidget::doSearchBackward() { LOG(logDEBUG) << "QuickFindWidget::doSearchBackward()"; // The user has clicked on a button, so we assume she wants // the widget to stay visible. userRequested_ = true; emit patternConfirmed( editQuickFind_->text(), isIgnoreCase() ); emit searchBackward(); } // Close and search when the user presses Return void QuickFindWidget::returnHandler() { emit patternConfirmed( editQuickFind_->text(), isIgnoreCase() ); // Close the widget userRequested_ = false; this->hide(); emit close(); } // Close and reset flag when the user clicks 'close' void QuickFindWidget::closeHandler() { userRequested_ = false; this->hide(); emit close(); emit cancelSearch(); } void QuickFindWidget::notificationTimeout() { // We close the widget if the user hasn't explicitely requested it. if ( userRequested_ == false ) this->hide(); } void QuickFindWidget::textChanged() { emit patternUpdated( editQuickFind_->text(), isIgnoreCase() ); } // // Private functions // QToolButton* QuickFindWidget::setupToolButton( const QString &text, const QString &icon) { QToolButton *toolButton = new QToolButton(this); toolButton->setText(text); toolButton->setAutoRaise(true); toolButton->setIcon(QIcon(icon)); toolButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); return toolButton; } bool QuickFindWidget::isIgnoreCase() const { return ( ignoreCaseCheck_->checkState() == Qt::Checked ); } glogg-1.1.0/src/watchtowerlist.cpp0000664000175000017500000000003612657420553016170 0ustar nickonicko#include "watchtowerlist.h" glogg-1.1.0/src/savedsearches.cpp0000664000175000017500000000775612657420553015745 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2011 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ // This file implements class SavedSearch #include #include #include "log.h" #include "savedsearches.h" const int SavedSearches::SAVEDSEARCHES_VERSION = 1; const int SavedSearches::maxNumberOfRecentSearches = 50; SavedSearches::SavedSearches() : savedSearches_() { qRegisterMetaTypeStreamOperators( "SavedSearches" ); } void SavedSearches::addRecent( const QString& text ) { // We're not interested in blank lines if ( text.isEmpty() ) return; // Remove any copy of the about to be added text savedSearches_.removeAll( text ); // Add at the front savedSearches_.push_front( text ); // Trim the list if it's too long while (savedSearches_.size() > maxNumberOfRecentSearches) savedSearches_.pop_back(); } QStringList SavedSearches::recentSearches() const { return savedSearches_; } // // Operators for serialization // QDataStream& operator<<( QDataStream& out, const SavedSearches& object ) { LOG(logDEBUG) << "<>( QDataStream& in, SavedSearches& object ) { LOG(logDEBUG) << ">>operator from SavedSearches"; in >> object.savedSearches_; return in; } // // Persistable virtual functions implementation // void SavedSearches::saveToStorage( QSettings& settings ) const { LOG(logDEBUG) << "SavedSearches::saveToStorage"; settings.beginGroup( "SavedSearches" ); // Remove everything in case the array is shorter than the previous one settings.remove(""); settings.setValue( "version", SAVEDSEARCHES_VERSION ); settings.beginWriteArray( "searchHistory" ); for (int i = 0; i < savedSearches_.size(); ++i) { settings.setArrayIndex( i ); settings.setValue( "string", savedSearches_.at( i ) ); } settings.endArray(); settings.endGroup(); } void SavedSearches::retrieveFromStorage( QSettings& settings ) { LOG(logDEBUG) << "SavedSearches::retrieveFromStorage"; savedSearches_.clear(); if ( settings.contains( "SavedSearches/version" ) ) { // Unserialise the "new style" stored history settings.beginGroup( "SavedSearches" ); if ( settings.value( "version" ) == SAVEDSEARCHES_VERSION ) { int size = settings.beginReadArray( "searchHistory" ); for (int i = 0; i < size; ++i) { settings.setArrayIndex(i); QString search = settings.value( "string" ).toString(); savedSearches_.append( search ); } settings.endArray(); } else { LOG(logERROR) << "Unknown version of FilterSet, ignoring it..."; } settings.endGroup(); } else { LOG(logWARNING) << "Trying to import legacy (<=0.8.2) saved searches..."; SavedSearches tmp_saved_searches = settings.value( "savedSearches" ).value(); *this = tmp_saved_searches; LOG(logWARNING) << "...imported searches: " << savedSearches_.count() << " elements"; // Remove the old key once migration is done settings.remove( "savedSearches" ); // And replace it with the new one saveToStorage( settings ); settings.sync(); } } glogg-1.1.0/src/quickfindmux.cpp0000664000175000017500000001374112657420553015623 0ustar nickonicko/* * Copyright (C) 2013, 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include "log.h" #include "persistentinfo.h" #include "configuration.h" #include "quickfindmux.h" #include "qfnotifications.h" QuickFindMux::QuickFindMux( std::shared_ptr pattern ) : QObject(), pattern_( pattern ), registeredSearchables_() { selector_ = nullptr; // Forward the pattern's signal to our listeners connect( pattern_.get(), SIGNAL( patternUpdated() ), this, SLOT( notifyPatternChanged() ) ); } // // Public member functions // void QuickFindMux::registerSelector( const QuickFindMuxSelectorInterface* selector ) { LOG(logDEBUG) << "QuickFindMux::registerSelector"; // The selector object we will use when forwarding search requests selector_ = selector; unregisterAllSearchables(); if ( selector ) { for ( auto i: selector_->getAllSearchables() ) registerSearchable( i ); } else { // null selector, all is well, we don't do anything. } } void QuickFindMux::setDirection( QFDirection direction ) { LOG(logDEBUG) << "QuickFindMux::setDirection: new direction: " << direction; currentDirection_ = direction; } // // Public slots // void QuickFindMux::searchNext() { LOG(logDEBUG) << "QuickFindMux::searchNext"; if ( currentDirection_ == Forward ) searchForward(); else searchBackward(); } void QuickFindMux::searchPrevious() { LOG(logDEBUG) << "QuickFindMux::searchPrevious"; if ( currentDirection_ == Forward ) searchBackward(); else searchForward(); } void QuickFindMux::searchForward() { LOG(logDEBUG) << "QuickFindMux::searchForward"; if ( auto searchable = getSearchableWidget() ) searchable->searchForward(); } void QuickFindMux::searchBackward() { LOG(logDEBUG) << "QuickFindMux::searchBackward"; if ( auto searchable = getSearchableWidget() ) searchable->searchBackward(); } void QuickFindMux::setNewPattern( const QString& new_pattern, bool ignore_case ) { static std::shared_ptr config = Persistent( "settings" ); LOG(logDEBUG) << "QuickFindMux::setNewPattern"; // If we must do an incremental search, we do it now if ( config->isQuickfindIncremental() ) { pattern_->changeSearchPattern( new_pattern, ignore_case ); if ( auto searchable = getSearchableWidget() ) { if ( currentDirection_ == Forward ) searchable->incrementallySearchForward(); else searchable->incrementallySearchBackward(); } } } void QuickFindMux::confirmPattern( const QString& new_pattern, bool ignore_case ) { static std::shared_ptr config = Persistent( "settings" ); pattern_->changeSearchPattern( new_pattern, ignore_case ); // if non-incremental, we perform the search now if ( ! config->isQuickfindIncremental() ) { searchNext(); } else { if ( auto searchable = getSearchableWidget() ) searchable->incrementalSearchStop(); } } void QuickFindMux::cancelSearch() { static std::shared_ptr config = Persistent( "settings" ); if ( config->isQuickfindIncremental() ) { if ( auto searchable = getSearchableWidget() ) searchable->incrementalSearchAbort(); } } // // Private slots // void QuickFindMux::changeQuickFind( const QString& new_pattern, QFDirection new_direction ) { pattern_->changeSearchPattern( new_pattern ); setDirection( new_direction ); } void QuickFindMux::notifyPatternChanged() { emit patternChanged( pattern_->getPattern() ); } // // Private member functions // // Use the registered 'selector' to determine where to send the search requests. SearchableWidgetInterface* QuickFindMux::getSearchableWidget() const { LOG(logDEBUG) << "QuickFindMux::getSearchableWidget"; SearchableWidgetInterface* searchable = nullptr; if ( selector_ ) searchable = selector_->getActiveSearchable(); else LOG(logWARNING) << "QuickFindMux::getActiveSearchable() no registered selector"; return searchable; } void QuickFindMux::registerSearchable( QObject* searchable ) { LOG(logDEBUG) << "QuickFindMux::registerSearchable"; // The searchable can change our qf pattern connect( searchable, SIGNAL( changeQuickFind( const QString&, QuickFindMux::QFDirection ) ), this, SLOT( changeQuickFind( const QString&, QuickFindMux::QFDirection ) ) ); // Send us notifications connect( searchable, SIGNAL( notifyQuickFind( const QFNotification& ) ), this, SIGNAL( notify( const QFNotification& ) ) ); // And clear them connect( searchable, SIGNAL( clearQuickFindNotification() ), this, SIGNAL( clearNotification() ) ); // Search can be initiated by the view itself connect( searchable, SIGNAL( searchNext() ), this, SLOT( searchNext() ) ); connect( searchable, SIGNAL( searchPrevious() ), this, SLOT( searchPrevious() ) ); registeredSearchables_.push_back( searchable ); } void QuickFindMux::unregisterAllSearchables() { for ( auto searchable: registeredSearchables_ ) disconnect( searchable, 0, this, 0 ); registeredSearchables_.clear(); } glogg-1.1.0/src/optionsdialog.cpp0000664000175000017500000001471012657420553015764 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2011, 2013 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include #include "optionsdialog.h" #include "log.h" #include "persistentinfo.h" #include "configuration.h" static const uint32_t POLL_INTERVAL_MIN = 10; static const uint32_t POLL_INTERVAL_MAX = 3600000; // Constructor OptionsDialog::OptionsDialog( QWidget* parent ) : QDialog(parent) { setupUi( this ); setupTabs(); setupFontList(); setupRegexp(); // Validators QValidator* polling_interval_validator_ = new QIntValidator( POLL_INTERVAL_MIN, POLL_INTERVAL_MAX, this ); pollIntervalLineEdit->setValidator( polling_interval_validator_ ); connect(buttonBox, SIGNAL( clicked( QAbstractButton* ) ), this, SLOT( onButtonBoxClicked( QAbstractButton* ) ) ); connect(fontFamilyBox, SIGNAL( currentIndexChanged(const QString& ) ), this, SLOT( updateFontSize( const QString& ) )); connect(incrementalCheckBox, SIGNAL( toggled( bool ) ), this, SLOT( onIncrementalChanged() ) ); connect(pollingCheckBox, SIGNAL( toggled( bool ) ), this, SLOT( onPollingChanged() ) ); updateDialogFromConfig(); setupIncremental(); setupPolling(); } // // Private functions // // Setups the tabs depending on the configuration void OptionsDialog::setupTabs() { #ifndef GLOGG_SUPPORTS_POLLING tabWidget->removeTab( 1 ); #endif } // Populates the 'family' ComboBox void OptionsDialog::setupFontList() { QFontDatabase database; // We only show the fixed fonts foreach ( const QString &str, database.families() ) { if ( database.isFixedPitch( str ) ) fontFamilyBox->addItem( str ); } } // Populate the regexp ComboBoxes void OptionsDialog::setupRegexp() { QStringList regexpTypes; regexpTypes << tr("Extended Regexp") << tr("Wildcards") << tr("Fixed Strings"); mainSearchBox->addItems( regexpTypes ); quickFindSearchBox->addItems( regexpTypes ); } // Enable/disable the QuickFind options depending on the state // of the "incremental" checkbox. void OptionsDialog::setupIncremental() { if ( incrementalCheckBox->isChecked() ) { quickFindSearchBox->setCurrentIndex( getRegexpIndex( FixedString ) ); quickFindSearchBox->setEnabled( false ); } else { quickFindSearchBox->setEnabled( true ); } } void OptionsDialog::setupPolling() { pollIntervalLineEdit->setEnabled( pollingCheckBox->isChecked() ); } // Convert a regexp type to its index in the list int OptionsDialog::getRegexpIndex( SearchRegexpType syntax ) const { return static_cast( syntax ); } // Convert the index of a regexp type to its type SearchRegexpType OptionsDialog::getRegexpTypeFromIndex( int index ) const { return static_cast( index );; } // Updates the dialog box using values in global Config() void OptionsDialog::updateDialogFromConfig() { std::shared_ptr config = Persistent( "settings" ); // Main font QFontInfo fontInfo = QFontInfo( config->mainFont() ); int familyIndex = fontFamilyBox->findText( fontInfo.family() ); if ( familyIndex != -1 ) fontFamilyBox->setCurrentIndex( familyIndex ); int sizeIndex = fontSizeBox->findText( QString::number(fontInfo.pointSize()) ); if ( sizeIndex != -1 ) fontSizeBox->setCurrentIndex( sizeIndex ); // Regexp types mainSearchBox->setCurrentIndex( getRegexpIndex( config->mainRegexpType() ) ); quickFindSearchBox->setCurrentIndex( getRegexpIndex( config->quickfindRegexpType() ) ); incrementalCheckBox->setChecked( config->isQuickfindIncremental() ); // Polling pollingCheckBox->setChecked( config->pollingEnabled() ); pollIntervalLineEdit->setText( QString::number( config->pollIntervalMs() ) ); } // // Slots // void OptionsDialog::updateFontSize(const QString& fontFamily) { QFontDatabase database; QString oldFontSize = fontSizeBox->currentText(); QList sizes = database.pointSizes( fontFamily, "" ); fontSizeBox->clear(); foreach (int size, sizes) { fontSizeBox->addItem( QString::number(size) ); } // Now restore the size we had before int i = fontSizeBox->findText(oldFontSize); if ( i != -1 ) fontSizeBox->setCurrentIndex(i); } void OptionsDialog::updateConfigFromDialog() { std::shared_ptr config = Persistent( "settings" ); QFont font = QFont( fontFamilyBox->currentText(), (fontSizeBox->currentText()).toInt() ); config->setMainFont(font); config->setMainRegexpType( getRegexpTypeFromIndex( mainSearchBox->currentIndex() ) ); config->setQuickfindRegexpType( getRegexpTypeFromIndex( quickFindSearchBox->currentIndex() ) ); config->setQuickfindIncremental( incrementalCheckBox->isChecked() ); config->setPollingEnabled( pollingCheckBox->isChecked() ); uint32_t poll_interval = pollIntervalLineEdit->text().toUInt(); if ( poll_interval < POLL_INTERVAL_MIN ) poll_interval = POLL_INTERVAL_MIN; else if (poll_interval > POLL_INTERVAL_MAX ) poll_interval = POLL_INTERVAL_MAX; config->setPollIntervalMs( poll_interval ); emit optionsChanged(); } void OptionsDialog::onButtonBoxClicked( QAbstractButton* button ) { QDialogButtonBox::ButtonRole role = buttonBox->buttonRole( button ); if ( ( role == QDialogButtonBox::AcceptRole ) || ( role == QDialogButtonBox::ApplyRole ) ) { updateConfigFromDialog(); } if ( role == QDialogButtonBox::AcceptRole ) accept(); else if ( role == QDialogButtonBox::RejectRole ) reject(); } void OptionsDialog::onIncrementalChanged() { setupIncremental(); } void OptionsDialog::onPollingChanged() { setupPolling(); } glogg-1.1.0/src/quickfindwidget.h0000664000175000017500000000526212657420553015741 0ustar nickonicko/* * Copyright (C) 2010, 2013 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef QUICKFINDWIDGET_H #define QUICKFINDWIDGET_H #include #include class QHBoxLayout; class QLineEdit; class QToolButton; class QLabel; class QCheckBox; class QFNotification; enum QFDirection { Forward, Backward, }; class QuickFindWidget : public QWidget { Q_OBJECT public: QuickFindWidget( QWidget* parent = 0 ); // Show the widget with the given direction // when requested by the user (the widget won't timeout) void userActivate(); public slots: // Instructs the widget to change the pattern displayed void changeDisplayedPattern( const QString& newPattern ); // Show the widget for a notification (will timeout) void notify( const QFNotification& message ); // Clear the notification void clearNotification(); private slots: void doSearchForward(); void doSearchBackward(); void returnHandler(); void closeHandler(); void notificationTimeout(); void textChanged(); signals: // Sent when Return is pressed to confirm the pattern // (pattern and ignor_case flag) void patternConfirmed( const QString&, bool ); // Sent every time the pattern is modified // (pattern and ignor_case flag) void patternUpdated( const QString&, bool ); void close(); // Emitted when the user closes the window void cancelSearch(); void searchForward(); void searchBackward(); void searchNext(); private: const static int NOTIFICATION_TIMEOUT; QHBoxLayout* layout_; QToolButton* closeButton_; QToolButton* nextButton_; QToolButton* previousButton_; QLineEdit* editQuickFind_; QCheckBox* ignoreCaseCheck_; QLabel* notificationText_; QToolButton* setupToolButton(const QString &text, const QString &icon); bool isIgnoreCase() const; QTimer* notificationTimer_; QFDirection direction_; // Whether the user explicitely wants us on the screen bool userRequested_; }; #endif glogg-1.1.0/src/config.h0000664000175000017500000000033112657420553014015 0ustar nickonicko/* This is a temporary file to build with qmake, * to be removed when we move to cmake */ #ifndef CONFIG_H_ #define CONFIG_H_ #ifndef _WIN32 #define HAVE_SYMLINK 1 #endif /* #undef HAS_TEMPLATE_ALIASES */ #endif glogg-1.1.0/src/logmainview.cpp0000664000175000017500000000407512657420553015435 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2011, 2013 Nicolas Bonnefon * and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ // This file implements the LogMainView concrete class. // Most of the actual drawing and event management is done in AbstractLogView // Only behaviour specific to the main (top) view is implemented here. #include "logmainview.h" #include "data/logfiltereddata.h" #include "overview.h" LogMainView::LogMainView( const LogData* newLogData, const QuickFindPattern* const quickFindPattern, Overview* overview, OverviewWidget* overview_widget, QWidget* parent) : AbstractLogView( newLogData, quickFindPattern, parent ) { filteredData_ = NULL; // The main data has a real (non NULL) Overview setOverview( overview, overview_widget ); } // Just update our internal record. void LogMainView::useNewFiltering( LogFilteredData* filteredData ) { filteredData_ = filteredData; if ( getOverview() != NULL ) getOverview()->setFilteredData( filteredData_ ); } AbstractLogView::LineType LogMainView::lineType( int lineNumber ) const { if ( filteredData_ != NULL ) { LineType line_type; if ( filteredData_->isLineMarked( lineNumber ) ) line_type = Marked; else if ( filteredData_->isLineInMatchingList( lineNumber ) ) line_type = Match; else line_type = Normal; return line_type; } else return Normal; } glogg-1.1.0/src/watchtower.cpp0000664000175000017500000000003012657420553015266 0ustar nickonicko#include "watchtower.h" glogg-1.1.0/src/filteredview.h0000664000175000017500000000304012657420553015241 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2012 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef FILTEREDVIEW_H #define FILTEREDVIEW_H #include "abstractlogview.h" #include "data/logfiltereddata.h" // Class implementing the filtered (bottom) view widget. class FilteredView : public AbstractLogView { public: FilteredView( LogFilteredData* newLogData, const QuickFindPattern* const quickFindPattern, QWidget* parent = 0 ); // What is visible in the view. enum Visibility { MatchesOnly, MarksOnly, MarksAndMatches }; void setVisibility( Visibility visi ); protected: virtual LineType lineType( int lineNumber ) const; // Number of the filtered line relative to the unfiltered source virtual qint64 displayLineNumber( int lineNumber ) const; virtual qint64 maxDisplayLineNumber() const; private: LogFilteredData* logFilteredData_; }; #endif glogg-1.1.0/src/encodingspeculator.h0000664000175000017500000000276112657420553016451 0ustar nickonicko/* * Copyright (C) 2016 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef ENCODINGSPECULATOR_H #define ENCODINGSPECULATOR_H #include // The encoder speculator tries to determine the likely encoding // of the stream of bytes which is passed to it. class EncodingSpeculator { public: enum class Encoding { ASCII7, ASCII8, UTF8 }; EncodingSpeculator() : state_( State::ASCIIOnly ) {} // Inject one byte into the speculator void inject_byte( uint8_t byte ); // Returns the current guess based on the previously injected bytes Encoding guess() const; private: enum class State { ASCIIOnly, Unknown8Bit, UTF8LeadingByteSeen, ValidUTF8, }; State state_; uint32_t code_point_; int continuation_left_; uint32_t min_value_; }; #endif glogg-1.1.0/src/qfnotifications.h0000664000175000017500000000405012657420553015752 0ustar nickonicko/* * Copyright (C) 2013 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef QFNOTIFICATIONS_H #define QFNOTIFICATIONS_H #include #include #include // Notifications sent by the QF for displaying to the user // and their translation in UI text. class QFNotification { public: virtual QString message() const = 0; // Max width of the message (in pixels) static int maxWidth( const QWidget* widget ) { QFontMetrics fm = widget->fontMetrics(); return qMax( fm.size( Qt::TextSingleLine, REACHED_BOF ).width(), fm.size( Qt::TextSingleLine, REACHED_EOF ).width() ); } protected: static const QString REACHED_EOF; static const QString REACHED_BOF; }; class QFNotificationReachedEndOfFile : public QFNotification { QString message() const { return REACHED_EOF; } }; class QFNotificationReachedBegininningOfFile : public QFNotification { QString message() const { return REACHED_BOF; } }; class QFNotificationProgress : public QFNotification { public: // Constructor taking the progress (in percent) QFNotificationProgress( int progress_percent ) { progressPercent_ = progress_percent; } QString message() const { return QString( QObject::tr("Searching (position %1 %)") .arg( progressPercent_ ) ); } private: int progressPercent_; }; #endif glogg-1.1.0/src/mainwindow.cpp0000664000175000017500000010152112657420553015262 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2011, 2013, 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ // This file implements MainWindow. It is responsible for creating and // managing the menus, the toolbar, and the CrawlerWidget. It also // load/save the settings on opening/closing of the app #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "log.h" #include "mainwindow.h" #include "sessioninfo.h" #include "recentfiles.h" #include "crawlerwidget.h" #include "filtersdialog.h" #include "optionsdialog.h" #include "persistentinfo.h" #include "menuactiontooltipbehavior.h" #include "tabbedcrawlerwidget.h" #include "externalcom.h" // Returns the size in human readable format static QString readableSize( qint64 size ); MainWindow::MainWindow( std::unique_ptr session, std::shared_ptr external_communicator ) : session_( std::move( session ) ), externalCommunicator_( external_communicator ), recentFiles_( Persistent( "recentFiles" ) ), mainIcon_(), signalMux_(), quickFindMux_( session_->getQuickFindPattern() ), mainTabWidget_() #ifdef GLOGG_SUPPORTS_VERSION_CHECKING ,versionChecker_() #endif { createActions(); createMenus(); createToolBars(); // createStatusBar(); setAcceptDrops( true ); // Default geometry const QRect geometry = QApplication::desktop()->availableGeometry( this ); setGeometry( geometry.x() + 20, geometry.y() + 40, geometry.width() - 140, geometry.height() - 140 ); mainIcon_.addFile( ":/images/hicolor/16x16/glogg.png" ); mainIcon_.addFile( ":/images/hicolor/24x24/glogg.png" ); mainIcon_.addFile( ":/images/hicolor/32x32/glogg.png" ); mainIcon_.addFile( ":/images/hicolor/48x48/glogg.png" ); setWindowIcon( mainIcon_ ); readSettings(); // Connect the signals to the mux (they will be forwarded to the // "current" crawlerwidget // Send actions to the crawlerwidget signalMux_.connect( this, SIGNAL( followSet( bool ) ), SIGNAL( followSet( bool ) ) ); signalMux_.connect( this, SIGNAL( optionsChanged() ), SLOT( applyConfiguration() ) ); signalMux_.connect( this, SIGNAL( enteringQuickFind() ), SLOT( enteringQuickFind() ) ); signalMux_.connect( &quickFindWidget_, SIGNAL( close() ), SLOT( exitingQuickFind() ) ); // Actions from the CrawlerWidget signalMux_.connect( SIGNAL( followModeChanged( bool ) ), this, SLOT( changeFollowMode( bool ) ) ); signalMux_.connect( SIGNAL( updateLineNumber( int ) ), this, SLOT( lineNumberHandler( int ) ) ); // Register for progress status bar signalMux_.connect( SIGNAL( loadingProgressed( int ) ), this, SLOT( updateLoadingProgress( int ) ) ); signalMux_.connect( SIGNAL( loadingFinished( LoadingStatus ) ), this, SLOT( handleLoadingFinished( LoadingStatus ) ) ); // Register for checkbox changes signalMux_.connect( SIGNAL( searchRefreshChanged( int ) ), this, SLOT( handleSearchRefreshChanged( int ) ) ); signalMux_.connect( SIGNAL( ignoreCaseChanged( int ) ), this, SLOT( handleIgnoreCaseChanged( int ) ) ); // Configure the main tabbed widget mainTabWidget_.setDocumentMode( true ); mainTabWidget_.setMovable( true ); //mainTabWidget_.setTabShape( QTabWidget::Triangular ); mainTabWidget_.setTabsClosable( true ); connect( &mainTabWidget_, SIGNAL( tabCloseRequested( int ) ), this, SLOT( closeTab( int ) ) ); connect( &mainTabWidget_, SIGNAL( currentChanged( int ) ), this, SLOT( currentTabChanged( int ) ) ); // Establish the QuickFindWidget and mux ( to send requests from the // QFWidget to the right window ) connect( &quickFindWidget_, SIGNAL( patternConfirmed( const QString&, bool ) ), &quickFindMux_, SLOT( confirmPattern( const QString&, bool ) ) ); connect( &quickFindWidget_, SIGNAL( patternUpdated( const QString&, bool ) ), &quickFindMux_, SLOT( setNewPattern( const QString&, bool ) ) ); connect( &quickFindWidget_, SIGNAL( cancelSearch() ), &quickFindMux_, SLOT( cancelSearch() ) ); connect( &quickFindWidget_, SIGNAL( searchForward() ), &quickFindMux_, SLOT( searchForward() ) ); connect( &quickFindWidget_, SIGNAL( searchBackward() ), &quickFindMux_, SLOT( searchBackward() ) ); connect( &quickFindWidget_, SIGNAL( searchNext() ), &quickFindMux_, SLOT( searchNext() ) ); // QuickFind changes coming from the views connect( &quickFindMux_, SIGNAL( patternChanged( const QString& ) ), this, SLOT( changeQFPattern( const QString& ) ) ); connect( &quickFindMux_, SIGNAL( notify( const QFNotification& ) ), &quickFindWidget_, SLOT( notify( const QFNotification& ) ) ); connect( &quickFindMux_, SIGNAL( clearNotification() ), &quickFindWidget_, SLOT( clearNotification() ) ); // Actions from external instances connect( externalCommunicator_.get(), SIGNAL( loadFile( const QString& ) ), this, SLOT( loadFileNonInteractive( const QString& ) ) ); #ifdef GLOGG_SUPPORTS_VERSION_CHECKING // Version checker notification connect( &versionChecker_, SIGNAL( newVersionFound( const QString& ) ), this, SLOT( newVersionNotification( const QString& ) ) ); #endif // Construct the QuickFind bar quickFindWidget_.hide(); QWidget* central_widget = new QWidget(); QVBoxLayout* main_layout = new QVBoxLayout(); main_layout->setContentsMargins( 0, 0, 0, 0 ); main_layout->addWidget( &mainTabWidget_ ); main_layout->addWidget( &quickFindWidget_ ); central_widget->setLayout( main_layout ); setCentralWidget( central_widget ); } void MainWindow::reloadSession() { QByteArray geometry; session_->storedGeometry( &geometry ); restoreGeometry( geometry ); int current_file_index = -1; for ( auto open_file: session_->restore( []() { return new CrawlerWidget(); }, ¤t_file_index ) ) { QString file_name = { open_file.first.c_str() }; CrawlerWidget* crawler_widget = dynamic_cast( open_file.second ); assert( crawler_widget ); mainTabWidget_.addTab( crawler_widget, strippedName( file_name ) ); } if ( current_file_index >= 0 ) mainTabWidget_.setCurrentIndex( current_file_index ); } void MainWindow::loadInitialFile( QString fileName ) { LOG(logDEBUG) << "loadInitialFile"; // Is there a file passed as argument? if ( !fileName.isEmpty() ) loadFile( fileName ); } void MainWindow::startBackgroundTasks() { LOG(logDEBUG) << "startBackgroundTasks"; #ifdef GLOGG_SUPPORTS_VERSION_CHECKING versionChecker_.startCheck(); #endif } // // Private functions // const MainWindow::EncodingList MainWindow::encoding_list[] = { { "&Auto" }, { "ASCII / &ISO-8859-1" }, { "&UTF-8" }, }; // Menu actions void MainWindow::createActions() { std::shared_ptr config = Persistent( "settings" ); openAction = new QAction(tr("&Open..."), this); openAction->setShortcut(QKeySequence::Open); openAction->setIcon( QIcon( ":/images/open14.png" ) ); openAction->setStatusTip(tr("Open a file")); connect(openAction, SIGNAL(triggered()), this, SLOT(open())); closeAction = new QAction(tr("&Close"), this); closeAction->setShortcut(tr("Ctrl+W")); closeAction->setStatusTip(tr("Close document")); connect(closeAction, SIGNAL(triggered()), this, SLOT(closeTab())); closeAllAction = new QAction(tr("Close &All"), this); closeAllAction->setStatusTip(tr("Close all documents")); connect(closeAllAction, SIGNAL(triggered()), this, SLOT(closeAll())); // Recent files for (int i = 0; i < MaxRecentFiles; ++i) { recentFileActions[i] = new QAction(this); recentFileActions[i]->setVisible(false); connect(recentFileActions[i], SIGNAL(triggered()), this, SLOT(openRecentFile())); } exitAction = new QAction(tr("E&xit"), this); exitAction->setShortcut(tr("Ctrl+Q")); exitAction->setStatusTip(tr("Exit the application")); connect( exitAction, SIGNAL(triggered()), this, SLOT(close()) ); copyAction = new QAction(tr("&Copy"), this); copyAction->setShortcut(QKeySequence::Copy); copyAction->setStatusTip(tr("Copy the selection")); connect( copyAction, SIGNAL(triggered()), this, SLOT(copy()) ); selectAllAction = new QAction(tr("Select &All"), this); selectAllAction->setShortcut(tr("Ctrl+A")); selectAllAction->setStatusTip(tr("Select all the text")); connect( selectAllAction, SIGNAL(triggered()), this, SLOT( selectAll() ) ); findAction = new QAction(tr("&Find..."), this); findAction->setShortcut(QKeySequence::Find); findAction->setStatusTip(tr("Find the text")); connect( findAction, SIGNAL(triggered()), this, SLOT( find() ) ); overviewVisibleAction = new QAction( tr("Matches &overview"), this ); overviewVisibleAction->setCheckable( true ); overviewVisibleAction->setChecked( config->isOverviewVisible() ); connect( overviewVisibleAction, SIGNAL( toggled( bool ) ), this, SLOT( toggleOverviewVisibility( bool )) ); lineNumbersVisibleInMainAction = new QAction( tr("Line &numbers in main view"), this ); lineNumbersVisibleInMainAction->setCheckable( true ); lineNumbersVisibleInMainAction->setChecked( config->mainLineNumbersVisible() ); connect( lineNumbersVisibleInMainAction, SIGNAL( toggled( bool ) ), this, SLOT( toggleMainLineNumbersVisibility( bool )) ); lineNumbersVisibleInFilteredAction = new QAction( tr("Line &numbers in filtered view"), this ); lineNumbersVisibleInFilteredAction->setCheckable( true ); lineNumbersVisibleInFilteredAction->setChecked( config->filteredLineNumbersVisible() ); connect( lineNumbersVisibleInFilteredAction, SIGNAL( toggled( bool ) ), this, SLOT( toggleFilteredLineNumbersVisibility( bool )) ); followAction = new QAction( tr("&Follow File"), this ); followAction->setShortcut(Qt::Key_F); followAction->setCheckable(true); connect( followAction, SIGNAL(toggled( bool )), this, SIGNAL(followSet( bool )) ); reloadAction = new QAction( tr("&Reload"), this ); reloadAction->setShortcut(QKeySequence::Refresh); reloadAction->setIcon( QIcon(":/images/reload14.png") ); signalMux_.connect( reloadAction, SIGNAL(triggered()), SLOT(reload()) ); stopAction = new QAction( tr("&Stop"), this ); stopAction->setIcon( QIcon(":/images/stop14.png") ); stopAction->setEnabled( true ); signalMux_.connect( stopAction, SIGNAL(triggered()), SLOT(stopLoading()) ); filtersAction = new QAction(tr("&Filters..."), this); filtersAction->setStatusTip(tr("Show the Filters box")); connect( filtersAction, SIGNAL(triggered()), this, SLOT(filters()) ); optionsAction = new QAction(tr("&Options..."), this); optionsAction->setStatusTip(tr("Show the Options box")); connect( optionsAction, SIGNAL(triggered()), this, SLOT(options()) ); aboutAction = new QAction(tr("&About"), this); aboutAction->setStatusTip(tr("Show the About box")); connect( aboutAction, SIGNAL(triggered()), this, SLOT(about()) ); aboutQtAction = new QAction(tr("About &Qt"), this); aboutQtAction->setStatusTip(tr("Show the Qt library's About box")); connect( aboutQtAction, SIGNAL(triggered()), this, SLOT(aboutQt()) ); encodingGroup = new QActionGroup( this ); for ( int i = 0; i < CrawlerWidget::ENCODING_MAX; ++i ) { encodingAction[i] = new QAction( tr( encoding_list[i].name ), this ); encodingAction[i]->setCheckable( true ); encodingGroup->addAction( encodingAction[i] ); } encodingAction[0]->setStatusTip(tr("Automatically detect the file's encoding")); encodingAction[0]->setChecked( true ); connect( encodingGroup, SIGNAL( triggered( QAction* ) ), this, SLOT( encodingChanged( QAction* ) ) ); } void MainWindow::createMenus() { fileMenu = menuBar()->addMenu( tr("&File") ); fileMenu->addAction( openAction ); fileMenu->addAction( closeAction ); fileMenu->addAction( closeAllAction ); fileMenu->addSeparator(); for (int i = 0; i < MaxRecentFiles; ++i) { fileMenu->addAction( recentFileActions[i] ); recentFileActionBehaviors[i] = new MenuActionToolTipBehavior(recentFileActions[i], fileMenu, this); } fileMenu->addSeparator(); fileMenu->addAction( exitAction ); editMenu = menuBar()->addMenu( tr("&Edit") ); editMenu->addAction( copyAction ); editMenu->addAction( selectAllAction ); editMenu->addSeparator(); editMenu->addAction( findAction ); viewMenu = menuBar()->addMenu( tr("&View") ); viewMenu->addAction( overviewVisibleAction ); viewMenu->addSeparator(); viewMenu->addAction( lineNumbersVisibleInMainAction ); viewMenu->addAction( lineNumbersVisibleInFilteredAction ); viewMenu->addSeparator(); viewMenu->addAction( followAction ); viewMenu->addSeparator(); viewMenu->addAction( reloadAction ); toolsMenu = menuBar()->addMenu( tr("&Tools") ); toolsMenu->addAction( filtersAction ); toolsMenu->addSeparator(); toolsMenu->addAction( optionsAction ); encodingMenu = menuBar()->addMenu( tr("En&coding") ); encodingMenu->addAction( encodingAction[0] ); encodingMenu->addSeparator(); for ( int i = 1; i < CrawlerWidget::ENCODING_MAX; ++i ) { encodingMenu->addAction( encodingAction[i] ); } menuBar()->addSeparator(); helpMenu = menuBar()->addMenu( tr("&Help") ); helpMenu->addAction( aboutAction ); } void MainWindow::createToolBars() { infoLine = new InfoLine(); infoLine->setFrameStyle( QFrame::WinPanel | QFrame::Sunken ); infoLine->setLineWidth( 0 ); lineNbField = new QLabel( ); lineNbField->setText( "Line 0" ); lineNbField->setAlignment( Qt::AlignLeft | Qt::AlignVCenter ); lineNbField->setMinimumSize( lineNbField->fontMetrics().size( 0, "Line 0000000") ); toolBar = addToolBar( tr("&Toolbar") ); toolBar->setIconSize( QSize( 14, 14 ) ); toolBar->setMovable( false ); toolBar->addAction( openAction ); toolBar->addAction( reloadAction ); toolBar->addWidget( infoLine ); toolBar->addAction( stopAction ); toolBar->addWidget( lineNbField ); } // // Slots // // Opens the file selection dialog to select a new log file void MainWindow::open() { QString defaultDir = "."; // Default to the path of the current file if there is one if ( auto current = currentCrawlerWidget() ) { std::string current_file = session_->getFilename( current ); QFileInfo fileInfo = QFileInfo( QString( current_file.c_str() ) ); defaultDir = fileInfo.path(); } QString fileName = QFileDialog::getOpenFileName(this, tr("Open file"), defaultDir, tr("All files (*)")); if (!fileName.isEmpty()) loadFile(fileName); } // Opens a log file from the recent files list void MainWindow::openRecentFile() { QAction* action = qobject_cast(sender()); if (action) loadFile(action->data().toString()); } // Close current tab void MainWindow::closeTab() { int currentIndex = mainTabWidget_.currentIndex(); if ( currentIndex >= 0 ) { closeTab(currentIndex); } } // Close all tabs void MainWindow::closeAll() { while ( mainTabWidget_.count() ) { closeTab(0); } } // Select all the text in the currently selected view void MainWindow::selectAll() { CrawlerWidget* current = currentCrawlerWidget(); if ( current ) current->selectAll(); } // Copy the currently selected line into the clipboard void MainWindow::copy() { static QClipboard* clipboard = QApplication::clipboard(); CrawlerWidget* current = currentCrawlerWidget(); if ( current ) { clipboard->setText( current->getSelectedText() ); // Put it in the global selection as well (X11 only) clipboard->setText( current->getSelectedText(), QClipboard::Selection ); } } // Display the QuickFind bar void MainWindow::find() { displayQuickFindBar( QuickFindMux::Forward ); } // Opens the 'Filters' dialog box void MainWindow::filters() { FiltersDialog dialog(this); signalMux_.connect(&dialog, SIGNAL( optionsChanged() ), SLOT( applyConfiguration() )); dialog.exec(); signalMux_.disconnect(&dialog, SIGNAL( optionsChanged() ), SLOT( applyConfiguration() )); } // Opens the 'Options' modal dialog box void MainWindow::options() { OptionsDialog dialog(this); signalMux_.connect(&dialog, SIGNAL( optionsChanged() ), SLOT( applyConfiguration() )); dialog.exec(); signalMux_.disconnect(&dialog, SIGNAL( optionsChanged() ), SLOT( applyConfiguration() )); } // Opens the 'About' dialog box. void MainWindow::about() { QMessageBox::about(this, tr("About glogg"), tr("

glogg " GLOGG_VERSION "

" "

A fast, advanced log explorer." #ifdef GLOGG_COMMIT "

Built " GLOGG_DATE " from " GLOGG_COMMIT #endif "

http://glogg.bonnefon.org/

" "

Copyright © 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Nicolas Bonnefon and other contributors" "

You may modify and redistribute the program under the terms of the GPL (version 3 or later)." ) ); } // Opens the 'About Qt' dialog box. void MainWindow::aboutQt() { } void MainWindow::encodingChanged( QAction* action ) { int i = 0; for ( i = 0; i < CrawlerWidget::ENCODING_MAX; ++i ) if ( action == encodingAction[i] ) break; LOG(logDEBUG) << "encodingChanged, encoding " << i; currentCrawlerWidget()->setEncoding( static_cast( i ) ); updateInfoLine(); } void MainWindow::toggleOverviewVisibility( bool isVisible ) { std::shared_ptr config = Persistent( "settings" ); config->setOverviewVisible( isVisible ); emit optionsChanged(); } void MainWindow::toggleMainLineNumbersVisibility( bool isVisible ) { std::shared_ptr config = Persistent( "settings" ); config->setMainLineNumbersVisible( isVisible ); emit optionsChanged(); } void MainWindow::toggleFilteredLineNumbersVisibility( bool isVisible ) { std::shared_ptr config = Persistent( "settings" ); config->setFilteredLineNumbersVisible( isVisible ); emit optionsChanged(); } void MainWindow::changeFollowMode( bool follow ) { followAction->setChecked( follow ); } void MainWindow::lineNumberHandler( int line ) { // The line number received is the internal (starts at 0) lineNbField->setText( tr( "Line %1" ).arg( line + 1 ) ); } void MainWindow::updateLoadingProgress( int progress ) { LOG(logDEBUG) << "Loading progress: " << progress; QString current_file = session_->getFilename( currentCrawlerWidget() ).c_str(); // We ignore 0% and 100% to avoid a flash when the file (or update) // is very short. if ( progress > 0 && progress < 100 ) { infoLine->setText( current_file + tr( " - Indexing lines... (%1 %)" ).arg( progress ) ); infoLine->displayGauge( progress ); stopAction->setEnabled( true ); reloadAction->setEnabled( false ); } } void MainWindow::handleLoadingFinished( LoadingStatus status ) { LOG(logDEBUG) << "handleLoadingFinished success=" << ( status == LoadingStatus::Successful ); // No file is loading loadingFileName.clear(); if ( status == LoadingStatus::Successful ) { updateInfoLine(); infoLine->hideGauge(); stopAction->setEnabled( false ); reloadAction->setEnabled( true ); // Now everything is ready, we can finally show the file! currentCrawlerWidget()->show(); } else { if ( status == LoadingStatus::NoMemory ) { QMessageBox alertBox; alertBox.setText( "Not enough memory." ); alertBox.setInformativeText( "The system does not have enough \ memory to hold the index for this file. The file will now be closed." ); alertBox.setIcon( QMessageBox::Critical ); alertBox.exec(); } closeTab( mainTabWidget_.currentIndex() ); } // mainTabWidget_.setEnabled( true ); } void MainWindow::handleSearchRefreshChanged( int state ) { auto config = Persistent( "settings" ); config->setSearchAutoRefreshDefault( state == Qt::Checked ); } void MainWindow::handleIgnoreCaseChanged( int state ) { auto config = Persistent( "settings" ); config->setSearchIgnoreCaseDefault( state == Qt::Checked ); } void MainWindow::closeTab( int index ) { auto widget = dynamic_cast( mainTabWidget_.widget( index ) ); assert( widget ); widget->stopLoading(); mainTabWidget_.removeTab( index ); session_->close( widget ); delete widget; } void MainWindow::currentTabChanged( int index ) { LOG(logDEBUG) << "currentTabChanged"; if ( index >= 0 ) { CrawlerWidget* crawler_widget = dynamic_cast( mainTabWidget_.widget( index ) ); signalMux_.setCurrentDocument( crawler_widget ); quickFindMux_.registerSelector( crawler_widget ); // New tab is set up with fonts etc... emit optionsChanged(); // Update the menu bar updateMenuBarFromDocument( crawler_widget ); // Update the title bar updateTitleBar( QString( session_->getFilename( crawler_widget ).c_str() ) ); } else { // No tab left signalMux_.setCurrentDocument( nullptr ); quickFindMux_.registerSelector( nullptr ); infoLine->hideGauge(); infoLine->clear(); updateTitleBar( QString() ); } } void MainWindow::changeQFPattern( const QString& newPattern ) { quickFindWidget_.changeDisplayedPattern( newPattern ); } void MainWindow::loadFileNonInteractive( const QString& file_name ) { LOG(logDEBUG) << "loadFileNonInteractive( " << file_name.toStdString() << " )"; loadFile( file_name ); // Try to get the window to the front // This is a bit of a hack but has been tested on: // Qt 5.3 / Gnome / Linux // Qt 4.8 / Win7 #ifdef _WIN32 // Hack copied from http://qt-project.org/forums/viewthread/6164 ::SetWindowPos((HWND) effectiveWinId(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); ::SetWindowPos((HWND) effectiveWinId(), HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); #else Qt::WindowFlags window_flags = windowFlags(); window_flags |= Qt::WindowStaysOnTopHint; setWindowFlags( window_flags ); #endif activateWindow(); raise(); #ifndef _WIN32 window_flags = windowFlags(); window_flags &= ~Qt::WindowStaysOnTopHint; setWindowFlags( window_flags ); #endif showNormal(); } void MainWindow::newVersionNotification( const QString& new_version ) { LOG(logDEBUG) << "newVersionNotification( " << new_version.toStdString() << " )"; QMessageBox msgBox; msgBox.setText( QString( "A new version of glogg (%1) is available for download

" "http://glogg.bonnefon.org/download.html" ).arg( new_version ) ); msgBox.exec(); } // // Events // // Closes the application void MainWindow::closeEvent( QCloseEvent *event ) { writeSettings(); event->accept(); } // Accepts the drag event if it looks like a filename void MainWindow::dragEnterEvent( QDragEnterEvent* event ) { if ( event->mimeData()->hasFormat( "text/uri-list" ) ) event->acceptProposedAction(); } // Tries and loads the file if the URL dropped is local void MainWindow::dropEvent( QDropEvent* event ) { QList urls = event->mimeData()->urls(); if ( urls.isEmpty() ) return; QString fileName = urls.first().toLocalFile(); if ( fileName.isEmpty() ) return; loadFile( fileName ); } void MainWindow::keyPressEvent( QKeyEvent* keyEvent ) { LOG(logDEBUG4) << "keyPressEvent received"; switch ( (keyEvent->text())[0].toLatin1() ) { case '/': displayQuickFindBar( QuickFindMux::Forward ); break; case '?': displayQuickFindBar( QuickFindMux::Backward ); break; default: keyEvent->ignore(); } if ( !keyEvent->isAccepted() ) QMainWindow::keyPressEvent( keyEvent ); } // // Private functions // // Create a CrawlerWidget for the passed file, start its loading // and update the title bar. // The loading is done asynchronously. bool MainWindow::loadFile( const QString& fileName ) { LOG(logDEBUG) << "loadFile ( " << fileName.toStdString() << " )"; // First check if the file is already open... CrawlerWidget* existing_crawler = dynamic_cast( session_->getViewIfOpen( fileName.toStdString() ) ); if ( existing_crawler ) { // ... and switch to it. mainTabWidget_.setCurrentWidget( existing_crawler ); return true; } // Load the file loadingFileName = fileName; try { CrawlerWidget* crawler_widget = dynamic_cast( session_->open( fileName.toStdString(), []() { return new CrawlerWidget(); } ) ); assert( crawler_widget ); // We won't show the widget until the file is fully loaded crawler_widget->hide(); // We disable the tab widget to avoid having someone switch // tab during loading. (maybe FIXME) //mainTabWidget_.setEnabled( false ); int index = mainTabWidget_.addTab( crawler_widget, strippedName( fileName ) ); // Setting the new tab, the user will see a blank page for the duration // of the loading, with no way to switch to another tab mainTabWidget_.setCurrentIndex( index ); // Update the recent files list // (reload the list first in case another glogg changed it) GetPersistentInfo().retrieve( "recentFiles" ); recentFiles_->addRecent( fileName ); GetPersistentInfo().save( "recentFiles" ); updateRecentFileActions(); } catch ( FileUnreadableErr ) { LOG(logDEBUG) << "Can't open file " << fileName.toStdString(); return false; } LOG(logDEBUG) << "Success loading file " << fileName.toStdString(); return true; } // Strips the passed filename from its directory part. QString MainWindow::strippedName( const QString& fullFileName ) const { return QFileInfo( fullFileName ).fileName(); } // Return the currently active CrawlerWidget, or NULL if none CrawlerWidget* MainWindow::currentCrawlerWidget() const { auto current = dynamic_cast( mainTabWidget_.currentWidget() ); return current; } // Update the title bar. void MainWindow::updateTitleBar( const QString& file_name ) { QString shownName = tr( "Untitled" ); if ( !file_name.isEmpty() ) shownName = strippedName( file_name ); setWindowTitle( tr("%1 - %2").arg(shownName).arg(tr("glogg")) #ifdef GLOGG_COMMIT + " (dev build " GLOGG_VERSION ")" #endif ); } // Updates the actions for the recent files. // Must be called after having added a new name to the list. void MainWindow::updateRecentFileActions() { QStringList recent_files = recentFiles_->recentFiles(); for ( int j = 0; j < MaxRecentFiles; ++j ) { if ( j < recent_files.count() ) { QString text = tr("&%1 %2").arg(j + 1).arg(strippedName(recent_files[j])); recentFileActions[j]->setText( text ); recentFileActions[j]->setToolTip( recent_files[j] ); recentFileActions[j]->setData( recent_files[j] ); recentFileActions[j]->setVisible( true ); } else { recentFileActions[j]->setVisible( false ); } } // separatorAction->setVisible(!recentFiles.isEmpty()); } // Update our menu bar to match the settings of the crawler // (used when the tab is changed) void MainWindow::updateMenuBarFromDocument( const CrawlerWidget* crawler ) { auto encoding = crawler->encodingSetting(); encodingAction[static_cast( encoding )]->setChecked( true ); } // Update the top info line from the session void MainWindow::updateInfoLine() { QLocale defaultLocale; // Following should always work as we will only receive enter // this slot if there is a crawler connected. QString current_file = session_->getFilename( currentCrawlerWidget() ).c_str(); uint64_t fileSize; uint32_t fileNbLine; QDateTime lastModified; session_->getFileInfo( currentCrawlerWidget(), &fileSize, &fileNbLine, &lastModified ); if ( lastModified.isValid() ) { const QString date = defaultLocale.toString( lastModified, QLocale::NarrowFormat ); infoLine->setText( tr( "%1 (%2 - %3 lines - modified on %4 - %5)" ) .arg(current_file).arg(readableSize(fileSize)) .arg(fileNbLine).arg( date ) .arg(currentCrawlerWidget()->encodingText()) ); } else { infoLine->setText( tr( "%1 (%2 - %3 lines - %4)" ) .arg(current_file).arg(readableSize(fileSize)) .arg(fileNbLine) .arg(currentCrawlerWidget()->encodingText()) ); } } // Write settings to permanent storage void MainWindow::writeSettings() { // Save the session // Generate the ordered list of widgets and their topLine std::vector< std::tuple> > widget_list; for ( int i = 0; i < mainTabWidget_.count(); ++i ) { auto view = dynamic_cast( mainTabWidget_.widget( i ) ); widget_list.push_back( std::make_tuple( view, 0UL, view->context() ) ); } session_->save( widget_list, saveGeometry() ); // User settings GetPersistentInfo().save( QString( "settings" ) ); } // Read settings from permanent storage void MainWindow::readSettings() { // Get and restore the session // GetPersistentInfo().retrieve( QString( "session" ) ); // SessionInfo session = Persistent( "session" ); /* * FIXME: should be in the session crawlerWidget->restoreState( session.crawlerState() ); */ // History of recent files GetPersistentInfo().retrieve( QString( "recentFiles" ) ); updateRecentFileActions(); // GetPersistentInfo().retrieve( QString( "settings" ) ); GetPersistentInfo().retrieve( QString( "filterSet" ) ); } void MainWindow::displayQuickFindBar( QuickFindMux::QFDirection direction ) { LOG(logDEBUG) << "MainWindow::displayQuickFindBar"; // Warn crawlers so they can save the position of the focus in order // to do incremental search in the right view. emit enteringQuickFind(); quickFindMux_.setDirection( direction ); quickFindWidget_.userActivate(); } // Returns the size in human readable format static QString readableSize( qint64 size ) { static const QString sizeStrs[] = { QObject::tr("B"), QObject::tr("KiB"), QObject::tr("MiB"), QObject::tr("GiB"), QObject::tr("TiB") }; QLocale defaultLocale; unsigned int i; double humanSize = size; for ( i=0; i+1 < (sizeof(sizeStrs)/sizeof(QString)) && (humanSize/1024.0) >= 1024.0; i++ ) humanSize /= 1024.0; if ( humanSize >= 1024.0 ) { humanSize /= 1024.0; i++; } QString output; if ( i == 0 ) // No decimal part if we display straight bytes. output = defaultLocale.toString( (int) humanSize ); else output = defaultLocale.toString( humanSize, 'f', 1 ); output += QString(" ") + sizeStrs[i]; return output; } glogg-1.1.0/src/crawlerwidget.cpp0000664000175000017500000010135312657420553015754 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ // This file implements the CrawlerWidget class. // It is responsible for creating and managing the two views and all // the UI elements. It implements the connection between the UI elements. // It also interacts with the sets of data (full and filtered). #include "log.h" #include #include #include #include #include #include #include #include #include #include "crawlerwidget.h" #include "quickfindpattern.h" #include "overview.h" #include "infoline.h" #include "savedsearches.h" #include "quickfindwidget.h" #include "persistentinfo.h" #include "configuration.h" // Palette for error signaling (yellow background) const QPalette CrawlerWidget::errorPalette( QColor( "yellow" ) ); // Implementation of the view context for the CrawlerWidget class CrawlerWidgetContext : public ViewContextInterface { public: // Construct from the stored string representation CrawlerWidgetContext( const char* string ); // Construct from the value passsed CrawlerWidgetContext( QList sizes, bool ignore_case, bool auto_refresh ) : sizes_( sizes ), ignore_case_( ignore_case ), auto_refresh_( auto_refresh ) {} // Implementation of the ViewContextInterface function std::string toString() const; // Access the Qt sizes array for the QSplitter QList sizes() const { return sizes_; } bool ignoreCase() const { return ignore_case_; } bool autoRefresh() const { return auto_refresh_; } private: QList sizes_; bool ignore_case_; bool auto_refresh_; }; // Constructor only does trivial construction. The real work is done once // the data is attached. CrawlerWidget::CrawlerWidget( QWidget *parent ) : QSplitter( parent ), overview_() { logData_ = nullptr; logFilteredData_ = nullptr; quickFindPattern_ = nullptr; savedSearches_ = nullptr; qfSavedFocus_ = nullptr; // Until we have received confirmation loading is finished, we // should consider we are loading something. loadingInProgress_ = true; // and it's the first time firstLoadDone_ = false; nbMatches_ = 0; dataStatus_ = DataStatus::OLD_DATA; currentLineNumber_ = 0; } // The top line is first one on the main display int CrawlerWidget::getTopLine() const { return logMainView->getTopLine(); } QString CrawlerWidget::getSelectedText() const { if ( filteredView->hasFocus() ) return filteredView->getSelection(); else return logMainView->getSelection(); } void CrawlerWidget::selectAll() { activeView()->selectAll(); } CrawlerWidget::Encoding CrawlerWidget::encodingSetting() const { return encodingSetting_; } QString CrawlerWidget::encodingText() const { return encoding_text_; } // Return a pointer to the view in which we should do the QuickFind SearchableWidgetInterface* CrawlerWidget::doGetActiveSearchable() const { return activeView(); } // Return all the searchable widgets (views) std::vector CrawlerWidget::doGetAllSearchables() const { std::vector searchables = { logMainView, filteredView }; return searchables; } // Update the state of the parent void CrawlerWidget::doSendAllStateSignals() { emit updateLineNumber( currentLineNumber_ ); if ( !loadingInProgress_ ) emit loadingFinished( LoadingStatus::Successful ); } // // Public slots // void CrawlerWidget::stopLoading() { logFilteredData_->interruptSearch(); logData_->interruptLoading(); } void CrawlerWidget::reload() { searchState_.resetState(); logFilteredData_->clearSearch(); filteredView->updateData(); printSearchInfoMessage(); logData_->reload(); // A reload is considered as a first load, // this is to prevent the "new data" icon to be triggered. firstLoadDone_ = false; } void CrawlerWidget::setEncoding( Encoding encoding ) { encodingSetting_ = encoding; updateEncoding(); update(); } // // Protected functions // void CrawlerWidget::doSetData( std::shared_ptr log_data, std::shared_ptr filtered_data ) { logData_ = log_data.get(); logFilteredData_ = filtered_data.get(); } void CrawlerWidget::doSetQuickFindPattern( std::shared_ptr qfp ) { quickFindPattern_ = qfp; } void CrawlerWidget::doSetSavedSearches( std::shared_ptr saved_searches ) { savedSearches_ = saved_searches; // We do setup now, assuming doSetData has been called before // us, that's not great really... setup(); } void CrawlerWidget::doSetViewContext( const char* view_context ) { LOG(logDEBUG) << "CrawlerWidget::doSetViewContext: " << view_context; CrawlerWidgetContext context = { view_context }; setSizes( context.sizes() ); ignoreCaseCheck->setCheckState( context.ignoreCase() ? Qt::Checked : Qt::Unchecked ); auto auto_refresh_check_state = context.autoRefresh() ? Qt::Checked : Qt::Unchecked; searchRefreshCheck->setCheckState( auto_refresh_check_state ); // Manually call the handler as it is not called when changing the state programmatically searchRefreshChangedHandler( auto_refresh_check_state ); } std::shared_ptr CrawlerWidget::doGetViewContext() const { auto context = std::make_shared( sizes(), ( ignoreCaseCheck->checkState() == Qt::Checked ), ( searchRefreshCheck->checkState() == Qt::Checked ) ); return static_cast>( context ); } // // Slots // void CrawlerWidget::startNewSearch() { // Record the search line in the recent list // (reload the list first in case another glogg changed it) GetPersistentInfo().retrieve( "savedSearches" ); savedSearches_->addRecent( searchLineEdit->currentText() ); GetPersistentInfo().save( "savedSearches" ); // Update the SearchLine (history) updateSearchCombo(); // Call the private function to do the search replaceCurrentSearch( searchLineEdit->currentText() ); } void CrawlerWidget::stopSearch() { logFilteredData_->interruptSearch(); searchState_.stopSearch(); printSearchInfoMessage(); } // When receiving the 'newDataAvailable' signal from LogFilteredData void CrawlerWidget::updateFilteredView( int nbMatches, int progress ) { LOG(logDEBUG) << "updateFilteredView received."; if ( progress == 100 ) { // Searching done printSearchInfoMessage( nbMatches ); searchInfoLine->hideGauge(); // De-activate the stop button stopButton->setEnabled( false ); } else { // Search in progress // We ignore 0% and 100% to avoid a flash when the search is very short if ( progress > 0 ) { searchInfoLine->setText( tr("Search in progress (%1 %)... %2 match%3 found so far.") .arg( progress ) .arg( nbMatches ) .arg( nbMatches > 1 ? "es" : "" ) ); searchInfoLine->displayGauge( progress ); } } // If more matches have been found if ( nbMatches > nbMatches_ ) { nbMatches_ = nbMatches; // Recompute the content of the filtered window. filteredView->updateData(); // Update the match overview overview_.updateData( logData_->getNbLine() ); // New data found icon changeDataStatus( DataStatus::NEW_FILTERED_DATA ); // Also update the top window for the coloured bullets. update(); } } void CrawlerWidget::jumpToMatchingLine(int filteredLineNb) { int mainViewLine = logFilteredData_->getMatchingLineNumber(filteredLineNb); logMainView->selectAndDisplayLine(mainViewLine); // FIXME: should be done with a signal. } void CrawlerWidget::updateLineNumberHandler( int line ) { currentLineNumber_ = line; emit updateLineNumber( line ); } void CrawlerWidget::markLineFromMain( qint64 line ) { if ( line < logData_->getNbLine() ) { if ( logFilteredData_->isLineMarked( line ) ) logFilteredData_->deleteMark( line ); else logFilteredData_->addMark( line ); // Recompute the content of the filtered window. filteredView->updateData(); // Update the match overview overview_.updateData( logData_->getNbLine() ); // Also update the top window for the coloured bullets. update(); } } void CrawlerWidget::markLineFromFiltered( qint64 line ) { if ( line < logFilteredData_->getNbLine() ) { qint64 line_in_file = logFilteredData_->getMatchingLineNumber( line ); if ( logFilteredData_->filteredLineTypeByIndex( line ) == LogFilteredData::Mark ) logFilteredData_->deleteMark( line_in_file ); else logFilteredData_->addMark( line_in_file ); // Recompute the content of the filtered window. filteredView->updateData(); // Update the match overview overview_.updateData( logData_->getNbLine() ); // Also update the top window for the coloured bullets. update(); } } void CrawlerWidget::applyConfiguration() { std::shared_ptr config = Persistent( "settings" ); QFont font = config->mainFont(); LOG(logDEBUG) << "CrawlerWidget::applyConfiguration"; // Whatever font we use, we should NOT use kerning font.setKerning( false ); font.setFixedPitch( true ); #if QT_VERSION > 0x040700 // Necessary on systems doing subpixel positionning (e.g. Ubuntu 12.04) font.setStyleStrategy( QFont::ForceIntegerMetrics ); #endif logMainView->setFont(font); filteredView->setFont(font); logMainView->setLineNumbersVisible( config->mainLineNumbersVisible() ); filteredView->setLineNumbersVisible( config->filteredLineNumbersVisible() ); overview_.setVisible( config->isOverviewVisible() ); logMainView->refreshOverview(); logMainView->updateDisplaySize(); logMainView->update(); filteredView->updateDisplaySize(); filteredView->update(); // Polling interval logData_->setPollingInterval( config->pollingEnabled() ? config->pollIntervalMs() : 0 ); // Update the SearchLine (history) updateSearchCombo(); } void CrawlerWidget::enteringQuickFind() { LOG(logDEBUG) << "CrawlerWidget::enteringQuickFind"; // Remember who had the focus (only if it is one of our views) QWidget* focus_widget = QApplication::focusWidget(); if ( ( focus_widget == logMainView ) || ( focus_widget == filteredView ) ) qfSavedFocus_ = focus_widget; else qfSavedFocus_ = nullptr; } void CrawlerWidget::exitingQuickFind() { // Restore the focus once the QFBar has been hidden if ( qfSavedFocus_ ) qfSavedFocus_->setFocus(); } void CrawlerWidget::loadingFinishedHandler( LoadingStatus status ) { loadingInProgress_ = false; // We need to refresh the main window because the view lines on the // overview have probably changed. overview_.updateData( logData_->getNbLine() ); // FIXME, handle topLine // logMainView->updateData( logData_, topLine ); logMainView->updateData(); // Shall we Forbid starting a search when loading in progress? // searchButton->setEnabled( false ); // searchButton->setEnabled( true ); // See if we need to auto-refresh the search if ( searchState_.isAutorefreshAllowed() ) { if ( searchState_.isFileTruncated() ) // We need to restart the search replaceCurrentSearch( searchLineEdit->currentText() ); else logFilteredData_->updateSearch(); } // Set the encoding for the views updateEncoding(); emit loadingFinished( status ); // Also change the data available icon if ( firstLoadDone_ ) changeDataStatus( DataStatus::NEW_DATA ); else firstLoadDone_ = true; } void CrawlerWidget::fileChangedHandler( LogData::MonitoredFileStatus status ) { // Handle the case where the file has been truncated if ( status == LogData::Truncated ) { // Clear all marks (TODO offer the option to keep them) logFilteredData_->clearMarks(); if ( ! searchInfoLine->text().isEmpty() ) { // Invalidate the search logFilteredData_->clearSearch(); filteredView->updateData(); searchState_.truncateFile(); printSearchInfoMessage(); nbMatches_ = 0; } } } // Returns a pointer to the window in which the search should be done AbstractLogView* CrawlerWidget::activeView() const { QWidget* activeView; // Search in the window that has focus, or the window where 'Find' was // called from, or the main window. if ( filteredView->hasFocus() || logMainView->hasFocus() ) activeView = QApplication::focusWidget(); else activeView = qfSavedFocus_; if ( activeView ) { AbstractLogView* view = qobject_cast( activeView ); return view; } else { LOG(logWARNING) << "No active view, defaulting to logMainView"; return logMainView; } } void CrawlerWidget::searchForward() { LOG(logDEBUG) << "CrawlerWidget::searchForward"; activeView()->searchForward(); } void CrawlerWidget::searchBackward() { LOG(logDEBUG) << "CrawlerWidget::searchBackward"; activeView()->searchBackward(); } void CrawlerWidget::searchRefreshChangedHandler( int state ) { searchState_.setAutorefresh( state == Qt::Checked ); printSearchInfoMessage( logFilteredData_->getNbMatches() ); } void CrawlerWidget::searchTextChangeHandler() { // We suspend auto-refresh searchState_.changeExpression(); printSearchInfoMessage( logFilteredData_->getNbMatches() ); } void CrawlerWidget::changeFilteredViewVisibility( int index ) { QStandardItem* item = visibilityModel_->item( index ); FilteredView::Visibility visibility = static_cast< FilteredView::Visibility>( item->data().toInt() ); filteredView->setVisibility( visibility ); } void CrawlerWidget::addToSearch( const QString& string ) { QString text = searchLineEdit->currentText(); if ( text.isEmpty() ) text = string; else { // Escape the regexp chars from the string before adding it. text += ( '|' + QRegExp::escape( string ) ); } searchLineEdit->setEditText( text ); // Set the focus to lineEdit so that the user can press 'Return' immediately searchLineEdit->lineEdit()->setFocus(); } void CrawlerWidget::mouseHoveredOverMatch( qint64 line ) { qint64 line_in_mainview = logFilteredData_->getMatchingLineNumber( line ); overviewWidget_->highlightLine( line_in_mainview ); } void CrawlerWidget::activityDetected() { changeDataStatus( DataStatus::OLD_DATA ); } // // Private functions // // Build the widget and connect all the signals, this must be done once // the data are attached. void CrawlerWidget::setup() { setOrientation(Qt::Vertical); assert( logData_ ); assert( logFilteredData_ ); // The views bottomWindow = new QWidget; overviewWidget_ = new OverviewWidget(); logMainView = new LogMainView( logData_, quickFindPattern_.get(), &overview_, overviewWidget_ ); filteredView = new FilteredView( logFilteredData_, quickFindPattern_.get() ); overviewWidget_->setOverview( &overview_ ); overviewWidget_->setParent( logMainView ); // Connect the search to the top view logMainView->useNewFiltering( logFilteredData_ ); // Construct the visibility button visibilityModel_ = new QStandardItemModel( this ); QStandardItem *marksAndMatchesItem = new QStandardItem( tr( "Marks and matches" ) ); QPixmap marksAndMatchesPixmap( 16, 10 ); marksAndMatchesPixmap.fill( Qt::gray ); marksAndMatchesItem->setIcon( QIcon( marksAndMatchesPixmap ) ); marksAndMatchesItem->setData( FilteredView::MarksAndMatches ); visibilityModel_->appendRow( marksAndMatchesItem ); QStandardItem *marksItem = new QStandardItem( tr( "Marks" ) ); QPixmap marksPixmap( 16, 10 ); marksPixmap.fill( Qt::blue ); marksItem->setIcon( QIcon( marksPixmap ) ); marksItem->setData( FilteredView::MarksOnly ); visibilityModel_->appendRow( marksItem ); QStandardItem *matchesItem = new QStandardItem( tr( "Matches" ) ); QPixmap matchesPixmap( 16, 10 ); matchesPixmap.fill( Qt::red ); matchesItem->setIcon( QIcon( matchesPixmap ) ); matchesItem->setData( FilteredView::MatchesOnly ); visibilityModel_->appendRow( matchesItem ); QListView *visibilityView = new QListView( this ); visibilityView->setMovement( QListView::Static ); visibilityView->setMinimumWidth( 170 ); // Only needed with custom style-sheet visibilityBox = new QComboBox(); visibilityBox->setModel( visibilityModel_ ); visibilityBox->setView( visibilityView ); // Select "Marks and matches" by default (same default as the filtered view) visibilityBox->setCurrentIndex( 0 ); // TODO: Maybe there is some way to set the popup width to be // sized-to-content (as it is when the stylesheet is not overriden) in the // stylesheet as opposed to setting a hard min-width on the view above. visibilityBox->setStyleSheet( " \ QComboBox:on {\ padding: 1px 2px 1px 6px;\ width: 19px;\ } \ QComboBox:!on {\ padding: 1px 2px 1px 7px;\ width: 19px;\ height: 16px;\ border: 1px solid gray;\ } \ QComboBox::drop-down::down-arrow {\ width: 0px;\ border-width: 0px;\ } \ " ); // Construct the Search Info line searchInfoLine = new InfoLine(); searchInfoLine->setFrameStyle( QFrame::WinPanel | QFrame::Sunken ); searchInfoLine->setLineWidth( 1 ); searchInfoLineDefaultPalette = searchInfoLine->palette(); ignoreCaseCheck = new QCheckBox( "Ignore &case" ); searchRefreshCheck = new QCheckBox( "Auto-&refresh" ); // Construct the Search line searchLabel = new QLabel(tr("&Text: ")); searchLineEdit = new QComboBox; searchLineEdit->setEditable( true ); searchLineEdit->setCompleter( 0 ); searchLineEdit->addItems( savedSearches_->recentSearches() ); searchLineEdit->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum ); searchLineEdit->setSizeAdjustPolicy( QComboBox::AdjustToMinimumContentsLengthWithIcon ); searchLabel->setBuddy( searchLineEdit ); searchButton = new QToolButton(); searchButton->setText( tr("&Search") ); searchButton->setAutoRaise( true ); stopButton = new QToolButton(); stopButton->setIcon( QIcon(":/images/stop14.png") ); stopButton->setAutoRaise( true ); stopButton->setEnabled( false ); QHBoxLayout* searchLineLayout = new QHBoxLayout; searchLineLayout->addWidget(searchLabel); searchLineLayout->addWidget(searchLineEdit); searchLineLayout->addWidget(searchButton); searchLineLayout->addWidget(stopButton); searchLineLayout->setContentsMargins(6, 0, 6, 0); stopButton->setSizePolicy( QSizePolicy( QSizePolicy::Maximum, QSizePolicy::Maximum ) ); searchButton->setSizePolicy( QSizePolicy( QSizePolicy::Maximum, QSizePolicy::Maximum ) ); QHBoxLayout* searchInfoLineLayout = new QHBoxLayout; searchInfoLineLayout->addWidget( visibilityBox ); searchInfoLineLayout->addWidget( searchInfoLine ); searchInfoLineLayout->addWidget( ignoreCaseCheck ); searchInfoLineLayout->addWidget( searchRefreshCheck ); // Construct the bottom window QVBoxLayout* bottomMainLayout = new QVBoxLayout; bottomMainLayout->addLayout(searchLineLayout); bottomMainLayout->addLayout(searchInfoLineLayout); bottomMainLayout->addWidget(filteredView); bottomMainLayout->setContentsMargins(2, 1, 2, 1); bottomWindow->setLayout(bottomMainLayout); addWidget( logMainView ); addWidget( bottomWindow ); // Default splitter position (usually overridden by the config file) QList splitterSizes; splitterSizes += 400; splitterSizes += 100; setSizes( splitterSizes ); // Default search checkboxes auto config = Persistent( "settings" ); searchRefreshCheck->setCheckState( config->isSearchAutoRefreshDefault() ? Qt::Checked : Qt::Unchecked ); // Manually call the handler as it is not called when changing the state programmatically searchRefreshChangedHandler( searchRefreshCheck->checkState() ); ignoreCaseCheck->setCheckState( config->isSearchIgnoreCaseDefault() ? Qt::Checked : Qt::Unchecked ); // Connect the signals connect(searchLineEdit->lineEdit(), SIGNAL( returnPressed() ), searchButton, SIGNAL( clicked() )); connect(searchLineEdit->lineEdit(), SIGNAL( textEdited( const QString& ) ), this, SLOT( searchTextChangeHandler() )); connect(searchButton, SIGNAL( clicked() ), this, SLOT( startNewSearch() ) ); connect(stopButton, SIGNAL( clicked() ), this, SLOT( stopSearch() ) ); connect(visibilityBox, SIGNAL( currentIndexChanged( int ) ), this, SLOT( changeFilteredViewVisibility( int ) ) ); connect(logMainView, SIGNAL( newSelection( int ) ), logMainView, SLOT( update() ) ); connect(filteredView, SIGNAL( newSelection( int ) ), this, SLOT( jumpToMatchingLine( int ) ) ); connect(filteredView, SIGNAL( newSelection( int ) ), filteredView, SLOT( update() ) ); connect(logMainView, SIGNAL( updateLineNumber( int ) ), this, SLOT( updateLineNumberHandler( int ) ) ); connect(logMainView, SIGNAL( markLine( qint64 ) ), this, SLOT( markLineFromMain( qint64 ) ) ); connect(filteredView, SIGNAL( markLine( qint64 ) ), this, SLOT( markLineFromFiltered( qint64 ) ) ); connect(logMainView, SIGNAL( addToSearch( const QString& ) ), this, SLOT( addToSearch( const QString& ) ) ); connect(filteredView, SIGNAL( addToSearch( const QString& ) ), this, SLOT( addToSearch( const QString& ) ) ); connect(filteredView, SIGNAL( mouseHoveredOverLine( qint64 ) ), this, SLOT( mouseHoveredOverMatch( qint64 ) ) ); connect(filteredView, SIGNAL( mouseLeftHoveringZone() ), overviewWidget_, SLOT( removeHighlight() ) ); // Follow option (up and down) connect(this, SIGNAL( followSet( bool ) ), logMainView, SLOT( followSet( bool ) ) ); connect(this, SIGNAL( followSet( bool ) ), filteredView, SLOT( followSet( bool ) ) ); connect(logMainView, SIGNAL( followModeChanged( bool ) ), this, SIGNAL( followModeChanged( bool ) ) ); connect(filteredView, SIGNAL( followModeChanged( bool ) ), this, SIGNAL( followModeChanged( bool ) ) ); // Detect activity in the views connect(logMainView, SIGNAL( activity() ), this, SLOT( activityDetected() ) ); connect(filteredView, SIGNAL( activity() ), this, SLOT( activityDetected() ) ); connect( logFilteredData_, SIGNAL( searchProgressed( int, int ) ), this, SLOT( updateFilteredView( int, int ) ) ); // Sent load file update to MainWindow (for status update) connect( logData_, SIGNAL( loadingProgressed( int ) ), this, SIGNAL( loadingProgressed( int ) ) ); connect( logData_, SIGNAL( loadingFinished( LoadingStatus ) ), this, SLOT( loadingFinishedHandler( LoadingStatus ) ) ); connect( logData_, SIGNAL( fileChanged( LogData::MonitoredFileStatus ) ), this, SLOT( fileChangedHandler( LogData::MonitoredFileStatus ) ) ); // Search auto-refresh connect( searchRefreshCheck, SIGNAL( stateChanged( int ) ), this, SLOT( searchRefreshChangedHandler( int ) ) ); // Advise the parent the checkboxes have been changed // (for maintaining default config) connect( searchRefreshCheck, SIGNAL( stateChanged( int ) ), this, SIGNAL( searchRefreshChanged( int ) ) ); connect( ignoreCaseCheck, SIGNAL( stateChanged( int ) ), this, SIGNAL( ignoreCaseChanged( int ) ) ); } // Create a new search using the text passed, replace the currently // used one and destroy the old one. void CrawlerWidget::replaceCurrentSearch( const QString& searchText ) { // Interrupt the search if it's ongoing logFilteredData_->interruptSearch(); nbMatches_ = 0; // We have to wait for the last search update (100%) // before clearing/restarting to avoid having remaining results. // FIXME: this is a bit of a hack, we call processEvents // for Qt to empty its event queue, including (hopefully) // the search update event sent by logFilteredData_. It saves // us the overhead of having proper sync. QApplication::processEvents( QEventLoop::ExcludeUserInputEvents ); if ( !searchText.isEmpty() ) { // Determine the type of regexp depending on the config QRegExp::PatternSyntax syntax; static std::shared_ptr config = Persistent( "settings" ); switch ( config->mainRegexpType() ) { case Wildcard: syntax = QRegExp::Wildcard; break; case FixedString: syntax = QRegExp::FixedString; break; default: syntax = QRegExp::RegExp2; break; } // Set the pattern case insensitive if needed Qt::CaseSensitivity case_sensitivity = Qt::CaseSensitive; if ( ignoreCaseCheck->checkState() == Qt::Checked ) case_sensitivity = Qt::CaseInsensitive; // Constructs the regexp QRegExp regexp( searchText, case_sensitivity, syntax ); if ( regexp.isValid() ) { // Activate the stop button stopButton->setEnabled( true ); // Start a new asynchronous search logFilteredData_->runSearch( regexp ); // Accept auto-refresh of the search searchState_.startSearch(); } else { // The regexp is wrong logFilteredData_->clearSearch(); filteredView->updateData(); searchState_.resetState(); // Inform the user QString errorMessage = tr("Error in expression: "); errorMessage += regexp.errorString(); searchInfoLine->setPalette( errorPalette ); searchInfoLine->setText( errorMessage ); } } else { logFilteredData_->clearSearch(); filteredView->updateData(); searchState_.resetState(); printSearchInfoMessage(); } } // Updates the content of the drop down list for the saved searches, // called when the SavedSearch has been changed. void CrawlerWidget::updateSearchCombo() { const QString text = searchLineEdit->lineEdit()->text(); searchLineEdit->clear(); searchLineEdit->addItems( savedSearches_->recentSearches() ); // In case we had something that wasn't added to the list (blank...): searchLineEdit->lineEdit()->setText( text ); } // Print the search info message. void CrawlerWidget::printSearchInfoMessage( int nbMatches ) { QString text; switch ( searchState_.getState() ) { case SearchState::NoSearch: // Blank text is fine break; case SearchState::Static: text = tr("%1 match%2 found.").arg( nbMatches ) .arg( nbMatches > 1 ? "es" : "" ); break; case SearchState::Autorefreshing: text = tr("%1 match%2 found. Search is auto-refreshing...").arg( nbMatches ) .arg( nbMatches > 1 ? "es" : "" ); break; case SearchState::FileTruncated: case SearchState::TruncatedAutorefreshing: text = tr("File truncated on disk, previous search results are not valid anymore."); break; } searchInfoLine->setPalette( searchInfoLineDefaultPalette ); searchInfoLine->setText( text ); } // Change the data status and, if needed, advise upstream. void CrawlerWidget::changeDataStatus( DataStatus status ) { if ( ( status != dataStatus_ ) && (! ( dataStatus_ == DataStatus::NEW_FILTERED_DATA && status == DataStatus::NEW_DATA ) ) ) { dataStatus_ = status; emit dataStatusChanged( dataStatus_ ); } } // Determine the right encoding and set the views. void CrawlerWidget::updateEncoding() { static const char* latin1_encoding = "iso-8859-1"; static const char* utf8_encoding = "utf-8"; const char* encoding; switch ( encodingSetting_ ) { case ENCODING_AUTO: switch ( logData_->getDetectedEncoding() ) { case EncodingSpeculator::Encoding::ASCII7: encoding = latin1_encoding; encoding_text_ = tr( "US-ASCII" ); break; case EncodingSpeculator::Encoding::ASCII8: encoding = latin1_encoding; encoding_text_ = tr( "ISO-8859-1" ); break; case EncodingSpeculator::Encoding::UTF8: encoding = utf8_encoding; encoding_text_ = tr( "UTF-8" ); break; } break; case ENCODING_UTF8: encoding = utf8_encoding; encoding_text_ = tr( "Displayed as UTF-8" ); break; case ENCODING_ISO_8859_1: default: encoding = latin1_encoding; encoding_text_ = tr( "Displayed as ISO-8859-1" ); break; } logData_->setDisplayEncoding( encoding ); logMainView->forceRefresh(); logFilteredData_->setDisplayEncoding( encoding ); filteredView->forceRefresh(); } // // SearchState implementation // void CrawlerWidget::SearchState::resetState() { state_ = NoSearch; } void CrawlerWidget::SearchState::setAutorefresh( bool refresh ) { autoRefreshRequested_ = refresh; if ( refresh ) { if ( state_ == Static ) state_ = Autorefreshing; /* else if ( state_ == FileTruncated ) state_ = TruncatedAutorefreshing; */ } else { if ( state_ == Autorefreshing ) state_ = Static; else if ( state_ == TruncatedAutorefreshing ) state_ = FileTruncated; } } void CrawlerWidget::SearchState::truncateFile() { if ( state_ == Autorefreshing || state_ == TruncatedAutorefreshing ) { state_ = TruncatedAutorefreshing; } else { state_ = FileTruncated; } } void CrawlerWidget::SearchState::changeExpression() { if ( state_ == Autorefreshing ) state_ = Static; } void CrawlerWidget::SearchState::stopSearch() { if ( state_ == Autorefreshing ) state_ = Static; } void CrawlerWidget::SearchState::startSearch() { if ( autoRefreshRequested_ ) state_ = Autorefreshing; else state_ = Static; } /* * CrawlerWidgetContext */ CrawlerWidgetContext::CrawlerWidgetContext( const char* string ) { QRegExp regex = QRegExp( "S(\\d+):(\\d+)" ); if ( regex.indexIn( string ) > -1 ) { sizes_ = { regex.cap(1).toInt(), regex.cap(2).toInt() }; LOG(logDEBUG) << "sizes_: " << sizes_[0] << " " << sizes_[1]; } else { LOG(logWARNING) << "Unrecognised view size: " << string; // Default values; sizes_ = { 100, 400 }; } QRegExp case_refresh_regex = QRegExp( "IC(\\d+):AR(\\d+)" ); if ( case_refresh_regex.indexIn( string ) > -1 ) { ignore_case_ = ( case_refresh_regex.cap(1).toInt() == 1 ); auto_refresh_ = ( case_refresh_regex.cap(2).toInt() == 1 ); LOG(logDEBUG) << "ignore_case_: " << ignore_case_ << " auto_refresh_: " << auto_refresh_; } else { LOG(logWARNING) << "Unrecognised case/refresh: " << string; ignore_case_ = false; auto_refresh_ = false; } } std::string CrawlerWidgetContext::toString() const { char string[160]; snprintf( string, sizeof string, "S%d:%d:IC%d:AR%d", sizes_[0], sizes_[1], ignore_case_, auto_refresh_ ); return { string }; } glogg-1.1.0/src/session.h0000664000175000017500000001133512657420553014241 0ustar nickonicko/* * Copyright (C) 2013, 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef SESSION_H #define SESSION_H #include #include #include #include #include #include #include #include "quickfindpattern.h" class ViewInterface; class ViewContextInterface; class LogData; class LogFilteredData; class SavedSearches; // File unreadable error class FileUnreadableErr {}; // The session is responsible for maintaining the list of open log files // and their association with Views. // It also maintains the domain objects which are common to all log files // (SavedSearches, FileHistory, QFPattern...) class Session { public: Session(); ~Session(); // No copy/assignment please Session( const Session& ) = delete; Session& operator =( const Session& ) = delete; // Return the view associated to a file if it is open // The filename must be strictly identical to trigger a match // (no match in case of e.g. relative vs. absolute pathname. ViewInterface* getViewIfOpen( const std::string& file_name ) const; // Open a new file, starts its asynchronous loading, and construct a new // view for it (the caller passes a factory to build the concrete view) // The ownership of the view is given to the caller // Throw exceptions if the file is already open or if it cannot be open. ViewInterface* open( const std::string& file_name, std::function view_factory ); // Close the file identified by the view passed // Throw an exception if it does not exist. void close( const ViewInterface* view ); // Open all the files listed in the stored session // (see ::open) // returns a vector of pairs (file_name, view) and the index of the // current file (or -1 if none). std::vector> restore( std::function view_factory, int *current_file_index ); // Save the session to persistent storage. An ordered list of // (view, topline, ViewContextInterface) is passed, this is because only // the main window know the order in which the views are presented to // the user (it might have changed since file were opened). // Also, the geometry information is passed as an opaque string. void save( std::vector< std::tuple> > view_list, const QByteArray& geometry ); // Get the geometry string from persistent storage for this session. void storedGeometry( QByteArray* geometry ) const; // Get the file name for the passed view. std::string getFilename( const ViewInterface* view ) const; // Get the size (in bytes) and number of lines in the current file. // The file is identified by the view attached to it. void getFileInfo( const ViewInterface* view, uint64_t* fileSize, uint32_t* fileNbLine, QDateTime* lastModified ) const; // Get a (non-const) reference to the QuickFind pattern. std::shared_ptr getQuickFindPattern() const { return quickFindPattern_; } private: struct OpenFile { std::string fileName; std::shared_ptr logData; std::shared_ptr logFilteredData; ViewInterface* view; }; // Open a file without checking if it is existing/readable ViewInterface* openAlways( const std::string& file_name, std::function view_factory, const char* view_context ); // Find an open file from its associated view OpenFile* findOpenFileFromView( const ViewInterface* view ); const OpenFile* findOpenFileFromView( const ViewInterface* view ) const; // List of open files typedef std::unordered_map OpenFileMap; OpenFileMap openFiles_; // Global search history std::shared_ptr savedSearches_; // Global quickfind pattern std::shared_ptr quickFindPattern_; }; #endif glogg-1.1.0/src/filterset.cpp0000664000175000017500000001350712657420553015115 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2011 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ // This file implements classes Filter and FilterSet #include #include #include "log.h" #include "filterset.h" const int FilterSet::FILTERSET_VERSION = 1; Filter::Filter() { } Filter::Filter( const QString& pattern, const QString& foreColorName, const QString& backColorName ) : regexp_( pattern ), foreColorName_( foreColorName ), backColorName_( backColorName ), enabled_( true ) { LOG(logDEBUG) << "New Filter, fore: " << foreColorName_.toStdString() << " back: " << backColorName_.toStdString(); } QString Filter::pattern() const { return regexp_.pattern(); } void Filter::setPattern( const QString& pattern ) { regexp_.setPattern( pattern ); } const QString& Filter::foreColorName() const { return foreColorName_; } void Filter::setForeColor( const QString& foreColorName ) { foreColorName_ = foreColorName; } const QString& Filter::backColorName() const { return backColorName_; } void Filter::setBackColor( const QString& backColorName ) { backColorName_ = backColorName; } int Filter::indexIn( const QString& string ) const { return regexp_.indexIn( string ); } // // Operators for serialization // QDataStream& operator<<( QDataStream& out, const Filter& object ) { LOG(logDEBUG) << "<>( QDataStream& in, Filter& object ) { LOG(logDEBUG) << ">>operator from Filter"; in >> object.regexp_; in >> object.foreColorName_; in >> object.backColorName_; return in; } // Default constructor FilterSet::FilterSet() { qRegisterMetaTypeStreamOperators( "Filter" ); qRegisterMetaTypeStreamOperators( "FilterSet" ); qRegisterMetaTypeStreamOperators( "FilterSet::FilterList" ); } bool FilterSet::matchLine( const QString& line, QColor* foreColor, QColor* backColor ) const { for ( QList::const_iterator i = filterList.constBegin(); i != filterList.constEnd(); i++ ) { if ( i->indexIn( line ) != -1 ) { foreColor->setNamedColor( i->foreColorName() ); backColor->setNamedColor( i->backColorName() ); return true; } } return false; } // // Operators for serialization // QDataStream& operator<<( QDataStream& out, const FilterSet& object ) { LOG(logDEBUG) << "<>( QDataStream& in, FilterSet& object ) { LOG(logDEBUG) << ">>operator from FilterSet"; in >> object.filterList; return in; } // // Persistable virtual functions implementation // void Filter::saveToStorage( QSettings& settings ) const { LOG(logDEBUG) << "Filter::saveToStorage"; settings.setValue( "regexp", regexp_.pattern() ); settings.setValue( "fore_colour", foreColorName_ ); settings.setValue( "back_colour", backColorName_ ); } void Filter::retrieveFromStorage( QSettings& settings ) { LOG(logDEBUG) << "Filter::retrieveFromStorage"; regexp_ = QRegExp( settings.value( "regexp" ).toString() ); foreColorName_ = settings.value( "fore_colour" ).toString(); backColorName_ = settings.value( "back_colour" ).toString(); } void FilterSet::saveToStorage( QSettings& settings ) const { LOG(logDEBUG) << "FilterSet::saveToStorage"; settings.beginGroup( "FilterSet" ); // Remove everything in case the array is shorter than the previous one settings.remove(""); settings.setValue( "version", FILTERSET_VERSION ); settings.beginWriteArray( "filters" ); for (int i = 0; i < filterList.size(); ++i) { settings.setArrayIndex(i); filterList[i].saveToStorage( settings ); } settings.endArray(); settings.endGroup(); } void FilterSet::retrieveFromStorage( QSettings& settings ) { LOG(logDEBUG) << "FilterSet::retrieveFromStorage"; filterList.clear(); if ( settings.contains( "FilterSet/version" ) ) { settings.beginGroup( "FilterSet" ); if ( settings.value( "version" ) == FILTERSET_VERSION ) { int size = settings.beginReadArray( "filters" ); for (int i = 0; i < size; ++i) { settings.setArrayIndex(i); Filter filter; filter.retrieveFromStorage( settings ); filterList.append( filter ); } settings.endArray(); } else { LOG(logERROR) << "Unknown version of FilterSet, ignoring it..."; } settings.endGroup(); } else { LOG(logWARNING) << "Trying to import legacy (<=0.8.2) filters..."; FilterSet tmp_filter_set = settings.value( "filterSet" ).value(); *this = tmp_filter_set; LOG(logWARNING) << "...imported filterset: " << filterList.count() << " elements"; // Remove the old key once migration is done settings.remove( "filterSet" ); // And replace it with the new one saveToStorage( settings ); settings.sync(); } } glogg-1.1.0/src/marks.cpp0000664000175000017500000000424512657420553014230 0ustar nickonicko/* * Copyright (C) 2011 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include "marks.h" #include "log.h" #include "utils.h" // This file implements the list of marks for a file. // It is implemented as a QList which is kept in order when inserting, // it is simpler than something fancy like a heap, and insertion are // not done very often anyway. Oh and we need to iterate through the // list, disqualifying a straight heap. Marks::Marks() : marks_() { } void Marks::addMark( qint64 line, QChar mark ) { // Look for the index immediately before int index; if ( ! lookupLineNumber< QList >( marks_, line, &index ) ) { // If a mark is not already set for this line LOG(logDEBUG) << "Inserting mark at line " << line << " (index " << index << ")"; marks_.insert( index, Mark( line ) ); } else { LOG(logERROR) << "Trying to add an existing mark at line " << line; } // 'mark' is not used yet mark = mark; } qint64 Marks::getMark( QChar mark ) const { // 'mark' is not used yet mark = mark; return 0; } bool Marks::isLineMarked( qint64 line ) const { int index; return lookupLineNumber< QList >( marks_, line, &index ); } void Marks::deleteMark( QChar mark ) { // 'mark' is not used yet mark = mark; } void Marks::deleteMark( qint64 line ) { int index; if ( lookupLineNumber< QList >( marks_, line, &index ) ) { marks_.removeAt( index ); } } void Marks::clear() { marks_.clear(); } glogg-1.1.0/src/viewinterface.h0000664000175000017500000000553612657420553015417 0ustar nickonicko/* * Copyright (C) 2013, 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef VIEWINTERFACE_H #define VIEWINTERFACE_H #include class LogData; class LogFilteredData; class SavedSearches; class QuickFindPattern; // ViewContextInterface represents the private information // the concrete view will be able to save and restore. // It can be marshalled to persistent storage. class ViewContextInterface { public: virtual ~ViewContextInterface() {} virtual std::string toString() const = 0; }; // ViewInterface represents a high-level view on a log file. // This a pure virtual class (interface) which is subclassed // for each type of view. class ViewInterface { public: // Set the log data and filtered data to associate to this view // Ownership stay with the caller but is shared void setData( std::shared_ptr log_data, std::shared_ptr filtered_data ) { doSetData( log_data, filtered_data ); } // Set the (shared) quickfind pattern object void setQuickFindPattern( std::shared_ptr qfp ) { doSetQuickFindPattern( qfp ); } // Set the (shared) search history object void setSavedSearches( std::shared_ptr saved_searches ) { doSetSavedSearches( saved_searches ); } // For save/restore of the context void setViewContext( const char* view_context ) { doSetViewContext( view_context ); } // (returned object ownership is transferred to the caller) std::shared_ptr context( void ) const { return doGetViewContext(); } // To allow polymorphic destruction virtual ~ViewInterface() {} protected: // Virtual functions (using NVI) virtual void doSetData( std::shared_ptr log_data, std::shared_ptr filtered_data ) = 0; virtual void doSetQuickFindPattern( std::shared_ptr qfp ) = 0; virtual void doSetSavedSearches( std::shared_ptr saved_searches ) = 0; virtual void doSetViewContext( const char* view_context ) = 0; virtual std::shared_ptr doGetViewContext( void ) const = 0; }; #endif glogg-1.1.0/src/qtfilewatcher.h0000664000175000017500000000362112657420553015417 0ustar nickonicko/* * Copyright (C) 2010 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef QTFILEWATCHER_H #define QTFILEWATCHER_H #include "filewatcher.h" #include // This class encapsulate Qt's QFileSystemWatcher and additionally support // watching a file that doesn't exist yet (the class will watch the owning // directory) // Only supports one file at the moment. class QtFileWatcher : public FileWatcher { Q_OBJECT public: // Create an empty object QtFileWatcher(); // Destroy the object ~QtFileWatcher(); // Adds the file to the list of file to watch // (do nothing if a file is already monitored) void addFile( const QString& fileName ); // Removes the file to the list of file to watch // (do nothing if said file is not monitored) void removeFile( const QString& fileName ); signals: // Sent when the file on disk has changed in any way. void fileChanged( const QString& ); private slots: void fileChangedOnDisk( const QString& filename ); void directoryChangedOnDisk( const QString& filename ); private: enum MonitoringState { None, FileExists, FileRemoved }; QFileSystemWatcher qtFileWatcher_; QString fileMonitored_; MonitoringState monitoringState_; }; #endif glogg-1.1.0/src/session.cpp0000664000175000017500000001445312657420553014600 0ustar nickonicko/* * Copyright (C) 2013, 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include "session.h" #include "log.h" #include #include #include #include "viewinterface.h" #include "persistentinfo.h" #include "savedsearches.h" #include "sessioninfo.h" #include "data/logdata.h" #include "data/logfiltereddata.h" Session::Session() { GetPersistentInfo().retrieve( QString( "savedSearches" ) ); // Get the global search history (it remains the property // of the Persistent) savedSearches_ = Persistent( "savedSearches" ); quickFindPattern_ = std::make_shared(); } Session::~Session() { // FIXME Clean up all the data objects... } ViewInterface* Session::getViewIfOpen( const std::string& file_name ) const { auto result = std::find_if( openFiles_.begin(), openFiles_.end(), [&](const std::pair& o) { return ( o.second.fileName == file_name ); } ); if ( result != openFiles_.end() ) return result->second.view; else return nullptr; } ViewInterface* Session::open( const std::string& file_name, std::function view_factory ) { ViewInterface* view = nullptr; QFileInfo fileInfo( file_name.c_str() ); if ( fileInfo.isReadable() ) { return openAlways( file_name, view_factory, nullptr ); } else { throw FileUnreadableErr(); } return view; } void Session::close( const ViewInterface* view ) { openFiles_.erase( openFiles_.find( view ) ); } void Session::save( std::vector< std::tuple> > view_list, const QByteArray& geometry ) { LOG(logDEBUG) << "Session::save"; std::vector session_files; for ( auto view: view_list ) { const ViewInterface* view_object; uint64_t top_line; std::shared_ptr view_context; std::tie( view_object, top_line, view_context ) = view; const OpenFile* file = findOpenFileFromView( view_object ); assert( file ); LOG(logDEBUG) << "Saving " << file->fileName << " in session."; session_files.push_back( { file->fileName, top_line, view_context->toString() } ); } std::shared_ptr session = Persistent( "session" ); session->setOpenFiles( session_files ); session->setGeometry( geometry ); GetPersistentInfo().save( QString( "session" ) ); } std::vector> Session::restore( std::function view_factory, int *current_file_index ) { GetPersistentInfo().retrieve( QString( "session" ) ); std::shared_ptr session = Persistent( "session" ); std::vector session_files = session->openFiles(); LOG(logDEBUG) << "Session returned " << session_files.size(); std::vector> result; for ( auto file: session_files ) { LOG(logDEBUG) << "Create view for " << file.fileName; ViewInterface* view = openAlways( file.fileName, view_factory, file.viewContext.c_str() ); result.push_back( { file.fileName, view } ); } *current_file_index = -1; return result; } void Session::storedGeometry( QByteArray* geometry ) const { GetPersistentInfo().retrieve( QString( "session" ) ); std::shared_ptr session = Persistent( "session" ); *geometry = session->geometry(); } std::string Session::getFilename( const ViewInterface* view ) const { const OpenFile* file = findOpenFileFromView( view ); assert( file ); return file->fileName; } void Session::getFileInfo( const ViewInterface* view, uint64_t* fileSize, uint32_t* fileNbLine, QDateTime* lastModified ) const { const OpenFile* file = findOpenFileFromView( view ); assert( file ); *fileSize = file->logData->getFileSize(); *fileNbLine = file->logData->getNbLine(); *lastModified = file->logData->getLastModifiedDate(); } /* * Private methods */ ViewInterface* Session::openAlways( const std::string& file_name, std::function view_factory, const char* view_context ) { // Create the data objects auto log_data = std::make_shared(); auto log_filtered_data = std::shared_ptr( log_data->getNewFilteredData() ); ViewInterface* view = view_factory(); view->setData( log_data, log_filtered_data ); view->setQuickFindPattern( quickFindPattern_ ); view->setSavedSearches( savedSearches_ ); if ( view_context ) view->setViewContext( view_context ); // Insert in the hash openFiles_.insert( { view, { file_name, log_data, log_filtered_data, view } } ); // Start loading the file log_data->attachFile( QString( file_name.c_str() ) ); return view; } Session::OpenFile* Session::findOpenFileFromView( const ViewInterface* view ) { assert( view ); OpenFile* file = &( openFiles_.at( view ) ); // OpenfileMap::at might throw out_of_range but since a view MUST always // be attached to a file, we don't handle it! return file; } const Session::OpenFile* Session::findOpenFileFromView( const ViewInterface* view ) const { assert( view ); const OpenFile* file = &( openFiles_.at( view ) ); // OpenfileMap::at might throw out_of_range but since a view MUST always // be attached to a file, we don't handle it! return file; } glogg-1.1.0/src/filterset.h0000664000175000017500000000676112657420553014566 0ustar nickonicko/* * Copyright (C) 2009, 2010 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef FILTERSET_H #define FILTERSET_H #include #include #include #include "persistable.h" // Represents a filter, i.e. a regexp and the colors matching text // should be rendered in. class Filter { public: // Construct an uninitialized Filter (when reading from a config file) Filter(); Filter( const QString& pattern, const QString& foreColor, const QString& backColor ); // Tests the string passed for a match, returns a value just like // QRegExp::indexIn (i.e. -1 if no match) int indexIn( const QString& string ) const; // Accessor functions QString pattern() const; void setPattern( const QString& pattern ); const QString& foreColorName() const; void setForeColor( const QString& foreColorName ); const QString& backColorName() const; void setBackColor( const QString& backColorName ); // Operators for serialization // (must be kept to migrate filters from <=0.8.2) friend QDataStream& operator<<( QDataStream& out, const Filter& object ); friend QDataStream& operator>>( QDataStream& in, Filter& object ); // Reads/writes the current config in the QSettings object passed void saveToStorage( QSettings& settings ) const; void retrieveFromStorage( QSettings& settings ); private: QRegExp regexp_; QString foreColorName_; QString backColorName_; bool enabled_; }; // Represents an ordered set of filters to be applied to each line displayed. class FilterSet : public Persistable { public: // Construct an empty filter set FilterSet(); // Returns weither the passed line match a filter of the set, // if so, it returns the fore/back colors the line should use. // Ownership of the colors is transfered to the caller. bool matchLine( const QString& line, QColor* foreColor, QColor* backColor ) const; // Reads/writes the current config in the QSettings object passed virtual void saveToStorage( QSettings& settings ) const; virtual void retrieveFromStorage( QSettings& settings ); // Should be private really, but I don't know how to have // it recognised by QVariant then. typedef QList FilterList; // Operators for serialization // (must be kept to migrate filters from <=0.8.2) friend QDataStream& operator<<( QDataStream& out, const FilterSet& object ); friend QDataStream& operator>>( QDataStream& in, FilterSet& object ); private: static const int FILTERSET_VERSION; FilterList filterList; // To simplify this class interface, FilterDialog can access our // internal structure directly. friend class FiltersDialog; }; Q_DECLARE_METATYPE(Filter) Q_DECLARE_METATYPE(FilterSet) Q_DECLARE_METATYPE(FilterSet::FilterList) #endif glogg-1.1.0/src/versionchecker.cpp0000664000175000017500000001235312657420553016124 0ustar nickonicko/* * Copyright (C) 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include "versionchecker.h" #include "persistentinfo.h" #include "log.h" #if defined(_WIN64) # define GLOGG_OS "win64" #elif defined(_WIN32) # define GLOGG_OS "win32" #elif defined(__APPLE__) # define GLOGG_OS "OSX" #elif defined(__linux__) # define GLOGG_OS "linux" #else # define GLOGG_OS "other" #endif const char* VersionChecker::VERSION_URL = "http://gloggversion.bonnefon.org/latest"; const uint64_t VersionChecker::CHECK_INTERVAL_S = 3600 * 24 * 7; /* 7 days */ namespace { bool isVersionNewer( const QString& current, const QString& new_version ); }; VersionCheckerConfig::VersionCheckerConfig() { enabled_ = true; next_deadline_ = 0; } void VersionCheckerConfig::retrieveFromStorage( QSettings& settings ) { if ( settings.contains( "versionchecker.enabled" ) ) enabled_ = settings.value( "versionchecker.enabled" ).toBool(); if ( settings.contains( "versionchecker.nextDeadline" ) ) next_deadline_ = settings.value( "versionchecker.nextDeadline" ).toLongLong(); } void VersionCheckerConfig::saveToStorage( QSettings& settings ) const { settings.setValue( "versionchecker.enabled", enabled_ ); settings.setValue( "versionchecker.nextDeadline", static_cast( next_deadline_ ) ); } VersionChecker::VersionChecker() : QObject(), manager_( this ) { } VersionChecker::~VersionChecker() { } void VersionChecker::startCheck() { LOG(logDEBUG) << "VersionChecker::startCheck()"; GetPersistentInfo().retrieve( "versionChecker" ); auto config = Persistent( "versionChecker" ); if ( config->versionCheckingEnabled() ) { // Check the deadline has been reached if ( config->nextDeadline() < std::time( nullptr ) ) { connect( &manager_, SIGNAL( finished( QNetworkReply* ) ), this, SLOT( downloadFinished( QNetworkReply* ) ) ); QNetworkRequest request; request.setUrl( QUrl( VERSION_URL ) ); request.setRawHeader( "User-Agent", "glogg-" GLOGG_VERSION " (" GLOGG_OS ")" ); manager_.get( request ); } else { LOG(logDEBUG) << "Deadline not reached yet, next check in " << std::difftime( config->nextDeadline(), std::time( nullptr ) ); } } } void VersionChecker::downloadFinished( QNetworkReply* reply ) { LOG(logDEBUG) << "VersionChecker::downloadFinished()"; if ( reply->error() == QNetworkReply::NoError ) { QString new_version = QString( reply->read( 256 ) ).remove( '\n' ); LOG(logDEBUG) << "Latest version is " << new_version.toStdString(); if ( isVersionNewer( QString( GLOGG_VERSION ), new_version ) ) { LOG(logDEBUG) << "Sending new version notification"; emit newVersionFound( new_version ); } } else { LOG(logWARNING) << "Download failed: err " << reply->error(); } reply->deleteLater(); // Extend the deadline auto config = Persistent( "versionChecker" ); config->setNextDeadline( std::time( nullptr ) + CHECK_INTERVAL_S ); GetPersistentInfo().save( "versionChecker" ); } namespace { bool isVersionNewer( const QString& current, const QString& new_version ) { QRegExp version_regex( "(\\d+)\\.(\\d+)\\.(\\d+)(-(\\S+))?", Qt::CaseSensitive, QRegExp::RegExp2 ); // Main version is the three first digits // Add is the part after '-' if there unsigned current_main_version = 0; unsigned current_add_version = 0; unsigned new_main_version = 0; unsigned new_add_version = 0; if ( version_regex.indexIn( current ) != -1 ) { current_main_version = version_regex.cap(3).toInt() + version_regex.cap(2).toInt() * 100 + version_regex.cap(1).toInt() * 10000; current_add_version = version_regex.cap(5).isEmpty() ? 0 : 1; } if ( version_regex.indexIn( new_version ) != -1 ) { new_main_version = version_regex.cap(3).toInt() + version_regex.cap(2).toInt() * 100 + version_regex.cap(1).toInt() * 10000; new_add_version = version_regex.cap(5).isEmpty() ? 0 : 1; } LOG(logDEBUG) << "Current version: " << current_main_version; LOG(logDEBUG) << "New version: " << new_main_version; // We only consider the main part for testing for now return new_main_version > current_main_version; } }; glogg-1.1.0/src/savedsearches.h0000664000175000017500000000360312657420553015375 0ustar nickonicko/* * Copyright (C) 2009, 2011 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef SAVEDSEARCHES_H #define SAVEDSEARCHES_H #include #include #include #include "persistable.h" // Keeps track of the previously used searches and allows the application // to retrieve them. class SavedSearches : public Persistable { public: // Creates an empty set of saved searches SavedSearches(); // Adds the passed search to the list of recently used searches void addRecent( const QString& text ); // Returns a list of recent searches (newer first) QStringList recentSearches() const; // Operators for serialization // (only for migrating pre 0.8.2 settings, will be removed) friend QDataStream& operator<<( QDataStream& out, const SavedSearches& object ); friend QDataStream& operator>>( QDataStream& in, SavedSearches& object ); // Reads/writes the current config in the QSettings object passed void saveToStorage( QSettings& settings ) const; void retrieveFromStorage( QSettings& settings ); private: static const int SAVEDSEARCHES_VERSION; static const int maxNumberOfRecentSearches; QStringList savedSearches_; }; Q_DECLARE_METATYPE(SavedSearches) #endif glogg-1.1.0/src/optionsdialog.ui0000664000175000017500000001515712657420553015625 0ustar nickonicko OptionsDialog 0 0 420 320 0 0 Options 50 280 341 32 Qt::Horizontal QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok true 10 10 401 261 0 General 8 78 381 151 Search options 10 30 361 111 Main search type: QuickFind search type: Qt::LeftToRight Incremental QuickFind 8 0 381 71 Font 10 30 361 31 Family: Size: true Advanced 8 0 381 111 File polling 10 20 361 91 Enable Polling Time Interval (ms): 100 16777215 Qt::ImhDigitsOnly Files will be polled at the interval specified here. Qt::AutoText true 10 10 true true true glogg-1.1.0/src/platformfilewatcher.cpp0000664000175000017500000000342712657420553017156 0ustar nickonicko/* * Copyright (C) 2010, 2014, 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include "platformfilewatcher.h" #include "log.h" std::shared_ptr PlatformFileWatcher::watch_tower_; PlatformFileWatcher::PlatformFileWatcher() : FileWatcher() { // Caution, this is NOT thread-safe or re-entrant! if ( !watch_tower_ ) { watch_tower_ = std::make_shared(); } } PlatformFileWatcher::~PlatformFileWatcher() { } void PlatformFileWatcher::addFile( const QString& fileName ) { LOG(logDEBUG) << "FileWatcher::addFile " << fileName.toStdString(); watched_file_name_ = fileName; notification_ = std::make_shared( watch_tower_->addFile( fileName.toStdString(), [this, fileName] { emit fileChanged( fileName ); } ) ); } void PlatformFileWatcher::removeFile( const QString& fileName ) { LOG(logDEBUG) << "FileWatcher::removeFile " << fileName.toStdString(); notification_ = nullptr; } void PlatformFileWatcher::setPollingInterval( uint32_t interval_ms ) { watch_tower_->setPollingInterval( interval_ms ); } glogg-1.1.0/src/infoline.h0000664000175000017500000000306312657420553014360 0ustar nickonicko/* * Copyright (C) 2009, 2010 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef INFOLINE_H #define INFOLINE_H #include #include // Information line with integrated completion gauge // used for the file name and the search results. class InfoLine : public QLabel { public: // Default constructor InfoLine(); // Display the gauge in the background with the passed value (0-100) // This function doesn't change the text of the widget. void displayGauge( int completion ); // Hide the gauge and make the widget like a normal QLabel void hideGauge(); protected: void paintEvent(QPaintEvent* paintEvent); private: // The original palette of the QLabel QPalette origPalette_; // Color of the background const QColor backgroundColor_; // Color of the darkened background (left part of the gauge) const QColor darkBackgroundColor_; }; #endif glogg-1.1.0/src/tabbedcrawlerwidget.cpp0000664000175000017500000001251612657420553017120 0ustar nickonicko/* * Copyright (C) 2014, 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include "tabbedcrawlerwidget.h" #include #include #include "crawlerwidget.h" #include "log.h" TabbedCrawlerWidget::TabbedCrawlerWidget() : QTabWidget(), olddata_icon_( ":/images/olddata_icon.png" ), newdata_icon_( ":/images/newdata_icon.png" ), newfiltered_icon_( ":/images/newfiltered_icon.png" ), myTabBar_() { #ifdef WIN32 myTabBar_.setStyleSheet( "QTabBar::tab {\ height: 20px; " "} " "QTabBar::close-button {\ height: 6px; width: 6px;\ subcontrol-origin: padding;\ subcontrol-position: left;\ }" ); #else // On GTK style, it looks better with a smaller font myTabBar_.setStyleSheet( "QTabBar::tab {" " height: 20px; " " font-size: 9pt; " "} " "QTabBar::close-button {\ height: 6px; width: 6px;\ subcontrol-origin: padding;\ subcontrol-position: left;\ }" ); #endif setTabBar( &myTabBar_ ); myTabBar_.hide(); } // I know hiding non-virtual functions from the base class is bad form // and I do it here out of pure laziness: I don't want to encapsulate // QTabBar with all signals and all just to implement this very simple logic. // Maybe one day that should be done better... int TabbedCrawlerWidget::addTab( QWidget* page, const QString& label ) { int index = QTabWidget::addTab( page, label ); if ( auto crawler = dynamic_cast( page ) ) { // Mmmmhhhh... new Qt5 signal syntax create tight coupling between // us and the sender, baaaaad.... // Listen for a changing data status: connect( crawler, &CrawlerWidget::dataStatusChanged, [ this, index ]( DataStatus status ) { setTabDataStatus( index, status ); } ); } // Display the icon QLabel* icon_label = new QLabel(); icon_label->setPixmap( olddata_icon_.pixmap( 11, 12 ) ); icon_label->setAlignment( Qt::AlignCenter ); myTabBar_.setTabButton( index, QTabBar::RightSide, icon_label ); LOG(logDEBUG) << "addTab, count = " << count(); LOG(logDEBUG) << "width = " << olddata_icon_.pixmap( 11, 12 ).devicePixelRatio(); if ( count() > 1 ) myTabBar_.show(); return index; } void TabbedCrawlerWidget::removeTab( int index ) { QTabWidget::removeTab( index ); if ( count() <= 1 ) myTabBar_.hide(); } void TabbedCrawlerWidget::mouseReleaseEvent( QMouseEvent *event) { LOG(logDEBUG) << "TabbedCrawlerWidget::mouseReleaseEvent"; if (event->button() == Qt::MidButton) { int tab = this->myTabBar_.tabAt( event->pos() ); if (-1 != tab) { emit tabCloseRequested( tab ); } } } void TabbedCrawlerWidget::keyPressEvent( QKeyEvent* event ) { LOG(logDEBUG) << "TabbedCrawlerWidget::keyPressEvent"; char c = ( event->text() )[0].toLatin1(); Qt::KeyboardModifiers mod = event->modifiers(); // Ctrl + tab if ( mod == Qt::ControlModifier && c == '\t' ) { setCurrentIndex( ( currentIndex() + 1 ) % count() ); } // Ctrl + shift + tab else if ( mod == ( Qt::ControlModifier | Qt::ShiftModifier ) && c == '\t' ) { setCurrentIndex( ( currentIndex() + 1 ) % count() ); } // Ctrl + numbers else if ( mod == Qt::ControlModifier && ( c >= '1' && c <= '8' ) ) { LOG(logDEBUG) << "number " << c; int new_index = c - '0'; if ( new_index <= count() ) setCurrentIndex( new_index - 1 ); } // Ctrl + 9 else if ( mod == Qt::ControlModifier && c == '9' ) { LOG(logDEBUG) << "last"; setCurrentIndex( count() - 1 ); } else if ( mod == Qt::ControlModifier && (c == 'q' || c == 'w') ) { LOG(logDEBUG) << "Close tab " << currentIndex(); emit tabCloseRequested( currentIndex() ); } else { QTabWidget::keyPressEvent( event ); } } void TabbedCrawlerWidget::setTabDataStatus( int index, DataStatus status ) { LOG(logDEBUG) << "TabbedCrawlerWidget::setTabDataStatus " << index; QLabel* icon_label = dynamic_cast( myTabBar_.tabButton( index, QTabBar::RightSide ) ); if ( icon_label ) { const QIcon* icon; switch ( status ) { case DataStatus::OLD_DATA: icon = &olddata_icon_; break; case DataStatus::NEW_DATA: icon = &newdata_icon_; break; case DataStatus::NEW_FILTERED_DATA: icon = &newfiltered_icon_; break; } icon_label->setPixmap ( icon->pixmap(12,12) ); } } glogg-1.1.0/src/winexternalcom.cpp0000664000175000017500000001140212657420553016143 0ustar nickonicko/* * Copyright (C) 2014 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include "winexternalcom.h" #include #include #include "log.h" #define GLOGG_UUID "45394779-2422-49BE-AEC5-A0541FAE3127" const QString WINDOW_TITLE = "glogg-" GLOGG_VERSION "-" GLOGG_UUID; WinExternalCommunicator::WinExternalCommunicator() { mutex_held_elsewhere_ = false; message_listener_ = nullptr; (void)::CreateMutex( NULL, TRUE, TEXT( GLOGG_UUID ) ); switch ( ::GetLastError() ) { case ERROR_SUCCESS: LOG(logINFO) << "Mutex returned ERROR_SUCCESS"; break; case ERROR_ALREADY_EXISTS: mutex_held_elsewhere_ = true; LOG(logINFO) << "Mutex returned ERROR_ALREADY_EXISTS"; break; default: break; } } void WinExternalCommunicator::startListening() { LOG(logDEBUG) << "startListening"; message_listener_ = std::make_shared(); // Horrible Hack!!! // Why is it necessary I have no idea, but if winId is not read, // the message_listener_ is never called by Windows. // If some Windows expert could explain me.... volatile WId id = message_listener_->winId(); LOG(logINFO) << "Listener winID = " << id; connect( message_listener_.get(), SIGNAL( messageReceived( MessageId, const QString& ) ), this, SLOT( handleMessageReceived( MessageId, const QString& ) ) ); } void WinExternalCommunicator::handleMessageReceived( MessageId message_id, const QString& data ) { LOG(logDEBUG) << "handleMessageReceived"; switch ( message_id ) { case MessageId::LOAD_FILE: emit loadFile( data ); break; default: LOG( logWARNING ) << "Unrecognised IPC message: " << static_cast( message_id ); } } ExternalInstance* WinExternalCommunicator::otherInstance() const { if ( mutex_held_elsewhere_ ) return static_cast( new WinExternalInstance() ); else { LOG(logINFO) << "Cannot find external correspondant, we are the only glogg out there."; return nullptr; } } qint32 WinExternalCommunicator::version() const { return 3; } WinExternalInstance::WinExternalInstance() { LPCWSTR window_title = (LPCWSTR) WINDOW_TITLE.utf16(); window_handle_ = FindWindow( NULL, window_title ); LOG(logDEBUG) << "Window handle = " << window_handle_; } void WinExternalInstance::loadFile( const QString& file_name ) const { COPYDATASTRUCT data; LOG(logDEBUG) << "loadFile: " << file_name.toStdString(); data.dwData = static_cast( MessageId::LOAD_FILE ); data.lpData = const_cast( file_name.toUtf8().constData() ); data.cbData = file_name.length(); SendMessage( window_handle_, WM_COPYDATA, 0, (LPARAM) &data ); QString file_name2 = QString::fromLatin1( static_cast( data.lpData ), data.cbData ); LOG(logDEBUG) << "length: " << data.cbData; LOG(logDEBUG) << "loadFile2: " << file_name2.toStdString(); } uint32_t WinExternalInstance::getVersion() const { return 6; } /* * WinMessageListener class */ WinMessageListener::WinMessageListener() : QWidget() { setWindowTitle( WINDOW_TITLE ); } bool WinMessageListener::winEvent( MSG* message, long* result ) { if( message->message == WM_COPYDATA ) { // Extract the data from Windows' lParam COPYDATASTRUCT* data = (COPYDATASTRUCT *) message->lParam; LOG(logDEBUG) << "data->dwData: " << data->dwData; LOG(logDEBUG) << "data->cbData: " << data->cbData; LOG(logDEBUG) << "data->lpData: " << (const char*) data->lpData; QString file_name = QString::fromLatin1( static_cast( data->lpData ), data->cbData ); LOG(logINFO) << "WM_COPYDATA received, param: " << file_name.toStdString(); emit messageReceived( static_cast( data->dwData ), file_name ); // We process the event here *result = 0; return true; } else { // Give the event to qt return false; } } glogg-1.1.0/src/crawlerwidget.h0000664000175000017500000002356612657420553015432 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2011, 2013, 2014, 2015 Nicolas Bonnefon * and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef CRAWLERWIDGET_H #define CRAWLERWIDGET_H #include #include #include #include #include #include #include #include #include "logmainview.h" #include "filteredview.h" #include "data/logdata.h" #include "data/logfiltereddata.h" #include "viewinterface.h" #include "signalmux.h" #include "overview.h" #include "loadingstatus.h" class InfoLine; class QuickFindPattern; class SavedSearches; class QStandardItemModel; class OverviewWidget; // Implements the central widget of the application. // It includes both windows, the search line, the info // lines and various buttons. class CrawlerWidget : public QSplitter, public QuickFindMuxSelectorInterface, public ViewInterface, public MuxableDocumentInterface { Q_OBJECT public: CrawlerWidget( QWidget *parent=0 ); // Get the line number of the first line displayed. int getTopLine() const; // Get the selected text as a string (from the main window) QString getSelectedText() const; // Display the QFB at the bottom, remembering where the focus was void displayQuickFindBar( QuickFindMux::QFDirection direction ); // Instructs the widget to select all the text in the window the user // is interacting with void selectAll(); enum Encoding { ENCODING_AUTO = 0, ENCODING_ISO_8859_1, ENCODING_UTF8, ENCODING_MAX, }; Encoding encodingSetting() const; // Get the text description of the encoding effectively used, // suitable to display to the user. QString encodingText() const; public slots: // Stop the asynchoronous loading of the file if one is in progress // The file is identified by the view attached to it. void stopLoading(); // Reload the displayed file void reload(); // Set the encoding void setEncoding( Encoding encoding ); protected: // Implementation of the ViewInterface functions virtual void doSetData( std::shared_ptr log_data, std::shared_ptr filtered_data ); virtual void doSetQuickFindPattern( std::shared_ptr qfp ); virtual void doSetSavedSearches( std::shared_ptr saved_searches ); virtual void doSetViewContext( const char* view_context ); virtual std::shared_ptr doGetViewContext( void ) const; // Implementation of the mux selector interface // (for dispatching QuickFind to the right widget) virtual SearchableWidgetInterface* doGetActiveSearchable() const; virtual std::vector doGetAllSearchables() const; // Implementation of the MuxableDocumentInterface virtual void doSendAllStateSignals(); signals: // Sent to signal the client load has progressed, // passing the completion percentage. void loadingProgressed( int progress ); // Sent to the client when the loading has finished // weither succesfull or not. void loadingFinished( LoadingStatus status ); // Sent when follow mode is enabled/disabled void followSet( bool checked ); // Sent up to the MainWindow to enable/disable the follow mode void followModeChanged( bool follow ); // Sent up when the current line number is updated void updateLineNumber( int line ); // "auto-refresh" check has been changed void searchRefreshChanged( int state ); // "ignore case" check has been changed void ignoreCaseChanged( int state ); // Sent when the data status (whether new not seen data are // available) has changed void dataStatusChanged( DataStatus status ); private slots: // Instructs the widget to start a search using the current search line. void startNewSearch(); // Stop the currently ongoing search (if one exists) void stopSearch(); // Instructs the widget to reconfigure itself because Config() has changed. void applyConfiguration(); // QuickFind is being entered, save the focus for incremental qf. void enteringQuickFind(); // QuickFind is being closed. void exitingQuickFind(); // Called when new data must be displayed in the filtered window. void updateFilteredView( int nbMatches, int progress ); // Called when a new line has been selected in the filtered view, // to instruct the main view to jump to the matching line. void jumpToMatchingLine( int filteredLineNb ); // Called when the main view is on a new line number void updateLineNumberHandler( int line ); // Mark a line that has been clicked on the main (top) view. void markLineFromMain( qint64 line ); // Mark a line that has been clicked on the filtered (bottom) view. void markLineFromFiltered( qint64 line ); void loadingFinishedHandler( LoadingStatus status ); // Manages the info lines to inform the user the file has changed. void fileChangedHandler( LogData::MonitoredFileStatus ); void searchForward(); void searchBackward(); // Called when the checkbox for search auto-refresh is changed void searchRefreshChangedHandler( int state ); // Called when the text on the search line is modified void searchTextChangeHandler(); // Called when the user change the visibility combobox void changeFilteredViewVisibility( int index ); // Called when the user add the string to the search void addToSearch( const QString& string ); // Called when a match is hovered on in the filtered view void mouseHoveredOverMatch( qint64 line ); // Called when there was activity in the views void activityDetected(); private: // State machine holding the state of the search, used to allow/disallow // auto-refresh and inform the user via the info line. class SearchState { public: enum State { NoSearch, Static, Autorefreshing, FileTruncated, TruncatedAutorefreshing, }; SearchState() { state_ = NoSearch; autoRefreshRequested_ = false; } // Reset the state (no search active) void resetState(); // The user changed auto-refresh request void setAutorefresh( bool refresh ); // The file has been truncated (stops auto-refresh) void truncateFile(); // The expression has been changed (stops auto-refresh) void changeExpression(); // The search has been stopped (stops auto-refresh) void stopSearch(); // The search has been started (enable auto-refresh) void startSearch(); // Get the state in order to display the proper message State getState() const { return state_; } // Is auto-refresh allowed bool isAutorefreshAllowed() const { return ( state_ == Autorefreshing || state_ == TruncatedAutorefreshing ); } bool isFileTruncated() const { return ( state_ == FileTruncated || state_ == TruncatedAutorefreshing ); } private: State state_; bool autoRefreshRequested_; }; // Private functions void setup(); void replaceCurrentSearch( const QString& searchText ); void updateSearchCombo(); AbstractLogView* activeView() const; void printSearchInfoMessage( int nbMatches = 0 ); void changeDataStatus( DataStatus status ); void updateEncoding(); // Palette for error notification (yellow background) static const QPalette errorPalette; LogMainView* logMainView; QWidget* bottomWindow; QLabel* searchLabel; QComboBox* searchLineEdit; QToolButton* searchButton; QToolButton* stopButton; FilteredView* filteredView; QComboBox* visibilityBox; InfoLine* searchInfoLine; QCheckBox* ignoreCaseCheck; QCheckBox* searchRefreshCheck; OverviewWidget* overviewWidget_; QVBoxLayout* bottomMainLayout; QHBoxLayout* searchLineLayout; QHBoxLayout* searchInfoLineLayout; // Default palette to be remembered QPalette searchInfoLineDefaultPalette; std::shared_ptr savedSearches_; // Reference to the QuickFind Pattern (not owned) std::shared_ptr quickFindPattern_; LogData* logData_; LogFilteredData* logFilteredData_; qint64 logFileSize_; QWidget* qfSavedFocus_; // Search state (for auto-refresh and truncation) SearchState searchState_; // Matches overview Overview overview_; // Model for the visibility selector QStandardItemModel* visibilityModel_; // Last main line number received qint64 currentLineNumber_; // Are we loading something? // Set to false when we receive a completion message from the LogData bool loadingInProgress_; // Is it not the first time we are loading something? bool firstLoadDone_; // Current number of matches int nbMatches_; // the current dataStatus (whether we have new, not seen, data) DataStatus dataStatus_; // Current encoding setting; Encoding encodingSetting_ = ENCODING_AUTO; QString encoding_text_; }; #endif glogg-1.1.0/src/overview.h0000664000175000017500000000770712657420553014434 0ustar nickonicko/* * Copyright (C) 2011, 2012 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef OVERVIEW_H #define OVERVIEW_H #include #include class LogFilteredData; // Class implementing the logic behind the matches overview bar. // This class converts the matches found in a LogFilteredData in // a screen dependent set of coloured lines, which is cached. // This class is not a UI class, actual display is left to the client. // // This class is NOT thread-safe. class Overview { public: // A line with a position in pixel and a weight (darkness) class WeightedLine { public: static const int WEIGHT_STEPS = 3; WeightedLine() { pos_ = 0; weight_ = 0; } // (Necessary for QVector) WeightedLine( int pos ) { pos_ = pos; weight_ = 0; } int position() const { return pos_; } int weight() const { return weight_; } void load() { weight_ = qMin( weight_ + 1, WEIGHT_STEPS - 1 ); } private: int pos_; int weight_; }; Overview(); ~Overview(); // Associate the passed filteredData to this Overview void setFilteredData( const LogFilteredData* logFilteredData ); // Signal the overview its attached LogFilteredData has been changed and // the overview must be updated with the provided total number // of line of the file. void updateData( int totalNbLine ); // Set the visibility flag of this overview. void setVisible( bool visible ) { visible_ = visible; dirty_ = visible; } // Update the current position in the file (to draw the view line) void updateCurrentPosition( int firstLine, int lastLine ) { topLine_ = firstLine; nbLines_ = lastLine - firstLine; } // Returns weither this overview is visible. bool isVisible() { return visible_; } // Signal the overview the height of the display has changed, triggering // an update of its cache. void updateView( int height ); // Returns a list of lines (between 0 and 'height') representing matches. // (pointer returned is valid until next call to update*() const QVector* getMatchLines() const; // Returns a list of lines (between 0 and 'height') representing marks. // (pointer returned is valid until next call to update*() const QVector* getMarkLines() const; // Return a pair of lines (between 0 and 'height') representing the current view. std::pair getViewLines() const; // Return the line number corresponding to the passed overview y coordinate. int fileLineFromY( int y ) const; // Return the y coordinate corresponding to the passed line number. int yFromFileLine( int file_line ) const; private: // List of matches associated with this Overview. const LogFilteredData* logFilteredData_; // Total number of lines in the file. int linesInFile_; // Whether the overview is visible. bool visible_; // First and last line currently viewed. int topLine_; int nbLines_; // Current height of view window. int height_; // Does the cache (matchesLines, markLines) need to be recalculated. int dirty_; // List of lines representing matches and marks (are shared with the client) QVector matchLines_; QVector markLines_; void recalculatesLines(); }; #endif glogg-1.1.0/src/selection.cpp0000664000175000017500000001302712657420553015076 0ustar nickonicko/* * Copyright (C) 2010, 2013 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ // This file implements Selection. // This class implements the selection handling. No check is made on // the validity of the selection, it must be handled by the caller. // There are three types of selection, only one type might be active // at any time. #include "selection.h" #include "data/abstractlogdata.h" Selection::Selection() { selectedLine_ = -1; selectedPartial_.line = -1; selectedPartial_.startColumn = 0; selectedPartial_.endColumn = 0; selectedRange_.startLine = -1; selectedRange_.endLine = 0; } void Selection::selectPortion( int line, int start_column, int end_column ) { // First unselect any whole line or range selectedLine_ = -1; selectedRange_.startLine = -1; selectedPartial_.line = line; selectedPartial_.startColumn = qMin ( start_column, end_column ); selectedPartial_.endColumn = qMax ( start_column, end_column ); } void Selection::selectRange( int start_line, int end_line ) { // First unselect any whole line and portion selectedLine_ = -1; selectedPartial_.line = -1; selectedRange_.startLine = qMin ( start_line, end_line ); selectedRange_.endLine = qMax ( start_line, end_line ); selectedRange_.firstLine = start_line; } void Selection::selectRangeFromPrevious( int line ) { int previous_line; if ( selectedLine_ >= 0 ) previous_line = selectedLine_; else if ( selectedRange_.startLine >= 0 ) previous_line = selectedRange_.firstLine; else if ( selectedPartial_.line >= 0 ) previous_line = selectedPartial_.line; else previous_line = 0; selectRange( previous_line, line ); } void Selection::crop( int last_line ) { if ( selectedLine_ > last_line ) selectedLine_ = -1; if ( selectedPartial_.line > last_line ) selectedPartial_.line = -1; if ( selectedRange_.endLine > last_line ) selectedRange_.endLine = last_line; if ( selectedRange_.startLine > last_line ) selectedRange_.startLine = last_line; }; bool Selection::getPortionForLine( int line, int* start_column, int* end_column ) const { if ( selectedPartial_.line == line ) { *start_column = selectedPartial_.startColumn; *end_column = selectedPartial_.endColumn; return true; } else { return false; } } bool Selection::isLineSelected( int line ) const { if ( line == selectedLine_ ) return true; else if ( selectedRange_.startLine >= 0 ) return ( ( line >= selectedRange_.startLine ) && ( line <= selectedRange_.endLine ) ); else return false; } qint64 Selection::selectedLine() const { return selectedLine_; } QList Selection::getLines() const { QList selection; if ( selectedLine_ >= 0 ) selection.append( selectedLine_ ); else if ( selectedPartial_.line >= 0 ) selection.append( selectedPartial_.line ); else if ( selectedRange_.startLine >= 0 ) for ( int i = selectedRange_.startLine; i <= selectedRange_.endLine; i++ ) selection.append( i ); return selection; } // The tab behaviour is a bit odd at the moment, full lines are not expanded // but partials (part of line) are, they probably should not ideally. QString Selection::getSelectedText( const AbstractLogData* logData ) const { QString text; if ( selectedLine_ >= 0 ) { text = logData->getLineString( selectedLine_ ); } else if ( selectedPartial_.line >= 0 ) { text = logData->getExpandedLineString( selectedPartial_.line ). mid( selectedPartial_.startColumn, ( selectedPartial_.endColumn - selectedPartial_.startColumn ) + 1 ); } else if ( selectedRange_.startLine >= 0 ) { QStringList list = logData->getLines( selectedRange_.startLine, selectedRange_.endLine - selectedRange_.startLine + 1 ); text = list.join( "\n" ); } return text; } FilePosition Selection::getNextPosition() const { qint64 line = 0; int column = 0; if ( selectedLine_ >= 0 ) { line = selectedLine_ + 1; } else if ( selectedRange_.startLine >= 0 ) { line = selectedRange_.endLine + 1; } else if ( selectedPartial_.line >= 0 ) { line = selectedPartial_.line; column = selectedPartial_.endColumn + 1; } return FilePosition( line, column ); } FilePosition Selection::getPreviousPosition() const { qint64 line = 0; int column = 0; if ( selectedLine_ >= 0 ) { line = selectedLine_; } else if ( selectedRange_.startLine >= 0 ) { line = selectedRange_.startLine; } else if ( selectedPartial_.line >= 0 ) { line = selectedPartial_.line; column = qMax( selectedPartial_.startColumn - 1, 0 ); } return FilePosition( line, column ); } glogg-1.1.0/src/data/0000775000175000017500000000000012657420553013313 5ustar nickonickoglogg-1.1.0/src/data/linepositionarray.h0000664000175000017500000000624612657420553017247 0ustar nickonicko/* * Copyright (C) 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef LINEPOSITIONARRAY_H #define LINEPOSITIONARRAY_H #include #include "data/compressedlinestorage.h" typedef std::vector SimpleLinePositionStorage; // This class is a list of end of lines position, // in addition to a list of uint64_t (positions within the files) // it can keep track of whether the final LF was added (for non-LF terminated // files) and remove it when more data are added. template class LinePosition { public: template friend class LinePosition; // Default constructor LinePosition() : array() { fakeFinalLF_ = false; } // Copy constructor (slow: deleted) LinePosition( const LinePosition& orig ) = delete; // Move assignement LinePosition& operator=( LinePosition&& orig ) { array = std::move( orig.array ); fakeFinalLF_ = orig.fakeFinalLF_; return *this; } // Add a new line position at the given position // Invariant: pos must be greater than the previous one // (this is NOT checked!) inline void append( uint64_t pos ) { if ( fakeFinalLF_ ) array.pop_back(); array.push_back( pos ); } // Size of the array inline int size() const { return array.size(); } // Extract an element inline uint64_t at( int i ) const { return array.at( i ); } inline uint64_t operator[]( int i ) const { return array.at( i ); } // Set the presence of a fake final LF // Must be used after 'append'-ing a fake LF at the end. void setFakeFinalLF( bool finalLF=true ) { fakeFinalLF_ = finalLF; } // Add another list to this one, removing any fake LF on this list. // Invariant: all pos in other must be greater than any pos in this // (this is NOT checked!) void append_list( const LinePosition& other ) { // If our final LF is fake, we remove it if ( fakeFinalLF_ ) this->array.pop_back(); // Append the arrays this->array.append_list( other.array ); //array += other.array; // In case the 'other' object has a fake LF this->fakeFinalLF_ = other.fakeFinalLF_; } private: Storage array; bool fakeFinalLF_; }; // Use the non-optimised storage typedef LinePosition FastLinePositionArray; //typedef LinePosition LinePositionArray; typedef LinePosition LinePositionArray; #endif glogg-1.1.0/src/data/compressedlinestorage.cpp0000664000175000017500000003225312657420553020425 0ustar nickonicko/* * Copyright (C) 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include #include #include #include "utils.h" #include "data/compressedlinestorage.h" #ifdef HAVE_HTONS # include #else # define htons(a) glogg_htons(a) #endif namespace { // Functions to manipulate blocks // Create a new 32 bits block of the passed size, // initialised at the passed position char* block32_new( int block_size, uint32_t initial_position, char** block_ptr ) { // malloc a block of the maximum possible size // (where every line is >16384) char* ptr = static_cast( malloc( 4 + block_size * 6 ) ); if ( ptr ) { // Write the initial_position *(reinterpret_cast(ptr)) = initial_position; *block_ptr = ptr + 4; } return ptr; } // Create a new 64 bits block of the passed size, // initialised at the passed position char* block64_new( int block_size, uint64_t initial_position, char** block_ptr ) { // malloc a block of the maximum possible size // (where every line is >16384) char* ptr = static_cast( malloc( 8 + block_size * 10 ) ); if ( ptr ) { // Write the initial_position *(reinterpret_cast(ptr)) = initial_position; *block_ptr = ptr + 8; } return ptr; } // Add a one byte relative delta (0-127) and inc pointer // First bit is always 0 void block_add_one_byte_relative( char** ptr, uint8_t value ) { **ptr = value; *ptr += sizeof( value ); } // Add a two bytes relative delta (0-16383) and inc pointer // First 2 bits are always 10 void block_add_two_bytes_relative( char** ptr, uint16_t value ) { // Stored in big endian format in order to recognise the initial pattern: // 10xx xxxx xxxx xxxx // HO byte | LO byte *(reinterpret_cast(*ptr)) = htons( value | (1 << 15) ); *ptr += sizeof( value ); } // Add a signal and a 4 bytes absolute position and inc pointer void block32_add_absolute( char** ptr, uint32_t value ) { // 2 bytes marker (actually only the first two bits are tested) *(reinterpret_cast(*ptr)) = 0xFF; *ptr += sizeof( uint16_t ); // Absolute value (machine endian) *(reinterpret_cast(*ptr)) = value; *ptr += sizeof( uint32_t ); } // Initialise the passed block for reading, returning // the initial position and a pointer to the second entry. uint64_t block32_initial_pos( char* block, char** ptr ) { *ptr = block + sizeof( uint32_t ); return *(reinterpret_cast(block)); } // Give the next position in the block based on the previous // position, then increase the pointer. uint64_t block32_next_pos( char** ptr, uint64_t previous_pos ) { uint64_t pos = previous_pos; uint8_t byte = **ptr; ++(*ptr); if ( ! ( byte & 0x80 ) ) { // High order bit is 0 pos += byte; } else if ( ( byte & 0xC0 ) == 0x80 ) { // We need to read the low order byte uint8_t lo_byte = **ptr; ++(*ptr); // Remove the starting 10b byte &= ~0xC0; // And form the displacement (stored as big endian) pos += ( (uint16_t) byte << 8 ) | (uint16_t) lo_byte; } else { // Skip the next byte (not used) ++(*ptr); // And read the new absolute pos (machine endian) pos = *(reinterpret_cast(*ptr)); *ptr += sizeof( uint32_t ); } return pos; } // Add a signal and a 8 bytes absolute position and inc pointer void block64_add_absolute( char** ptr, uint64_t value ) { // This is unaligned, can cause problem on some CPUs // 2 bytes marker (actually only the first two bits are tested) *(reinterpret_cast(*ptr)) = 0xFF; *ptr += sizeof( uint16_t ); // Absolute value (machine endian) *(reinterpret_cast(*ptr)) = value; *ptr += sizeof( uint64_t ); } // Initialise the passed block for reading, returning // the initial position and a pointer to the second entry. uint64_t block64_initial_pos( char* block, char** ptr ) { *ptr = block + sizeof( uint64_t ); return *(reinterpret_cast(block)); } // Give the next position in the block based on the previous // position, then increase the pointer. uint64_t block64_next_pos( char** ptr, uint64_t previous_pos ) { uint64_t pos = previous_pos; uint8_t byte = **ptr; ++(*ptr); if ( ! ( byte & 0x80 ) ) { // High order bit is 0 pos += byte; } else if ( ( byte & 0xC0 ) == 0x80 ) { // We need to read the low order byte uint8_t lo_byte = **ptr; ++(*ptr); // Remove the starting 10b byte &= ~0xC0; // And form the displacement (stored as big endian) pos += ( (uint16_t) byte << 8 ) | (uint16_t) lo_byte; } else { // Skip the next byte (not used) ++(*ptr); // And read the new absolute pos (machine endian) pos = *(reinterpret_cast(*ptr)); *ptr += sizeof( uint64_t ); } return pos; } } void CompressedLinePositionStorage::move_from( CompressedLinePositionStorage&& orig ) { nb_lines_ = orig.nb_lines_; first_long_line_ = orig.first_long_line_; current_pos_ = orig.current_pos_; block_pointer_ = orig.block_pointer_; previous_block_pointer_ = orig.previous_block_pointer_; orig.nb_lines_ = 0; } // Move constructor CompressedLinePositionStorage::CompressedLinePositionStorage( CompressedLinePositionStorage&& orig ) : block32_index_( std::move( orig.block32_index_ ) ) { move_from( std::move( orig ) ); } // Move assignement CompressedLinePositionStorage& CompressedLinePositionStorage::operator=( CompressedLinePositionStorage&& orig ) { block32_index_ = std::move( orig.block32_index_ ); move_from( std::move( orig ) ); return *this; } CompressedLinePositionStorage::~CompressedLinePositionStorage() { for ( char* block : block32_index_ ) { void* p = static_cast( block ); // std::cerr << "block = " << p << std::endl; free( p ); } for ( char* block : block64_index_ ) { void* p = static_cast( block ); // std::cerr << "block = " << p << std::endl; free( p ); } } // template void CompressedLinePositionStorage::append( uint64_t pos ) { // Save the pointer in case we need to "pop_back" previous_block_pointer_ = block_pointer_; bool store_in_big = false; if ( pos > UINT32_MAX ) { store_in_big = true; if ( first_long_line_ == UINT32_MAX ) { // First "big" end of line, we will start a new (64) block first_long_line_ = nb_lines_; block_pointer_ = nullptr; } } if ( ! block_pointer_ ) { // We need to start a new block if ( ! store_in_big ) block32_index_.push_back( block32_new( BLOCK_SIZE, pos, &block_pointer_ ) ); else block64_index_.push_back( block64_new( BLOCK_SIZE, pos, &block_pointer_ ) ); } else { uint64_t delta = pos - current_pos_; if ( delta < 128 ) { // Code relative on one byte block_add_one_byte_relative( &block_pointer_, delta ); } else if ( delta < 16384 ) { // Code relative on two bytes block_add_two_bytes_relative( &block_pointer_, delta ); } else { // Code absolute if ( ! store_in_big ) block32_add_absolute( &block_pointer_, pos ); else block64_add_absolute( &block_pointer_, pos ); } } current_pos_ = pos; ++nb_lines_; if ( ! store_in_big ) { if ( nb_lines_ % BLOCK_SIZE == 0 ) { // We have finished the block // Let's reduce its size to what is actually used int block_index = nb_lines_ / BLOCK_SIZE - 1; char* block = block32_index_[block_index]; // We allocate extra space for the last element in case it // is replaced by an absolute value in the future (following a pop_back) size_t new_size = ( previous_block_pointer_ + sizeof( uint16_t ) + sizeof( uint32_t ) ) - block; void* new_location = realloc( block, new_size ); if ( new_location ) block32_index_[block_index] = static_cast( new_location ); block_pointer_ = nullptr; } } else { if ( ( nb_lines_ - first_long_line_ ) % BLOCK_SIZE == 0 ) { // We have finished the block // Let's reduce its size to what is actually used int block_index = ( nb_lines_ - first_long_line_ ) / BLOCK_SIZE - 1; char* block = block64_index_[block_index]; // We allocate extra space for the last element in case it // is replaced by an absolute value in the future (following a pop_back) size_t new_size = ( previous_block_pointer_ + sizeof( uint16_t ) + sizeof( uint64_t ) ) - block; void* new_location = realloc( block, new_size ); if ( new_location ) block64_index_[block_index] = static_cast( new_location ); block_pointer_ = nullptr; } } } // template uint64_t CompressedLinePositionStorage::at( uint32_t index ) const { char* ptr; uint64_t position; Cache* last_read = last_read_.getPtr(); if ( index < first_long_line_ ) { if ( ( index == last_read->index + 1 ) && ( index % BLOCK_SIZE != 0 ) ) { position = last_read->position; ptr = last_read->ptr; position = block32_next_pos( &ptr, position ); } else { char* block = block32_index_[ index / BLOCK_SIZE ]; position = block32_initial_pos( block, &ptr ); for ( uint32_t i = 0; i < index % BLOCK_SIZE; i++ ) { // Go through all the lines in the block till the one we want position = block32_next_pos( &ptr, position ); } } } else { const uint32_t index_in_64 = index - first_long_line_; if ( ( index == last_read->index + 1 ) && ( index_in_64 % BLOCK_SIZE != 0 ) ) { position = last_read->position; ptr = last_read->ptr; position = block64_next_pos( &ptr, position ); } else { char* block = block64_index_[ index_in_64 / BLOCK_SIZE ]; position = block64_initial_pos( block, &ptr ); for ( uint32_t i = 0; i < index_in_64 % BLOCK_SIZE; i++ ) { // Go through all the lines in the block till the one we want position = block64_next_pos( &ptr, position ); } } } // Populate our cache ready for next consecutive read last_read->index = index; last_read->position = position; last_read->ptr = ptr; return position; } void CompressedLinePositionStorage::append_list( const std::vector& positions ) { // This is not very clever, but caching should make it // reasonably fast. for ( uint32_t i = 0; i < positions.size(); i++ ) append( positions.at( i ) ); } // template void CompressedLinePositionStorage::pop_back() { // Removing the last entered data, there are two cases if ( previous_block_pointer_ ) { // The last append was a normal entry in an existing block, // so we can just revert the pointer block_pointer_ = previous_block_pointer_; previous_block_pointer_ = nullptr; } else { // A new block has been created for the last entry, we need // to de-alloc it. // If we try to pop_back() twice, we're dead! assert( ( nb_lines_ - 1 ) % BLOCK_SIZE == 0 ); char* block = block32_index_.back(); block32_index_.pop_back(); free( block ); block_pointer_ = nullptr; } --nb_lines_; current_pos_ = at( nb_lines_ - 1 ); } glogg-1.1.0/src/data/logdataworkerthread.cpp0000664000175000017500000002460612657420553020064 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2014, 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include #include "log.h" #include "logdata.h" #include "logdataworkerthread.h" // Size of the chunk to read (5 MiB) const int IndexOperation::sizeChunk = 5*1024*1024; qint64 IndexingData::getSize() const { QMutexLocker locker( &dataMutex_ ); return indexedSize_; } int IndexingData::getMaxLength() const { QMutexLocker locker( &dataMutex_ ); return maxLength_; } LineNumber IndexingData::getNbLines() const { QMutexLocker locker( &dataMutex_ ); return linePosition_.size(); } qint64 IndexingData::getPosForLine( LineNumber line ) const { QMutexLocker locker( &dataMutex_ ); return linePosition_.at( line ); } EncodingSpeculator::Encoding IndexingData::getEncodingGuess() const { QMutexLocker locker( &dataMutex_ ); return encoding_; } void IndexingData::addAll( qint64 size, int length, const FastLinePositionArray& linePosition, EncodingSpeculator::Encoding encoding ) { QMutexLocker locker( &dataMutex_ ); indexedSize_ += size; maxLength_ = qMax( maxLength_, length ); linePosition_.append_list( linePosition ); encoding_ = encoding; } void IndexingData::clear() { maxLength_ = 0; indexedSize_ = 0; linePosition_ = LinePositionArray(); encoding_ = EncodingSpeculator::Encoding::ASCII7; } LogDataWorkerThread::LogDataWorkerThread( IndexingData* indexing_data ) : QThread(), mutex_(), operationRequestedCond_(), nothingToDoCond_(), fileName_(), indexing_data_( indexing_data ) { terminate_ = false; interruptRequested_ = false; operationRequested_ = NULL; } LogDataWorkerThread::~LogDataWorkerThread() { { QMutexLocker locker( &mutex_ ); terminate_ = true; operationRequestedCond_.wakeAll(); } wait(); } void LogDataWorkerThread::attachFile( const QString& fileName ) { QMutexLocker locker( &mutex_ ); // to protect fileName_ fileName_ = fileName; } void LogDataWorkerThread::indexAll() { QMutexLocker locker( &mutex_ ); // to protect operationRequested_ LOG(logDEBUG) << "FullIndex requested"; // If an operation is ongoing, we will block while ( (operationRequested_ != NULL) ) nothingToDoCond_.wait( &mutex_ ); interruptRequested_ = false; operationRequested_ = new FullIndexOperation( fileName_, indexing_data_, &interruptRequested_, &encodingSpeculator_ ); operationRequestedCond_.wakeAll(); } void LogDataWorkerThread::indexAdditionalLines( qint64 position ) { QMutexLocker locker( &mutex_ ); // to protect operationRequested_ LOG(logDEBUG) << "AddLines requested"; // If an operation is ongoing, we will block while ( (operationRequested_ != NULL) ) nothingToDoCond_.wait( &mutex_ ); interruptRequested_ = false; operationRequested_ = new PartialIndexOperation( fileName_, indexing_data_, &interruptRequested_, &encodingSpeculator_, position ); operationRequestedCond_.wakeAll(); } void LogDataWorkerThread::interrupt() { LOG(logDEBUG) << "Load interrupt requested"; // No mutex here, setting a bool is probably atomic! interruptRequested_ = true; } // This is the thread's main loop void LogDataWorkerThread::run() { QMutexLocker locker( &mutex_ ); forever { while ( (terminate_ == false) && (operationRequested_ == NULL) ) operationRequestedCond_.wait( &mutex_ ); LOG(logDEBUG) << "Worker thread signaled"; // Look at what needs to be done if ( terminate_ ) return; // We must die if ( operationRequested_ ) { connect( operationRequested_, SIGNAL( indexingProgressed( int ) ), this, SIGNAL( indexingProgressed( int ) ) ); // Run the operation try { if ( operationRequested_->start() ) { LOG(logDEBUG) << "... finished copy in workerThread."; emit indexingFinished( LoadingStatus::Successful ); } else { emit indexingFinished( LoadingStatus::Interrupted ); } } catch ( std::bad_alloc& ba ) { LOG(logERROR) << "Out of memory whilst indexing!"; emit indexingFinished( LoadingStatus::NoMemory ); } delete operationRequested_; operationRequested_ = NULL; nothingToDoCond_.wakeAll(); } } } // // Operations implementation // IndexOperation::IndexOperation( const QString& fileName, IndexingData* indexingData, bool* interruptRequest, EncodingSpeculator* encodingSpeculator ) : fileName_( fileName ) { interruptRequest_ = interruptRequest; indexing_data_ = indexingData; encoding_speculator_ = encodingSpeculator; } PartialIndexOperation::PartialIndexOperation( const QString& fileName, IndexingData* indexingData, bool* interruptRequest, EncodingSpeculator* speculator, qint64 position ) : IndexOperation( fileName, indexingData, interruptRequest, speculator ) { initialPosition_ = position; } void IndexOperation::doIndex( IndexingData* indexing_data, EncodingSpeculator* encoding_speculator, qint64 initialPosition ) { qint64 pos = initialPosition; // Absolute position of the start of current line qint64 end = 0; // Absolute position of the end of current line int additional_spaces = 0; // Additional spaces due to tabs QFile file( fileName_ ); if ( file.open( QIODevice::ReadOnly ) ) { // Count the number of lines and max length // (read big chunks to speed up reading from disk) file.seek( pos ); while ( !file.atEnd() ) { FastLinePositionArray line_positions; int max_length = 0; if ( *interruptRequest_ ) // a bool is always read/written atomically isn't it? break; // Read a chunk of 5MB const qint64 block_beginning = file.pos(); const QByteArray block = file.read( sizeChunk ); // Count the number of lines in each chunk qint64 pos_within_block = 0; while ( pos_within_block != -1 ) { pos_within_block = qMax( pos - block_beginning, 0LL); // Looking for the next \n, expanding tabs in the process do { if ( pos_within_block < block.length() ) { const char c = block.at(pos_within_block); encoding_speculator->inject_byte( c ); if ( c == '\n' ) break; else if ( c == '\t' ) additional_spaces += AbstractLogData::tabStop - ( ( ( block_beginning - pos ) + pos_within_block + additional_spaces ) % AbstractLogData::tabStop ) - 1; pos_within_block++; } else { pos_within_block = -1; } } while ( pos_within_block != -1 ); // When a end of line has been found... if ( pos_within_block != -1 ) { end = pos_within_block + block_beginning; const int length = end-pos + additional_spaces; if ( length > max_length ) max_length = length; pos = end + 1; additional_spaces = 0; line_positions.append( pos ); } } // Update the shared data indexing_data->addAll( block.length(), max_length, line_positions, encoding_speculator->guess() ); // Update the caller for progress indication int progress = ( file.size() > 0 ) ? pos*100 / file.size() : 100; emit indexingProgressed( progress ); } // Check if there is a non LF terminated line at the end of the file qint64 file_size = file.size(); if ( !*interruptRequest_ && file_size > pos ) { LOG( logWARNING ) << "Non LF terminated file, adding a fake end of line"; FastLinePositionArray line_position; line_position.append( file_size + 1 ); line_position.setFakeFinalLF(); indexing_data->addAll( 0, 0, line_position, encoding_speculator->guess() ); } } else { // TODO: Check that the file is seekable? // If the file cannot be open, we do as if it was empty LOG(logWARNING) << "Cannot open file " << fileName_.toStdString(); emit indexingProgressed( 100 ); } } // Called in the worker thread's context bool FullIndexOperation::start() { LOG(logDEBUG) << "FullIndexOperation::start(), file " << fileName_.toStdString(); LOG(logDEBUG) << "FullIndexOperation: Starting the count..."; emit indexingProgressed( 0 ); // First empty the index indexing_data_->clear(); doIndex( indexing_data_, encoding_speculator_, 0 ); LOG(logDEBUG) << "FullIndexOperation: ... finished counting." "interrupt = " << *interruptRequest_; return ( *interruptRequest_ ? false : true ); } bool PartialIndexOperation::start() { LOG(logDEBUG) << "PartialIndexOperation::start(), file " << fileName_.toStdString(); LOG(logDEBUG) << "PartialIndexOperation: Starting the count at " << initialPosition_ << " ..."; emit indexingProgressed( 0 ); doIndex( indexing_data_, encoding_speculator_, initialPosition_ ); LOG(logDEBUG) << "PartialIndexOperation: ... finished counting."; return ( *interruptRequest_ ? false : true ); } glogg-1.1.0/src/data/abstractlogdata.cpp0000664000175000017500000000435712657420553017167 0ustar nickonicko/* * Copyright (C) 2009, 2010 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ // This file implements AbstractLogData. // This base class is primarily an interface class and should not // implement anything. // It exists so that AbstractLogView can manipulate an abtract set of data // (either full or filtered). #include "abstractlogdata.h" AbstractLogData::AbstractLogData() { } // Simple wrapper in order to use a clean Template Method QString AbstractLogData::getLineString( qint64 line ) const { return doGetLineString(line); } // Simple wrapper in order to use a clean Template Method QString AbstractLogData::getExpandedLineString( qint64 line ) const { return doGetExpandedLineString(line); } // Simple wrapper in order to use a clean Template Method QStringList AbstractLogData::getLines( qint64 first_line, int number ) const { return doGetLines( first_line, number ); } // Simple wrapper in order to use a clean Template Method QStringList AbstractLogData::getExpandedLines( qint64 first_line, int number ) const { return doGetExpandedLines( first_line, number ); } // Simple wrapper in order to use a clean Template Method qint64 AbstractLogData::getNbLine() const { return doGetNbLine(); } // Simple wrapper in order to use a clean Template Method int AbstractLogData::getMaxLength() const { return doGetMaxLength(); } // Simple wrapper in order to use a clean Template Method int AbstractLogData::getLineLength( qint64 line ) const { return doGetLineLength( line ); } void AbstractLogData::setDisplayEncoding( const char* encoding ) { doSetDisplayEncoding( encoding ); } glogg-1.1.0/src/data/logfiltereddataworkerthread.cpp0000664000175000017500000002024612657420553021577 0ustar nickonicko/* * Copyright (C) 2009, 2010 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include #include "log.h" #include "logfiltereddataworkerthread.h" #include "logdata.h" // Number of lines in each chunk to read const int SearchOperation::nbLinesInChunk = 5000; void SearchData::getAll( int* length, SearchResultArray* matches, qint64* lines) const { QMutexLocker locker( &dataMutex_ ); *length = maxLength_; *lines = nbLinesProcessed_; // This is a copy (potentially slow) *matches = matches_; } void SearchData::setAll( int length, SearchResultArray&& matches ) { QMutexLocker locker( &dataMutex_ ); maxLength_ = length; matches_ = matches; } void SearchData::addAll( int length, const SearchResultArray& matches, LineNumber lines ) { QMutexLocker locker( &dataMutex_ ); maxLength_ = qMax( maxLength_, length ); nbLinesProcessed_ = lines; // This does a copy as we want the final array to be // linear. matches_.insert( std::end( matches_ ), std::begin( matches ), std::end( matches ) ); } LineNumber SearchData::getNbMatches() const { QMutexLocker locker( &dataMutex_ ); return matches_.size(); } // This function starts searching from the end since we use it // to remove the final match. void SearchData::deleteMatch( LineNumber line ) { QMutexLocker locker( &dataMutex_ ); SearchResultArray::iterator i = matches_.end(); while ( i != matches_.begin() ) { i--; const LineNumber this_line = i->lineNumber(); if ( this_line == line ) { matches_.erase(i); break; } // Exit if we have passed the line number to look for. if ( this_line < line ) break; } } void SearchData::clear() { QMutexLocker locker( &dataMutex_ ); maxLength_ = 0; nbLinesProcessed_ = 0; matches_.clear(); } LogFilteredDataWorkerThread::LogFilteredDataWorkerThread( const LogData* sourceLogData ) : QThread(), mutex_(), operationRequestedCond_(), nothingToDoCond_(), searchData_() { terminate_ = false; interruptRequested_ = false; operationRequested_ = NULL; sourceLogData_ = sourceLogData; } LogFilteredDataWorkerThread::~LogFilteredDataWorkerThread() { { QMutexLocker locker( &mutex_ ); terminate_ = true; operationRequestedCond_.wakeAll(); } wait(); } void LogFilteredDataWorkerThread::search( const QRegExp& regExp ) { QMutexLocker locker( &mutex_ ); // to protect operationRequested_ LOG(logDEBUG) << "Search requested"; // If an operation is ongoing, we will block while ( (operationRequested_ != NULL) ) nothingToDoCond_.wait( &mutex_ ); interruptRequested_ = false; operationRequested_ = new FullSearchOperation( sourceLogData_, regExp, &interruptRequested_ ); operationRequestedCond_.wakeAll(); } void LogFilteredDataWorkerThread::updateSearch( const QRegExp& regExp, qint64 position ) { QMutexLocker locker( &mutex_ ); // to protect operationRequested_ LOG(logDEBUG) << "Search requested"; // If an operation is ongoing, we will block while ( (operationRequested_ != NULL) ) nothingToDoCond_.wait( &mutex_ ); interruptRequested_ = false; operationRequested_ = new UpdateSearchOperation( sourceLogData_, regExp, &interruptRequested_, position ); operationRequestedCond_.wakeAll(); } void LogFilteredDataWorkerThread::interrupt() { LOG(logDEBUG) << "Search interruption requested"; // No mutex here, setting a bool is probably atomic! interruptRequested_ = true; // We wait for the interruption to be done { QMutexLocker locker( &mutex_ ); while ( (operationRequested_ != NULL) ) nothingToDoCond_.wait( &mutex_ ); } } // This will do an atomic copy of the object void LogFilteredDataWorkerThread::getSearchResult( int* maxLength, SearchResultArray* searchMatches, qint64* nbLinesProcessed ) { searchData_.getAll( maxLength, searchMatches, nbLinesProcessed ); } // This is the thread's main loop void LogFilteredDataWorkerThread::run() { QMutexLocker locker( &mutex_ ); forever { while ( (terminate_ == false) && (operationRequested_ == NULL) ) operationRequestedCond_.wait( &mutex_ ); LOG(logDEBUG) << "Worker thread signaled"; // Look at what needs to be done if ( terminate_ ) return; // We must die if ( operationRequested_ ) { connect( operationRequested_, SIGNAL( searchProgressed( int, int ) ), this, SIGNAL( searchProgressed( int, int ) ) ); // Run the search operation operationRequested_->start( searchData_ ); LOG(logDEBUG) << "... finished copy in workerThread."; emit searchFinished(); delete operationRequested_; operationRequested_ = NULL; nothingToDoCond_.wakeAll(); } } } // // Operations implementation // SearchOperation::SearchOperation( const LogData* sourceLogData, const QRegExp& regExp, bool* interruptRequest ) : regexp_( regExp ), sourceLogData_( sourceLogData ) { interruptRequested_ = interruptRequest; } void SearchOperation::doSearch( SearchData& searchData, qint64 initialLine ) { const qint64 nbSourceLines = sourceLogData_->getNbLine(); int maxLength = 0; int nbMatches = searchData.getNbMatches(); SearchResultArray currentList = SearchResultArray(); // Ensure no re-alloc will be done currentList.reserve( nbLinesInChunk ); LOG(logDEBUG) << "Searching from line " << initialLine << " to " << nbSourceLines; for ( qint64 i = initialLine; i < nbSourceLines; i += nbLinesInChunk ) { if ( *interruptRequested_ ) break; const int percentage = ( i - initialLine ) * 100 / ( nbSourceLines - initialLine ); emit searchProgressed( nbMatches, percentage ); const QStringList lines = sourceLogData_->getLines( i, qMin( nbLinesInChunk, (int) ( nbSourceLines - i ) ) ); LOG(logDEBUG) << "Chunk starting at " << i << ", " << lines.size() << " lines read."; int j = 0; for ( ; j < lines.size(); j++ ) { if ( regexp_.indexIn( lines[j] ) != -1 ) { // FIXME: increase perf by removing temporary const int length = sourceLogData_->getExpandedLineString(i+j).length(); if ( length > maxLength ) maxLength = length; currentList.push_back( MatchingLine( i+j ) ); nbMatches++; } } // After each block, copy the data to shared data // and update the client searchData.addAll( maxLength, currentList, i+j ); currentList.clear(); } emit searchProgressed( nbMatches, 100 ); } // Called in the worker thread's context void FullSearchOperation::start( SearchData& searchData ) { // Clear the shared data searchData.clear(); doSearch( searchData, 0 ); } // Called in the worker thread's context void UpdateSearchOperation::start( SearchData& searchData ) { qint64 initial_line = initialPosition_; if ( initial_line >= 1 ) { // We need to re-search the last line because it might have // been updated (if it was not LF-terminated) --initial_line; // In case the last line matched, we don't want it to match twice. searchData.deleteMatch( initial_line ); } doSearch( searchData, initial_line ); } glogg-1.1.0/src/data/logfiltereddata.cpp0000664000175000017500000002637112657420553017162 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2011, 2012, 2013 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ // This file implements LogFilteredData // It stores a pointer to the LogData that created it, // so should always be destroyed before the LogData. #include "log.h" #include #include #include #include "utils.h" #include "logdata.h" #include "marks.h" #include "logfiltereddata.h" // Creates an empty set. It must be possible to display it without error. // FIXME LogFilteredData::LogFilteredData() : AbstractLogData(), matching_lines_(), currentRegExp_(), visibility_(), filteredItemsCache_(), workerThread_( nullptr ), marks_() { /* Prevent any more searching */ maxLength_ = 0; maxLengthMarks_ = 0; searchDone_ = true; visibility_ = MarksAndMatches; filteredItemsCacheDirty_ = true; } // Usual constructor: just copy the data, the search is started by runSearch() LogFilteredData::LogFilteredData( const LogData* logData ) : AbstractLogData(), matching_lines_( SearchResultArray() ), currentRegExp_(), visibility_(), filteredItemsCache_(), workerThread_( logData ), marks_() { // Starts with an empty result list maxLength_ = 0; maxLengthMarks_ = 0; nbLinesProcessed_ = 0; sourceLogData_ = logData; searchDone_ = false; visibility_ = MarksAndMatches; filteredItemsCacheDirty_ = true; // Forward the update signal connect( &workerThread_, SIGNAL( searchProgressed( int, int ) ), this, SLOT( handleSearchProgressed( int, int ) ) ); // Starts the worker thread workerThread_.start(); } LogFilteredData::~LogFilteredData() { // FIXME // workerThread_.stop(); } // // Public functions // // Run the search and send newDataAvailable() signals. void LogFilteredData::runSearch( const QRegExp& regExp ) { LOG(logDEBUG) << "Entering runSearch"; clearSearch(); currentRegExp_ = regExp; workerThread_.search( currentRegExp_ ); } void LogFilteredData::updateSearch() { LOG(logDEBUG) << "Entering updateSearch"; workerThread_.updateSearch( currentRegExp_, nbLinesProcessed_ ); } void LogFilteredData::interruptSearch() { LOG(logDEBUG) << "Entering interruptSearch"; workerThread_.interrupt(); } void LogFilteredData::clearSearch() { currentRegExp_ = QRegExp(); matching_lines_.clear(); maxLength_ = 0; maxLengthMarks_ = 0; nbLinesProcessed_ = 0; filteredItemsCacheDirty_ = true; } qint64 LogFilteredData::getMatchingLineNumber( int matchNum ) const { qint64 matchingLine = findLogDataLine( matchNum ); return matchingLine; } // Scan the list for the 'lineNumber' passed bool LogFilteredData::isLineInMatchingList( qint64 lineNumber ) { int index; // Not used return lookupLineNumber( matching_lines_, lineNumber, &index); } LineNumber LogFilteredData::getNbTotalLines() const { return sourceLogData_->getNbLine(); } LineNumber LogFilteredData::getNbMatches() const { return matching_lines_.size(); } LineNumber LogFilteredData::getNbMarks() const { return marks_.size(); } LogFilteredData::FilteredLineType LogFilteredData::filteredLineTypeByIndex( int index ) const { // If we are only showing one type, the line is there because // it is of this type. if ( visibility_ == MatchesOnly ) return Match; else if ( visibility_ == MarksOnly ) return Mark; else { // If it is MarksAndMatches, we have to look. // Regenerate the cache if needed if ( filteredItemsCacheDirty_ ) regenerateFilteredItemsCache(); return filteredItemsCache_[ index ].type(); } } // Delegation to our Marks object void LogFilteredData::addMark( qint64 line, QChar mark ) { if ( ( line >= 0 ) && ( line < sourceLogData_->getNbLine() ) ) { marks_.addMark( line, mark ); maxLengthMarks_ = qMax( maxLengthMarks_, sourceLogData_->getLineLength( line ) ); filteredItemsCacheDirty_ = true; } else LOG(logERROR) << "LogFilteredData::addMark\ trying to create a mark outside of the file."; } qint64 LogFilteredData::getMark( QChar mark ) const { return marks_.getMark( mark ); } bool LogFilteredData::isLineMarked( qint64 line ) const { return marks_.isLineMarked( line ); } void LogFilteredData::deleteMark( QChar mark ) { marks_.deleteMark( mark ); filteredItemsCacheDirty_ = true; // FIXME: maxLengthMarks_ } void LogFilteredData::deleteMark( qint64 line ) { marks_.deleteMark( line ); filteredItemsCacheDirty_ = true; // Now update the max length if needed if ( sourceLogData_->getLineLength( line ) >= maxLengthMarks_ ) { LOG(logDEBUG) << "deleteMark recalculating longest mark"; maxLengthMarks_ = 0; for ( Marks::const_iterator i = marks_.begin(); i != marks_.end(); ++i ) { LOG(logDEBUG) << "line " << i->lineNumber(); maxLengthMarks_ = qMax( maxLengthMarks_, sourceLogData_->getLineLength( i->lineNumber() ) ); } } } void LogFilteredData::clearMarks() { marks_.clear(); filteredItemsCacheDirty_ = true; maxLengthMarks_ = 0; } void LogFilteredData::setVisibility( Visibility visi ) { visibility_ = visi; } // // Slots // void LogFilteredData::handleSearchProgressed( int nbMatches, int progress ) { LOG(logDEBUG) << "LogFilteredData::handleSearchProgressed matches=" << nbMatches << " progress=" << progress; // searchDone_ = true; workerThread_.getSearchResult( &maxLength_, &matching_lines_, &nbLinesProcessed_ ); filteredItemsCacheDirty_ = true; emit searchProgressed( nbMatches, progress ); } LineNumber LogFilteredData::findLogDataLine( LineNumber lineNum ) const { LineNumber line = std::numeric_limits::max(); if ( visibility_ == MatchesOnly ) { if ( lineNum < matching_lines_.size() ) { line = matching_lines_[lineNum].lineNumber(); } else { LOG(logERROR) << "Index too big in LogFilteredData: " << lineNum; } } else if ( visibility_ == MarksOnly ) { if ( lineNum < marks_.size() ) line = marks_.getLineMarkedByIndex( lineNum ); else LOG(logERROR) << "Index too big in LogFilteredData: " << lineNum; } else { // Regenerate the cache if needed if ( filteredItemsCacheDirty_ ) regenerateFilteredItemsCache(); if ( lineNum < filteredItemsCache_.size() ) line = filteredItemsCache_[ lineNum ].lineNumber(); else LOG(logERROR) << "Index too big in LogFilteredData: " << lineNum; } return line; } // Implementation of the virtual function. QString LogFilteredData::doGetLineString( qint64 lineNum ) const { qint64 line = findLogDataLine( lineNum ); QString string = sourceLogData_->getLineString( line ); return string; } // Implementation of the virtual function. QString LogFilteredData::doGetExpandedLineString( qint64 lineNum ) const { qint64 line = findLogDataLine( lineNum ); QString string = sourceLogData_->getExpandedLineString( line ); return string; } // Implementation of the virtual function. QStringList LogFilteredData::doGetLines( qint64 first_line, int number ) const { QStringList list; for ( int i = first_line; i < first_line + number; i++ ) { list.append( doGetLineString( i ) ); } return list; } // Implementation of the virtual function. QStringList LogFilteredData::doGetExpandedLines( qint64 first_line, int number ) const { QStringList list; for ( int i = first_line; i < first_line + number; i++ ) { list.append( doGetExpandedLineString( i ) ); } return list; } // Implementation of the virtual function. qint64 LogFilteredData::doGetNbLine() const { qint64 nbLines; if ( visibility_ == MatchesOnly ) nbLines = matching_lines_.size(); else if ( visibility_ == MarksOnly ) nbLines = marks_.size(); else { // Regenerate the cache if needed (hopefully most of the time // it won't be necessarily) if ( filteredItemsCacheDirty_ ) regenerateFilteredItemsCache(); nbLines = filteredItemsCache_.size(); } return nbLines; } // Implementation of the virtual function. int LogFilteredData::doGetMaxLength() const { int max_length; if ( visibility_ == MatchesOnly ) max_length = maxLength_; else if ( visibility_ == MarksOnly ) max_length = maxLengthMarks_; else max_length = qMax( maxLength_, maxLengthMarks_ ); return max_length; } // Implementation of the virtual function. int LogFilteredData::doGetLineLength( qint64 lineNum ) const { qint64 line = findLogDataLine( lineNum ); return sourceLogData_->getExpandedLineString( line ).length(); } void LogFilteredData::doSetDisplayEncoding( const char* encoding ) { LOG(logDEBUG) << "AbstractLogData::setDisplayEncoding: " << encoding; } // TODO: We might be a bit smarter and not regenerate the whole thing when // e.g. stuff is added at the end of the search. void LogFilteredData::regenerateFilteredItemsCache() const { LOG(logDEBUG) << "regenerateFilteredItemsCache"; filteredItemsCache_.clear(); filteredItemsCache_.reserve( matching_lines_.size() + marks_.size() ); // (it's an overestimate but probably not by much so it's fine) auto i = matching_lines_.cbegin(); Marks::const_iterator j = marks_.begin(); while ( ( i != matching_lines_.cend() ) || ( j != marks_.end() ) ) { qint64 next_mark = ( j != marks_.end() ) ? j->lineNumber() : std::numeric_limits::max(); qint64 next_match = ( i != matching_lines_.cend() ) ? i->lineNumber() : std::numeric_limits::max(); // We choose a Mark over a Match if a line is both, just an arbitrary choice really. if ( next_mark <= next_match ) { // LOG(logDEBUG) << "Add mark at " << next_mark; filteredItemsCache_.push_back( FilteredItem( next_mark, Mark ) ); if ( j != marks_.end() ) ++j; if ( ( next_mark == next_match ) && ( i != matching_lines_.cend() ) ) ++i; // Case when it's both match and mark. } else { // LOG(logDEBUG) << "Add match at " << next_match; filteredItemsCache_.push_back( FilteredItem( next_match, Match ) ); if ( i != matching_lines_.cend() ) ++i; } } filteredItemsCacheDirty_ = false; LOG(logDEBUG) << "finished regenerateFilteredItemsCache"; } glogg-1.1.0/src/data/logdata.h0000664000175000017500000001454212657420553015105 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2013, 2014, 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef LOGDATA_H #define LOGDATA_H #include #include #include #include #include #include #include #include #include "utils.h" #include "abstractlogdata.h" #include "logdataworkerthread.h" #include "filewatcher.h" #include "loadingstatus.h" class LogFilteredData; // Thrown when trying to attach an already attached LogData class CantReattachErr {}; // Represents a complete set of data to be displayed (ie. a log file content) // This class is thread-safe. class LogData : public AbstractLogData { Q_OBJECT public: // Creates an empty LogData LogData(); // Destroy an object ~LogData(); enum MonitoredFileStatus { Unchanged, DataAdded, Truncated }; // Attaches the LogData to a file on disk // It starts the asynchronous indexing and returns (almost) immediately // Attaching to a non existant file works and the file is reported // to be empty. // Reattaching is forbidden and will throw. void attachFile( const QString& fileName ); // Interrupt the loading and report a null file. // Does nothing if no loading in progress. void interruptLoading(); // Creates a new filtered data. // ownership is passed to the caller LogFilteredData* getNewFilteredData() const; // Returns the size if the file in bytes qint64 getFileSize() const; // Returns the last modification date for the file. // Null if the file is not on disk. QDateTime getLastModifiedDate() const; // Throw away all the file data and reload/reindex. void reload(); // Update the polling interval (in ms, 0 means disabled) void setPollingInterval( uint32_t interval_ms ); // Get the auto-detected encoding for the indexed text. EncodingSpeculator::Encoding getDetectedEncoding() const; signals: // Sent during the 'attach' process to signal progress // percent being the percentage of completion. void loadingProgressed( int percent ); // Signal the client the file is fully loaded and available. void loadingFinished( LoadingStatus status ); // Sent when the file on disk has changed, will be followed // by loadingProgressed if needed and then a loadingFinished. void fileChanged( LogData::MonitoredFileStatus status ); private slots: // Consider reloading the file when it changes on disk updated void fileChangedOnDisk(); // Called when the worker thread signals the current operation ended void indexingFinished( LoadingStatus status ); private: // This class models an indexing operation. // It exists to permit LogData to delay the operation if another // one is ongoing (operations are asynchronous) class LogDataOperation { public: LogDataOperation( const QString& fileName ) : filename_( fileName ) {} // Permit each child to have its destructor virtual ~LogDataOperation() {}; void start( LogDataWorkerThread& workerThread ) const { doStart( workerThread ); } const QString& getFilename() const { return filename_; } protected: virtual void doStart( LogDataWorkerThread& workerThread ) const = 0; QString filename_; }; // Attaching a new file (change name + full index) class AttachOperation : public LogDataOperation { public: AttachOperation( const QString& fileName ) : LogDataOperation( fileName ) {} ~AttachOperation() {}; protected: void doStart( LogDataWorkerThread& workerThread ) const; }; // Reindexing the current file class FullIndexOperation : public LogDataOperation { public: FullIndexOperation() : LogDataOperation( QString() ) {} ~FullIndexOperation() {}; protected: void doStart( LogDataWorkerThread& workerThread ) const; }; // Indexing part of the current file (from fileSize) class PartialIndexOperation : public LogDataOperation { public: PartialIndexOperation( qint64 fileSize ) : LogDataOperation( QString() ), filesize_( fileSize ) {} ~PartialIndexOperation() {}; protected: void doStart( LogDataWorkerThread& workerThread ) const; private: qint64 filesize_; }; std::shared_ptr fileWatcher_; MonitoredFileStatus fileChangedOnDisk_; // Implementation of virtual functions QString doGetLineString( qint64 line ) const override; QString doGetExpandedLineString( qint64 line ) const override; QStringList doGetLines( qint64 first, int number ) const override; QStringList doGetExpandedLines( qint64 first, int number ) const override; qint64 doGetNbLine() const override; int doGetMaxLength() const override; int doGetLineLength( qint64 line ) const override; void doSetDisplayEncoding( const char* encoding ) override; void enqueueOperation( std::shared_ptr newOperation ); void startOperation(); QString indexingFileName_; std::unique_ptr attached_file_; // Indexing data, read by us, written by the worker thread IndexingData indexing_data_; QDateTime lastModifiedDate_; std::shared_ptr currentOperation_; std::shared_ptr nextOperation_; // Codec to decode text QTextCodec* codec_; // To protect the file: mutable QMutex fileMutex_; // (are mutable to allow 'const' function to touch it, // while remaining const) // When acquiring both, data should be help before locking file. LogDataWorkerThread workerThread_; }; Q_DECLARE_METATYPE( LogData::MonitoredFileStatus ); #endif glogg-1.1.0/src/data/logfiltereddata.h0000664000175000017500000001437212657420553016625 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2011, 2012 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef LOGFILTEREDDATA_H #define LOGFILTEREDDATA_H #include #include #include #include #include #include #include "abstractlogdata.h" #include "logfiltereddataworkerthread.h" #include "marks.h" class LogData; class Marks; // A list of matches found in a LogData, it stores all the matching lines, // which can be accessed using the AbstractLogData interface, together with // the original line number where they were found. // Constructing such objet does not start the search. // This object should be constructed by a LogData. class LogFilteredData : public AbstractLogData { Q_OBJECT public: // Creates an empty LogFilteredData LogFilteredData(); // Constructor used by LogData LogFilteredData( const LogData* logData ); ~LogFilteredData(); // Starts the async search, sending newDataAvailable() when new data found. // If a search is already in progress this function will block until // it is done, so the application should call interruptSearch() first. void runSearch( const QRegExp& regExp ); // Add to the existing search, starting at the line when the search was // last stopped. Used when the file on disk has been added too. void updateSearch(); // Interrupt the running search if one is in progress. // Nothing is done if no search is in progress. void interruptSearch(); // Clear the search and the list of results. void clearSearch(); // Returns the line number in the original LogData where the element // 'index' was found. qint64 getMatchingLineNumber( int index ) const; // Returns whether the line number passed is in our list of matching ones. bool isLineInMatchingList( qint64 lineNumber ); // Returns the number of lines in the source log data LineNumber getNbTotalLines() const; // Returns the number of matches (independently of the visibility) LineNumber getNbMatches() const; // Returns the number of marks (independently of the visibility) LineNumber getNbMarks() const; // Returns the reason why the line at the passed index is in the filtered // data. It can be because it is either a mark or a match. enum FilteredLineType { Match, Mark }; FilteredLineType filteredLineTypeByIndex( int index ) const; // Marks interface (delegated to a Marks object) // Add a mark at the given line, optionally identified by the given char // If a mark for this char already exist, the previous one is replaced. void addMark( qint64 line, QChar mark = QChar() ); // Get the (unique) mark identified by the passed char. qint64 getMark( QChar mark ) const; // Returns wheither the passed line has a mark on it. bool isLineMarked( qint64 line ) const; // Delete the mark identified by the passed char. void deleteMark( QChar mark ); // Delete the mark present on the passed line or do nothing if there is // none. void deleteMark( qint64 line ); // Completely clear the marks list. void clearMarks(); // Changes what the AbstractLogData returns via its getXLines/getNbLines // API. enum Visibility { MatchesOnly, MarksOnly, MarksAndMatches }; void setVisibility( Visibility visibility ); signals: // Sent when the search has progressed, give the number of matches (so far) // and the percentage of completion void searchProgressed( int nbMatches, int progress ); private slots: void handleSearchProgressed( int NbMatches, int progress ); private: class FilteredItem; // Implementation of virtual functions QString doGetLineString( qint64 line ) const; QString doGetExpandedLineString( qint64 line ) const; QStringList doGetLines( qint64 first, int number ) const; QStringList doGetExpandedLines( qint64 first, int number ) const; qint64 doGetNbLine() const; int doGetMaxLength() const; int doGetLineLength( qint64 line ) const; void doSetDisplayEncoding( const char* encoding ); // List of the matching line numbers SearchResultArray matching_lines_; const LogData* sourceLogData_; QRegExp currentRegExp_; bool searchDone_; int maxLength_; int maxLengthMarks_; // Number of lines of the LogData that has been searched for: qint64 nbLinesProcessed_; Visibility visibility_; // Cache used to combine Marks and Matches // when visibility_ == MarksAndMatches // (QVector store actual objects instead of pointers) mutable std::vector filteredItemsCache_; mutable bool filteredItemsCacheDirty_; LogFilteredDataWorkerThread workerThread_; Marks marks_; // Utility functions LineNumber findLogDataLine( LineNumber lineNum ) const; void regenerateFilteredItemsCache() const; }; // A class representing a Mark or Match. // Conceptually it should be a base class for Mark and MatchingLine, // but we implement it this way for performance reason as we create plenty of // those everytime we refresh the cache. // Specifically it allows to store this in the cache by value instead // of pointer (less small allocations and no RTTI). class LogFilteredData::FilteredItem { public: // A default ctor seems to be necessary for QVector FilteredItem() { lineNumber_ = 0; } FilteredItem( LineNumber lineNumber, FilteredLineType type ) { lineNumber_ = lineNumber; type_ = type; } LineNumber lineNumber() const { return lineNumber_; } FilteredLineType type() const { return type_; } private: LineNumber lineNumber_; FilteredLineType type_; }; #endif glogg-1.1.0/src/data/logfiltereddataworkerthread.h0000664000175000017500000001305112657420553021240 0ustar nickonicko/* * Copyright (C) 2009, 2010 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef LOGFILTEREDDATAWORKERTHREAD_H #define LOGFILTEREDDATAWORKERTHREAD_H #include #include #include #include #include #include class LogData; // Line number are unsigned 32 bits for now. typedef uint32_t LineNumber; // Class encapsulating a single matching line // Contains the line number the line was found in and its content. class MatchingLine { public: MatchingLine( LineNumber line ) { lineNumber_ = line; }; // Accessors LineNumber lineNumber() const { return lineNumber_; } private: LineNumber lineNumber_; }; // This is an array of matching lines. // It shall be implemented for random lookup speed, so // a fixed "in-place" array (vector) is probably fine. typedef std::vector SearchResultArray; // This class is a mutex protected set of search result data. // It is thread safe. class SearchData { public: SearchData() : dataMutex_(), matches_(), maxLength_(0) { } // Atomically get all the search data void getAll( int* length, SearchResultArray* matches, qint64* nbLinesProcessed ) const; // Atomically set all the search data // (overwriting the existing) // (the matches are always moved) void setAll( int length, SearchResultArray&& matches ); // Atomically add to all the existing search data. void addAll( int length, const SearchResultArray& matches, LineNumber nbLinesProcessed ); // Get the number of matches LineNumber getNbMatches() const; // Delete the match for the passed line (if it exist) void deleteMatch( LineNumber line ); // Atomically clear the data. void clear(); private: mutable QMutex dataMutex_; SearchResultArray matches_; int maxLength_; LineNumber nbLinesProcessed_; }; class SearchOperation : public QObject { Q_OBJECT public: SearchOperation( const LogData* sourceLogData, const QRegExp& regExp, bool* interruptRequest ); virtual ~SearchOperation() { } // Start the search operation, returns true if it has been done // and false if it has been cancelled (results not copied) virtual void start( SearchData& result ) = 0; signals: void searchProgressed( int percent, int nbMatches ); protected: static const int nbLinesInChunk; // Implement the common part of the search, passing // the shared results and the line to begin the search from. void doSearch( SearchData& result, qint64 initialLine ); bool* interruptRequested_; const QRegExp regexp_; const LogData* sourceLogData_; }; class FullSearchOperation : public SearchOperation { public: FullSearchOperation( const LogData* sourceLogData, const QRegExp& regExp, bool* interruptRequest ) : SearchOperation( sourceLogData, regExp, interruptRequest ) {} virtual void start( SearchData& result ); }; class UpdateSearchOperation : public SearchOperation { public: UpdateSearchOperation( const LogData* sourceLogData, const QRegExp& regExp, bool* interruptRequest, qint64 position ) : SearchOperation( sourceLogData, regExp, interruptRequest ), initialPosition_( position ) {} virtual void start( SearchData& result ); private: qint64 initialPosition_; }; // Create and manage the thread doing loading/indexing for // the creating LogData. One LogDataWorkerThread is used // per LogData instance. // Note everything except the run() function is in the LogData's // thread. class LogFilteredDataWorkerThread : public QThread { Q_OBJECT public: LogFilteredDataWorkerThread( const LogData* sourceLogData ); ~LogFilteredDataWorkerThread(); // Start the search with the passed regexp void search( const QRegExp& regExp ); // Continue the previous search starting at the passed position // in the source file (line number) void updateSearch( const QRegExp& regExp, qint64 position ); // Interrupts the search if one is in progress void interrupt(); // Returns a copy of the current indexing data void getSearchResult( int* maxLength, SearchResultArray* searchMatches, qint64* nbLinesProcessed ); signals: // Sent during the indexing process to signal progress // percent being the percentage of completion. void searchProgressed( int percent, int nbMatches ); // Sent when indexing is finished, signals the client // to copy the new data back. void searchFinished(); protected: void run(); private: const LogData* sourceLogData_; // Mutex to protect operationRequested_ and friends QMutex mutex_; QWaitCondition operationRequestedCond_; QWaitCondition nothingToDoCond_; // Set when the thread must die bool terminate_; bool interruptRequested_; SearchOperation* operationRequested_; // Shared indexing data SearchData searchData_; }; #endif glogg-1.1.0/src/data/compressedlinestorage.h0000664000175000017500000001256212657420553020073 0ustar nickonicko/* * Copyright (C) 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include #include #include "threadprivatestore.h" // This class is a compressed storage backend for LinePositionArray // It emulates the interface of a vector, but take advantage of the nature // of the stored data (increasing end of line addresses) to apply some // compression in memory, while still providing fast, constant-time look-up. /* The current algorithm takes advantage of the fact most lines are reasonably * short, it codes each line on: * - Line < 127 bytes : 1 byte * - 127 < line < 16383 : 2 bytes * - line > 16383 : 6 bytes (or 10 bytes) * Uncompressed backend stores line on 4 bytes or 8 bytes. * * The algorithm is quite simple, the file is first divided in two parts: * - The lines whose end are located before UINT32_MAX * - The lines whose end are located after UINT32_MAX * Those end of lines are stored separately in the table32 and the table64 * respectively. * * The EOL list is then divided in blocks of BLOCK_SIZE (128) lines. * A block index vector (per table) contains pointers to each block. * * Each block is then defined as such: * Block32 (sizes in byte) * 00 - Absolute EOF address (4 bytes) * 04 - ( 0xxx xxxx if second line is < 127 ) (1 byte, relative) * - ( 10xx xxxx * xxxx xxxx if second line is < 16383 ) (2 bytes, relative) * - ( 1111 1111 * xxxx xxxx * xxxx xxxx if second line is > 16383 ) (6 bytes, absolute) * ... * (126 more lines) * * Block64 (sizes in byte) * 00 - Absolute EOF address (8 bytes) * 08 - ( 0xxx xxxx if second line is < 127 ) (1 byte, relative) * - ( 10xx xxxx * xxxx xxxx if second line is < 16383 ) (2 bytes, relative) * - ( 1111 1111 * xxxx xxxx * xxxx xxxx * xxxx xxxx * xxxx xxxx if second line is > 16383 ) (10 bytes, absolute) * ... * (126 more lines) * * Absolute addressing has been adopted for line > 16383 to bound memory usage in case * of pathologically long (MBs or GBs) lines, even if it is a bit less efficient for * long-ish (30 KB) lines. * * The table32 always starts at 0, the table64 starts at first_long_line_ */ #ifndef COMPRESSEDLINESTORAGE_H #define COMPRESSEDLINESTORAGE_H #define BLOCK_SIZE 256 //template class CompressedLinePositionStorage { public: // Default constructor CompressedLinePositionStorage() { nb_lines_ = 0; first_long_line_ = UINT32_MAX; current_pos_ = 0; block_pointer_ = nullptr; previous_block_pointer_ = nullptr; } // Copy constructor would be slow, delete! CompressedLinePositionStorage( const CompressedLinePositionStorage& orig ) = delete; // Move constructor CompressedLinePositionStorage( CompressedLinePositionStorage&& orig ); // Move assignement CompressedLinePositionStorage& operator=( CompressedLinePositionStorage&& orig ); // Destructor ~CompressedLinePositionStorage(); // Append the passed end-of-line to the storage void append( uint64_t pos ); void push_back( uint64_t pos ) { append( pos ); } // Size of the array uint32_t size() const { return nb_lines_; } // Element at index uint64_t at( uint32_t i ) const; // Add one list to the other void append_list( const std::vector& positions ); // Pop the last element of the storage void pop_back(); private: // Utility for move ctor/assign void move_from( CompressedLinePositionStorage&& orig ); // The two indexes std::vector block32_index_; std::vector block64_index_; // Total number of lines in storage uint32_t nb_lines_; // Current position (position of the end of the last line added) uint64_t current_pos_; // Address of the next position (not yet written) within the current // block. nullptr means there is no current block (previous block // finished or no data) char* block_pointer_; // The index of the first line whose end is stored in a block64 // Initialised at UINT32_MAX, meaning "unset" // this is the origin point for all calculations in block64 uint32_t first_long_line_; // For pop_back: // Previous pointer to block element, it is restored when we // "pop_back" the last element. // A null pointer here means pop_back need to free the block // that has just been created. char* previous_block_pointer_; // Cache the last position read // This is to speed up consecutive reads (whole page) struct Cache { uint32_t index; uint64_t position; char* ptr; }; mutable ThreadPrivateStore last_read_; // = { UINT32_MAX - 1U, 0, nullptr }; // mutable Cache last_read; }; #endif glogg-1.1.0/src/data/logdata.cpp0000664000175000017500000002744312657420553015444 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2013, 2014, 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ // This file implements LogData, the content of a log file. #include #include #include #include "log.h" #include "logdata.h" #include "logfiltereddata.h" #if defined(GLOGG_SUPPORTS_INOTIFY) || defined(WIN32) #include "platformfilewatcher.h" #else #include "qtfilewatcher.h" #endif // Implementation of the 'start' functions for each operation void LogData::AttachOperation::doStart( LogDataWorkerThread& workerThread ) const { LOG(logDEBUG) << "Attaching " << filename_.toStdString(); workerThread.attachFile( filename_ ); workerThread.indexAll(); } void LogData::FullIndexOperation::doStart( LogDataWorkerThread& workerThread ) const { LOG(logDEBUG) << "Reindexing (full)"; workerThread.indexAll(); } void LogData::PartialIndexOperation::doStart( LogDataWorkerThread& workerThread ) const { LOG(logDEBUG) << "Reindexing (partial)"; workerThread.indexAdditionalLines( filesize_ ); } // Constructs an empty log file. // It must be displayed without error. LogData::LogData() : AbstractLogData(), indexing_data_(), fileMutex_(), workerThread_( &indexing_data_ ) { // Start with an "empty" log attached_file_ = nullptr; currentOperation_ = nullptr; nextOperation_ = nullptr; codec_ = QTextCodec::codecForName( "ISO-8859-1" ); #if defined(GLOGG_SUPPORTS_INOTIFY) || defined(WIN32) fileWatcher_ = std::make_shared(); #else fileWatcher_ = std::make_shared(); #endif // Initialise the file watcher connect( fileWatcher_.get(), SIGNAL( fileChanged( const QString& ) ), this, SLOT( fileChangedOnDisk() ) ); // Forward the update signal connect( &workerThread_, SIGNAL( indexingProgressed( int ) ), this, SIGNAL( loadingProgressed( int ) ) ); connect( &workerThread_, SIGNAL( indexingFinished( LoadingStatus ) ), this, SLOT( indexingFinished( LoadingStatus ) ) ); // Starts the worker thread workerThread_.start(); } LogData::~LogData() { // Remove the current file from the watch list if ( attached_file_ ) fileWatcher_->removeFile( attached_file_->fileName() ); // FIXME // workerThread_.stop(); } // // Public functions // void LogData::attachFile( const QString& fileName ) { LOG(logDEBUG) << "LogData::attachFile " << fileName.toStdString(); if ( attached_file_ ) { // We cannot reattach throw CantReattachErr(); } attached_file_.reset( new QFile( fileName ) ); attached_file_->open( QIODevice::ReadOnly ); std::shared_ptr operation( new AttachOperation( fileName ) ); enqueueOperation( std::move( operation ) ); } void LogData::interruptLoading() { workerThread_.interrupt(); } qint64 LogData::getFileSize() const { return indexing_data_.getSize(); } QDateTime LogData::getLastModifiedDate() const { return lastModifiedDate_; } // Return an initialised LogFilteredData. The search is not started. LogFilteredData* LogData::getNewFilteredData() const { LogFilteredData* newFilteredData = new LogFilteredData( this ); return newFilteredData; } void LogData::reload() { workerThread_.interrupt(); enqueueOperation( std::make_shared() ); } void LogData::setPollingInterval( uint32_t interval_ms ) { fileWatcher_->setPollingInterval( interval_ms ); } // // Private functions // // Add an operation to the queue and perform it immediately if // there is none ongoing. void LogData::enqueueOperation( std::shared_ptr new_operation ) { if ( currentOperation_ == nullptr ) { // We do it immediately currentOperation_ = new_operation; startOperation(); } else { // An operation is in progress... // ... we schedule the attach op for later nextOperation_ = new_operation; } } // Performs the current operation asynchronously, a indexingFinished // signal will be received when it's finished. void LogData::startOperation() { if ( currentOperation_ ) { LOG(logDEBUG) << "startOperation found something to do."; // Let the operation do its stuff currentOperation_->start( workerThread_ ); } } // // Slots // void LogData::fileChangedOnDisk() { LOG(logDEBUG) << "signalFileChanged"; const QString name = attached_file_->fileName(); QFileInfo info( name ); // Need to open the file in case it was absent attached_file_->open( QIODevice::ReadOnly ); std::shared_ptr newOperation; qint64 file_size = indexing_data_.getSize(); LOG(logDEBUG) << "current fileSize=" << file_size; LOG(logDEBUG) << "info file_->size()=" << info.size(); if ( info.size() < file_size ) { fileChangedOnDisk_ = Truncated; LOG(logINFO) << "File truncated"; newOperation = std::make_shared(); } else if ( fileChangedOnDisk_ != DataAdded ) { fileChangedOnDisk_ = DataAdded; LOG(logINFO) << "New data on disk"; newOperation = std::make_shared( file_size ); } if ( newOperation ) enqueueOperation( newOperation ); lastModifiedDate_ = info.lastModified(); emit fileChanged( fileChangedOnDisk_ ); // TODO: fileChangedOnDisk_, fileSize_ } void LogData::indexingFinished( LoadingStatus status ) { LOG(logDEBUG) << "indexingFinished: " << ( status == LoadingStatus::Successful ) << ", found " << indexing_data_.getNbLines() << " lines."; if ( status == LoadingStatus::Successful ) { // Start watching we watch the file for updates fileChangedOnDisk_ = Unchanged; fileWatcher_->addFile( attached_file_->fileName() ); // Update the modified date/time if the file exists lastModifiedDate_ = QDateTime(); QFileInfo fileInfo( *attached_file_ ); if ( fileInfo.exists() ) lastModifiedDate_ = fileInfo.lastModified(); } // FIXME be cleverer here as a notification might have arrived whilst we // were indexing. fileChangedOnDisk_ = Unchanged; LOG(logDEBUG) << "Sending indexingFinished."; emit loadingFinished( status ); // So now the operation is done, let's see if there is something // else to do, in which case, do it! assert( currentOperation_ ); currentOperation_ = std::move( nextOperation_ ); nextOperation_.reset(); if ( currentOperation_ ) { LOG(logDEBUG) << "indexingFinished is performing the next operation"; startOperation(); } } // // Implementation of virtual functions // qint64 LogData::doGetNbLine() const { return indexing_data_.getNbLines(); } int LogData::doGetMaxLength() const { return indexing_data_.getMaxLength(); } int LogData::doGetLineLength( qint64 line ) const { if ( line >= indexing_data_.getNbLines() ) { return 0; /* exception? */ } int length = doGetExpandedLineString( line ).length(); return length; } void LogData::doSetDisplayEncoding( const char* encoding ) { LOG(logDEBUG) << "AbstractLogData::setDisplayEncoding: " << encoding; codec_ = QTextCodec::codecForName( encoding ); } QString LogData::doGetLineString( qint64 line ) const { if ( line >= indexing_data_.getNbLines() ) { return 0; /* exception? */ } fileMutex_.lock(); attached_file_->seek( (line == 0) ? 0 : indexing_data_.getPosForLine( line-1 ) ); QString string = codec_->toUnicode( attached_file_->readLine() ); fileMutex_.unlock(); string.chop( 1 ); return string; } QString LogData::doGetExpandedLineString( qint64 line ) const { if ( line >= indexing_data_.getNbLines() ) { return 0; /* exception? */ } fileMutex_.lock(); attached_file_->seek( (line == 0) ? 0 : indexing_data_.getPosForLine( line-1 ) ); QByteArray rawString = attached_file_->readLine(); fileMutex_.unlock(); QString string = untabify( codec_->toUnicode( rawString ) ); string.chop( 1 ); return string; } // Note this function is also called from the LogFilteredDataWorker thread, so // data must be protected because they are changed in the main thread (by // indexingFinished). QStringList LogData::doGetLines( qint64 first_line, int number ) const { QStringList list; const qint64 last_line = first_line + number - 1; // LOG(logDEBUG) << "LogData::doGetLines first_line:" << first_line << " nb:" << number; if ( number == 0 ) { return QStringList(); } if ( last_line >= indexing_data_.getNbLines() ) { LOG(logWARNING) << "LogData::doGetLines Lines out of bound asked for"; return QStringList(); /* exception? */ } fileMutex_.lock(); const qint64 first_byte = (first_line == 0) ? 0 : indexing_data_.getPosForLine( first_line-1 ); const qint64 last_byte = indexing_data_.getPosForLine( last_line ); // LOG(logDEBUG) << "LogData::doGetLines first_byte:" << first_byte << " last_byte:" << last_byte; attached_file_->seek( first_byte ); QByteArray blob = attached_file_->read( last_byte - first_byte ); fileMutex_.unlock(); qint64 beginning = 0; qint64 end = 0; for ( qint64 line = first_line; (line <= last_line); line++ ) { end = indexing_data_.getPosForLine( line ) - first_byte; // LOG(logDEBUG) << "Getting line " << line << " beginning " << beginning << " end " << end; QByteArray this_line = blob.mid( beginning, end - beginning - 1 ); // LOG(logDEBUG) << "Line is: " << QString( this_line ).toStdString(); list.append( codec_->toUnicode( this_line ) ); beginning = end; } return list; } QStringList LogData::doGetExpandedLines( qint64 first_line, int number ) const { QStringList list; const qint64 last_line = first_line + number - 1; if ( number == 0 ) { return QStringList(); } if ( last_line >= indexing_data_.getNbLines() ) { LOG(logWARNING) << "LogData::doGetExpandedLines Lines out of bound asked for"; return QStringList(); /* exception? */ } fileMutex_.lock(); const qint64 first_byte = (first_line == 0) ? 0 : indexing_data_.getPosForLine( first_line-1 ); const qint64 last_byte = indexing_data_.getPosForLine( last_line ); // LOG(logDEBUG) << "LogData::doGetExpandedLines first_byte:" << first_byte << " last_byte:" << last_byte; attached_file_->seek( first_byte ); QByteArray blob = attached_file_->read( last_byte - first_byte ); fileMutex_.unlock(); qint64 beginning = 0; qint64 end = 0; for ( qint64 line = first_line; (line <= last_line); line++ ) { end = indexing_data_.getPosForLine( line ) - first_byte; // LOG(logDEBUG) << "Getting line " << line << " beginning " << beginning << " end " << end; QByteArray this_line = blob.mid( beginning, end - beginning - 1 ); // LOG(logDEBUG) << "Line is: " << QString( this_line ).toStdString(); list.append( untabify( codec_->toUnicode( this_line ) ) ); beginning = end; } return list; } EncodingSpeculator::Encoding LogData::getDetectedEncoding() const { return indexing_data_.getEncodingGuess(); } glogg-1.1.0/src/data/logdataworkerthread.h0000664000175000017500000001315712657420553017530 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2014, 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef LOGDATAWORKERTHREAD_H #define LOGDATAWORKERTHREAD_H #include #include #include #include #include #include "loadingstatus.h" #include "linepositionarray.h" #include "encodingspeculator.h" #include "utils.h" // This class is a thread-safe set of indexing data. class IndexingData { public: IndexingData() : dataMutex_(), linePosition_(), maxLength_(0), indexedSize_(0), encoding_(EncodingSpeculator::Encoding::ASCII7) { } // Get the total indexed size qint64 getSize() const; // Get the length of the longest line int getMaxLength() const; // Get the total number of lines LineNumber getNbLines() const; // Get the position (in byte from the beginning of the file) // of the end of the passed line. qint64 getPosForLine( LineNumber line ) const; // Get the guessed encoding for the content. EncodingSpeculator::Encoding getEncodingGuess() const; // Atomically add to all the existing // indexing data. void addAll( qint64 size, int length, const FastLinePositionArray& linePosition, EncodingSpeculator::Encoding encoding ); // Completely clear the indexing data. void clear(); private: mutable QMutex dataMutex_; LinePositionArray linePosition_; int maxLength_; qint64 indexedSize_; EncodingSpeculator::Encoding encoding_; }; class IndexOperation : public QObject { Q_OBJECT public: IndexOperation( const QString& fileName, IndexingData* indexingData, bool* interruptRequest, EncodingSpeculator* encodingSpeculator ); virtual ~IndexOperation() { } // Start the indexing operation, returns true if it has been done // and false if it has been cancelled (results not copied) virtual bool start() = 0; signals: void indexingProgressed( int ); protected: static const int sizeChunk; // Returns the total size indexed // Modify the passed linePosition and maxLength void doIndex( IndexingData* linePosition, EncodingSpeculator* encodingSpeculator, qint64 initialPosition ); QString fileName_; bool* interruptRequest_; IndexingData* indexing_data_; EncodingSpeculator* encoding_speculator_; }; class FullIndexOperation : public IndexOperation { public: FullIndexOperation( const QString& fileName, IndexingData* indexingData, bool* interruptRequest, EncodingSpeculator* speculator ) : IndexOperation( fileName, indexingData, interruptRequest, speculator ) { } virtual bool start(); }; class PartialIndexOperation : public IndexOperation { public: PartialIndexOperation( const QString& fileName, IndexingData* indexingData, bool* interruptRequest, EncodingSpeculator* speculator, qint64 position ); virtual bool start(); private: qint64 initialPosition_; }; // Create and manage the thread doing loading/indexing for // the creating LogData. One LogDataWorkerThread is used // per LogData instance. // Note everything except the run() function is in the LogData's // thread. class LogDataWorkerThread : public QThread { Q_OBJECT public: // Pass a pointer to the IndexingData (initially empty) // This object will change it when indexing (IndexingData must be thread safe!) LogDataWorkerThread( IndexingData* indexing_data ); ~LogDataWorkerThread(); // Attaches to a file on disk. Attaching to a non existant file // will work, it will just appear as an empty file. void attachFile( const QString& fileName ); // Instructs the thread to start a new full indexing of the file, sending // signals as it progresses. void indexAll(); // Instructs the thread to start a partial indexing (starting at // the index passed). void indexAdditionalLines( qint64 position ); // Interrupts the indexing if one is in progress void interrupt(); // Returns a copy of the current indexing data void getIndexingData( qint64* indexedSize, int* maxLength, LinePositionArray* linePosition ); signals: // Sent during the indexing process to signal progress // percent being the percentage of completion. void indexingProgressed( int percent ); // Sent when indexing is finished, signals the client // to copy the new data back. void indexingFinished( LoadingStatus status ); protected: void run(); private: void doIndexAll(); // Mutex to protect operationRequested_ and friends QMutex mutex_; QWaitCondition operationRequestedCond_; QWaitCondition nothingToDoCond_; QString fileName_; // Set when the thread must die bool terminate_; bool interruptRequested_; IndexOperation* operationRequested_; // Pointer to the owner's indexing data (we modify it) IndexingData* indexing_data_; // To guess the encoding EncodingSpeculator encodingSpeculator_; }; #endif glogg-1.1.0/src/data/threadprivatestore.h0000664000175000017500000000476512657420553017417 0ustar nickonicko/* * Copyright (C) 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef THREADPRIVATESTORE_H #define THREADPRIVATESTORE_H #include #include #include #include "log.h" // This template stores data that is private to the calling thread // in a completely wait-free manner. template class ThreadPrivateStore { public: // Construct an empty ThreadPrivateStore ThreadPrivateStore() : nb_threads_( 0 ) { // Last one is a guard for ( int i=0; i<(MAX_THREADS+1); ++i ) thread_ids_[i] = 0; } // Conversion to a T operator T() const { return get(); } // Getter (thread-safe, wait-free) T get() const { return data_[threadIndex()]; } T* getPtr() { return &data_[threadIndex()]; } // Setter (thread-safe, wait-free) void set( const T& value ) { data_[threadIndex()] = value; } private: // Nb of threads that have registered int nb_threads_; // The actual data array (one element per thread from 0 to nb_threads_) T data_[MAX_THREADS]; mutable std::atomic thread_ids_[MAX_THREADS+1]; int threadIndex() const { int i; for ( i=0; thread_ids_[i]; ++i ) { if ( thread_ids_[i].load() == std::hash()(std::this_thread::get_id() ) ) { return i; } } // Current thread is missing, let's add it size_t thread_id = std::hash()( std::this_thread::get_id() ); while ( i < MAX_THREADS ) { size_t expected = 0; if ( thread_ids_[i++].compare_exchange_weak( expected, thread_id ) ) { LOG(logDEBUG) << "Created thread for " << thread_id << " at index " << i-1; return i-1; } } assert( 1 ); return 0; } }; #endif glogg-1.1.0/src/data/abstractlogdata.h0000664000175000017500000001045112657420553016624 0ustar nickonicko/* * Copyright (C) 2009, 2010 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef ABSTRACTLOGDATA_H #define ABSTRACTLOGDATA_H #include #include #include // Base class representing a set of data. // It can be either a full set or a filtered set. class AbstractLogData : public QObject { Q_OBJECT public: AbstractLogData(); // Permit each child to have its destructor virtual ~AbstractLogData() {}; // Returns the line passed as a QString QString getLineString( qint64 line ) const; // Returns the line passed as a QString, with tabs expanded QString getExpandedLineString( qint64 line ) const; // Returns a set of lines as a QStringList QStringList getLines( qint64 first_line, int number ) const; // Returns a set of lines with tabs expanded QStringList getExpandedLines( qint64 first_line, int number ) const; // Returns the total number of lines qint64 getNbLine() const; // Returns the visible length of the longest line // Tabs are expanded int getMaxLength() const; // Returns the visible length of the passed line // Tabs are expanded int getLineLength( qint64 line ) const; // Set the view to use the passed encoding for display void setDisplayEncoding( const char* encoding_name ); // Length of a tab stop static const int tabStop = 8; protected: // Internal function called to get a given line virtual QString doGetLineString( qint64 line ) const = 0; // Internal function called to get a given line virtual QString doGetExpandedLineString( qint64 line ) const = 0; // Internal function called to get a set of lines virtual QStringList doGetLines( qint64 first_line, int number ) const = 0; // Internal function called to get a set of expanded lines virtual QStringList doGetExpandedLines( qint64 first_line, int number ) const = 0; // Internal function called to get the number of lines virtual qint64 doGetNbLine() const = 0; // Internal function called to get the maximum length virtual int doGetMaxLength() const = 0; // Internal function called to get the line length virtual int doGetLineLength( qint64 line ) const = 0; // Internal function called to set the encoding virtual void doSetDisplayEncoding( const char* encoding ) = 0; static inline QString untabify( const QString& line ) { QString untabified_line; int total_spaces = 0; for ( int j = 0; j < line.length(); j++ ) { if ( line[j] == '\t' ) { int spaces = tabStop - ( ( j + total_spaces ) % tabStop ); // LOG(logDEBUG4) << "Replacing tab at char " << j << " (" << spaces << " spaces)"; QString blanks( spaces, QChar(' ') ); untabified_line.append( blanks ); total_spaces += spaces - 1; } else { untabified_line.append( line[j] ); } } return untabified_line; } static inline QString untabify( const char* line ) { QString untabified_line; int total_spaces = 0; for ( const char* i = line; *i != '\0'; i++ ) { if ( *i == '\t' ) { int spaces = tabStop - ( ( (i - line) + total_spaces ) % tabStop ); // LOG(logDEBUG4) << "Replacing tab at char " << j << " (" << spaces << " spaces)"; QString blanks( spaces, QChar(' ') ); untabified_line.append( blanks ); total_spaces += spaces - 1; } else { untabified_line.append( *i ); } } return untabified_line; } }; #endif glogg-1.1.0/src/logmainview.h0000664000175000017500000000302712657420553015076 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2011, 2013 Nicolas Bonnefon * and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef LOGMAINVIEW_H #define LOGMAINVIEW_H #include "abstractlogview.h" #include "data/logdata.h" // Class implementing the main (top) view widget. class LogMainView : public AbstractLogView { public: LogMainView( const LogData* newLogData, const QuickFindPattern* const quickFindPattern, Overview* overview, OverviewWidget* overview_widget, QWidget* parent = 0 ); // Configure the view to use the passed filtered list // (used for couloured bullets) // Should be NULL or the empty LFD if no filtering is used void useNewFiltering( LogFilteredData* filteredData ); protected: // Implements the virtual function virtual LineType lineType( int lineNumber ) const; private: LogFilteredData* filteredData_; }; #endif glogg-1.1.0/src/signalmux.h0000664000175000017500000000602212657420553014562 0ustar nickonicko/* * Copyright (C) 2013 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef SIGNALMUX_H #define SIGNALMUX_H // This class multiplexes Qt signals exchanged between one main // window and several 'document' widgets. // The main window register signals with the mux (instead of directly // with the document) and then just notify the mux when the 'current // document' changes /* +---------------------------------------+ | | | Main Window |--+ | | | +---------------------------------------+ | + ^ | v + | +---------------------------------------+ | | MUX |<-+ +---------------------------------------+ +----------^ + | +----------+ | v +--+-----+ +--------+ +-------+ +-------+ | | | | | | | | | Doc 1 | | Doc 2 | | Doc 3 | | Doc 4 | | | | | | | | | +--------+ +--------+ +-------+ +-------+ */ // Largely inspired by http://doc.qt.digia.com/qq/qq08-action-multiplexer.html #include #include class QObject; class MuxableDocumentInterface { public: // Send all signals refering to a state of the document to update // the parent widget. void sendAllStateSignals() { doSendAllStateSignals(); } protected: virtual void doSendAllStateSignals() = 0; }; class SignalMux { public: SignalMux(); // Connect an 'downstream' signal void connect(QObject *sender, const char *signal, const char *slot); void disconnect(QObject *sender, const char *signal, const char *slot); // Connect an 'upstream' signal void connect(const char *signal, QObject *receiver, const char *slot); void disconnect(const char *signal, QObject *receiver, const char *slot); // Change the current document void setCurrentDocument( QObject* current_document ); private: struct Connection { QPointer source; QPointer sink; const char* signal; const char* slot; }; void connect( const Connection& connection); void disconnect( const Connection& connection); std::list connectionList_; QPointer currentDocument_; }; #endif glogg-1.1.0/src/winfilewatcher.cpp0000664000175000017500000000044712657420553016126 0ustar nickonicko#include "winfilewatcher.h" // Create the empty object WinFileWatcher::WinFileWatcher() : FileWatcher() { } // Destroy the object WinFileWatcher::~WinFileWatcher() { } void WinFileWatcher::addFile( const QString& fileName ) { } void WinFileWatcher::removeFile( const QString& fileName ) { } glogg-1.1.0/src/abstractlogview.h0000664000175000017500000003111212657420553015751 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2011, 2012, 2013 Nicolas Bonnefon * and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #ifndef ABSTRACTLOGVIEW_H #define ABSTRACTLOGVIEW_H #include #include #ifdef GLOGG_PERF_MEASURE_FPS # include "perfcounter.h" #endif #include "selection.h" #include "quickfind.h" #include "overviewwidget.h" #include "quickfindmux.h" #include "viewtools.h" class QMenu; class QAction; class AbstractLogData; class LineChunk { public: enum ChunkType { Normal, Highlighted, Selected, }; LineChunk( int first_col, int end_col, ChunkType type ); int start() const { return start_; } int end() const { return end_; } ChunkType type() const { return type_; } // Returns 'true' if the selection is part of this chunk // (at least partially), if so, it should be replaced by the list returned QList select( int selection_start, int selection_end ) const; private: int start_; int end_; ChunkType type_; }; // Utility class for syntax colouring. // It stores the chunks of line to draw // each chunk having a different colour class LineDrawer { public: LineDrawer( const QColor& back_color) : list(), backColor_( back_color ) { }; // Add a chunk of line using the given colours. // Both first_col and last_col are included // An empty chunk will be ignored. // the first column will be set to 0 if negative // The column are relative to the screen void addChunk( int first_col, int last_col, QColor fore, QColor back ); void addChunk( const LineChunk& chunk, QColor fore, QColor back ); // Draw the current line of text using the given painter, // in the passed block (in pixels) // The line must be cut to fit on the screen. // leftExtraBackgroundPx is the an extra margin to start drawing // the coloured // background, going all the way to the element // left of the line looks better. void draw( QPainter& painter, int xPos, int yPos, int line_width, const QString& line, int leftExtraBackgroundPx ); private: class Chunk { public: // Create a new chunk Chunk( int start, int length, QColor fore, QColor back ) : start_( start ), length_( length ), foreColor_ ( fore ), backColor_ ( back ) { }; // Accessors int start() const { return start_; } int length() const { return length_; } const QColor& foreColor() const { return foreColor_; } const QColor& backColor() const { return backColor_; } private: int start_; int length_; QColor foreColor_; QColor backColor_; }; QList list; QColor backColor_; }; // Utility class representing a buffer for number entered on the keyboard // The buffer keep at most 7 digits, and reset itself after a timeout. class DigitsBuffer : public QObject { Q_OBJECT public: DigitsBuffer(); // Reset the buffer. void reset(); // Add a single digit to the buffer (discarded if it's not a digit), // the timeout timer is reset. void add( char character ); // Get the content of the buffer (0 if empty) and reset it. int content(); protected: void timerEvent( QTimerEvent* event ); private: // Duration of the timeout in milliseconds. static const int timeout_; QString digits_; QBasicTimer timer_; }; class Overview; // Base class representing the log view widget. // It can be either the top (full) or bottom (filtered) view. class AbstractLogView : public QAbstractScrollArea, public SearchableWidgetInterface { Q_OBJECT public: // Constructor of the widget, the data set is passed. // The caller retains ownership of the data set. // The pointer to the QFP is used for colouring and QuickFind searches AbstractLogView( const AbstractLogData* newLogData, const QuickFindPattern* const quickFind, QWidget* parent=0 ); ~AbstractLogView(); // Refresh the widget when the data set has changed. void updateData(); // Instructs the widget to update it's content geometry, // used when the font is changed. void updateDisplaySize(); // Return the line number of the top line of the view int getTopLine() const; // Return the text of the current selection. QString getSelection() const; // Instructs the widget to select the whole text. void selectAll(); protected: virtual void mousePressEvent( QMouseEvent* mouseEvent ); virtual void mouseMoveEvent( QMouseEvent* mouseEvent ); virtual void mouseReleaseEvent( QMouseEvent* ); virtual void mouseDoubleClickEvent( QMouseEvent* mouseEvent ); virtual void timerEvent( QTimerEvent* timerEvent ); virtual void changeEvent( QEvent* changeEvent ); virtual void paintEvent( QPaintEvent* paintEvent ); virtual void resizeEvent( QResizeEvent* resizeEvent ); virtual void scrollContentsBy( int dx, int dy ); virtual void keyPressEvent( QKeyEvent* keyEvent ); virtual void wheelEvent( QWheelEvent* wheelEvent ); virtual bool event( QEvent * e ); // Must be implemented to return wether the line number is // a match, a mark or just a normal line (used for coloured bullets) enum LineType { Normal, Marked, Match }; virtual LineType lineType( int lineNumber ) const = 0; // Line number to display for line at the given index virtual qint64 displayLineNumber( int lineNumber ) const; virtual qint64 maxDisplayLineNumber() const; // Get the overview associated with this view, or NULL if there is none Overview* getOverview() const { return overview_; } // Set the Overview and OverviewWidget void setOverview( Overview* overview, OverviewWidget* overview_widget ); signals: // Sent when a new line has been selected by the user. void newSelection(int line); // Sent up to the MainWindow to enable/disable the follow mode void followModeChanged( bool enabled ); // Sent when the view wants the QuickFind widget pattern to change. void changeQuickFind( const QString& newPattern, QuickFindMux::QFDirection newDirection ); // Sent up when the current line number is updated void updateLineNumber( int line ); // Sent up when quickFind wants to show a message to the user. void notifyQuickFind( const QFNotification& message ); // Sent up when quickFind wants to clear the notification. void clearQuickFindNotification(); // Sent when the view ask for a line to be marked // (click in the left margin). void markLine( qint64 line ); // Sent up when the user wants to add the selection to the search void addToSearch( const QString& selection ); // Sent up when the mouse is hovered over a line's margin void mouseHoveredOverLine( qint64 line ); // Sent up when the mouse leaves a line's margin void mouseLeftHoveringZone(); // Sent up for view initiated quickfind searches void searchNext(); void searchPrevious(); // Sent up when the user has moved within the view void activity(); public slots: // Makes the widget select and display the passed line. // Scrolling as necessary void selectAndDisplayLine( int line ); // Use the current QFP to go and select the next match. virtual void searchForward(); // Use the current QFP to go and select the previous match. virtual void searchBackward(); // Use the current QFP to go and select the next match (incremental) virtual void incrementallySearchForward(); // Use the current QFP to go and select the previous match (incremental) virtual void incrementallySearchBackward(); // Stop the current incremental search (typically when user press return) virtual void incrementalSearchStop(); // Abort the current incremental search (typically when user press esc) virtual void incrementalSearchAbort(); // Signals the follow mode has been enabled. void followSet( bool checked ); // Signal the on/off status of the overview has been changed. void refreshOverview(); // Make the view jump to the specified line, regardless of weither it // is on the screen or not. // (does NOT emit followDisabled() ) void jumpToLine( int line ); // Configure the setting of whether to show line number margin void setLineNumbersVisible( bool lineNumbersVisible ); // Force the next refresh to fully redraw the view by invalidating the cache. // To be used if the data might have changed. void forceRefresh(); private slots: void handlePatternUpdated(); void addToSearch(); void findNextSelected(); void findPreviousSelected(); void copy(); private: // Graphic parameters static constexpr int OVERVIEW_WIDTH = 27; static constexpr int HOOK_THRESHOLD = 600; static constexpr int PULL_TO_FOLLOW_HOOKED_HEIGHT = 10; // Width of the bullet zone, including decoration int bulletZoneWidthPx_; // Total size of all margins and decorations in pixels int leftMarginPx_; // Digits buffer (for numeric keyboard entry) DigitsBuffer digitsBuffer_; // Follow mode bool followMode_; // ElasticHook for follow mode ElasticHook followElasticHook_; // Whether to show line numbers or not bool lineNumbersVisible_; // Pointer to the CrawlerWidget's data set const AbstractLogData* logData; // Pointer to the Overview object Overview* overview_; // Pointer to the OverviewWidget, this class doesn't own it, // but is responsible for displaying it (for purely aesthetic // reasons). OverviewWidget* overviewWidget_; bool selectionStarted_; // Start of the selection (characters) QPoint selectionStartPos_; // Current end of the selection (characters) QPoint selectionCurrentEndPos_; QBasicTimer autoScrollTimer_; // Hovering state // Last line that has been hoovered on, -1 if none qint64 lastHoveredLine_; // Marks (left margin click) bool markingClickInitiated_; qint64 markingClickLine_; Selection selection_; LineNumber firstLine; int firstCol; // Text handling int charWidth_; int charHeight_; // Popup menu QMenu* popupMenu_; QAction* copyAction_; QAction* findNextAction_; QAction* findPreviousAction_; QAction* addToSearchAction_; // Pointer to the CrawlerWidget's QFP object const QuickFindPattern* const quickFindPattern_; // Our own QuickFind object QuickFind quickFind_; #ifdef GLOGG_PERF_MEASURE_FPS // Performance measurement PerfCounter perfCounter_; #endif // Current "pull to follow" bar height int pullToFollowHeight_; // Cache pixmap and associated info struct TextAreaCache { QPixmap pixmap_; bool invalid_; int first_line_; int last_line_; int first_column_; }; struct PullToFollowCache { QPixmap pixmap_; int nb_columns_; }; TextAreaCache textAreaCache_ = { {}, true, 0, 0, 0 }; PullToFollowCache pullToFollowCache_ = { {}, 0 }; LineNumber getNbVisibleLines() const; int getNbVisibleCols() const; QPoint convertCoordToFilePos( const QPoint& pos ) const; int convertCoordToLine( int yPos ) const; int convertCoordToColumn( int xPos ) const; void displayLine( LineNumber line ); void moveSelection( int y ); void jumpToStartOfLine(); void jumpToEndOfLine(); void jumpToRightOfScreen(); void jumpToTop(); void jumpToBottom(); void selectWordAtPosition( const QPoint& pos ); void createMenu(); void considerMouseHovering( int x_pos, int y_pos ); // Search functions (for n/N) void searchUsingFunction( qint64 (QuickFind::*search_function)() ); void updateScrollBars(); void drawTextArea( QPaintDevice* paint_device, int32_t delta_y ); QPixmap drawPullToFollowBar( int width, float pixel_ratio ); void disableFollow(); // Utils functions bool isCharWord( char c ); void updateGlobalSelection(); }; #endif glogg-1.1.0/glogg.nsi0000664000175000017500000001261612657420553013433 0ustar nickonicko# NSIS script creating the Windows installer for glogg # Is passed to the script using -DVERSION=$(git describe) on the command line !ifndef VERSION !define VERSION 'anonymous-build' !endif # Headers !include "MUI2.nsh" !include "FileAssociation.nsh" # General OutFile "glogg-${VERSION}-setup.exe" XpStyle on SetCompressor /SOLID lzma ; Registry key to keep track of the directory we are installed in !ifdef ARCH32 InstallDir "$PROGRAMFILES\glogg" !else InstallDir "$PROGRAMFILES64\glogg" !endif InstallDirRegKey HKLM Software\glogg "" ; glogg icon ; !define MUI_ICON glogg.ico RequestExecutionLevel admin Name "glogg" Caption "glogg ${VERSION} Setup" # Pages !define MUI_WELCOMEPAGE_TITLE "Welcome to the glogg ${VERSION} Setup Wizard" !define MUI_WELCOMEPAGE_TEXT "This wizard will guide you through the installation of glogg\ , a fast, advanced log explorer.$\r$\n$\r$\n\ glogg and the Qt libraries are released under the GPL, see \ the COPYING file.$\r$\n$\r$\n$_CLICK" ; MUI_FINISHPAGE_LINK_LOCATION "http://nsis.sf.net/" !insertmacro MUI_PAGE_WELCOME ;!insertmacro MUI_PAGE_LICENSE "COPYING" # !ifdef VER_MAJOR & VER_MINOR & VER_REVISION & VER_BUILD... !insertmacro MUI_PAGE_COMPONENTS !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_FINISH !insertmacro MUI_UNPAGE_WELCOME !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES !insertmacro MUI_UNPAGE_FINISH # Languages !insertmacro MUI_LANGUAGE "English" # Installer sections Section "glogg" glogg ; Prevent this section from being unselected SectionIn RO SetOutPath $INSTDIR File release\glogg.exe File COPYING File README ; Create the 'sendto' link CreateShortCut "$SENDTO\glogg.lnk" "$INSTDIR\glogg,exe" "" "$INSTDIR\glogg.exe" 0 ; Register as an otion (but not main handler) for some files (.txt, .Log, .cap) WriteRegStr HKCR "Applications\glogg.exe" "" "" WriteRegStr HKCR "Applications\glogg.exe\shell" "" "open" WriteRegStr HKCR "Applications\glogg.exe\shell\open" "FriendlyAppName" "glogg" WriteRegStr HKCR "Applications\glogg.exe\shell\open\command" "" '"$INSTDIR\glogg.exe" "%1"' WriteRegStr HKCR "*\OpenWithList\glogg.exe" "" "" WriteRegStr HKCR ".txt\OpenWithList\glogg.exe" "" "" WriteRegStr HKCR ".Log\OpenWithList\glogg.exe" "" "" WriteRegStr HKCR ".cap\OpenWithList\glogg.exe" "" "" ; Register uninstaller WriteRegExpandStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\glogg"\ "UninstallString" '"$INSTDIR\Uninstall.exe"' WriteRegExpandStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\glogg"\ "InstallLocation" "$INSTDIR" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\glogg" "DisplayName" "glogg" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\glogg" "DisplayVersion" "${VERSION}" WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\glogg" "NoModify" "1" WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\glogg" "NoRepair" "1" ; Create uninstaller WriteUninstaller "$INSTDIR\Uninstall.exe" SectionEnd Section "Qt4 Runtime libraries" qtlibs SetOutPath $INSTDIR File release\QtCore4.dll File release\QtGui4.dll File release\QtNetwork4.dll File release\libwinpthread-1.dll SectionEnd Section "Create Start menu shortcut" shortcut SetShellVarContext all CreateShortCut "$SMPROGRAMS\glogg.lnk" "$INSTDIR\glogg.exe" "" "$INSTDIR\glogg.exe" 0 SectionEnd Section /o "Associate with .log files" associate ${registerExtension} "$INSTDIR\glogg.exe" ".log" "Log file" SectionEnd # Descriptions !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN !insertmacro MUI_DESCRIPTION_TEXT ${glogg} "The core files required to use glogg." !insertmacro MUI_DESCRIPTION_TEXT ${qtlibs} "Needed by glogg, you have to install these unless \ you already have the Qt4 development kit installed." !insertmacro MUI_DESCRIPTION_TEXT ${shortcut} "Create a shortcut in the Start menu for glogg." !insertmacro MUI_DESCRIPTION_TEXT ${associate} "Make glogg the default viewer for .log files." !insertmacro MUI_FUNCTION_DESCRIPTION_END # Uninstaller Section "Uninstall" Delete "$INSTDIR\Uninstall.exe" Delete "$INSTDIR\glogg.exe" Delete "$INSTDIR\README" Delete "$INSTDIR\COPYING" Delete "$INSTDIR\mingwm10.dll" Delete "$INSTDIR\libgcc_s_dw2-1.dll" Delete "$INSTDIR\QtCore4.dll" Delete "$INSTDIR\QtGui4.dll" Delete "$INSTDIR\QtNetwork4.dll" Delete "$INSTDIR\libwinpthread-1.dll" RMDir "$INSTDIR" ; Remove settings in %appdata% Delete "$APPDATA\glogg\glogg.ini" RMDir "$APPDATA\glogg" DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\glogg" ; Remove settings in the registry (from glogg < 0.9) DeleteRegKey HKCU "Software\glogg" ; Remove the file associations ${unregisterExtension} ".log" "Log file" DeleteRegKey HKCR "*\OpenWithList\glogg.exe" DeleteRegKey HKCR ".txt\OpenWithList\glogg.exe" DeleteRegKey HKCR ".Log\OpenWithList\glogg.exe" DeleteRegKey HKCR ".cap\OpenWithList\glogg.exe" DeleteRegKey HKCR "Applications\glogg.exe\shell\open\command" DeleteRegKey HKCR "Applications\glogg.exe\shell\open" DeleteRegKey HKCR "Applications\glogg.exe\shell" DeleteRegKey HKCR "Applications\glogg.exe" ; Remove the shortcut, if any SetShellVarContext all Delete "$SMPROGRAMS\glogg.lnk" SectionEnd glogg-1.1.0/release-win64-x.sh0000775000175000017500000000212112657420553015000 0ustar nickonicko#!/bin/bash # Build glogg for win32 using the cross-compiler QTXDIR=$HOME/qt-x-win32 QTVERSION=4.8.6-64 BOOSTDIR=$QTXDIR/boost_1_50_0 make clean if [ "$1" == "debug" ]; then echo "Building a debug version" qmake-qt4 glogg.pro -spec win64-x-g++ -r CONFIG+="debug win32 rtti no-dbus version_checker" BOOST_PATH=$BOOSTDIR elif [ -z "$VERSION" ]; then echo "Building default version" qmake-qt4 glogg.pro -spec win64-x-g++ -r CONFIG+="release win32 rtti no-dbus version_checker" BOOST_PATH=$BOOSTDIR QMAKE_CXXFLAGS="-m64" else echo "Building version $VERSION-x86_64" qmake-qt4 glogg.pro -spec win64-x-g++ -r CONFIG+="release win32 rtti no-dbus version_checker" BOOST_PATH=$BOOSTDIR VERSION="$VERSION-x86_64" fi make -j3 cp $QTXDIR/$QTVERSION/lib/{QtCore4,QtGui4,QtNetwork4}.dll release/ cp $QTXDIR/$QTVERSION/lib/{QtCored4,QtGuid4,QtNetworkd4}.dll debug/ cp /usr/x86_64-w64-mingw32/lib/libwinpthread-1.dll release/ if [ -z "$VERSION" ]; then VERSION=`git describe`; fi echo Generating installer for glogg-$VERSION wine $QTXDIR/NSIS/makensis -DVERSION="$VERSION-x86_64" glogg.nsi glogg-1.1.0/glogg.rc0000664000175000017500000000006212657420553013236 0ustar nickonickoIDI_ICON1 ICON DISCARDABLE "glogg48.ico" glogg-1.1.0/TODO0000664000175000017500000000102212657420553012276 0ustar nickonickoNew features: - Line number in filtered view - Matches overview (right bar) - Bookmarks - Multi files - Improved savable search text - Save position with latest files (not just the last one) - Non modal filter dialog - On/off search item window - Drag 'n drop in filters Fixes: - Error in regexp - Yellow background for errors/file changed - Keep colours (partially) of selected text - Greater number of latest files - Long file name makes the status bar extend Perf: - Search slows down when there is a large number of matches glogg-1.1.0/release-win32.sh0000775000175000017500000000133012657420553014527 0ustar nickonicko#!/bin/bash BOOSTDIR=$HOME/boost_1_43_0 PATH=/cygdrive/c/qt/2010.02.1/qt/bin:/cygdrive/c/qt/2010.02.1/mingw/bin/:$PATH if [ -z "$VERSION" ]; then echo "Building default version" qmake glogg.pro -spec win32-g++ -r CONFIG+=release BOOST_PATH=$BOOSTDIR else echo "Building version $VERSION" qmake glogg.pro -spec win32-g++ -r CONFIG+=release BOOST_PATH=$BOOSTDIR VERSION="$VERSION" fi mingw32-make cp /cygdrive/c/qt/2010.02.1/qt/bin/{QtCore4,QtGui4}.dll release/ cp /cygdrive/c/qt/2010.02.1/mingw/bin/{mingwm10,libgcc_s_dw2-1}.dll release/ if [ -z "$VERSION" ]; then VERSION=`git describe`; fi echo Generating installer for glogg-$VERSION /cygdrive/c/Program\ Files/NSIS/makensis -DVERSION=$VERSION glogg.nsi glogg-1.1.0/COPYING0000664000175000017500000010451312657420553012652 0ustar nickonicko GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . glogg-1.1.0/tests/0000775000175000017500000000000012657420553012755 5ustar nickonickoglogg-1.1.0/tests/linepositionarrayTest.cpp0000664000175000017500000001161512657420553020100 0ustar nickonicko#include "gmock/gmock.h" #include "config.h" #include "log.h" #include "data/linepositionarray.h" using namespace std; using namespace testing; class LinePositionArraySmall: public testing::Test { public: LinePositionArray line_array; LinePositionArraySmall() { line_array.append( 4 ); line_array.append( 8 ); line_array.append( 10 ); // A longer (>128) line line_array.append( 345 ); // An even longer (>16384) line line_array.append( 20000 ); // And a short one again line_array.append( 20020 ); } }; TEST_F( LinePositionArraySmall, HasACorrectSize ) { ASSERT_THAT( line_array.size(), Eq( 6 ) ); } TEST_F( LinePositionArraySmall, RememberAddedLines ) { ASSERT_THAT( line_array[0], Eq( 4 ) ); ASSERT_THAT( line_array[1], Eq( 8 ) ); ASSERT_THAT( line_array[2], Eq( 10 ) ); ASSERT_THAT( line_array[3], Eq( 345 ) ); ASSERT_THAT( line_array[4], Eq( 20000 ) ); ASSERT_THAT( line_array[5], Eq( 20020 ) ); // This one again to eliminate caching effects ASSERT_THAT( line_array[3], Eq( 345 ) ); } TEST_F( LinePositionArraySmall, FakeLFisNotKeptWhenAddingAfterIt ) { line_array.setFakeFinalLF(); ASSERT_THAT( line_array[5], Eq( 20020 ) ); line_array.append( 20030 ); ASSERT_THAT( line_array[5], Eq( 20030 ) ); } class LinePositionArrayConcatOperation: public LinePositionArraySmall { public: FastLinePositionArray other_array; LinePositionArrayConcatOperation() { other_array.append( 150000 ); other_array.append( 150023 ); } }; TEST_F( LinePositionArrayConcatOperation, SimpleConcat ) { line_array.append_list( other_array ); ASSERT_THAT( line_array.size(), Eq( 8 ) ); ASSERT_THAT( line_array[0], Eq( 4 ) ); ASSERT_THAT( line_array[1], Eq( 8 ) ); ASSERT_THAT( line_array[5], Eq( 20020 ) ); ASSERT_THAT( line_array[6], Eq( 150000 ) ); ASSERT_THAT( line_array[7], Eq( 150023 ) ); } TEST_F( LinePositionArrayConcatOperation, DoesNotKeepFakeLf ) { line_array.setFakeFinalLF(); ASSERT_THAT( line_array[5], Eq( 20020 ) ); line_array.append_list( other_array ); ASSERT_THAT( line_array[5], Eq( 150000 ) ); ASSERT_THAT( line_array.size(), Eq( 7 ) ); } class LinePositionArrayLong: public testing::Test { public: LinePositionArray line_array; LinePositionArrayLong() { // Add 126 lines (of various sizes for ( int i = 0; i < 127; i++ ) line_array.append( i * 4 ); // Line no 127 line_array.append( 514 ); } }; TEST_F( LinePositionArrayLong, LineNo128HasRightValue ) { line_array.append( 524 ); ASSERT_THAT( line_array[128], Eq( 524 ) ); } TEST_F( LinePositionArrayLong, FakeLFisNotKeptWhenAddingAfterIt ) { line_array.append( 524 ); line_array.setFakeFinalLF(); ASSERT_THAT( line_array[128], Eq( 524 ) ); line_array.append( 600 ); ASSERT_THAT( line_array[128], Eq( 600 ) ); } class LinePositionArrayBig: public testing::Test { public: LinePositionArray line_array; LinePositionArrayBig() { line_array.append( 4 ); line_array.append( 8 ); // A very big line line_array.append( UINT32_MAX - 10 ); line_array.append( (uint64_t) UINT32_MAX + 10LL ); line_array.append( (uint64_t) UINT32_MAX + 30LL ); line_array.append( (uint64_t) 2*UINT32_MAX ); line_array.append( (uint64_t) 2*UINT32_MAX + 10LL ); line_array.append( (uint64_t) 2*UINT32_MAX + 1000LL ); line_array.append( (uint64_t) 3*UINT32_MAX ); } }; TEST_F( LinePositionArrayBig, IsTheRightSize ) { ASSERT_THAT( line_array.size(), 9 ); } TEST_F( LinePositionArrayBig, HasRightData ) { ASSERT_THAT( line_array[0], Eq( 4 ) ); ASSERT_THAT( line_array[1], Eq( 8 ) ); ASSERT_THAT( line_array[2], Eq( UINT32_MAX - 10 ) ); ASSERT_THAT( line_array[3], Eq( (uint64_t) UINT32_MAX + 10LL ) ); ASSERT_THAT( line_array[4], Eq( (uint64_t) UINT32_MAX + 30LL ) ); ASSERT_THAT( line_array[5], Eq( (uint64_t) 2*UINT32_MAX ) ); ASSERT_THAT( line_array[6], Eq( (uint64_t) 2*UINT32_MAX + 10LL ) ); ASSERT_THAT( line_array[7], Eq( (uint64_t) 2*UINT32_MAX + 1000LL ) ); ASSERT_THAT( line_array[8], Eq( (uint64_t) 3*UINT32_MAX ) ); } class LinePositionArrayBigConcat: public testing::Test { public: LinePositionArray line_array; FastLinePositionArray other_array; LinePositionArrayBigConcat() { line_array.append( 4 ); line_array.append( 8 ); other_array.append( UINT32_MAX + 10 ); other_array.append( UINT32_MAX + 30 ); } }; TEST_F( LinePositionArrayBigConcat, SimpleBigConcat ) { line_array.append_list( other_array ); ASSERT_THAT( line_array.size(), Eq( 4 ) ); ASSERT_THAT( line_array[0], Eq( 4 ) ); ASSERT_THAT( line_array[1], Eq( 8 ) ); ASSERT_THAT( line_array[2], Eq( UINT32_MAX + 10 ) ); ASSERT_THAT( line_array[3], Eq( UINT32_MAX + 30 ) ); } glogg-1.1.0/tests/coverage.sh0000775000175000017500000000054212657420553015110 0ustar nickonicko#!/bin/bash FILES="logdata.cpp logfiltereddata.cpp logdataworkerthread.cpp logfiltereddataworkerthread.cpp" for i in $FILES; do gcov -b $i | perl -e "while(<>) { if (/^File '.*$i'/) { \$print = 1; } if ( \$print ) { if (/:creating '/) { \$print = 0; print \"\n\" } else { print; } } }" done mkdir coverage mv *.gcov coverage/ rm *.{gcda,gcno} glogg-1.1.0/tests/testlogdata.h0000664000175000017500000000076012657420553015444 0ustar nickonicko#include #include #include class TestLogData: public QObject { Q_OBJECT private slots: void initTestCase(); void simpleLoad(); void multipleLoad(); void changingFile(); void sequentialRead(); void sequentialReadExpanded(); void randomPageRead(); void randomPageReadExpanded(); public slots: void loadingFinished(); private: bool generateDataFiles(); }; glogg-1.1.0/tests/main.cpp0000664000175000017500000000047612657420553014414 0ustar nickonicko#include #include "testlogdata.h" #include "testlogfiltereddata.h" int main(int argc, char** argv) { QApplication app(argc, argv); int retval(0); retval += QTest::qExec(&TestLogData(), argc, argv); retval += QTest::qExec(&TestLogFilteredData(), argc, argv); return (retval ? 1 : 0); } glogg-1.1.0/tests/itests.cpp0000664000175000017500000000040412657420553014772 0ustar nickonicko#include "gmock/gmock.h" #include int main(int argc, char *argv[]) { QApplication a( argc, argv ); ::testing::InitGoogleTest(&argc, argv); int iReturn = RUN_ALL_TESTS(); // qDebug()<<"rcode:"< #include "winfilewatcher.h" using namespace std; class FileWatcherBehaviour: public testing::Test { public: shared_ptr file_watcher; void SetUp() override { #ifdef _WIN32 // file_watcher = make_shared(); #endif } }; TEST_F( FileWatcherBehaviour, DetectsAnAppendedFile ) { EXPECT_EQ(1, 1); } glogg-1.1.0/tests/tests.pro0000664000175000017500000000201012657420553014632 0ustar nickonickoTEMPLATE = app DEPENDPATH += ./ ../ INCLUDEPATH += ./ ../ DESTDIR = ./ CONFIG += qtestlib debug QMAKE_CXXFLAGS += -O2 -fpermissive isEmpty(TMPDIR) { DEFINES = TMPDIR=\\\"/tmp\\\" } else { DEFINES = TMPDIR=\\\"$${TMPDIR}\\\" } mac { CONFIG -= app_bundle } TARGET = logcrawler_tests HEADERS += testlogdata.h testlogfiltereddata.h\ ../src/data/logdata.h ../src/data/logfiltereddata.h ../src/data/logdataworkerthread.h\ ../src/data/abstractlogdata.h ../src/data/logfiltereddataworkerthread.h\ ../src/platformfilewatcher.h ../src/marks.h SOURCES += testlogdata.cpp testlogfiltereddata.cpp \ ../src/data/abstractlogdata.cpp ../src/data/logdata.cpp ../src/main.cpp\ ../src/data/logfiltereddata.cpp ../src/data/logdataworkerthread.cpp\ ../src/data/logfiltereddataworkerthread.cpp\ ../src/filewatcher.cpp ../src/marks.cpp coverage:QMAKE_CXXFLAGS += -g -fprofile-arcs -ftest-coverage -O0 coverage:QMAKE_LFLAGS += -fprofile-arcs -ftest-coverage prof:QMAKE_CXXFLAGS += -pg prof:QMAKE_LFLAGS += -pg glogg-1.1.0/tests/test_utils.h0000664000175000017500000000255412657420553015333 0ustar nickonicko#ifndef TEST_UTILS_H #define TEST_UTILS_H #include "gmock/gmock.h" #include #include struct TestTimer { TestTimer() : TestTimer( ::testing::UnitTest::GetInstance()->current_test_info()->test_case_name() ) { text_ += std::string {"."} + std::string {::testing::UnitTest::GetInstance()->current_test_info()->name() }; } TestTimer(const std::string& text) : Start { std::chrono::system_clock::now() } , text_ {text} {} virtual ~TestTimer() { using namespace std; Stop = chrono::system_clock::now(); Elapsed = chrono::duration_cast(Stop - Start); cout << endl << text_ << " elapsed time = " << Elapsed.count() * 0.001 << "ms" << endl; } std::chrono::time_point Start; std::chrono::time_point Stop; std::chrono::microseconds Elapsed; std::string text_; }; class SafeQSignalSpy : public QSignalSpy { public: template SafeQSignalSpy( Args&&... args ) : QSignalSpy( std::forward(args)... ) {} bool safeWait( int timeout = 10000 ) { // If it has already been received bool result = count() > 0; if ( ! result ) { result = wait( timeout ); } return result; } }; #endif glogg-1.1.0/tests/CMakeLists.txt0000664000175000017500000001057712657420553015527 0ustar nickonickoINCLUDE (CheckSymbolExists) INCLUDE (CheckCXXSourceCompiles) INCLUDE (CheckCXXCompilerFlag) cmake_minimum_required(VERSION 2.8.12) project(glogg_tests) # Qt find_program(QT_QMAKE_EXECUTABLE NAMES qmake PATHS $ENV{QT_DIR}/bin) find_package(Qt5Widgets REQUIRED) find_package(Qt5Test REQUIRED) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) check_cxx_compiler_flag(-Wall HAS_WALL) if(HAS_WALL) set(PARANOID_FLAGS "${PARANOID_FLAGS} -Wall") endif(HAS_WALL) # Compiler check_cxx_compiler_flag("-std=c++11" COMPILER_SUPPORTS_CXX11) check_cxx_compiler_flag("-std=c++0x" COMPILER_SUPPORTS_CXX0X) if(COMPILER_SUPPORTS_CXX11) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") elseif(COMPILER_SUPPORTS_CXX0X) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") else() message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support.") endif() set(CMAKE_REQUIRED_FLAGS "-std=c++11") check_cxx_source_compiles(" #include int main(int, int) { auto test = std::make_unique(); return 0; } " HAVE_MAKE_UNIQUE) check_cxx_source_compiles(" #include struct Base { virtual void func() {} }; struct Derived : public Base { void func() override {} } int main(int, int) { return 0; } " HAVE_OVERRIDE) configure_file(../config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) # Configuration check_symbol_exists( symlink "unistd.h" HAVE_SYMLINK ) check_symbol_exists( htons "arpa/inet.h" HAVE_HTONS ) # find_library(WS2_LIBRARY NAMES ws2_32) # Libs set(LIBS gmock gtest) # Setup testing enable_testing() include_directories( $ENV{GMOCK_HOME}/include $ENV{GMOCK_HOME}/gtest/include $ENV{BOOST_ROOT}/ ../src/ ) link_directories($ENV{GMOCK_HOME}/mybuild $ENV{GMOCK_HOME}/mybuild/gtest) # Sources set(glogg_SOURCES ../src/session.cpp ../src/data/abstractlogdata.cpp ../src/data/logdata.cpp ../src/data/logfiltereddata.cpp ../src/data/logfiltereddataworkerthread.cpp ../src/data/logdataworkerthread.cpp ../src/data/compressedlinestorage.cpp ../src/mainwindow.cpp ../src/crawlerwidget.cpp ../src/abstractlogview.cpp ../src/logmainview.cpp ../src/filteredview.cpp ../src/optionsdialog.cpp ../src/persistentinfo.cpp ../src/configuration.cpp ../src/filtersdialog.cpp ../src/filterset.cpp ../src/savedsearches.cpp ../src/infoline.cpp ../src/menuactiontooltipbehavior.cpp ../src/selection.cpp ../src/quickfind.cpp ../src/quickfindpattern.cpp ../src/quickfindwidget.cpp ../src/sessioninfo.cpp ../src/recentfiles.cpp ../src/overview.cpp ../src/overviewwidget.cpp ../src/marks.cpp ../src/quickfindmux.cpp ../src/signalmux.cpp ../src/qtfilewatcher.cpp ../src/tabbedcrawlerwidget.cpp ../src/filewatcher.cpp ../src/watchtowerlist.cpp ../src/watchtower.cpp ../src/viewtools.cpp ../src/encodingspeculator.cpp # ../src/platformfilewatcher.cpp ) set(glogg_HEADERS ../src/mainwindow.h ../src/marks.h ../src/filewatcher.h) # Unit tests set(glogg_UTESTS # watchtowerTest.cpp linepositionarrayTest.cpp encodingspeculatorTest.cpp ) # Integration tests set(glogg_ITESTS logdataTest.cpp logfiltereddataTest.cpp ) # Performance tests set(glogg_PTESTS logdataPerfTest.cpp logfiltereddataPerfTest.cpp ) # Options if (WIN32) set(FileWatcherEngine_SOURCES ../src/winwatchtowerdriver.cpp ) elseif (LINUX) # set(FileWatcherEngine_SOURCES ../src/inotifywatchtowerdriver.cpp) endif (WIN32) # Compiler flags set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${PARANOID_FLAGS} -fPIC -DGLOGG_VERSION=\\\"unit_tests\\\" -g -gdwarf-2") # Add test cpp file add_executable(glogg_tests ${glogg_SOURCES} ${FileWatcherEngine_SOURCES} ${glogg_UTESTS} ) # Link test executable against gtest & gtest_main target_link_libraries(glogg_tests ${LIBS} gtest_main pthread Qt5::Widgets) add_executable(glogg_itests ${glogg_SOURCES} ${FileWatcherEngine_SOURCES} ${glogg_ITESTS} itests.cpp ) target_link_libraries(glogg_itests ${LIBS} pthread Qt5::Widgets Qt5::Test) add_executable(glogg_ptests ${glogg_SOURCES} ${FileWatcherEngine_SOURCES} ${glogg_PTESTS} itests.cpp ) target_link_libraries(glogg_ptests ${LIBS} pthread Qt5::Widgets Qt5::Test) add_test( NAME glogg_tests COMMAND glogg_tests ) glogg-1.1.0/tests/watchtowerTest.cpp0000664000175000017500000003610312657420553016513 0ustar nickonicko#include "gmock/gmock.h" #include "config.h" #include #include #include #include #include #include #include #include "log.h" #include "watchtower.h" #ifdef _WIN32 # include "winwatchtowerdriver.h" using PlatformWatchTower = WatchTower; #else # include "inotifywatchtowerdriver.h" using PlatformWatchTower = WatchTower; #endif using namespace std; using namespace testing; class WatchTowerBehaviour: public testing::Test { public: shared_ptr watch_tower = make_shared(); const char* createTempName() { const char* name; #if _WIN32 name = _tempnam( "c:\\temp", "glogg_test" ); #else name = tmpnam( nullptr ); #endif return name; } string createTempEmptyFile( string file_name = "" ) { const char* name; if ( ! file_name.empty() ) { name = file_name.c_str(); } else { // I know tmpnam is bad but I need control over the file // and it is the only one which exits on Windows. name = createTempName(); } int fd = creat( name, S_IRUSR | S_IWUSR ); close( fd ); return string( name ); } string getNonExistingFileName() { #if _WIN32 return string( _tempnam( "c:\\temp", "inexistant" ) ); #else return string( tmpnam( nullptr ) ); #endif } WatchTowerBehaviour() { // Default to quiet, but increase to debug FILELog::setReportingLevel( logERROR ); } }; TEST_F( WatchTowerBehaviour, AcceptsAnExistingFileToWatch ) { auto file_name = createTempEmptyFile(); auto registration = watch_tower->addFile( file_name, [] (void) { } ); } TEST_F( WatchTowerBehaviour, AcceptsANonExistingFileToWatch ) { auto registration = watch_tower->addFile( getNonExistingFileName(), [] (void) { } ); } /*****/ class WatchTowerSingleFile: public WatchTowerBehaviour { public: static const int TIMEOUT; mutex mutex_; condition_variable cv_; string file_name; Registration registration; int notification_received = 0; Registration registerFile( const string& filename ) { weak_ptr weakHeartbeat( heartbeat_ ); auto reg = watch_tower->addFile( filename, [this, weakHeartbeat] (void) { // Ensure the fixture object is still alive using the heartbeat if ( auto keep = weakHeartbeat.lock() ) { unique_lock lock(mutex_); ++notification_received; cv_.notify_one(); } } ); return reg; } WatchTowerSingleFile() : heartbeat_( shared_ptr( (void*) 0xDEADC0DE, [] (void*) {} ) ) { file_name = createTempEmptyFile(); registration = registerFile( file_name ); } ~WatchTowerSingleFile() { remove( file_name.c_str() ); } bool waitNotificationReceived( int number = 1, int timeout_ms = TIMEOUT ) { unique_lock lock(mutex_); bool result = ( cv_.wait_for( lock, std::chrono::milliseconds(timeout_ms), [this, number] { return notification_received >= number; } ) ); // Reinit the notification notification_received = 0; return result; } void appendDataToFile( const string& file_name ) { static const char* string = "Test line\n"; int fd = open( file_name.c_str(), O_WRONLY | O_APPEND ); write( fd, (void*) string, strlen( string ) ); close( fd ); } private: // Heartbeat ensures the object is still alive shared_ptr heartbeat_; }; #ifdef _WIN32 const int WatchTowerSingleFile::TIMEOUT = 2000; #else const int WatchTowerSingleFile::TIMEOUT = 20; #endif TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsAppended ) { appendDataToFile( file_name ); ASSERT_TRUE( waitNotificationReceived() ); } TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsRemoved) { remove( file_name.c_str() ); ASSERT_TRUE( waitNotificationReceived() ); } TEST_F( WatchTowerSingleFile, SignalsWhenADeletedFileReappears ) { remove( file_name.c_str() ); waitNotificationReceived(); createTempEmptyFile( file_name ); ASSERT_TRUE( waitNotificationReceived() ); } TEST_F( WatchTowerSingleFile, SignalsWhenAReappearedFileIsAppended ) { remove( file_name.c_str() ); waitNotificationReceived(); createTempEmptyFile( file_name ); waitNotificationReceived(); appendDataToFile( file_name ); ASSERT_TRUE( waitNotificationReceived() ); } TEST_F( WatchTowerSingleFile, StopSignalingWhenWatchDeleted ) { auto second_file_name = createTempEmptyFile(); std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); // Ensure file creation has been 'digested' { auto second_registration = registerFile( second_file_name ); appendDataToFile( second_file_name ); ASSERT_TRUE( waitNotificationReceived() ); } // The registration will be removed here. appendDataToFile( second_file_name ); ASSERT_FALSE( waitNotificationReceived() ); remove( second_file_name.c_str() ); } TEST_F( WatchTowerSingleFile, SignalsWhenSameFileIsFollowedMultipleTimes ) { auto second_file_name = createTempEmptyFile(); for ( int i = 0; i < 100; i++ ) { auto second_registration = registerFile( second_file_name ); appendDataToFile( second_file_name ); ASSERT_TRUE( waitNotificationReceived() ); } // The registration will be removed here. appendDataToFile( second_file_name ); ASSERT_FALSE( waitNotificationReceived() ); remove( second_file_name.c_str() ); } TEST_F( WatchTowerSingleFile, TwoWatchesOnSameFileYieldsTwoNotifications ) { auto second_registration = registerFile( file_name ); appendDataToFile( file_name ); ASSERT_TRUE( waitNotificationReceived( 2 ) ); } TEST_F( WatchTowerSingleFile, RemovingOneWatchOfTwoStillYieldsOneNotification ) { { auto second_registration = registerFile( file_name ); } appendDataToFile( file_name ); ASSERT_TRUE( waitNotificationReceived( 1 ) ); } TEST_F( WatchTowerSingleFile, RenamingTheFileYieldsANotification ) { auto new_file_name = createTempName(); rename( file_name.c_str(), new_file_name ); ASSERT_TRUE( waitNotificationReceived() ); rename( new_file_name, file_name.c_str() ); } TEST_F( WatchTowerSingleFile, RenamingAFileToTheWatchedNameYieldsANotification ) { remove( file_name.c_str() ); waitNotificationReceived(); std::string new_file_name = createTempEmptyFile(); appendDataToFile( new_file_name ); rename( new_file_name.c_str(), file_name.c_str() ); ASSERT_TRUE( waitNotificationReceived() ); } /*****/ #ifdef HAVE_SYMLINK class WatchTowerSymlink: public WatchTowerSingleFile { public: string symlink_name; void SetUp() override { file_name = createTempEmptyFile(); symlink_name = createTempEmptyFile(); remove( symlink_name.c_str() ); symlink( file_name.c_str(), symlink_name.c_str() ); registration = registerFile( symlink_name ); } void TearDown() override { remove( symlink_name.c_str() ); remove( file_name.c_str() ); } }; TEST_F( WatchTowerSymlink, AppendingToTheSymlinkYieldsANotification ) { appendDataToFile( symlink_name ); ASSERT_TRUE( waitNotificationReceived() ); } TEST_F( WatchTowerSymlink, AppendingToTheTargetYieldsANotification ) { appendDataToFile( file_name ); ASSERT_TRUE( waitNotificationReceived() ); } TEST_F( WatchTowerSymlink, RemovingTheSymlinkYieldsANotification ) { remove( symlink_name.c_str() ); ASSERT_TRUE( waitNotificationReceived() ); } TEST_F( WatchTowerSymlink, RemovingTheTargetYieldsANotification ) { remove( file_name.c_str() ); ASSERT_TRUE( waitNotificationReceived() ); } TEST_F( WatchTowerSymlink, ReappearingSymlinkYieldsANotification ) { auto new_target = createTempEmptyFile(); remove( symlink_name.c_str() ); waitNotificationReceived(); symlink( new_target.c_str(), symlink_name.c_str() ); ASSERT_TRUE( waitNotificationReceived() ); remove( new_target.c_str() ); } TEST_F( WatchTowerSymlink, DataAddedInAReappearingSymlinkYieldsANotification ) { auto new_target = createTempEmptyFile(); remove( symlink_name.c_str() ); waitNotificationReceived(); symlink( new_target.c_str(), symlink_name.c_str() ); waitNotificationReceived(); appendDataToFile( new_target ); ASSERT_TRUE( waitNotificationReceived() ); remove( new_target.c_str() ); } #endif //HAVE_SYMLINK /*****/ TEST( WatchTowerLifetime, RegistrationCanBeDeletedWhenWeAreDead ) { auto mortal_watch_tower = new PlatformWatchTower(); auto reg = mortal_watch_tower->addFile( "/tmp/test_file", [] (void) { } ); delete mortal_watch_tower; // reg will be destroyed after the watch_tower } /*****/ class WatchTowerDirectories: public WatchTowerSingleFile { public: string second_dir_name; string second_file_name; string third_file_name; Registration registration_two; Registration registration_three; WatchTowerDirectories() { second_dir_name = createTempDir(); second_file_name = createTempEmptyFileInDir( second_dir_name ); third_file_name = createTempEmptyFileInDir( second_dir_name ); } ~WatchTowerDirectories() { remove( third_file_name.c_str() ); remove( second_file_name.c_str() ); removeDir( second_dir_name ); } string createTempDir() { #ifdef _WIN32 static int counter = 1; char temp_dir[255]; GetTempPath( sizeof temp_dir, temp_dir ); string dir_name = string { temp_dir } + string { "\\test" } + to_string( counter++ ); mkdir( dir_name.c_str() ); return dir_name; #else char dir_template[] = "/tmp/XXXXXX"; return { mkdtemp( dir_template ) }; #endif } string createTempEmptyFileInDir( const string& dir ) { static int counter = 1; return createTempEmptyFile( dir + std::string { "/temp" } + to_string( counter++ ) ); } void removeDir( const string& name ) { rmdir( name.c_str() ); } }; TEST_F( WatchTowerDirectories, FollowThreeFilesInTwoDirs ) { registration_two = registerFile( second_file_name ); registration_three = registerFile( third_file_name ); ASSERT_THAT( watch_tower->numberWatchedDirectories(), Eq( 2 ) ); } TEST_F( WatchTowerDirectories, FollowTwoFilesInTwoDirs ) { registration_two = registerFile( second_file_name ); { auto temp_registration_three = registerFile( third_file_name ); } ASSERT_THAT( watch_tower->numberWatchedDirectories(), Eq( 2 ) ); } TEST_F( WatchTowerDirectories, FollowOneFileInOneDir ) { { auto temp_registration_two = registerFile( second_file_name ); auto temp_registration_three = registerFile( third_file_name ); ASSERT_THAT( watch_tower->numberWatchedDirectories(), Eq( 2 ) ); } ASSERT_THAT( watch_tower->numberWatchedDirectories(), Eq( 1 ) ); } /*****/ class WatchTowerInexistantDirectory: public WatchTowerDirectories { public: WatchTowerInexistantDirectory() { test_dir = createTempDir(); rmdir( test_dir.c_str() ); } ~WatchTowerInexistantDirectory() { rmdir( test_dir.c_str() ); } string test_dir; }; TEST_F( WatchTowerInexistantDirectory, LaterCreatedDirIsFollowed ) { /* Dir (and file) don't exist */ auto file_name = createTempEmptyFileInDir( test_dir ); { auto registration = registerFile( file_name ); #ifdef _WIN32 mkdir( test_dir.c_str() ); #else mkdir( test_dir.c_str(), 0777 ); #endif createTempEmptyFile( file_name ); } auto registration2 = registerFile( file_name ); appendDataToFile( file_name ); ASSERT_TRUE( waitNotificationReceived() ); remove( file_name.c_str() ); } /*****/ #ifdef _WIN32 class WinNotificationInfoListTest : public testing::Test { public: using Action = WinNotificationInfo::Action; struct Buffer { uint32_t next_addr; uint32_t action; uint32_t filename_length; wchar_t filename[13]; }; static struct Buffer buffer[2]; WinNotificationInfoList list { reinterpret_cast( buffer ), sizeof( buffer ) }; WinNotificationInfoList::iterator iterator { std::begin( list ) }; }; struct WinNotificationInfoListTest::Buffer WinNotificationInfoListTest::buffer[] = { { 40, 1, 26, L"Filename.txt" }, { 0, 3, 18, L"file2.txt" } }; TEST_F( WinNotificationInfoListTest, FirstNotificationCanBeObtained ) { auto notification = *iterator; ASSERT_THAT( ¬ification, NotNull() ); } TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightAction ) { ASSERT_THAT( (*iterator).action(), Eq( Action::ADDED ) ); } TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightFileName ) { ASSERT_THAT( (*iterator).fileName(), Eq( std::wstring( L"Filename.txt\0", 13 ) ) ); } TEST_F( WinNotificationInfoListTest, SecondNotificationCanBeObtained ) { ++iterator; auto notification = *iterator; ASSERT_THAT( ¬ification, NotNull() ); } TEST_F( WinNotificationInfoListTest, SecondNotificationIsCorrect ) { iterator++; ASSERT_THAT( iterator->action(), Eq( Action::MODIFIED ) ); ASSERT_THAT( iterator->fileName(), Eq( L"file2.txt" ) ); } TEST_F( WinNotificationInfoListTest, CanBeIteratedByFor ) { for ( auto notification : list ) { notification.action(); } } #endif /*****/ #ifdef _WIN32 class WatchTowerPolling : public WatchTowerSingleFile { public: WatchTowerPolling() : WatchTowerSingleFile() { // FILELog::setReportingLevel( logDEBUG ); fd_ = open( file_name.c_str(), O_WRONLY | O_APPEND ); } ~WatchTowerPolling() { close( fd_ ); } void appendDataToFileWoClosing() { static const char* string = "Test line\n"; write( fd_, (void*) string, strlen( string ) ); } int fd_; }; TEST_F( WatchTowerPolling, OpenFileDoesNotGenerateImmediateNotification ) { appendDataToFileWoClosing(); ASSERT_FALSE( waitNotificationReceived() ); } TEST_F( WatchTowerPolling, OpenFileYieldsAPollNotification ) { std::this_thread::sleep_for( std::chrono::milliseconds( 500 ) ); watch_tower->setPollingInterval( 500 ); appendDataToFileWoClosing(); ASSERT_TRUE( waitNotificationReceived() ); } TEST_F( WatchTowerPolling, UnchangedFileDoesNotYieldANotification ) { watch_tower->setPollingInterval( 500 ); ASSERT_FALSE( waitNotificationReceived() ); } TEST_F( WatchTowerPolling, FileYieldsAnImmediateNotification ) { watch_tower->setPollingInterval( 4000 ); appendDataToFile( file_name ); ASSERT_TRUE( waitNotificationReceived( 1, 2000 ) ); } TEST_F( WatchTowerPolling, PollIsDelayedIfImmediateNotification ) { watch_tower->setPollingInterval( 500 ); appendDataToFile( file_name ); waitNotificationReceived(); appendDataToFileWoClosing(); std::this_thread::sleep_for( std::chrono::milliseconds( 400 ) ); ASSERT_FALSE( waitNotificationReceived( 1, 250 ) ); ASSERT_TRUE( waitNotificationReceived() ); } #endif glogg-1.1.0/tests/testlogdata.cpp0000664000175000017500000001172112657420553015776 0ustar nickonicko/* * Copyright (C) 2009, 2010 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include #include #include #include "testlogdata.h" #include "logdata.h" #if !defined( TMPDIR ) #define TMPDIR "/tmp" #endif static const qint64 VBL_NB_LINES = 4999999LL; static const int VBL_LINE_PER_PAGE = 70; static const char* vbl_format="LOGDATA is a part of glogg, we are going to test it thoroughly, this is line\t\t%07d\n"; static const int VBL_LINE_LENGTH = (76+2+7) ; // Without the final '\n' ! static const int VBL_VISIBLE_LINE_LENGTH = (76+8+4+7); // Without the final '\n' ! static const qint64 SL_NB_LINES = 5000LL; static const int SL_LINE_PER_PAGE = 70; static const char* sl_format="LOGDATA is a part of glogg, we are going to test it thoroughly, this is line %06d\n"; static const int SL_LINE_LENGTH = 83; // Without the final '\n' ! static const char* partial_line_begin = "123... beginning of line."; static const char* partial_line_end = " end of line 123.\n"; void TestLogData::initTestCase() { QVERIFY( generateDataFiles() ); } void TestLogData::simpleLoad() { LogData logData; QSignalSpy progressSpy( &logData, SIGNAL( loadingProgressed( int ) ) ); // Register for notification file is loaded connect( &logData, SIGNAL( loadingFinished( bool ) ), this, SLOT( loadingFinished() ) ); QBENCHMARK { logData.attachFile( TMPDIR "/verybiglog.txt" ); // Wait for the loading to be done { QApplication::exec(); } } // Disconnect all signals disconnect( &logData, 0 ); } void TestLogData::multipleLoad() { LogData logData; QSignalSpy finishedSpy( &logData, SIGNAL( loadingFinished( bool ) ) ); // Register for notification file is loaded connect( &logData, SIGNAL( loadingFinished( bool ) ), this, SLOT( loadingFinished() ) ); // Start loading the VBL logData.attachFile( TMPDIR "/verybiglog.txt" ); // Immediately interrupt the loading logData.interruptLoading(); // and wait for the signal QApplication::exec(); // Check we have an empty file QCOMPARE( finishedSpy.count(), 1 ); // TODO: check loadingFinished arg == false QCOMPARE( logData.getNbLine(), 0LL ); QCOMPARE( logData.getMaxLength(), 0 ); QCOMPARE( logData.getFileSize(), 0LL ); // Restart the VBL logData.attachFile( TMPDIR "/verybiglog.txt" ); // Ensure the counting has started { QMutex mutex; QWaitCondition sleep; // sleep.wait( &mutex, 10 ); } // Load the SL (should block until VBL is fully indexed) logData.attachFile( TMPDIR "/smalllog.txt" ); // and wait for the 2 signals (one for each file) QApplication::exec(); QApplication::exec(); // Check we have the small log loaded QCOMPARE( finishedSpy.count(), 3 ); QCOMPARE( logData.getNbLine(), SL_NB_LINES ); QCOMPARE( logData.getMaxLength(), SL_LINE_LENGTH ); QCOMPARE( logData.getFileSize(), SL_NB_LINES * (SL_LINE_LENGTH+1LL) ); // Restart the VBL again logData.attachFile( TMPDIR "/verybiglog.txt" ); // Immediately interrupt the loading logData.interruptLoading(); // and wait for the signal QApplication::exec(); // Check the small log has been restored QCOMPARE( finishedSpy.count(), 4 ); QCOMPARE( logData.getNbLine(), SL_NB_LINES ); QCOMPARE( logData.getMaxLength(), SL_LINE_LENGTH ); QCOMPARE( logData.getFileSize(), SL_NB_LINES * (SL_LINE_LENGTH+1LL) ); // Disconnect all signals disconnect( &logData, 0 ); } // // Private functions // void TestLogData::loadingFinished() { QApplication::quit(); } bool TestLogData::generateDataFiles() { char newLine[90]; QFile file( TMPDIR "/verybiglog.txt" ); if ( file.open( QIODevice::WriteOnly ) ) { for (int i = 0; i < VBL_NB_LINES; i++) { snprintf(newLine, 89, vbl_format, i); file.write( newLine, qstrlen(newLine) ); } } else { return false; } file.close(); file.setFileName( TMPDIR "/smalllog.txt" ); if ( file.open( QIODevice::WriteOnly ) ) { for (int i = 0; i < SL_NB_LINES; i++) { snprintf(newLine, 89, sl_format, i); file.write( newLine, qstrlen(newLine) ); } } else { return false; } file.close(); return true; } glogg-1.1.0/tests/test-results.txt0000664000175000017500000000443012657420553016175 0ustar nickonicko page 99999 page 9999 LogData size 9999 Legacy 1305.5 11.009 1,048,576B 16,777,216B QListString 0.055 0.0027 3,777,200B 18,644,700B Memory test: everything commented except RandomPage Memory using top: 99999 (8.5Mib file): legacy 44m,20m qliststr 33m,11m 4999999 (428MiB file): legacy 464m,442m qliststr 1090m,1.0g 'Legacy' implementation: ********* Start testing of TestLogData ********* Config: Using QTest library 4.5.0, Qt 4.5.0 PASS : TestLogData::initTestCase() - 14:12:30.621 DEBUG: Found 99999 lines. - 14:12:30.639 DEBUG: Found 99999 lines. - 14:12:30.707 DEBUG: Found 99999 lines. - 14:12:30.717 DEBUG: Found 99999 lines. - 14:12:30.728 DEBUG: Found 99999 lines. RESULT : TestLogData::simpleLoad(): 10 msec per iteration (total: 21, iterations: 2) PASS : TestLogData::simpleLoad() - 14:12:30.780 DEBUG: Found 99999 lines. RESULT : TestLogData::sequentialRead(): 650,750 msec per iteration (total: 650750, iterations: 1) PASS : TestLogData::sequentialRead() - 14:23:21.586 DEBUG: Found 99999 lines. RESULT : TestLogData::randomPageRead(): 1,305,466 msec per iteration (total: 1305466, iterations: 1) PASS : TestLogData::randomPageRead() PASS : TestLogData::cleanupTestCase() Totals: 5 passed, 0 failed, 0 skipped ********* Finished testing of TestLogData ********* With 9999 lines: RESULT : TestLogData::simpleLoad(): 1.3 msec per iteration (total: 21, iterations: 16) PASS : TestLogData::simpleLoad() - 14:53:26.232 DEBUG: Found 9999 lines. RESULT : TestLogData::sequentialRead(): 6,475 msec per iteration (total: 6475, iterations: 1) PASS : TestLogData::sequentialRead() - 14:53:32.713 DEBUG: Found 9999 lines. RESULT : TestLogData::randomPageRead(): 11,009 msec per iteration (total: 11009, iterations: 1) PASS : TestLogData::randomPageRead() PASS : TestLogData::cleanupTestCase() Totals: 5 passed, 0 failed, 0 skipped Memory: 1,048,576B Tab expansion fix: before: TestLogData::sequentialRead(): 58,011 msec TestLogData::randomPageRead(): 11,376 msec after step 1: TestLogData::sequentialRead(): 55,662 msec TestLogData::sequentialReadExpanded(): 91,141 msec TestLogData::randomPageRead(): 10,592 msec TestLogData::randomPageReadExpanded(): 183,567 msec glogg-1.1.0/tests/testlogfiltereddata.cpp0000664000175000017500000005110212657420553017512 0ustar nickonicko/* * Copyright (C) 2009, 2010, 2011 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include #include #include #include "testlogfiltereddata.h" #include "logdata.h" #include "logfiltereddata.h" #if !defined( TMPDIR ) #define TMPDIR "/tmp" #endif static const qint64 ML_NB_LINES = 15000LL; static const char* ml_format="LOGDATA is a part of glogg, we are going to test it thoroughly, this is line\t\t%06d\n"; static const int ML_VISIBLE_LINE_LENGTH = (76+8+4+6); // Without the final '\n' ! static const qint64 SL_NB_LINES = 2000LL; static const char* sl_format="LOGDATA is a part of glogg, we are going to test it thoroughly, this is line %06d\n"; static const char* partial_line_begin = "123... beginning of line."; static const char* partial_line_end = " end of line 123.\n"; static const char* partial_nonmatching_line_begin = "Beginning of line."; TestLogFilteredData::TestLogFilteredData() : QObject(), loadingFinishedMutex_(), searchProgressedMutex_(), loadingFinishedCondition_(), searchProgressedCondition_() { loadingFinished_received_ = false; loadingFinished_read_ = false; searchProgressed_received_ = false; searchProgressed_read_ = false; } void TestLogFilteredData::initTestCase() { QVERIFY( generateDataFiles() ); } void TestLogFilteredData::simpleSearch() { logData_ = new LogData(); // Register for notification file is loaded connect( logData_, SIGNAL( loadingFinished( bool ) ), this, SLOT( loadingFinished() ) ); filteredData_ = logData_->getNewFilteredData(); connect( filteredData_, SIGNAL( searchProgressed( int, int ) ), this, SLOT( searchProgressed( int, int ) ) ); QFuture future = QtConcurrent::run(this, &TestLogFilteredData::simpleSearchTest); QApplication::exec(); disconnect( filteredData_, 0 ); disconnect( logData_, 0 ); delete filteredData_; delete logData_; } void TestLogFilteredData::simpleSearchTest() { // First load the tests file logData_->attachFile( TMPDIR "/mediumlog.txt" ); // Wait for the loading to be done waitLoadingFinished(); QCOMPARE( logData_->getNbLine(), ML_NB_LINES ); signalLoadingFinishedRead(); // Now perform a simple search qint64 matches[] = { 0, 15, 20, 135 }; QBENCHMARK { // Start the search filteredData_->runSearch( QRegExp( "123" ) ); // And check we receive data in 4 chunks (the first being empty) for ( int i = 0; i < 4; i++ ) { std::pair progress = waitSearchProgressed(); // FIXME: The test for this is unfortunately not reliable // (race conditions) // QCOMPARE( (qint64) progress.first, matches[i] ); signalSearchProgressedRead(); } } QCOMPARE( filteredData_->getNbLine(), matches[3] ); // Check the search QCOMPARE( filteredData_->isLineInMatchingList( 123 ), true ); QCOMPARE( filteredData_->isLineInMatchingList( 124 ), false ); QCOMPARE( filteredData_->getMaxLength(), ML_VISIBLE_LINE_LENGTH ); QCOMPARE( filteredData_->getLineLength( 12 ), ML_VISIBLE_LINE_LENGTH ); QCOMPARE( filteredData_->getNbLine(), 135LL ); // Line beyond limit QCOMPARE( filteredData_->isLineInMatchingList( 60000 ), false ); QCOMPARE( filteredData_->getMatchingLineNumber( 0 ), 123LL ); // Now let's try interrupting a search filteredData_->runSearch( QRegExp( "123" ) ); // ... wait for two chunks. waitSearchProgressed(); signalSearchProgressedRead(); // and interrupt! filteredData_->interruptSearch(); { std::pair progress; do { progress = waitSearchProgressed(); signalSearchProgressedRead(); } while ( progress.second < 100 ); // (because there is no guarantee when the search is // interrupted, we are not sure how many chunk of result // we will get.) } QApplication::quit(); } void TestLogFilteredData::multipleSearch() { logData_ = new LogData(); // Register for notification file is loaded connect( logData_, SIGNAL( loadingFinished( bool ) ), this, SLOT( loadingFinished() ) ); filteredData_ = logData_->getNewFilteredData(); connect( filteredData_, SIGNAL( searchProgressed( int, int ) ), this, SLOT( searchProgressed( int, int ) ) ); QFuture future = QtConcurrent::run(this, &TestLogFilteredData::multipleSearchTest); QApplication::exec(); disconnect( filteredData_, 0 ); disconnect( logData_, 0 ); delete filteredData_; delete logData_; } void TestLogFilteredData::multipleSearchTest() { // First load the tests file logData_->attachFile( TMPDIR "/smalllog.txt" ); // Wait for the loading to be done waitLoadingFinished(); QCOMPARE( logData_->getNbLine(), SL_NB_LINES ); signalLoadingFinishedRead(); // Performs two searches in a row // Start the search, and immediately another one // (the second call should block until the first search is done) filteredData_->runSearch( QRegExp( "1234" ) ); filteredData_->runSearch( QRegExp( "123" ) ); for ( int i = 0; i < 3; i++ ) { waitSearchProgressed(); signalSearchProgressedRead(); } // We should have the result for the 2nd search after the last chunk waitSearchProgressed(); QCOMPARE( filteredData_->getNbLine(), 12LL ); signalSearchProgressedRead(); // Now a tricky one: we run a search and immediately attach a new file /* FIXME: sometimes we receive loadingFinished before searchProgressed * -> deadlock in the test. filteredData_->runSearch( QRegExp( "123" ) ); waitSearchProgressed(); signalSearchProgressedRead(); logData_->attachFile( TMPDIR "/mediumlog.txt" ); // We don't expect meaningful results but it should not crash! for ( int i = 0; i < 1; i++ ) { waitSearchProgressed(); signalSearchProgressedRead(); } */ sleep(10); QApplication::quit(); } void TestLogFilteredData::updateSearch() { logData_ = new LogData(); // Register for notification file is loaded connect( logData_, SIGNAL( loadingFinished( bool ) ), this, SLOT( loadingFinished() ) ); filteredData_ = logData_->getNewFilteredData(); connect( filteredData_, SIGNAL( searchProgressed( int, int ) ), this, SLOT( searchProgressed( int, int ) ) ); QFuture future = QtConcurrent::run(this, &TestLogFilteredData::updateSearchTest); QApplication::exec(); disconnect( filteredData_, 0 ); disconnect( logData_, 0 ); delete filteredData_; delete logData_; } void TestLogFilteredData::updateSearchTest() { // First load the tests file logData_->attachFile( TMPDIR "/smalllog.txt" ); // Wait for the loading to be done waitLoadingFinished(); QCOMPARE( logData_->getNbLine(), SL_NB_LINES ); signalLoadingFinishedRead(); // Perform a first search filteredData_->runSearch( QRegExp( "123" ) ); for ( int i = 0; i < 2; i++ ) { waitSearchProgressed(); signalSearchProgressedRead(); } // Check the result QCOMPARE( filteredData_->getNbLine(), 12LL ); sleep(1); QWARN("Starting stage 2"); // Add some data to the file char newLine[90]; QFile file( TMPDIR "/smalllog.txt" ); if ( file.open( QIODevice::Append ) ) { for (int i = 0; i < 3000; i++) { snprintf(newLine, 89, sl_format, i); file.write( newLine, qstrlen(newLine) ); } // To test the edge case when the final line is not complete and matching file.write( partial_line_begin, qstrlen( partial_line_begin ) ); } file.close(); // Let the system do the update (there might be several ones) do { waitLoadingFinished(); signalLoadingFinishedRead(); } while ( logData_->getNbLine() < 5001LL ); sleep(1); // Start an update search filteredData_->updateSearch(); for ( int i = 0; i < 2; i++ ) { waitSearchProgressed(); signalSearchProgressedRead(); } // Check the result QCOMPARE( logData_->getNbLine(), 5001LL ); QCOMPARE( filteredData_->getNbLine(), 26LL ); QWARN("Starting stage 3"); // Add a couple more lines, including the end of the unfinished one. if ( file.open( QIODevice::Append ) ) { file.write( partial_line_end, qstrlen( partial_line_end ) ); for (int i = 0; i < 20; i++) { snprintf(newLine, 89, sl_format, i); file.write( newLine, qstrlen(newLine) ); } // To test the edge case when the final line is not complete and not matching file.write( partial_nonmatching_line_begin, qstrlen( partial_nonmatching_line_begin ) ); } file.close(); // Let the system do the update waitLoadingFinished(); signalLoadingFinishedRead(); // Start an update search filteredData_->updateSearch(); for ( int i = 0; i < 2; i++ ) { waitSearchProgressed(); signalSearchProgressedRead(); } // Check the result QCOMPARE( logData_->getNbLine(), 5022LL ); QCOMPARE( filteredData_->getNbLine(), 26LL ); QWARN("Starting stage 4"); // Now test the case where a match is found at the end of an updated line. if ( file.open( QIODevice::Append ) ) { file.write( partial_line_end, qstrlen( partial_line_end ) ); for (int i = 0; i < 20; i++) { snprintf(newLine, 89, sl_format, i); file.write( newLine, qstrlen(newLine) ); } } file.close(); // Let the system do the update waitLoadingFinished(); signalLoadingFinishedRead(); // Start an update search filteredData_->updateSearch(); for ( int i = 0; i < 2; i++ ) { waitSearchProgressed(); signalSearchProgressedRead(); } // Check the result QCOMPARE( logData_->getNbLine(), 5042LL ); QCOMPARE( filteredData_->getNbLine(), 27LL ); QApplication::quit(); } void TestLogFilteredData::marks() { logData_ = new LogData(); // Register for notification file is loaded connect( logData_, SIGNAL( loadingFinished( bool ) ), this, SLOT( loadingFinished() ) ); filteredData_ = logData_->getNewFilteredData(); connect( filteredData_, SIGNAL( searchProgressed( int, int ) ), this, SLOT( searchProgressed( int, int ) ) ); QFuture future = QtConcurrent::run(this, &TestLogFilteredData::marksTest); QApplication::exec(); disconnect( filteredData_, 0 ); disconnect( logData_, 0 ); delete filteredData_; delete logData_; } void TestLogFilteredData::marksTest() { // First load the tests file logData_->attachFile( TMPDIR "/smalllog.txt" ); // Wait for the loading to be done waitLoadingFinished(); QCOMPARE( logData_->getNbLine(), SL_NB_LINES ); signalLoadingFinishedRead(); // First check no line is marked for ( int i = 0; i < SL_NB_LINES; i++ ) QVERIFY( filteredData_->isLineMarked( i ) == false ); // Try to create some "out of limit" marks filteredData_->addMark( -10 ); filteredData_->addMark( SL_NB_LINES + 25 ); // Check no line is marked still for ( int i = 0; i < SL_NB_LINES; i++ ) QVERIFY( filteredData_->isLineMarked( i ) == false ); // Create a couple of unnamed marks filteredData_->addMark( 10 ); filteredData_->addMark( 44 ); // This one will also be a match filteredData_->addMark( 25 ); // Check they are marked QVERIFY( filteredData_->isLineMarked( 10 ) ); QVERIFY( filteredData_->isLineMarked( 25 ) ); QVERIFY( filteredData_->isLineMarked( 44 ) ); // But others are not QVERIFY( filteredData_->isLineMarked( 15 ) == false ); QVERIFY( filteredData_->isLineMarked( 20 ) == false ); QCOMPARE( filteredData_->getNbLine(), 3LL ); // Performs a search QSignalSpy progressSpy( filteredData_, SIGNAL( searchProgressed( int, int ) ) ); filteredData_->runSearch( QRegExp( "0000.4" ) ); for ( int i = 0; i < 1; i++ ) { waitSearchProgressed(); signalSearchProgressedRead(); } // We should have the result of the search and the marks waitSearchProgressed(); QCOMPARE( filteredData_->getNbLine(), 12LL ); signalSearchProgressedRead(); QString startline = "LOGDATA is a part of glogg, we are going to test it thoroughly, this is line "; QCOMPARE( filteredData_->getLineString(0), startline + "000004" ); QCOMPARE( filteredData_->getLineString(1), startline + "000010" ); QCOMPARE( filteredData_->getLineString(2), startline + "000014" ); QCOMPARE( filteredData_->getLineString(3), startline + "000024" ); QCOMPARE( filteredData_->getLineString(4), startline + "000025" ); QCOMPARE( filteredData_->getLineString(5), startline + "000034" ); QCOMPARE( filteredData_->getLineString(6), startline + "000044" ); QCOMPARE( filteredData_->getLineString(7), startline + "000054" ); QCOMPARE( filteredData_->getLineString(8), startline + "000064" ); QCOMPARE( filteredData_->getLineString(9), startline + "000074" ); QCOMPARE( filteredData_->getLineString(10), startline + "000084" ); QCOMPARE( filteredData_->getLineString(11), startline + "000094" ); filteredData_->setVisibility( LogFilteredData::MatchesOnly ); QCOMPARE( filteredData_->getNbLine(), 10LL ); QCOMPARE( filteredData_->getLineString(0), startline + "000004" ); QCOMPARE( filteredData_->getLineString(1), startline + "000014" ); QCOMPARE( filteredData_->getLineString(2), startline + "000024" ); QCOMPARE( filteredData_->getLineString(3), startline + "000034" ); QCOMPARE( filteredData_->getLineString(4), startline + "000044" ); QCOMPARE( filteredData_->getLineString(5), startline + "000054" ); QCOMPARE( filteredData_->getLineString(6), startline + "000064" ); QCOMPARE( filteredData_->getLineString(7), startline + "000074" ); QCOMPARE( filteredData_->getLineString(8), startline + "000084" ); QCOMPARE( filteredData_->getLineString(9), startline + "000094" ); filteredData_->setVisibility( LogFilteredData::MarksOnly ); QCOMPARE( filteredData_->getNbLine(), 3LL ); QCOMPARE( filteredData_->getLineString(0), startline + "000010" ); QCOMPARE( filteredData_->getLineString(1), startline + "000025" ); QCOMPARE( filteredData_->getLineString(2), startline + "000044" ); // Another test with marks only filteredData_->clearSearch(); filteredData_->clearMarks(); filteredData_->setVisibility( LogFilteredData::MarksOnly ); filteredData_->addMark(18); filteredData_->addMark(19); filteredData_->addMark(20); QCOMPARE( filteredData_->getMatchingLineNumber(0), 18LL ); QCOMPARE( filteredData_->getMatchingLineNumber(1), 19LL ); QCOMPARE( filteredData_->getMatchingLineNumber(2), 20LL ); QApplication::quit(); } void TestLogFilteredData::lineLength() { logData_ = new LogData(); // Register for notification file is loaded connect( logData_, SIGNAL( loadingFinished( bool ) ), this, SLOT( loadingFinished() ) ); filteredData_ = logData_->getNewFilteredData(); connect( filteredData_, SIGNAL( searchProgressed( int, int ) ), this, SLOT( searchProgressed( int, int ) ) ); QFuture future = QtConcurrent::run(this, &TestLogFilteredData::lineLengthTest); QApplication::exec(); disconnect( filteredData_, 0 ); disconnect( logData_, 0 ); delete filteredData_; delete logData_; } void TestLogFilteredData::lineLengthTest() { // Line length tests logData_->attachFile( TMPDIR "/length_test.txt" ); // Wait for the loading to be done waitLoadingFinished(); QCOMPARE( logData_->getNbLine(), 4LL ); signalLoadingFinishedRead(); // Performs a search (the two middle lines matche) filteredData_->setVisibility( LogFilteredData::MatchesOnly ); filteredData_->runSearch( QRegExp( "longer" ) ); std::pair progress; do { progress = waitSearchProgressed(); signalSearchProgressedRead(); QWARN("progress"); } while ( progress.second < 100 ); filteredData_->addMark( 3 ); QCOMPARE( filteredData_->getNbLine(), 2LL ); QCOMPARE( filteredData_->getMaxLength(), 40 ); filteredData_->setVisibility( LogFilteredData::MarksAndMatches ); QCOMPARE( filteredData_->getNbLine(), 3LL ); QCOMPARE( filteredData_->getMaxLength(), 103 ); filteredData_->setVisibility( LogFilteredData::MarksOnly ); QCOMPARE( filteredData_->getNbLine(), 1LL ); QCOMPARE( filteredData_->getMaxLength(), 103 ); filteredData_->addMark( 0 ); QCOMPARE( filteredData_->getNbLine(), 2LL ); QCOMPARE( filteredData_->getMaxLength(), 103 ); filteredData_->deleteMark( 3 ); QCOMPARE( filteredData_->getNbLine(), 1LL ); QCOMPARE( filteredData_->getMaxLength(), 27 ); filteredData_->setVisibility( LogFilteredData::MarksAndMatches ); QCOMPARE( filteredData_->getMaxLength(), 40 ); QApplication::quit(); } // // Private functions // void TestLogFilteredData::loadingFinished() { QMutexLocker locker( &loadingFinishedMutex_ ); QWARN("loadingFinished"); loadingFinished_received_ = true; loadingFinished_read_ = false; loadingFinishedCondition_.wakeOne(); // Wait for the test thread to read the signal while ( ! loadingFinished_read_ ) loadingFinishedCondition_.wait( locker.mutex() ); } void TestLogFilteredData::searchProgressed( int nbMatches, int completion ) { QMutexLocker locker( &searchProgressedMutex_ ); QWARN("searchProgressed"); searchProgressed_received_ = true; searchProgressed_read_ = false; searchLastMatches_ = nbMatches; searchLastProgress_ = completion; searchProgressedCondition_.wakeOne(); // Wait for the test thread to read the signal while ( ! searchProgressed_read_ ) searchProgressedCondition_.wait( locker.mutex() ); } std::pair TestLogFilteredData::waitSearchProgressed() { QMutexLocker locker( &searchProgressedMutex_ ); while ( ! searchProgressed_received_ ) searchProgressedCondition_.wait( locker.mutex() ); QWARN("searchProgressed Received"); return std::pair(searchLastMatches_, searchLastProgress_); } void TestLogFilteredData::waitLoadingFinished() { QMutexLocker locker( &loadingFinishedMutex_ ); while ( ! loadingFinished_received_ ) loadingFinishedCondition_.wait( locker.mutex() ); QWARN("loadingFinished Received"); } void TestLogFilteredData::signalSearchProgressedRead() { QMutexLocker locker( &searchProgressedMutex_ ); searchProgressed_received_ = false; searchProgressed_read_ = true; searchProgressedCondition_.wakeOne(); } void TestLogFilteredData::signalLoadingFinishedRead() { QMutexLocker locker( &loadingFinishedMutex_ ); loadingFinished_received_ = false; loadingFinished_read_ = true; loadingFinishedCondition_.wakeOne(); } bool TestLogFilteredData::generateDataFiles() { char newLine[90]; QFile file( TMPDIR "/mediumlog.txt" ); if ( file.open( QIODevice::WriteOnly ) ) { for (int i = 0; i < ML_NB_LINES; i++) { snprintf(newLine, 89, ml_format, i); file.write( newLine, qstrlen(newLine) ); } } else { return false; } file.close(); file.setFileName( TMPDIR "/smalllog.txt" ); if ( file.open( QIODevice::WriteOnly ) ) { for (int i = 0; i < SL_NB_LINES; i++) { snprintf(newLine, 89, sl_format, i); file.write( newLine, qstrlen(newLine) ); } } else { return false; } file.close(); file.setFileName( TMPDIR "/length_test.txt" ); if ( file.open( QIODevice::WriteOnly ) ) { file.write( "This line is 27 characters.\n" ); file.write( "This line is longer: 36 characters.\n" ); file.write( "This line is even longer: 40 characters.\n" ); file.write( "This line is very long, it's actually hard to count but it is\ probably something around 103 characters.\n" ); } file.close(); return true; } glogg-1.1.0/tests/cmake.bat0000664000175000017500000000042612657420553014527 0ustar nickonickorem Generate makefiles suitable to make the tests on Windows/MinGW mkdir build cd build set QT_DIR=Y:\5.3.2-64 set CMAKE_PREFIX_PATH=%QT_DIR% set GMOCK_HOME=Y:\gmock-1.7.0 set QT_QPA_PLATFORM_PLUGIN_PATH=%QT_DIR%\plugins\platforms cmake -G "MinGW Makefiles" .. mingw32-make glogg-1.1.0/tests/encodingspeculatorTest.cpp0000664000175000017500000001362012657420553020213 0ustar nickonicko#include "gmock/gmock.h" #include "config.h" #include "log.h" #include "encodingspeculator.h" using namespace std; using namespace testing; class EncodingSpeculatorBehaviour: public testing::Test { public: EncodingSpeculator speculator; EncodingSpeculatorBehaviour() { } }; TEST_F( EncodingSpeculatorBehaviour, DefaultAsPureAscii ) { ASSERT_THAT( speculator.guess(), Eq( EncodingSpeculator::Encoding::ASCII7 ) ); } TEST_F( EncodingSpeculatorBehaviour, RecognisePureAscii ) { for ( uint8_t i = 0; i < 127; ++i ) speculator.inject_byte( i ); ASSERT_THAT( speculator.guess(), Eq( EncodingSpeculator::Encoding::ASCII7 ) ); } TEST_F( EncodingSpeculatorBehaviour, RecogniseRandom8bitEncoding ) { for ( uint8_t i = 0; i < 127; ++i ) speculator.inject_byte( i ); speculator.inject_byte( 0xFF ); ASSERT_THAT( speculator.guess(), Eq( EncodingSpeculator::Encoding::ASCII8 ) ); } pair utf8encode2bytes( uint16_t code_point ) { uint8_t cp_low = static_cast(code_point & 0xFF); uint8_t cp_hi = static_cast((code_point & 0xFF00) >> 8); uint8_t first_byte = 0xC0 | ( ( cp_hi & 0x7F ) << 2 ) | ( ( cp_low & 0xC0 ) >> 6 ); uint8_t second_byte = 0x80 | ( cp_low & 0x3F ); return { first_byte, second_byte }; } vector utf8encodeMultiBytes( uint32_t code_point ) { vector bytes = {}; if ( code_point <= 0xFFFF ) { uint8_t lead = static_cast( 0xE0 | ( ( code_point & 0xF000 ) >> 12 ) ); bytes.push_back( lead ); bytes.push_back( 0x80 | ( code_point & 0x0FC0 ) >> 6 ); bytes.push_back( 0x80 | ( code_point & 0x3F ) ); } else if ( code_point <= 0x1FFFFF ) { uint8_t lead = static_cast( 0xF0 | ( ( code_point & 0x1C0000 ) >> 18 ) ); bytes.push_back( lead ); bytes.push_back( 0x80 | ( code_point & 0x3F000 ) >> 12 ); bytes.push_back( 0x80 | ( code_point & 0x00FC0 ) >> 6 ); bytes.push_back( 0x80 | ( code_point & 0x0003F ) ); } return bytes; } TEST_F( EncodingSpeculatorBehaviour, RecogniseTwoBytesUTF8 ) { // All the code points encodable as 2 bytes. for ( uint16_t i = 0x80; i < ( 1 << 11 ); ++i ) { auto utf8_bytes = utf8encode2bytes( i ); // cout << bitset<8>(first_byte) << " " << bitset<8>(second_byte) << endl; speculator.inject_byte( utf8_bytes.first ); speculator.inject_byte( utf8_bytes.second ); } ASSERT_THAT( speculator.guess(), Eq( EncodingSpeculator::Encoding::UTF8 ) ); } TEST_F( EncodingSpeculatorBehaviour, RecogniseTwoBytesUTF8With7bitsInterleaved ) { // All the code points encodable as 2 bytes. for ( uint16_t i = 0x80; i < ( 1 << 11 ); ++i ) { auto utf8_bytes = utf8encode2bytes( i ); speculator.inject_byte( ' ' ); speculator.inject_byte( utf8_bytes.first ); speculator.inject_byte( utf8_bytes.second ); } ASSERT_THAT( speculator.guess(), Eq( EncodingSpeculator::Encoding::UTF8 ) ); } TEST_F( EncodingSpeculatorBehaviour, RecogniseIncompleteTwoBytesUTF8 ) { // All the code points encodable as 2 bytes. for ( uint16_t i = 0x80; i < ( 1 << 11 ); ++i ) { auto utf8_bytes = utf8encode2bytes( i ); speculator.inject_byte( ' ' ); speculator.inject_byte( utf8_bytes.first ); speculator.inject_byte( utf8_bytes.second ); } // Lead byte only speculator.inject_byte( 0xCF ); ASSERT_THAT( speculator.guess(), Eq( EncodingSpeculator::Encoding::ASCII8 ) ); } TEST_F( EncodingSpeculatorBehaviour, RecogniseIncorrectTwoBytesUTF8 ) { // All the code points encodable as 2 bytes. for ( uint16_t i = 0x80; i < ( 1 << 11 ); ++i ) { auto utf8_bytes = utf8encode2bytes( i ); speculator.inject_byte( ' ' ); speculator.inject_byte( utf8_bytes.first ); speculator.inject_byte( utf8_bytes.second ); } // Lead byte speculator.inject_byte( 0xCF ); // Incorrect continuation byte (should start with 1) speculator.inject_byte( 0x00 ); ASSERT_THAT( speculator.guess(), Eq( EncodingSpeculator::Encoding::ASCII8 ) ); } TEST_F( EncodingSpeculatorBehaviour, RecogniseOverlong2BytesUTF8 ) { speculator.inject_byte( ' ' ); speculator.inject_byte( 0xC1 ); speculator.inject_byte( 0xBF ); ASSERT_THAT( speculator.guess(), Eq( EncodingSpeculator::Encoding::ASCII8 ) ); } TEST_F( EncodingSpeculatorBehaviour, RecogniseThreeBytesUTF8 ) { for ( uint32_t i = 0x800; i <= 0xFFFF; ++i ) { auto utf8_bytes = utf8encodeMultiBytes( i ); speculator.inject_byte( ' ' ); for ( uint8_t byte: utf8_bytes ) { // cout << hex << i << " " << static_cast( byte ) << endl; speculator.inject_byte( byte ); } } ASSERT_THAT( speculator.guess(), Eq( EncodingSpeculator::Encoding::UTF8 ) ); } TEST_F( EncodingSpeculatorBehaviour, RecogniseOverlong3BytesUTF8 ) { speculator.inject_byte( ' ' ); speculator.inject_byte( 0xA0 ); speculator.inject_byte( 0x80 ); speculator.inject_byte( 0x80 ); ASSERT_THAT( speculator.guess(), Eq( EncodingSpeculator::Encoding::ASCII8 ) ); } TEST_F( EncodingSpeculatorBehaviour, RecogniseFourBytesUTF8 ) { for ( uint32_t i = 0x10000; i <= 0x1FFFFF; ++i ) { auto utf8_bytes = utf8encodeMultiBytes( i ); speculator.inject_byte( ' ' ); for ( uint8_t byte: utf8_bytes ) { // cout << hex << i << " " << static_cast( byte ) << endl; speculator.inject_byte( byte ); } } ASSERT_THAT( speculator.guess(), Eq( EncodingSpeculator::Encoding::UTF8 ) ); } TEST_F( EncodingSpeculatorBehaviour, RecogniseOverlong4BytesUTF8 ) { speculator.inject_byte( ' ' ); speculator.inject_byte( 0xF0 ); speculator.inject_byte( 0x80 ); speculator.inject_byte( 0x80 ); speculator.inject_byte( 0x80 ); ASSERT_THAT( speculator.guess(), Eq( EncodingSpeculator::Encoding::ASCII8 ) ); } glogg-1.1.0/tests/testlogfiltereddata.h0000664000175000017500000000263112657420553017162 0ustar nickonicko#include #include #include class LogData; class LogFilteredData; class TestLogFilteredData: public QObject { Q_OBJECT public: TestLogFilteredData(); private slots: void initTestCase(); void simpleSearch(); void multipleSearch(); void marks(); void lineLength(); void updateSearch(); public slots: void loadingFinished(); void searchProgressed( int completion, int nbMatches ); private: bool generateDataFiles(); void simpleSearchTest(); void multipleSearchTest(); void updateSearchTest(); void marksTest(); void lineLengthTest(); std::pair waitSearchProgressed(); void waitLoadingFinished(); void signalSearchProgressedRead(); void signalLoadingFinishedRead(); LogData* logData_; LogFilteredData* filteredData_; // Synchronisation variables (protected by the two mutexes) bool loadingFinished_received_; bool loadingFinished_read_; bool searchProgressed_received_; bool searchProgressed_read_; int searchLastMatches_; int searchLastProgress_; QMutex loadingFinishedMutex_; QMutex searchProgressedMutex_; QWaitCondition loadingFinishedCondition_; QWaitCondition searchProgressedCondition_; }; glogg-1.1.0/tests/logfiltereddataTest.cpp0000664000175000017500000000351212657420553017454 0ustar nickonicko#include #include #include "log.h" #include "test_utils.h" #include "data/logdata.h" #include "data/logfiltereddata.h" #include "gmock/gmock.h" #define TMPDIR "/tmp" static const qint64 SL_NB_LINES = 5000LL; static const int SL_LINE_PER_PAGE = 70; static const char* sl_format="LOGDATA is a part of glogg, we are going to test it thoroughly, this is line %06d\n"; static const int SL_LINE_LENGTH = 83; // Without the final '\n' ! class MarksBehaviour : public testing::Test { public: LogData log_data; SafeQSignalSpy endSpy; LogFilteredData* filtered_data = nullptr; MarksBehaviour() : endSpy( &log_data, SIGNAL( loadingFinished( LoadingStatus ) ) ) { generateDataFiles(); log_data.attachFile( TMPDIR "/smalllog.txt" ); endSpy.safeWait( 10000 ); filtered_data = log_data.getNewFilteredData(); } bool generateDataFiles() { char newLine[90]; QFile file( TMPDIR "/smalllog.txt" ); if ( file.open( QIODevice::WriteOnly ) ) { for (int i = 0; i < SL_NB_LINES; i++) { snprintf(newLine, 89, sl_format, i); file.write( newLine, qstrlen(newLine) ); } } else { return false; } file.close(); return true; } }; TEST_F( MarksBehaviour, marksIgnoresOutOfLimitLines ) { filtered_data->addMark( -10 ); filtered_data->addMark( SL_NB_LINES + 25 ); // Check we still have no mark for ( int i = 0; i < SL_NB_LINES; i++ ) ASSERT_FALSE( filtered_data->isLineMarked( i ) ); } TEST_F( MarksBehaviour, marksAreStored ) { filtered_data->addMark( 10 ); filtered_data->addMark( 25 ); // Check the marks have been put ASSERT_TRUE( filtered_data->isLineMarked( 10 ) ); ASSERT_TRUE( filtered_data->isLineMarked( 25 ) ); } glogg-1.1.0/tests/logfiltereddataPerfTest.cpp0000664000175000017500000000714612657420553020300 0ustar nickonicko#include #include #include "log.h" #include "test_utils.h" #include "data/logdata.h" #include "data/logfiltereddata.h" #include "gmock/gmock.h" #define TMPDIR "/tmp" static const qint64 VBL_NB_LINES = 4999999LL; static const int VBL_LINE_PER_PAGE = 70; static const char* vbl_format="LOGDATA is a part of glogg, we are going to test it thoroughly, this is line\t\t%07d\n"; static const int VBL_LINE_LENGTH = (76+2+7) ; // Without the final '\n' ! static const int VBL_VISIBLE_LINE_LENGTH = (76+8+4+7); // Without the final '\n' ! class PerfLogFilteredData : public testing::Test { public: PerfLogFilteredData() : log_data_(), filtered_data_( log_data_.getNewFilteredData() ), progressSpy( filtered_data_.get(), SIGNAL( searchProgressed( int, int ) ) ) { FILELog::setReportingLevel( logERROR ); generateDataFiles(); QSignalSpy endSpy( &log_data_, SIGNAL( loadingFinished( LoadingStatus ) ) ); log_data_.attachFile( TMPDIR "/verybiglog.txt" ); if ( ! endSpy.wait( 999000 ) ) std::cerr << "Unable to attach the file!"; } LogData log_data_; std::unique_ptr filtered_data_; QSignalSpy progressSpy; void search() { int percent = 0; do { if ( progressSpy.wait( 10000 ) ) percent = qvariant_cast(progressSpy.last().at(1)); else std::cout << "Missed...\n"; // std::cout << "Progress " << percent << std::endl; } while ( percent < 100 ); } bool generateDataFiles() { char newLine[90]; QFile file( TMPDIR "/verybiglog.txt" ); if ( file.open( QIODevice::WriteOnly ) ) { for (int i = 0; i < VBL_NB_LINES; i++) { snprintf(newLine, 89, vbl_format, i); file.write( newLine, qstrlen(newLine) ); } } else { return false; } file.close(); return true; } }; TEST_F( PerfLogFilteredData, allMatchingSearch ) { { TestTimer t; filtered_data_->runSearch( QRegExp( "glogg.*this" ) ); search(); } ASSERT_THAT( filtered_data_->getNbLine(), VBL_NB_LINES ); } TEST_F( PerfLogFilteredData, someMatchingSearch ) { { TestTimer t; filtered_data_->runSearch( QRegExp( "1?3|34" ) ); search(); } ASSERT_THAT( filtered_data_->getNbLine(), 2874236 ); } TEST_F( PerfLogFilteredData, noneMatchingSearch ) { { TestTimer t; filtered_data_->runSearch( QRegExp( "a1?3|(w|f)f34|blah" ) ); search(); } ASSERT_THAT( filtered_data_->getNbLine(), 0 ); } TEST_F( PerfLogFilteredData, browsingSearchResults ) { filtered_data_->runSearch( QRegExp( "1?3|34" ) ); search(); ASSERT_THAT( filtered_data_->getNbLine(), 2874236 ); // Read page by page from the beginning and the end, using the QStringList // function std::cout << "Start reading..." << std::endl; QStringList list; { TestTimer t; const int nb_results = filtered_data_->getNbLine(); for (int page = 0; page < (nb_results/VBL_LINE_PER_PAGE)-1; page++) { list = filtered_data_->getExpandedLines( page * VBL_LINE_PER_PAGE, VBL_LINE_PER_PAGE ); ASSERT_THAT(list.count(), VBL_LINE_PER_PAGE); int page_from_end = (nb_results/VBL_LINE_PER_PAGE) - page - 1; list = filtered_data_->getExpandedLines( page_from_end * VBL_LINE_PER_PAGE, VBL_LINE_PER_PAGE ); ASSERT_THAT(list.count(), VBL_LINE_PER_PAGE); } } } glogg-1.1.0/tests/logdataTest.cpp0000664000175000017500000001320612657420553015736 0ustar nickonicko#include #include #include #include "log.h" #include "test_utils.h" #include "data/logdata.h" #include "gmock/gmock.h" #define TMPDIR "/tmp" static const qint64 SL_NB_LINES = 5000LL; static const int SL_LINE_PER_PAGE = 70; static const char* sl_format="LOGDATA is a part of glogg, we are going to test it thoroughly, this is line %06d\n"; static const int SL_LINE_LENGTH = 83; // Without the final '\n' ! static const char* partial_line_begin = "123... beginning of line."; static const char* partial_line_end = " end of line 123.\n"; class LogDataChanging : public testing::Test { public: }; TEST_F( LogDataChanging, changingFile ) { char newLine[90]; LogData log_data; SafeQSignalSpy finishedSpy( &log_data, SIGNAL( loadingFinished( LoadingStatus ) ) ); SafeQSignalSpy progressSpy( &log_data, SIGNAL( loadingProgressed( int ) ) ); SafeQSignalSpy changedSpy( &log_data, SIGNAL( fileChanged( LogData::MonitoredFileStatus ) ) ); // Generate a small file QFile file( TMPDIR "/changingfile.txt" ); if ( file.open( QIODevice::WriteOnly ) ) { for (int i = 0; i < 200; i++) { snprintf(newLine, 89, sl_format, i); file.write( newLine, qstrlen(newLine) ); } } file.close(); // Start loading it log_data.attachFile( TMPDIR "/changingfile.txt" ); // and wait for the signal ASSERT_TRUE( finishedSpy.safeWait() ); // Check we have the small file ASSERT_THAT( finishedSpy.count(), 1 ); ASSERT_THAT( log_data.getNbLine(), 200LL ); ASSERT_THAT( log_data.getMaxLength(), SL_LINE_LENGTH ); ASSERT_THAT( log_data.getFileSize(), 200 * (SL_LINE_LENGTH+1LL) ); // Add some data to it if ( file.open( QIODevice::Append ) ) { for (int i = 0; i < 200; i++) { snprintf(newLine, 89, sl_format, i); file.write( newLine, qstrlen(newLine) ); } // To test the edge case when the final line is not complete file.write( partial_line_begin, qstrlen( partial_line_begin ) ); } file.close(); // and wait for the signals ASSERT_TRUE( finishedSpy.wait( 1000 ) ); // Check we have a bigger file ASSERT_THAT( changedSpy.count(), 1 ); ASSERT_THAT( finishedSpy.count(), 2 ); ASSERT_THAT( log_data.getNbLine(), 401LL ); ASSERT_THAT( log_data.getMaxLength(), SL_LINE_LENGTH ); ASSERT_THAT( log_data.getFileSize(), (qint64) (400 * (SL_LINE_LENGTH+1LL) + strlen( partial_line_begin ) ) ); { SafeQSignalSpy finishedSpy( &log_data, SIGNAL( loadingFinished( LoadingStatus ) ) ); // Add a couple more lines, including the end of the unfinished one. if ( file.open( QIODevice::Append ) ) { file.write( partial_line_end, qstrlen( partial_line_end ) ); for (int i = 0; i < 20; i++) { snprintf(newLine, 89, sl_format, i); file.write( newLine, qstrlen(newLine) ); } } file.close(); // and wait for the signals ASSERT_TRUE( finishedSpy.wait( 1000 ) ); // Check we have a bigger file ASSERT_THAT( changedSpy.count(), 2 ); ASSERT_THAT( finishedSpy.count(), 1 ); ASSERT_THAT( log_data.getNbLine(), 421LL ); ASSERT_THAT( log_data.getMaxLength(), SL_LINE_LENGTH ); ASSERT_THAT( log_data.getFileSize(), (qint64) ( 420 * (SL_LINE_LENGTH+1LL) + strlen( partial_line_begin ) + strlen( partial_line_end ) ) ); } { SafeQSignalSpy finishedSpy( &log_data, SIGNAL( loadingFinished( LoadingStatus ) ) ); // Truncate the file QVERIFY( file.open( QIODevice::WriteOnly ) ); file.close(); // and wait for the signals ASSERT_TRUE( finishedSpy.safeWait() ); // Check we have an empty file ASSERT_THAT( changedSpy.count(), 3 ); ASSERT_THAT( finishedSpy.count(), 1 ); ASSERT_THAT( log_data.getNbLine(), 0LL ); ASSERT_THAT( log_data.getMaxLength(), 0 ); ASSERT_THAT( log_data.getFileSize(), 0LL ); } } class LogDataBehaviour : public testing::Test { public: LogDataBehaviour() { generateDataFiles(); } bool generateDataFiles() { char newLine[90]; QFile file( TMPDIR "/smalllog.txt" ); if ( file.open( QIODevice::WriteOnly ) ) { for (int i = 0; i < SL_NB_LINES; i++) { snprintf(newLine, 89, sl_format, i); file.write( newLine, qstrlen(newLine) ); } } else { return false; } file.close(); return true; } }; TEST_F( LogDataBehaviour, interruptLoadYieldsAnEmptyFile ) { LogData log_data; SafeQSignalSpy endSpy( &log_data, SIGNAL( loadingFinished( LoadingStatus ) ) ); // Start loading the VBL log_data.attachFile( TMPDIR "/verybiglog.txt" ); // Immediately interrupt the loading log_data.interruptLoading(); ASSERT_TRUE( endSpy.safeWait( 10000 ) ); // Check we have an empty file ASSERT_THAT( endSpy.count(), 1 ); QList arguments = endSpy.takeFirst(); ASSERT_THAT( arguments.at(0).toInt(), static_cast( LoadingStatus::Interrupted ) ); ASSERT_THAT( log_data.getNbLine(), 0LL ); ASSERT_THAT( log_data.getMaxLength(), 0 ); ASSERT_THAT( log_data.getFileSize(), 0LL ); } TEST_F( LogDataBehaviour, cannotBeReAttached ) { LogData log_data; SafeQSignalSpy endSpy( &log_data, SIGNAL( loadingFinished( LoadingStatus ) ) ); log_data.attachFile( TMPDIR "/smalllog.txt" ); endSpy.safeWait( 10000 ); ASSERT_THROW( log_data.attachFile( TMPDIR "/verybiglog.txt" ), CantReattachErr ); } glogg-1.1.0/tests/logdataPerfTest.cpp0000664000175000017500000001062512657420553016555 0ustar nickonicko#include #include #include "log.h" #include "test_utils.h" #include "data/logdata.h" #include "gmock/gmock.h" #define TMPDIR "/tmp" static const qint64 VBL_NB_LINES = 4999999LL; static const int VBL_LINE_PER_PAGE = 70; static const char* vbl_format="LOGDATA is a part of glogg, we are going to test it thoroughly, this is line\t\t%07d\n"; static const int VBL_LINE_LENGTH = (76+2+7) ; // Without the final '\n' ! static const int VBL_VISIBLE_LINE_LENGTH = (76+8+4+7); // Without the final '\n' ! class PerfLogData : public testing::Test { public: PerfLogData() { FILELog::setReportingLevel( logERROR ); generateDataFiles(); } bool generateDataFiles() { char newLine[90]; QFile file( TMPDIR "/verybiglog.txt" ); if ( file.open( QIODevice::WriteOnly ) ) { for (int i = 0; i < VBL_NB_LINES; i++) { snprintf(newLine, 89, vbl_format, i); file.write( newLine, qstrlen(newLine) ); } } else { return false; } file.close(); return true; } }; TEST_F( PerfLogData, simpleLoad ) { LogData log_data; QSignalSpy progressSpy( &log_data, SIGNAL( loadingProgressed( int ) ) ); QSignalSpy endSpy( &log_data, SIGNAL( loadingFinished( LoadingStatus ) ) ); { TestTimer t; log_data.attachFile( TMPDIR "/verybiglog.txt" ); ASSERT_TRUE( endSpy.wait( 20000 ) ); } ASSERT_THAT( log_data.getNbLine(), VBL_NB_LINES ); ASSERT_THAT( log_data.getMaxLength(), VBL_VISIBLE_LINE_LENGTH ); ASSERT_THAT( log_data.getLineLength( 123 ), VBL_VISIBLE_LINE_LENGTH ); ASSERT_THAT( log_data.getFileSize(), VBL_NB_LINES * (VBL_LINE_LENGTH+1LL) ); // Blocks of 5 MiB + 1 for the start notification (0%) ASSERT_THAT( progressSpy.count(), log_data.getFileSize() / (5LL*1024*1024) + 2 ); ASSERT_THAT( endSpy.count(), 1 ); QList arguments = endSpy.takeFirst(); ASSERT_THAT( arguments.at(0).toInt(), static_cast( LoadingStatus::Successful ) ); } class PerfLogDataRead : public PerfLogData { public: PerfLogDataRead() : PerfLogData(), log_data(), endSpy( &log_data, SIGNAL( loadingFinished( LoadingStatus ) ) ) { log_data.attachFile( TMPDIR "/verybiglog.txt" ); endSpy.wait( 20000 ); } LogData log_data; SafeQSignalSpy endSpy; }; TEST_F( PerfLogDataRead, sequentialRead ) { ASSERT_THAT( log_data.getNbLine(), VBL_NB_LINES ); // Read all lines sequentially QString s; { TestTimer t; for (int i = 0; i < VBL_NB_LINES; i++) { s = log_data.getLineString(i); } } } TEST_F( PerfLogDataRead, sequentialReadExpanded ) { ASSERT_THAT( log_data.getNbLine(), VBL_NB_LINES ); // Read all lines sequentially (expanded) QString s; { TestTimer t; for (int i = 0; i < VBL_NB_LINES; i++) { s = log_data.getExpandedLineString(i); } } } TEST_F( PerfLogDataRead, randomPageRead ) { ASSERT_THAT( log_data.getNbLine(), VBL_NB_LINES ); // Read page by page from the beginning and the end, using the QStringList // function QStringList list; { TestTimer t; for (int page = 0; page < (VBL_NB_LINES/VBL_LINE_PER_PAGE)-1; page++) { list = log_data.getLines( page*VBL_LINE_PER_PAGE, VBL_LINE_PER_PAGE ); ASSERT_THAT(list.count(), VBL_LINE_PER_PAGE); int page_from_end = (VBL_NB_LINES/VBL_LINE_PER_PAGE) - page - 1; list = log_data.getLines( page_from_end*VBL_LINE_PER_PAGE, VBL_LINE_PER_PAGE ); ASSERT_THAT(list.count(), VBL_LINE_PER_PAGE); } } } TEST_F( PerfLogDataRead, randomPageReadExpanded ) { ASSERT_THAT( log_data.getNbLine(), VBL_NB_LINES ); // Read page by page from the beginning and the end, using the QStringList // function QStringList list; { TestTimer t; for (int page = 0; page < (VBL_NB_LINES/VBL_LINE_PER_PAGE)-1; page++) { list = log_data.getExpandedLines( page*VBL_LINE_PER_PAGE, VBL_LINE_PER_PAGE ); ASSERT_THAT(list.count(), VBL_LINE_PER_PAGE); int page_from_end = (VBL_NB_LINES/VBL_LINE_PER_PAGE) - page - 1; list = log_data.getExpandedLines( page_from_end*VBL_LINE_PER_PAGE, VBL_LINE_PER_PAGE ); ASSERT_THAT(list.count(), VBL_LINE_PER_PAGE); } } } glogg-1.1.0/.tarball-version0000664000175000017500000000000612657420553014714 0ustar nickonicko1.1.0 glogg-1.1.0/README0000664000175000017500000000374112657420553012500 0ustar nickonickoglogg - the fast, smart log explorer glogg is a multi-platform GUI application that helps browse and search through long and complex log files. It is designed with programmers and system administrators in mind and can be seen as a graphical, interactive combination of grep and less. ## Main features * Runs on Unix-like systems, Windows and Mac thanks to Qt * Provides a second window showing the result of the current search * Reads UTF-8 and ISO-8859-1 files * Supports grep/egrep like regular expressions * Colorizes the log and search results * Displays a context view of where in the log the lines of interest are * Is fast and reads the file directly from disk, without loading it into memory * Is open source, released under the GPL ## Requirements * GCC version 4.8.0 or later * Qt libraries (version 5.2.0 or later) * Boost "program-options" development libraries * Markdown HTML processor (optional, to generate HTML documentation) glogg version 0.9.X still support older versions of gcc and Qt if you need to build on an older platform. ## Building The build system uses qmake. Building and installation is done this way: tar xzf glogg-X.X.X.tar.gz cd glogg-X.X.X qmake make make install INSTALL_ROOT=/usr/local (as root if needed) qmake BOOST_PATH=/path/to/boost/ will statically compile the required parts of the Boost libraries whose source are found at the specified path. The path should be the directory where the tarball from www.boost.org is extracted. (use this method on Windows or if Boost is not available on the system) The documentation is built and installed automatically if 'markdown' is found. ## Tests The tests are built using CMake, and require Qt5 and the Google Mocks source. cd tests mkdir build cd build export QT_DIR=/path/to/qt/if/non/standard export GMOCK_HOME=/path/to/gmock cmake .. make ./glogg_tests ## Contact Please visit glogg's website: http://glogg.bonnefon.org/ The development mailing list is hosted at http://groups.google.co.uk/group/glogg-devel glogg-1.1.0/FileAssociation.nsh0000664000175000017500000001105012657420553015376 0ustar nickonicko/* _____________________________________________________________________________ File Association _____________________________________________________________________________ Based on code taken from http://nsis.sourceforge.net/File_Association Usage in script: 1. !include "FileAssociation.nsh" 2. [Section|Function] ${FileAssociationFunction} "Param1" "Param2" "..." $var [SectionEnd|FunctionEnd] FileAssociationFunction=[RegisterExtension|UnRegisterExtension] _____________________________________________________________________________ ${RegisterExtension} "[executable]" "[extension]" "[description]" "[executable]" ; executable which opens the file format ; "[extension]" ; extension, which represents the file format to open ; "[description]" ; description for the extension. This will be display in Windows Explorer. ; ${UnRegisterExtension} "[extension]" "[description]" "[extension]" ; extension, which represents the file format to open ; "[description]" ; description for the extension. This will be display in Windows Explorer. ; _____________________________________________________________________________ Macros _____________________________________________________________________________ Change log window verbosity (default: 3=no script) Example: !include "FileAssociation.nsh" !insertmacro RegisterExtension ${FileAssociation_VERBOSE} 4 # all verbosity !insertmacro UnRegisterExtension ${FileAssociation_VERBOSE} 3 # no script */ !ifndef FileAssociation_INCLUDED !define FileAssociation_INCLUDED !include Util.nsh !verbose push !verbose 3 !ifndef _FileAssociation_VERBOSE !define _FileAssociation_VERBOSE 3 !endif !verbose ${_FileAssociation_VERBOSE} !define FileAssociation_VERBOSE `!insertmacro FileAssociation_VERBOSE` !verbose pop !macro FileAssociation_VERBOSE _VERBOSE !verbose push !verbose 3 !undef _FileAssociation_VERBOSE !define _FileAssociation_VERBOSE ${_VERBOSE} !verbose pop !macroend !macro RegisterExtensionCall _EXECUTABLE _EXTENSION _DESCRIPTION !verbose push !verbose ${_FileAssociation_VERBOSE} Push `${_DESCRIPTION}` Push `${_EXTENSION}` Push `${_EXECUTABLE}` ${CallArtificialFunction} RegisterExtension_ !verbose pop !macroend !macro UnRegisterExtensionCall _EXTENSION _DESCRIPTION !verbose push !verbose ${_FileAssociation_VERBOSE} Push `${_EXTENSION}` Push `${_DESCRIPTION}` ${CallArtificialFunction} UnRegisterExtension_ !verbose pop !macroend !define RegisterExtension `!insertmacro RegisterExtensionCall` !define un.RegisterExtension `!insertmacro RegisterExtensionCall` !macro RegisterExtension !macroend !macro un.RegisterExtension !macroend !macro RegisterExtension_ !verbose push !verbose ${_FileAssociation_VERBOSE} Exch $R2 ;exe Exch Exch $R1 ;ext Exch Exch 2 Exch $R0 ;desc Exch 2 Push $0 Push $1 ReadRegStr $1 HKCR $R1 "" ; read current file association StrCmp "$1" "" NoBackup ; is it empty StrCmp "$1" "$R0" NoBackup ; is it our own WriteRegStr HKCR $R1 "backup_val" "$1" ; backup current value NoBackup: WriteRegStr HKCR $R1 "" "$R0" ; set our file association ReadRegStr $0 HKCR $R0 "" StrCmp $0 "" 0 Skip WriteRegStr HKCR "$R0" "" "$R0" WriteRegStr HKCR "$R0\shell" "" "open" WriteRegStr HKCR "$R0\DefaultIcon" "" "$R2,0" Skip: WriteRegStr HKCR "$R0\shell\open\command" "" '"$R2" "%1"' WriteRegStr HKCR "$R0\shell\edit" "" "Edit $R0" WriteRegStr HKCR "$R0\shell\edit\command" "" '"$R2" "%1"' Pop $1 Pop $0 Pop $R2 Pop $R1 Pop $R0 !verbose pop !macroend !define UnRegisterExtension `!insertmacro UnRegisterExtensionCall` !define un.UnRegisterExtension `!insertmacro UnRegisterExtensionCall` !macro UnRegisterExtension !macroend !macro un.UnRegisterExtension !macroend !macro UnRegisterExtension_ !verbose push !verbose ${_FileAssociation_VERBOSE} Exch $R1 ;desc Exch Exch $R0 ;ext Exch Push $0 Push $1 ReadRegStr $1 HKCR $R0 "" StrCmp $1 $R1 0 NoOwn ; only do this if we own it ReadRegStr $1 HKCR $R0 "backup_val" StrCmp $1 "" 0 Restore ; if backup="" then delete the whole key DeleteRegKey HKCR $R0 Goto NoOwn Restore: WriteRegStr HKCR $R0 "" $1 DeleteRegValue HKCR $R0 "backup_val" DeleteRegKey HKCR $R1 ;Delete key with association name settings NoOwn: Pop $1 Pop $0 Pop $R1 Pop $R0 !verbose pop !macroend !endif # !FileAssociation_INCLUDED glogg-1.1.0/config.h.in0000664000175000017500000000133712657420553013642 0ustar nickonicko/*-------------------------------------------------------------------------- * This file is autogenerated from config.h.in * during the cmake configuration of your project. If you need to make changes * edit the original file NOT THIS FILE. * --------------------------------------------------------------------------*/ #ifndef _CONFIGURATION_HEADER_GUARD_H_ #define _CONFIGURATION_HEADER_GUARD_H_ /* Whether the 'symlink' function exists */ #cmakedefine HAVE_SYMLINK 1 /* Whether C++14 std::make_unique is available */ #cmakedefine HAVE_MAKE_UNIQUE 1 /* Whether the keyword override is available */ #cmakedefine HAVE_OVERRIDE 1 /* Whether the compiler supports template aliases */ #cmakedefine HAVE_TEMPLATE_ALIASES 1 #endif glogg-1.1.0/.gitignore0000664000175000017500000000107712657420553013610 0ustar nickonicko# git-ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): *.[oa] *~ moc_*.cpp ui_*.h .*.sw? qrc_glogg.cpp Makefile glogg glogg_tests massif.out* core.* doc/*.html # File generated by Qt Creator glogg.config glogg.creator glogg.creator.user glogg.files glogg.includes glogg.pro.user Makefile.Debug Makefile.Release debug/ release/ object_script.glogg.Debug object_script.glogg.Release # Tests tests/build/ glogg-1.1.0/release-source.sh0000775000175000017500000000101712657420553015067 0ustar nickonicko#!/bin/bash VERSION=$(git describe | sed -e "s/^v//") echo "echo \"$VERSION\" > .tarball-version" echo 'touch --date="$(git log -n 1 --pretty=format:%ci)" .tarball-version' echo "tmp_tar=\"$(mktemp)\"" echo "git archive --format=tar --prefix=glogg-$VERSION/ v$VERSION >\$tmp_tar" echo "tmp_dir=\"$(mktemp -d)\"" echo "tar xf \$tmp_tar --directory \$tmp_dir" echo "cp -p .tarball-version \$tmp_dir/glogg-$VERSION/" echo "tar czf glogg-$VERSION.tar.gz --directory \$tmp_dir glogg-$VERSION/" echo "rm .tarball-version \$tmp_tar" glogg-1.1.0/doc/0000775000175000017500000000000012657420553012360 5ustar nickonickoglogg-1.1.0/doc/documentation.markdown0000664000175000017500000001131212657420553016773 0ustar nickonicko ## Getting started _glogg_ can be started from the command line, optionally passing the file to open as an argument, or via the desktop environment's menu or file association. If no file name is passed, _glogg_ will initially open the last used file. The main window is divided in three parts : the top displays the log file. The bottom part, called the "filtered view", shows the results of the search. The line separating the two contains the regular expression used as a filter. Entering a new regular expression, or a simple search term, will update the bottom view, displaying the results of the search. The lines matching the search criteria are listed in order in the results, and are marked with a red circle in both windows. ## Exploring log files Regular expressions are a powerful way to extract the information you are interested in from the log file. _glogg_ uses _extended regular expressions_. One of the most useful regexp feature when exploring logs is the _alternation_, using parentheses and the | operator. It searches for several alternatives, permitting to display several line types in the filtered window, in the same order they appear in the log file. For example, to check that every connection opened is also closed, one can use an expression similar to: `Entering (Open|Close)Connection` Any 'open' call without a matching 'close' will immediately be obvious in the filtered window. The alternation also works with the whole search line. For example if you also want to know what kind of connection has been opened: `Entering (Open|Close)Connection|Created a .* connection` `.*` will match any sequence of character on a single line, but _glogg_ will only display lines with a space and the word `connection` somewhere after `Created a` In addition to the filtered window, the match overview on the right hand side of the screen offers a view of the position of matches in the log file. Matches are showed as small red lines. ## Using filters _Filters_ can colorize some lines of the log being displayed, for example to draw attention to lines indicating an error, or to associate a color with each sort of event. Any number of filter can be defined in the 'Filters' configuration dialog, each using a regexp against which lines will be matched. For each line, all filters are tried in order and the fore and back colors of the first successful filter are applied. ## Marking lines in the log file In addition to regexp matches, _glogg_ enable the user to mark any interesting line in the log. To do this, click on the round bullet in the left margin in front of the line that needs to be marked. Marks are combined with matches and showed in the filtered window. They also appears as blue lines in the match overview. ## Browsing changing log files _glogg_ can display and search through logs while they are written to disk, as it might be the case when debugging a running program or server. The log is automatically updated when it grows, but the 'Auto-refresh' option must be enabled if you want the search results to be automatically refreshed as well. The 'f' key might be used to follow the end of the file as it grows (_a la_ `tail -f`). ## Settings ### Font The font used to display the log file. A clear, monospace font (like the free, open source, [DejaVu Mono](http://www.dejavu-fonts.org/) for example) is recommended. ### Search options Determines which type of regular expression _glogg_ will use when filtering lines for the bottom window, and when using QuickFind. * Extended Regexp: the default, uses regular expressions similar to those used by Perl * Wildcards: uses wildcards (\*, ? and []) in a similar fashion as a Unix shell * Fixed Strings: searches for the text exactly as it is written, no character is special ## Keyboard commands _glogg_ keyboard commands try to approximatively emulate the default bindings used by the classic Unix utilities _vi_ and _less_. The main commands are:
arrows scroll one line up/down or one column left/right
[number] j/k move the selection 'number' (or one) line down/up
h/l scroll left/right
[number] g jump to the line number given or the first one if no number is entered
G jump to the last line of the file (selecting it)
/ start a quickfind search in the current screen
n/N repeat the previous quickfind search forward/backward
*/# search for the next/previous occurence of the currently selected text
f activate 'follow' mode, which keep the display as the tail of the file (like "tail -f")
glogg-1.1.0/finish.sed0000664000175000017500000000072412657420553013573 0ustar nickonicko# Add header/footer around the Markdown generated documentation # Copied from Damian Cugley (http://www.alleged.org.uk/2005/marky/) 1 { h i\ \ \ s/h2>/title>/g rmetadata.inc a\ \ rheader.inc } 2 { x p x } $ { rfooter.inc a\ \ } glogg-1.1.0/tools/0000775000175000017500000000000012657420553012753 5ustar nickonickoglogg-1.1.0/tools/perfmeter.pl0000775000175000017500000000174412657420553015312 0ustar nickonicko#!/usr/bin/perl # Take a debug log from logcrawler and output some perf statistics # Can be plotted by echo "plot [ ] [0:0.1] 'foo.data'; pause mouse key;" | gnuplot - # Or an histogram: # plot './qvector.data' using ((floor($1/50)+0.5)*50):(1) smooth frequency w histeps, './qvar_default.data' using ((floor($1/50)+0.5)*50):(1) smooth frequency w histeps, './qvar_50000.data' using ((floor($1/50)+0.5)*50):(1) smooth frequency w histeps # Better: plot './0.6.0-3.data' using ((floor($1/0.005)+0.1)*0.005):(0.1) smooth frequency w histeps while (<>) { strip; if (/(\d\d\.\d\d\d) DEBUG4.*paintEvent.*firstLine=(\d+) lastLine=(\d+) /) { if ( ($3 - $2) > 35 ) { $beginning = $1; $first_line = $2; } } elsif (/(\d\d\.\d\d\d) DEBUG4.*End/) { if ($beginning) { $time = $1 - $beginning; # print "$first_line $time\n"; if ($time > 0) { print "$time\n"; } } } } glogg-1.1.0/tools/timer.pl0000775000175000017500000000100112657420553014423 0ustar nickonicko#!/usr/bin/perl use Time::Local; while (<>) { chomp; print "$_\n"; if (/^- (\d*):(\d*):(\d*)\.(\d*) /) { $time = timelocal($3, $2, $1, (localtime)[3,4,5]) + ($4/1000.0); # print "$time\n"; if (/DEBUG: FullIndexOperation: Starting the count\.\.\./) { $startcount = $time; print "$startcount\n"; } elsif (/DEBUG: FullIndexOperation: \.\.\. finished counting\./) { print "Counting: ",$time-$startcount,"\n"; } } } glogg-1.1.0/tools/genlogs.sh0000775000175000017500000000073012657420553014750 0ustar nickonicko#!/bin/bash # Generate a set of test logs # 5 million lines - 1, 429Mb perl -e 'for ($i = 0; $i < 4999999; $i++) { printf "LOGDATA is a part of LogCrawler, we are going to test it thoroughly, this is line %06d\n", $i; }'\ >/tmp/verybiglog.txt # 100 million lines - 1, Mb # perl -e 'for ($i = 0; $i < 100000000; $i++) # { printf "LOGDATA is a part of LogCrawler, we are going to test it thoroughly, this is line %07d\n", $i; }'\ # >~/stupidlybiglog.txt glogg-1.1.0/glogg.pro0000664000175000017500000001674412657420553013450 0ustar nickonicko# ------------------------------------------------- # glogg # ------------------------------------------------- # Debug builds: qmake CONFIG+=debug # Release builds: qmake TARGET = glogg TEMPLATE = app greaterThan(QT_MAJOR_VERSION, 4): QT += core widgets win32:Debug:CONFIG += console # Necessary when cross-compiling: win32:Release:QMAKE_LFLAGS += "-Wl,-subsystem,windows" # Input SOURCES += \ src/main.cpp \ src/session.cpp \ src/data/abstractlogdata.cpp \ src/data/logdata.cpp \ src/data/logfiltereddata.cpp \ src/data/logfiltereddataworkerthread.cpp \ src/data/logdataworkerthread.cpp \ src/data/compressedlinestorage.cpp \ src/mainwindow.cpp \ src/crawlerwidget.cpp \ src/abstractlogview.cpp \ src/logmainview.cpp \ src/filteredview.cpp \ src/optionsdialog.cpp \ src/persistentinfo.cpp \ src/configuration.cpp \ src/filtersdialog.cpp \ src/filterset.cpp \ src/savedsearches.cpp \ src/infoline.cpp \ src/menuactiontooltipbehavior.cpp \ src/selection.cpp \ src/quickfind.cpp \ src/quickfindpattern.cpp \ src/quickfindwidget.cpp \ src/sessioninfo.cpp \ src/recentfiles.cpp \ src/overview.cpp \ src/overviewwidget.cpp \ src/marks.cpp \ src/quickfindmux.cpp \ src/signalmux.cpp \ src/tabbedcrawlerwidget.cpp \ src/viewtools.cpp \ src/encodingspeculator.cpp \ INCLUDEPATH += src/ HEADERS += \ src/data/abstractlogdata.h \ src/data/logdata.h \ src/data/logfiltereddata.h \ src/data/logfiltereddataworkerthread.h \ src/data/logdataworkerthread.h \ src/mainwindow.h \ src/session.h \ src/viewinterface.h \ src/crawlerwidget.h \ src/logmainview.h \ src/log.h \ src/filteredview.h \ src/abstractlogview.h \ src/optionsdialog.h \ src/persistentinfo.h \ src/configuration.h \ src/filtersdialog.h \ src/filterset.h \ src/savedsearches.h \ src/infoline.h \ src/filewatcher.h \ src/selection.h \ src/quickfind.h \ src/quickfindpattern.h \ src/quickfindwidget.h \ src/sessioninfo.h \ src/persistable.h \ src/recentfiles.h \ src/menuactiontooltipbehavior.h \ src/overview.h \ src/overviewwidget.h \ src/marks.h \ src/qfnotifications.h \ src/quickfindmux.h \ src/signalmux.h \ src/tabbedcrawlerwidget.h \ src/loadingstatus.h \ src/externalcom.h \ src/viewtools.h \ src/encodingspeculator.h \ isEmpty(BOOST_PATH) { message(Building using system dynamic Boost libraries) macx { # Path for brew installed libs INCLUDEPATH += /usr/local/include LIBS += -L/usr/local/lib -lboost_program_options-mt } else { LIBS += -lboost_program_options } } else { message(Building using static Boost libraries at $$BOOST_PATH) SOURCES += $$BOOST_PATH/libs/program_options/src/*.cpp \ $$BOOST_PATH/libs/smart_ptr/src/*.cpp INCLUDEPATH += $$BOOST_PATH } FORMS += src/optionsdialog.ui FORMS += src/filtersdialog.ui # For Windows icon RC_FILE = glogg.rc RESOURCES = glogg.qrc # Build HTML documentation (if 'markdown' is available) system(type markdown >/dev/null) { MARKDOWN += doc/documentation.markdown } else { message("markdown not found, HTML doc will not be generated") } doc_processor.name = markdown doc_processor.input = MARKDOWN doc_processor.output = doc/${QMAKE_FILE_BASE}.html doc_processor.commands = markdown ${QMAKE_FILE_NAME} | \ sed -f finish.sed >${QMAKE_FILE_OUT} doc_processor.CONFIG += target_predeps doc_processor.variable_out = doc.files QMAKE_EXTRA_COMPILERS += doc_processor # Install (for unix) icon16.path = $$PREFIX/share/icons/hicolor/16x16/apps icon16.files = images/hicolor/16x16/glogg.png icon32.path = $$PREFIX/share/icons/hicolor/32x32/apps icon32.files = images/hicolor/32x32/glogg.png icon_svg.path = $$PREFIX/share/icons/hicolor/scalable/apps icon_svg.files = images/hicolor/scalable/glogg.svg doc.path = $$PREFIX/share/doc/glogg doc.files += README COPYING desktop.path = $$PREFIX/share/applications desktop.files = glogg.desktop target.path = $$PREFIX/bin INSTALLS = target icon16 icon32 icon_svg doc desktop # Build directories CONFIG(debug, debug|release) { DESTDIR = debug } else { DESTDIR = release } OBJECTS_DIR = $${OUT_PWD}/.obj/$${DESTDIR}-shared MOC_DIR = $${OUT_PWD}/.moc/$${DESTDIR}-shared UI_DIR = $${OUT_PWD}/.ui/$${DESTDIR}-shared # Debug symbols even in release build QMAKE_CXXFLAGS = -g # Which compiler are we using system( $${QMAKE_CXX} --version | grep -e " 4\\.[7-9]" ) || macx { message ( "g++ version 4.7 or newer, supports C++11" ) CONFIG += C++11 } else { CONFIG += C++0x } # Extra compiler arguments # QMAKE_CXXFLAGS += -Weffc++ QMAKE_CXXFLAGS += -Wextra C++0x:QMAKE_CXXFLAGS += -std=c++0x C++11:QMAKE_CXXFLAGS += -std=c++11 GPROF { QMAKE_CXXFLAGS += -pg QMAKE_LFLAGS += -pg } isEmpty(LOG_LEVEL) { CONFIG(debug, debug|release) { DEFINES += FILELOG_MAX_LEVEL=\"logDEBUG4\" } else { DEFINES += FILELOG_MAX_LEVEL=\"logDEBUG\" } } else { message("Using specified log level: $$LOG_LEVEL") DEFINES += FILELOG_MAX_LEVEL=\"$$LOG_LEVEL\" } macx { QMAKE_CXXFLAGS += -stdlib=libc++ QMAKE_LFLAGS += -stdlib=libc++ } # Official builds can be generated with `qmake VERSION="1.2.3"' isEmpty(VERSION):system(date >/dev/null) { system([ -f .tarball-version ]) { QMAKE_CXXFLAGS += -DGLOGG_VERSION=\\\"`cat .tarball-version`\\\" } else { QMAKE_CXXFLAGS += -DGLOGG_DATE=\\\"`date +'\"%F\"'`\\\" QMAKE_CXXFLAGS += -DGLOGG_VERSION=\\\"`git describe`\\\" QMAKE_CXXFLAGS += -DGLOGG_COMMIT=\\\"`git rev-parse --short HEAD`\\\" } } else { QMAKE_CXXFLAGS += -DGLOGG_VERSION=\\\"$$VERSION\\\" } # Optional features (e.g. CONFIG+=no-dbus) system(pkg-config --exists QtDBus):!no-dbus { message("Support for D-BUS will be included") QT += dbus QMAKE_CXXFLAGS += -DGLOGG_SUPPORTS_DBUS SOURCES += src/dbusexternalcom.cpp HEADERS += src/dbusexternalcom.h } else { message("Support for D-BUS will NOT be included") win32 { message("Support for Windows IPC will be included") QMAKE_CXXFLAGS += -DGLOGG_SUPPORTS_WINIPC SOURCES += src/winexternalcom.cpp HEADERS += src/winexternalcom.h } } # Version checking version_checker { message("Version checker will be included") QT += network QMAKE_CXXFLAGS += -DGLOGG_SUPPORTS_VERSION_CHECKING SOURCES += src/versionchecker.cpp HEADERS += src/versionchecker.h } else { message("Version checker will NOT be included") } # File watching linux-g++ || linux-g++-64 { CONFIG += inotify } win32 { message("File watching using Windows") SOURCES += src/platformfilewatcher.cpp src/winwatchtowerdriver.cpp src/watchtower.cpp src/watchtowerlist.cpp HEADERS += src/platformfilewatcher.h src/winwatchtowerdriver.h src/watchtower.h src/watchtowerlist.h QMAKE_CXXFLAGS += -DGLOGG_SUPPORTS_POLLING } else { inotify { message("File watching using inotify") QMAKE_CXXFLAGS += -DGLOGG_SUPPORTS_INOTIFY SOURCES += src/platformfilewatcher.cpp src/inotifywatchtowerdriver.cpp src/watchtower.cpp src/watchtowerlist.cpp HEADERS += src/platformfilewatcher.h src/inotifywatchtowerdriver.h src/watchtower.h src/watchtowerlist.h } else { message("File watching using Qt") SOURCES += src/qtfilewatcher.cpp HEADERS += src/qtfilewatcher.h } } # Performance measurement perf { QMAKE_CXXFLAGS += -DGLOGG_PERF_MEASURE_FPS } glogg-1.1.0/glogg48.ico0000664000175000017500000002267612657420553013577 0ustar nickonicko00 %(0` d !@#^ZZLrWttssb,,& `& `& `& `& `& `& `& `& `& `& `& `& `& `& `& `& `)/)*3-Q>;;ywwywwsqq n263􎒑􅌋YaZ;;TQQa^^|zz^ZZmkknwqqppKMJ9B9ݩӧ lgddEBBdaaqpp ebb|zzdaaswsqppDHB?DAyDhggB??_\\wvvÛĠZWWJPHnspgmjada^aZ_d^KPJ>}}a^^y䲃庑mJ_?!Z<#W<#ZZWWHD䲅軒ÞʬӸջʭ庒q0\!ebbtyw, >mkkϵ3g-䴆轗ÞíƲۺֲ֬դw'õTQQw}w, >wGWp9ϧٺwZBM0J-M/W3_5h;s?)anmm)&&|, >tssBa ^ʺ̺jNzV9W6Z5W/\-GVJGGMJJMJJMJJ^ZZ, >#ؗZҩӵ軒䵈}۞g,, >!päqaZTJ!qp, >)z<Ǭvw9)&, >VW޻W;6kg3qp|z, >a\լÚޠdܚ^HyYVV, > yAջ޵p3k-KHH}}|}}|}}|zz, >tssW/ӭкǬֲM#s)&&, >kjj&υDӵ̒\a9_\\, >ǻ,\ÚմmT /wvv, >W/k#zgWQB!j6GDMJMJKJKJKJKHZZ, >mkkʭj5-0m;̴/-/,, >MPMmmm, >^b^, >EJG^ZZ200200,))$##! ! ! ! ! ! ! ! ! ! ! , >DHDnmm)'')'')'')'')'')'')'')'')'')'')'')'')'')'')'', >^b^, >QVQ, >DHD, >TZVnsq$!!>wzywzywzywzywzywzywzywzywzywzywzywzywzywzywzyTWV2656;96;96;96;96;96;96;96;9050>ͱ>ͱ>ͱ>皛>^^^_<>qss >K$.YZZ $$$$$$$$$$$$$$$$$$$$$$$$?glogg-1.1.0/images/0000775000175000017500000000000012657420553013060 5ustar nickonickoglogg-1.1.0/images/arrowdown.png0000664000175000017500000000055212657420553015612 0ustar nickonickoPNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDAT8͒=N1 F#Ӎ~:FG4H܀Pq . ;Jpl쀐X\Eω8$A8a^3U}l&/4ff_!s.N)5p+"{#=B)11RJ`5'oL)i)7 `q\W"齿(=Y]f "vT˵jڶ-E{^ klo)IENDB`glogg-1.1.0/images/arrowup.png0000664000175000017500000000054012657420553015264 0ustar nickonickoPNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDAT8ݒ1n0 E?䑴 p}zޡ{ѥS)E hU^.va jx?RݱGaW-["6c E06)۷'X%Fa1&d>! Mݓyg+?%IENDB`glogg-1.1.0/images/up.png0000664000175000017500000000126412657420553014215 0ustar nickonickoPNG  IHDRĴl;gAMAOX2tEXtSoftwareAdobe ImageReadyqe<FIDAT8˵ԿOAYA 9-QƆ!D BJ!++mB ! Ƙ%THqp[yd̛7*;;a.7XDN/EDF@FUm"r9cx$"A~ig$]D|?< ρQf+` h[Y? ,QX"BU%Te]VZz (њ)灼b<㲪/'6+mY@6,bnuK/0eeWV+C} ܺ`WM,x4 cT*Jn;cWz~4|zwIENDB`glogg-1.1.0/images/reload14@2x.png0000664000175000017500000000206312657420553015554 0ustar nickonickoPNG  IHDR ½"sBIT|d pHYs^tEXtSoftwarewww.inkscape.org<IDATHŖOLu?ϯV0P2Mhu qAfߨED=dbv'xNs7gЃ#Ypz .),ʠ @kqsz{~޼BWom0RHGS5v^e%VH]eQ&SVMx<۰@yE*T`Bn+r/,1nDU}s8~ uF 2eS9322rsa`)EhT=4E qdt~ppf.rǀ6oǙ.)"U'R͖ds5QL6׿Zshr:1\/,n |:q`ShO]/Ũ ^c@pؓL60ЄL6  ն>:z#eZ.pW_k@:=U5mmCM07*C%MF#m4UT1*Fa #IENDB`glogg-1.1.0/images/minus.png0000664000175000017500000000065512657420553014727 0ustar nickonickoPNG  IHDRw=sBIT|d pHYsvv}ՂtEXtSoftwarewww.inkscape.org<*IDATH1KAɅHb킢T_&"haJ'γػKx0f 8!ǁ>YUH*L/O_rGj9TJkI@5U V-jfQj, ,3k79z0p-}>08ks*ڶc1*B2{Rob>?}C"mZّ;VAmJG$]b* Se9o& "z|U6EU#>j(xEoM^k4e2wR(KYطg *o\t3 XaK?ǃݮdS3%Kh"hu:{oAc"ICo+ $~VIENDB`glogg-1.1.0/images/hicolor/0000775000175000017500000000000012657420553014517 5ustar nickonickoglogg-1.1.0/images/hicolor/16x16/0000775000175000017500000000000012657420553015304 5ustar nickonickoglogg-1.1.0/images/hicolor/16x16/glogg.png0000664000175000017500000000206712657420553017116 0ustar nickonickoPNG  IHDRagAMA|Q cHRMz%u0`:oIDATxb477ssvv/@ Yi666+Wܲer 4_Ν9@ SAׯ_trKH29X3lc M߽{kǏ >g`bbb/s}}}'?|to"b9Ts~=~VV #ϟ?JK|3P9ϩ3w@5fx #f77wgN3|ۗ `U14fPUP`  W&/^1lzXo0ᗦjf&F =7  b"  1L)iv-~3_f`})v6> ?2ɰ#C[#ŻZb`av82 &X \,X{ ,?XM0n`j _0 @ _1|-0Y} ? ޝb;b`2w`e~(=~5ߏ םbPd`bf@L>y̙W ΋W?dg|6Ù;o< )ϐ*+1I1@|@ BPP~ΜsΝ;`aep;Ý71x 0peetׯ_ϙqO>}dl'wᄒ5V_?u@]|Y0,_d, [*)νwct9" /Y Cݻ8hflrD0ď:LӈIHYYCPQx5+0ğ={!ϰknMz" L(߿0`jp1L,$@!!!Б<0u4`Ęa~ze3g΂e^^>L66Hy…b߽ghkkg}SH&/0<@-=T#5]c X."ݻwbW}p@b``cHsgsf4i `l]*(dx`dSIM̐Ǡ t<@Y+1|_Hgؾf/Ͽ ^cPƠ:EAPPAA]e7];`@L( Px"*/gXp>wy ߀!0o".00C/6 LBL&?Ý2e2kpicccsgAݏ;ϴ4۷A4RȊ`M  Zzܤ(ch_ÐĠ& ,>M>p &c7#Ġ,'Űg>X < O>!Rd2{ wL`.'P9r`EV !!d;20Dfnc0 6bWca`g`a@͗l̰u J6Ж-u JXawd`&@v%8Yx qP xOOwpz?~Pٗ \l ?7^&4þNOQ~p`nn10&y8&]3 f3عO>}15@(@˷o89ܹ$t-YYY}}]###;w23AQA!{ #C"8D9~ĉw *@/@Xӧ=@ZA=҅>>>LoTUUVQQ)@ ށ|p" 6l`8Cww0Py8y5C `*O21ON`yEE>77ֻw(.F'C`I!snL^^^uuU@N#@INP3g&ֱTPRGA5˗>|xv~Ăɸ<@X=JBZ۝Y@jj* bb`ǃt/֞Y9810t-pl e;Ͻ0yUN7\@vPd,3 s++zAi]KKQ;7իyM׸ 8@x33e榌4rhn==yZt|+L 9}ȔǏ޼7-DN [~FP e*qq1_|,:~ OD@ `p?9 2t IPׯ _!-G/0N<|  x1 @Ʌ EDiyժU4/,,P] ?)@bf]]!/x daaa QQq߾}w> ECD41@rR8)8aHD)IENDB`glogg-1.1.0/images/hicolor/scalable/0000775000175000017500000000000012657420553016265 5ustar nickonickoglogg-1.1.0/images/hicolor/scalable/glogg.svg0000664000175000017500000020265212657420553020114 0ustar nickonicko image/svg+xml Jakub Steiner input keyboard keys peripheral HID http://jimmac.musichall.cz/ image/svg+xml glogg-1.1.0/images/hicolor/24x24/0000775000175000017500000000000012657420553015302 5ustar nickonickoglogg-1.1.0/images/hicolor/24x24/glogg.png0000664000175000017500000000371112657420553017111 0ustar nickonickoPNG  IHDRw=gAMA|Q cHRMz%u0`:oTIDATxbTRR7/&&FBm˖>|xݻp9m{ff1x Qo~XYYN:z۶UK z _|c`bbb`ccJ+Sӷo噘r&)Z76 @|A]]ӧo ǎ]l;;12xx؊nݺ[(oP ?v8`2#Yɓ  7oֱLebfF/k@wff칋  66 e`aaYr|TFF&_AcdxCd\f9PY@aXRȿ@X8};838,#H=pPd`cge`gf @ƍ{fgx0L4˰bf2 bB l@qښݭA?p$ JJr@_p3| 1˪10cGƿ$Yd~6T?2S<° /feghZ b?0̠*(.Ԥ! CG6<} qP*f(P<CRC0W?0C܃ s132&OqaAs#-/?tE6.> )Z  2l  < J 0([24X0g5#xh@> LR bx\P_ ~1{^n;2ܽ&AA?V p΅@|قW};%eq? o~3|bp_b5$pu~np  "[ << /cx/Ç_n_p#03q2||G h+` ** ,2 +ddāg=172 .e8v>맯xW Ɔ |d`bkA`ӧ/> L` ~A Q. >ag+/7Çw, ) ?a~6??q`7p %Km+ )ð{ '`߿å˯N^~ͰCu+/0H2DKދ ߿}X^11|_ `}֒% |tƌiq7ǢpDVO9fc#)+Le\ j">,UEo, >X=} ! ?fq))܊r /lhmi bf`eWl޼}ٳ ݾxr\^NNV!! wYYE`*f8zto=ZIIŋ }aebaw1ÅKghkGq>(r::QR2 {,yy9Xs޽!|{(6&0} ՛X/]>fbf${ 33VMM*C}}/v| (X5778 Lb12 : X3ެWGO X}qǾ}V {:F-PML Bx, ދ[ԴvIENDB`glogg-1.1.0/images/hicolor/32x32/0000775000175000017500000000000012657420553015300 5ustar nickonickoglogg-1.1.0/images/hicolor/32x32/glogg.png0000664000175000017500000000565012657420553017113 0ustar nickonickoPNG  IHDR szzgAMA|Q cHRMz%u0`:o 3IDATxb1006fgFF 3ӧϿ޹s4;;o>Eٹ44Oׯ^1{w,@,{'ï_5 89X>V NN ll ?b8xíۏ881C Wl{wJ: u5U2ӗO>[^`f>}ʕ{x`t:й_jkrxq'b#lǎ_aMLӆ9 p<77/+Õk7GX,jAn@a #n|pC~~^o3@ MM} |4l`C? ȱ_``g()g3$`~Pƿ Ofx%a\@u :85~f2&6l8C pF ""@a ?feRPf+de_>~/=A0}(,™pS`k$F)ɫ2p ߿~3:A!%Fۗ; ܮ^a#f` bbpp0ƛ0eb!(Cm?3{`rg;1lN?0ݎAaۊ a6 !?10"gXj?ѣ?ϐZz!.ߎ9I@ݳ zc[w3FdX1ᯧ Cѳ lw}=-b ;bB::  J* }gԍ%Jr>c; _ '&ap0bxvÕgM$@Xse@H 1Ы 51  5Ё`Mx$1dfYA1Ha eɠŠ- G\_011c@% X΁9v`(- X~K`:,sO6}?ΰ&ʧ>p=fxy?C LXYX@*CDbAr3(Pf`ay50*D땏߀ #0x9^|p/Gn߼ 1AKeorBV0@0bxi P"./!pU %48y>p};2Gfv>>.1\3 `ud ,-EBx"5DuΝ 7O45dY%;0<_d#On-Qݫ0|ӽ@_ !>988C7Ew \}pwg?3p13`ܾoeX83?Ll } RRb:] 1_[@,O<}'||\H0]voPSpp?sO^<0g<??203| 3C[{,ÆϘðh|p}/UUm'3p۝L,0˗/<8 x;Gp{==)[/3h`Wd&+ ì9 O]֖Wq,<mLL9ɰsGA _?ٳ뾬̣GձYXr#Ơ&g&`+&`&g]gp1.03xx80p s`;QX7;qaѲ ]QS]tb>HTRR# *3 .ή  << ^X1v͇9\E_خ /-pKW2<~~3?7 匌, @a͚o,->u^z7#Jf/v. oq1p{F_fx Ӈy9wܸp>, (g99<<\@_[__.-_ b` L&F-aַ0<|ϟ Ba@eo4EP@C>|xLϾxq/ޫ_*]Tn, o~ R>vÍ[Wӥ_>ձpoeu~@ Y=Q| N`x8qׇ/7xY?sXnKpeDr?~"".nذc'0*2`rff6))NÇ+\{5oV<{|o0v7](L̬,\l\NvV׭߁S9@z͚œZM...΃wNZis(Y9aT.(2a`bw: 9 2IENDB`glogg-1.1.0/images/stop14.png0000664000175000017500000000054112657420553014720 0ustar nickonickoPNG  IHDRH-bKGD pHYs eB|tIME88 IDAT(N0 P|NˁhbBh$>lh!Ү&洪)Q";1Ţ͆5wRfARF(y k{uR{8yUȲv.aKahWu!}q@^NѿkHź/#JB%lνvT>Ԕ\qG7*; Wev?Xx_ @*܈>ʑIENDB`glogg-1.1.0/images/down.png0000664000175000017500000000112212657420553014531 0ustar nickonickoPNG  IHDRĴl;gAMAOX2tEXtSoftwareAdobe ImageReadyqe<IDAT8O`[i;7̖hB8͋(HH nB#:cƏ9T@<)r/}jKg<}oYt&i)HF%(EF lxO-oxlP3zwQ4M=)칝^4xڽ!ǏC='/hwv9LKVQ=xGXr9:vQ*^vmTb/>ebսu־ϵ _`D*r`:x#P)v2Pqdd܉;#4Mlq3K9lil+RXxؤ*JS8%m1躏<=|ZQ7oJ #tV BIENDB`glogg-1.1.0/images/newdata_icon@2x.png0000664000175000017500000000033012657420553016567 0ustar nickonickoPNG  IHDR KbKGD pHYs  tIMEJeIDATHc`P6g >C%MF#?5ͤY =e#p$#E)9NI1hA g$YɲLG YIENDB`glogg-1.1.0/images/open14@2x.png0000664000175000017500000000117112657420553015246 0ustar nickonickoPNG  IHDR^sBIT|d pHYs&tEXtSoftwarewww.inkscape.org<IDATH=hA{\nBQQDTDA0V),B`%"`a* S H%hbrw"hgjw;@ޘMs4Nѫpň3&_]<z҇Ο?+񵶪恳&炣:Qq֨捳F9Pgraczy)ȚsQa@8_[~`\eϴ(kF\?0-˞mvbV+T Nu&Ҩ( kR,@_ގr&y S' 2wa@&YO:<t61I{="NpZ=/ݨ/Md/DdtneS>e+; @7FBruiYc,Iu m,_BL Fݑ59.KSOǀ (/J]FLFv~F%S!XRIENDB`glogg-1.1.0/glogg.desktop0000664000175000017500000000072712657420553014313 0ustar nickonicko[Desktop Entry] Name=glogg GenericName=Log file browser # %f because glogg supports only one file for now Exec=glogg %f Icon=glogg Type=Application Comment=A smart interactive log explorer. Terminal=false Categories=Qt;Utility;TextTools;Development; MimeType=text/plain; Actions=Session;NewInstance; [Desktop Action Session] Exec=glogg --load-session %f Name=Open With Previous Session [Desktop Action NewInstance] Exec=glogg --multi %f Name=Open In A New glogg Window glogg-1.1.0/INSTALL.win.md0000664000175000017500000001146712657420553014050 0ustar nickonicko# Building glogg for Windows As often on Windows, building _glogg_ is more complicated than we would like. The method we use to test and generate the builds available on glogg.bonnefon.org is to cross-build _glogg_ from a Debian/Ubuntu machine. The compiler currently used is MinGW-W64 4.6.3, which is available on Debian unstable and Ubuntu 12.04 LTS: apt-get install g++-mingw-w64-i686 There are two dependencies that must be built for Windows: - Qt, we currently use version 4.8.2 - Boost, we currently use version 1.50.0 Once the dependencies are installed (see below), a script takes care of the building and the packaging: ./release-win32-x.sh ## Building Qt on Windows It is allegedly possible to cross-compile Qt from Windows but after a lot of frustration, the _glogg_ team is building it natively on Windows (using a Windows 7 64bit VM in VirtualBox) and then using the binary generated from the Linux machine. Amazingly, it works as long as we are using a similar version of gcc (MinGW-W64) on both machines. Here are instructions to do the build in a Windows 7 VM: - Download the source from qt website (tested 4.8.2), be sure to download it with DOS end-of-line conventions (zip archive instead of tar.bz2 or tar.gz). - Install the .NET Framework 4 (http://www.microsoft.com/en-us/download/details.aspx?id=17718) - Install the Windows SDK (http://www.microsoft.com/en-us/download/details.aspx?id=8279) - Install mingw-w64 from TDM-GCC (tdm64-gcc, tested with 4.6.1). - Extract the Qt source in c:\qt\4.8.2 If building a 32 bits version of Qt (what we do for _glogg_): - Modify qt/4.8.2/mkspecs/win32-g++/qmake.conf to add `-m32` to `QMAKE_CFLAGS` and `QMAKE_LFLAGS` - Modify qt/4.8.2/mkspecs/win32-g++/qmake.conf to replace: `QMAKE_RC = windres -F pe-i386` - (optionally make other changes here to improve performances) Build from the MinGW command prompt: configure.exe -platform win32-g++-4.6 -no-phonon -no-phonon-backend -no-webkit -fast -opensource -shared -no-qt3support -no-sql-sqlite -no-openvg -no-gif -no-opengl -no-scripttools -qt-style-windowsxp -qt-style-windowsvista mingw32-make - copy the whole `qt/4.8.2` to the linux machine in `~/qt-x-win32/qt_win/4.8.2` ### Creating the cross-compiled Qt target - Install ~/qt-x-win32 created before Then create a copy of the standard gcc target to create the new cross-gcc target: cd /usr/share/qt4/mkspecs/ sudo cp -a win32-g++ win32-x-g++ and modify it to point to the cross-compiler and our local version of Windows Qt: sudo sed -i -re 's/ (gcc|g\+\+)/ i686-w64-mingw32-\1/' win32-x-g++/qmake.conf sudo sed -i -re '/QMAKE_SH/iQMAKE_SH=1' win32-x-g++/qmake.conf sudo sed -i -re 's/QMAKE_COPY_DIR.*$/QMAKE_COPY_DIR = cp -r/' win32-x-g++/qmake.conf sudo sed -i -re '/QMAKE_LFLAGS/s/$/ -mwindows -static-libgcc -static-libstdc++/' win32-x-g++/qmake.conf sudo sed -i -re 's/QMAKE_RC.*$/QMAKE_RC = i686-w64-mingw32-windres/' win32-x-g++/qmake.conf sudo sed -i -re 's/QMAKE_STRIP\s.*$/QMAKE_STRIP = i686-w64-mingw32-strip/' win32-x-g++/qmake.conf sudo sed -i -re 's/\.exe//' win32-x-g++/qmake.conf sudo sed -i -re 's/QMAKE_INCDIR\s.*$/QMAKE_INCDIR = \/usr\/i686-w64-mingw32\/include/' win32-x-g++/qmake.conf sudo sed -i -re 's/QMAKE_INCDIR_QT\s.*$/QMAKE_INCDIR_QT = \/home\/$USER\/qt_win\/4.8.2\/include/' win32-x-g++/qmake.conf sudo sed -i -re 's/QMAKE_LIBDIR_QT\s.*$/QMAKE_LIBDIR_QT = \/home\/$USER\/qt_win\/4.8.2\/lib/' win32-x-g++/qmake.conf Now you can build a hello world to test Qt: mkdir /tmp/testqt cd /tmp/testqt echo '#include #include int main(int argc, char *argv[]) { QApplication app(argc, argv); QPushButton hello("Hello world!"); hello.resize(100, 30); hello.show(); return app.exec(); }' >main.cpp qmake -project qmake -spec win32-x-g++ -r CONFIG+=release make ## Building Boost on Windows Download the source from boost.org (tested with 1.50.0), DOS EOL mandatory! Extract it in a Windows VM Edit bootstrap.bat to read: call .\build.bat mingw %*... set toolset=gcc And from the MinGW prompt: bootstrap b2 toolset=gcc address-model=32 variant=debug,release link=static,shared threading=multi install - Copy the whole c:\boost_1_50_0 to the linux machine to ~/qt-x-win32/boost_1_50_0 ## (optional) Install NSIS If _wine_ and the NSIS compiler (available from [here](http://nsis.sourceforge.net/Main_Page)) are available, the script will generate the installer for _glogg_. The NSIS compiler should be installed in `~/qt-x-win32/NSIS`. ## Building _glogg_ From this point, building _glogg_ is hopefully straightforward: ./release-win32-x.sh The `release-win32-x.sh` script might need some changes if you use different paths for the dependencies. The object file is in `./release/` glogg-1.1.0/release-win32-x.sh0000775000175000017500000000205312657420553014777 0ustar nickonicko#!/bin/bash # Build glogg for win32 using the cross-compiler QTXDIR=$HOME/qt-x-win32 QTVERSION=4.8.6-32 BOOSTDIR=$QTXDIR/boost_1_50_0 make clean if [ "$1" == "debug" ]; then echo "Building a debug version" qmake-qt4 glogg.pro -spec win32-x-g++ -r CONFIG+="debug win32 rtti no-dbus version_checker" BOOST_PATH=$BOOSTDIR elif [ -z "$VERSION" ]; then echo "Building default version" qmake-qt4 glogg.pro -spec win32-x-g++ -r CONFIG+="release win32 rtti no-dbus version_checker" BOOST_PATH=$BOOSTDIR else echo "Building version $VERSION" qmake-qt4 glogg.pro -spec win32-x-g++ -r CONFIG+="release win32 rtti no-dbus version_checker" BOOST_PATH=$BOOSTDIR VERSION="$VERSION" fi make -j3 cp $QTXDIR/$QTVERSION/lib/{QtCore4,QtGui4,QtNetwork4}.dll release/ cp $QTXDIR/$QTVERSION/lib/{QtCored4,QtGuid4,QtNetworkd4}.dll debug/ cp /usr/i686-w64-mingw32/lib/libwinpthread-1.dll release/ if [ -z "$VERSION" ]; then VERSION=`git describe`; fi echo Generating installer for glogg-$VERSION wine $QTXDIR/NSIS/makensis -DVERSION=$VERSION -DARCH32 glogg.nsi