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