1 // Copyright (c) 2015, Axel Gembe <axel@gembe.net>
2 // All rights reserved.
3 
4 // Redistribution and use in source and binary forms, with or without modification,
5 // are permitted provided that the following conditions are met:
6 
7 // * Redistributions of source code must retain the above copyright notice, this
8 //   list of conditions and the following disclaimer.
9 // * Redistributions in binary form must reproduce the above copyright notice, this
10 //   list of conditions and the following disclaimer in the documentation and/or other
11 //   materials provided with the distribution.
12 // * The name of the contributors may not be used to endorse or promote products
13 //   derived from this software without specific prior written permission.
14 
15 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 // IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
19 // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20 // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23 // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
24 // OF THE POSSIBILITY OF SUCH DAMAGE.
25 
26 #include "QsLogWindow.h"
27 #include "QsLogWindowModel.h"
28 #include "QsLog.h"
29 #include "QsLogMessage.h"
30 #include "ui_QsLogWindow.h"
31 
32 #include <QIcon>
33 #include <QSortFilterProxyModel>
34 #include <QClipboard>
35 #include <QKeyEvent>
36 #include <QFileDialog>
37 #include <QtGlobal>
38 #include <cstddef>
39 
pauseIcon()40 static const QIcon& pauseIcon()
41 {
42     static QIcon icon(QString::fromLatin1(":/QsLogWindow/images/icon-pause-16.png"));
43     return icon;
44 }
45 
playIcon()46 static const QIcon& playIcon()
47 {
48     static QIcon icon(QString::fromLatin1(":/QsLogWindow/images/icon-resume-16.png"));
49     return icon;
50 }
51 
52 
53 class QsLogging::WindowLogFilterProxyModel : public QSortFilterProxyModel
54 {
55     Q_OBJECT
56 
57 public:
WindowLogFilterProxyModel(Level level,QObject * parent=0)58     WindowLogFilterProxyModel(Level level, QObject* parent = 0)
59         : QSortFilterProxyModel(parent)
60         , mLevel(level)
61         , mLastVisibleRow(0)
62     {
63     }
64 
level() const65     Level level() const
66     {
67         return mLevel;
68     }
69 
setLevel(const Level level)70     void setLevel(const Level level)
71     {
72         mLevel = level;
73         invalidateFilter();
74     }
75 
setPaused(bool paused)76     void setPaused(bool paused)
77     {
78         mLastVisibleRow = paused ? rowCount() : 0;
79         if (!paused) {
80             invalidateFilter();
81         }
82     }
83 
84 protected:
filterAcceptsRow(int source_row,const QModelIndex & source_parent) const85     virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
86     {
87         Q_UNUSED(source_parent);
88         if (!mLastVisibleRow) {
89             LogWindowModel* model = dynamic_cast<LogWindowModel*>(sourceModel());
90             const LogMessage& d = model->at(source_row);
91             return d.level >= mLevel;
92         }
93 
94         return source_row <= mLastVisibleRow;
95     }
96 
97 private:
98     Level mLevel;
99     int mLastVisibleRow;
100 };
101 
Window(QWidget * parent)102 QsLogging::Window::Window(QWidget* parent)
103     : QDialog(parent)
104     , mUi(nullptr)
105     , mProxyModel(nullptr)
106     , mIsPaused(false)
107     , mHasAutoScroll(true)
108 {
109     mUi.reset(new Ui::LogWindow());
110     mUi->setupUi(this);
111 
112     connect(mUi->toolButtonPause, SIGNAL(clicked()), SLOT(OnPauseClicked()));
113     connect(mUi->toolButtonSave, SIGNAL(clicked()), SLOT(OnSaveClicked()));
114     connect(mUi->toolButtonClear, SIGNAL(clicked()), SLOT(OnClearClicked()));
115     connect(mUi->toolButtonCopy, SIGNAL(clicked()), SLOT(OnCopyClicked()));
116     connect(mUi->comboBoxLevel, SIGNAL(currentIndexChanged(int)), SLOT(OnLevelChanged(int)));
117     connect(mUi->checkBoxAutoScroll, SIGNAL(toggled(bool)), SLOT(OnAutoScrollChanged(bool)));
118     connect(&mModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)),
119             SLOT(ModelRowsInserted(const QModelIndex&, int, int)));
120 
121     // Install the sort / filter model
122     mProxyModel.reset(new WindowLogFilterProxyModel(InfoLevel, this));
123     mProxyModel->setSourceModel(&mModel);
124     mUi->tableViewMessages->setModel(mProxyModel.get());
125 
126     mUi->tableViewMessages->installEventFilter(this);
127 
128     mUi->tableViewMessages->setSelectionBehavior(QAbstractItemView::SelectRows);
129 #if QT_VERSION >= 0x050000
130     mUi->tableViewMessages->horizontalHeader()->setSectionResizeMode(LogWindowModel::TimeColumn, QHeaderView::ResizeToContents);
131     mUi->tableViewMessages->horizontalHeader()->setSectionResizeMode(LogWindowModel::LevelNameColumn, QHeaderView::ResizeToContents);
132     mUi->tableViewMessages->horizontalHeader()->setSectionResizeMode(LogWindowModel::MessageColumn, QHeaderView::Stretch);
133     mUi->tableViewMessages->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
134 #else
135     mUi->tableViewMessages->horizontalHeader()->setResizeMode(LogWindowModel::TimeColumn, QHeaderView::ResizeToContents);
136     mUi->tableViewMessages->horizontalHeader()->setResizeMode(LogWindowModel::LevelNameColumn, QHeaderView::ResizeToContents);
137     mUi->tableViewMessages->horizontalHeader()->setResizeMode(LogWindowModel::MessageColumn, QHeaderView::Stretch);
138     mUi->tableViewMessages->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents);
139 #endif
140 
141     // Initialize log level selection
142     for (int l = TraceLevel; l < OffLevel; l++) {
143         const QString ln = LocalizedLevelName(static_cast<Level>(l));
144         mUi->comboBoxLevel->addItem(ln, l);
145     }
146     mUi->comboBoxLevel->setCurrentIndex(InfoLevel);
147 }
148 
149 QsLogging::Window::~Window() noexcept = default;
150 
eventFilter(QObject * obj,QEvent * event)151 bool QsLogging::Window::eventFilter(QObject *obj, QEvent *event)
152 {
153     if (obj == mUi->tableViewMessages) {
154         if (event->type() == QEvent::KeyPress) {
155             QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
156             if (keyEvent->key() == Qt::Key_C && (keyEvent->modifiers() & Qt::ControlModifier)) {
157                 copySelection();
158                 return true;
159             }
160         }
161 
162         return false;
163     } else {
164         return QDialog::eventFilter(obj, event);
165     }
166 }
167 
addLogMessage(const QsLogging::LogMessage & m)168 void QsLogging::Window::addLogMessage(const QsLogging::LogMessage &m)
169 {
170     mModel.addEntry(m);
171 }
172 
OnPauseClicked()173 void QsLogging::Window::OnPauseClicked()
174 {
175     mUi->toolButtonPause->setIcon(mIsPaused ? pauseIcon() : playIcon());
176     mUi->toolButtonPause->setText(mIsPaused ? tr("&Pause") : tr("&Resume"));
177 
178     mIsPaused = !mIsPaused;
179 
180     mProxyModel->setPaused(mIsPaused);
181 }
182 
OnSaveClicked()183 void QsLogging::Window::OnSaveClicked()
184 {
185     saveSelection();
186 }
187 
OnClearClicked()188 void QsLogging::Window::OnClearClicked()
189 {
190     mModel.clear();
191 }
192 
OnCopyClicked()193 void QsLogging::Window::OnCopyClicked()
194 {
195     copySelection();
196 }
197 
OnLevelChanged(int value)198 void QsLogging::Window::OnLevelChanged(int value)
199 {
200     mProxyModel->setLevel(static_cast<Level>(value));
201 }
202 
OnAutoScrollChanged(bool checked)203 void QsLogging::Window::OnAutoScrollChanged(bool checked)
204 {
205     mHasAutoScroll = checked;
206 }
207 
ModelRowsInserted(const QModelIndex & parent,int start,int last)208 void QsLogging::Window::ModelRowsInserted(const QModelIndex& parent, int start, int last)
209 {
210     Q_UNUSED(parent);
211     Q_UNUSED(start);
212     Q_UNUSED(last);
213     if (mHasAutoScroll && !mIsPaused) {
214         mUi->tableViewMessages->scrollToBottom();
215     }
216 }
217 
copySelection() const218 void QsLogging::Window::copySelection() const
219 {
220     const QString text = getSelectionText();
221     if (text.isEmpty()) {
222         return;
223     }
224 
225     QApplication::clipboard()->setText(text);
226 }
227 
saveSelection()228 void QsLogging::Window::saveSelection()
229 {
230     const QString text = getSelectionText();
231     if (text.isEmpty()) {
232         return;
233     }
234 
235     QFileDialog dialog(this);
236     dialog.setWindowTitle(tr("Save log"));
237     dialog.setNameFilter(tr("Log file (*.log)"));
238     dialog.setAcceptMode(QFileDialog::AcceptSave);
239     dialog.setDefaultSuffix("log");
240     dialog.exec();
241 
242     const QStringList sel = dialog.selectedFiles();
243     if (sel.size() < 1) {
244         return;
245     }
246 
247     QFile file(sel.at(0));
248     if (file.open(QIODevice::WriteOnly)) {
249         QTextStream stream(&file);
250         stream << text;
251         file.close();
252     }
253 }
254 
getSelectionText() const255 QString QsLogging::Window::getSelectionText() const
256 {
257     QModelIndexList rows = mUi->tableViewMessages->selectionModel()->selectedRows();
258     std::sort(rows.begin(), rows.end());
259 
260     QString text;
261 
262     if (rows.count() == 0) {
263         for (int i = 0; i < mProxyModel->rowCount(); i++) {
264             const int srow = mProxyModel->mapToSource(mProxyModel->index(i, 0)).row();
265             text += mModel.at(srow).formatted + "\n";
266         }
267     } else {
268         for (QModelIndexList::const_iterator i = rows.begin();i != rows.end();++i) {
269             const int srow = mProxyModel->mapToSource(*i).row();
270             text += mModel.at(srow).formatted + "\n";
271         }
272     }
273 
274     return text;
275 }
276 
277 #include "QsLogWindow.moc"
278