// Copyright (c) 2015, Axel Gembe // All rights reserved. // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // * Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // * The name of the contributors may not be used to endorse or promote products // derived from this software without specific prior written permission. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. // IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED // OF THE POSSIBILITY OF SUCH DAMAGE. #include "QsLogWindow.h" #include "QsLogWindowModel.h" #include "QsLog.h" #include "QsLogMessage.h" #include "ui_QsLogWindow.h" #include #include #include #include #include #include #include static const QIcon& pauseIcon() { static QIcon icon(QString::fromLatin1(":/QsLogWindow/images/icon-pause-16.png")); return icon; } static const QIcon& playIcon() { static QIcon icon(QString::fromLatin1(":/QsLogWindow/images/icon-resume-16.png")); return icon; } class QsLogging::WindowLogFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT public: WindowLogFilterProxyModel(Level level, QObject* parent = 0) : QSortFilterProxyModel(parent) , mLevel(level) , mLastVisibleRow(0) { } Level level() const { return mLevel; } void setLevel(const Level level) { mLevel = level; invalidateFilter(); } void setPaused(bool paused) { mLastVisibleRow = paused ? rowCount() : 0; if (!paused) { invalidateFilter(); } } protected: virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { Q_UNUSED(source_parent); if (!mLastVisibleRow) { LogWindowModel* model = dynamic_cast(sourceModel()); const LogMessage& d = model->at(source_row); return d.level >= mLevel; } return source_row <= mLastVisibleRow; } private: Level mLevel; int mLastVisibleRow; }; QsLogging::Window::Window(QWidget* parent) : QDialog(parent) , mUi(nullptr) , mProxyModel(nullptr) , mIsPaused(false) , mHasAutoScroll(true) { mUi.reset(new Ui::LogWindow()); mUi->setupUi(this); connect(mUi->toolButtonPause, SIGNAL(clicked()), SLOT(OnPauseClicked())); connect(mUi->toolButtonSave, SIGNAL(clicked()), SLOT(OnSaveClicked())); connect(mUi->toolButtonClear, SIGNAL(clicked()), SLOT(OnClearClicked())); connect(mUi->toolButtonCopy, SIGNAL(clicked()), SLOT(OnCopyClicked())); connect(mUi->comboBoxLevel, SIGNAL(currentIndexChanged(int)), SLOT(OnLevelChanged(int))); connect(mUi->checkBoxAutoScroll, SIGNAL(toggled(bool)), SLOT(OnAutoScrollChanged(bool))); connect(&mModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), SLOT(ModelRowsInserted(const QModelIndex&, int, int))); // Install the sort / filter model mProxyModel.reset(new WindowLogFilterProxyModel(InfoLevel, this)); mProxyModel->setSourceModel(&mModel); mUi->tableViewMessages->setModel(mProxyModel.get()); mUi->tableViewMessages->installEventFilter(this); mUi->tableViewMessages->setSelectionBehavior(QAbstractItemView::SelectRows); #if QT_VERSION >= 0x050000 mUi->tableViewMessages->horizontalHeader()->setSectionResizeMode(LogWindowModel::TimeColumn, QHeaderView::ResizeToContents); mUi->tableViewMessages->horizontalHeader()->setSectionResizeMode(LogWindowModel::LevelNameColumn, QHeaderView::ResizeToContents); mUi->tableViewMessages->horizontalHeader()->setSectionResizeMode(LogWindowModel::MessageColumn, QHeaderView::Stretch); mUi->tableViewMessages->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); #else mUi->tableViewMessages->horizontalHeader()->setResizeMode(LogWindowModel::TimeColumn, QHeaderView::ResizeToContents); mUi->tableViewMessages->horizontalHeader()->setResizeMode(LogWindowModel::LevelNameColumn, QHeaderView::ResizeToContents); mUi->tableViewMessages->horizontalHeader()->setResizeMode(LogWindowModel::MessageColumn, QHeaderView::Stretch); mUi->tableViewMessages->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents); #endif // Initialize log level selection for (int l = TraceLevel; l < OffLevel; l++) { const QString ln = LocalizedLevelName(static_cast(l)); mUi->comboBoxLevel->addItem(ln, l); } mUi->comboBoxLevel->setCurrentIndex(InfoLevel); } QsLogging::Window::~Window() noexcept = default; bool QsLogging::Window::eventFilter(QObject *obj, QEvent *event) { if (obj == mUi->tableViewMessages) { if (event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_C && (keyEvent->modifiers() & Qt::ControlModifier)) { copySelection(); return true; } } return false; } else { return QDialog::eventFilter(obj, event); } } void QsLogging::Window::addLogMessage(const QsLogging::LogMessage &m) { mModel.addEntry(m); } void QsLogging::Window::OnPauseClicked() { mUi->toolButtonPause->setIcon(mIsPaused ? pauseIcon() : playIcon()); mUi->toolButtonPause->setText(mIsPaused ? tr("&Pause") : tr("&Resume")); mIsPaused = !mIsPaused; mProxyModel->setPaused(mIsPaused); } void QsLogging::Window::OnSaveClicked() { saveSelection(); } void QsLogging::Window::OnClearClicked() { mModel.clear(); } void QsLogging::Window::OnCopyClicked() { copySelection(); } void QsLogging::Window::OnLevelChanged(int value) { mProxyModel->setLevel(static_cast(value)); } void QsLogging::Window::OnAutoScrollChanged(bool checked) { mHasAutoScroll = checked; } void QsLogging::Window::ModelRowsInserted(const QModelIndex& parent, int start, int last) { Q_UNUSED(parent); Q_UNUSED(start); Q_UNUSED(last); if (mHasAutoScroll && !mIsPaused) { mUi->tableViewMessages->scrollToBottom(); } } void QsLogging::Window::copySelection() const { const QString text = getSelectionText(); if (text.isEmpty()) { return; } QApplication::clipboard()->setText(text); } void QsLogging::Window::saveSelection() { const QString text = getSelectionText(); if (text.isEmpty()) { return; } QFileDialog dialog(this); dialog.setWindowTitle(tr("Save log")); dialog.setNameFilter(tr("Log file (*.log)")); dialog.setAcceptMode(QFileDialog::AcceptSave); dialog.setDefaultSuffix("log"); dialog.exec(); const QStringList sel = dialog.selectedFiles(); if (sel.size() < 1) { return; } QFile file(sel.at(0)); if (file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); stream << text; file.close(); } } QString QsLogging::Window::getSelectionText() const { QModelIndexList rows = mUi->tableViewMessages->selectionModel()->selectedRows(); std::sort(rows.begin(), rows.end()); QString text; if (rows.count() == 0) { for (int i = 0; i < mProxyModel->rowCount(); i++) { const int srow = mProxyModel->mapToSource(mProxyModel->index(i, 0)).row(); text += mModel.at(srow).formatted + "\n"; } } else { for (QModelIndexList::const_iterator i = rows.begin();i != rows.end();++i) { const int srow = mProxyModel->mapToSource(*i).row(); text += mModel.at(srow).formatted + "\n"; } } return text; } #include "QsLogWindow.moc"