1 /*
2     SPDX-FileCopyrightText: 2007 Nicolas Ternisien <nicolas.ternisien@gmail.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "logViewWidget.h"
8 
9 #include <QAction>
10 #include <QHeaderView>
11 
12 #include <KLocalizedString>
13 #include <QIcon>
14 
15 #include "ksystemlog_debug.h"
16 
17 #include "logViewColumn.h"
18 
19 #include "logLine.h"
20 #include "logViewModel.h"
21 #include "logViewWidgetItem.h"
22 
23 #include "ksystemlogConfig.h"
24 
LogViewWidget(QWidget * parent)25 LogViewWidget::LogViewWidget(QWidget *parent)
26     : QTreeWidget(parent)
27 {
28     // TODO Add this setWhatsThis() to all columns each time they change
29     // setWhatThis(i18n("<p>This is the main view of KSystemLog. It displays the last lines of the selected
30     // log. Please see the documentation to discovers the meaning of each icons and existing log.</p><p>Log
31     // lines in <b>bold</b> are the last added to the list.</p>"));
32 
33     const QStringList headerLabels{i18n("Date"), i18n("Message")};
34 
35     mLogViewModel = new LogViewModel(this);
36     mHeadersTogglingActions = new QActionGroup(this);
37     mHeadersTogglingActions->setExclusive(false);
38     connect(mHeadersTogglingActions, &QActionGroup::triggered, this, &LogViewWidget::toggleHeader);
39 
40     setHeaderLabels(headerLabels);
41 
42     // Header
43     header()->setContextMenuPolicy(Qt::ActionsContextMenu);
44     // header()->setMovable(true);
45     header()->setSectionsMovable(true);
46 
47     setSortingEnabled(true);
48     sortItems(0, Qt::AscendingOrder);
49 
50     setAnimated(true);
51 
52     setRootIsDecorated(false);
53 
54     setAllColumnsShowFocus(true);
55 
56     setAlternatingRowColors(true);
57 
58     setSelectionMode(QAbstractItemView::ExtendedSelection);
59 
60     setContextMenuPolicy(Qt::ActionsContextMenu);
61 }
62 
~LogViewWidget()63 LogViewWidget::~LogViewWidget()
64 {
65     delete mLogViewModel;
66 }
67 
setColumns(const LogViewColumns & columns)68 void LogViewWidget::setColumns(const LogViewColumns &columns)
69 {
70     qCDebug(KSYSTEMLOG) << "Updating columns using " << columns << "...";
71 
72     // First, delete all current columns
73     setColumnCount(0);
74 
75     setHeaderLabels(columns.toStringList());
76 
77     sortItems(0, Qt::AscendingOrder);
78 
79     // Remove previous header actions
80     QListIterator<QAction *> it(mHeadersTogglingActions->actions());
81     it.toBack();
82     while (it.hasPrevious()) {
83         QAction *action = it.previous();
84 
85         header()->removeAction(action);
86         mHeadersTogglingActions->removeAction(action);
87 
88         delete action;
89     }
90 
91     // Add new actions
92     int columnIndex = 0;
93 
94     const auto columnsLst = columns.columns();
95     for (const LogViewColumn &column : columnsLst) {
96         auto action = new QAction(this);
97         action->setText(column.columnName());
98         // helloAction->setIcon(QIcon::fromTheme( QLatin1String( "media-playback-start" )));
99         // helloAction->setShortcut(Qt::CTRL | Qt::Key_M);
100         action->setCheckable(true);
101         action->setChecked(true);
102         action->setToolTip(i18n("Display/Hide the '%1' column", column.columnName()));
103         action->setData(QVariant(columnIndex));
104 
105         mHeadersTogglingActions->addAction(action);
106 
107         ++columnIndex;
108     }
109 
110     header()->addActions(mHeadersTogglingActions->actions());
111 
112     Q_EMIT columnsChanged(columns);
113 
114     qCDebug(KSYSTEMLOG) << "Log View Widget updated...";
115 }
116 
resizeColumns()117 void LogViewWidget::resizeColumns()
118 {
119     // Resize all columns except the last one (which always take the last available space)
120     for (int i = 0; i < columnCount() - 1; ++i) {
121         resizeColumnToContents(i);
122     }
123 }
124 
selectAll()125 void LogViewWidget::selectAll()
126 {
127     if (notHiddenItemCount() > 0) {
128         QTreeWidget::selectAll();
129     }
130 }
131 
itemCount() const132 int LogViewWidget::itemCount() const
133 {
134     return topLevelItemCount();
135 }
136 
logLines()137 QList<LogLine *> LogViewWidget::logLines()
138 {
139     QList<LogLine *> logLines;
140 
141     QTreeWidgetItemIterator it(this);
142     while (*it) {
143         auto item = static_cast<LogViewWidgetItem *>(*it);
144         logLines.append(item->logLine());
145         ++it;
146     }
147 
148     return logLines;
149 }
150 
findNewestItem()151 LogViewWidgetItem *LogViewWidget::findNewestItem()
152 {
153     LogViewWidgetItem *newestItem = nullptr;
154 
155     QTreeWidgetItemIterator it(this);
156     while (*it) {
157         auto item = static_cast<LogViewWidgetItem *>(*it);
158         if (!newestItem || newestItem->logLine()->isOlderThan(*(item->logLine()))) {
159             newestItem = item;
160         }
161 
162         ++it;
163     }
164 
165     return newestItem;
166 }
167 
findItem(LogLine * searchedLogLine)168 LogViewWidgetItem *LogViewWidget::findItem(LogLine *searchedLogLine)
169 {
170     QTreeWidgetItemIterator it(this);
171     while (*it) {
172         auto item = static_cast<LogViewWidgetItem *>(*it);
173         if (item->logLine()->equals(*searchedLogLine)) {
174             return item;
175         }
176 
177         ++it;
178     }
179 
180     return nullptr;
181 }
182 
items()183 QList<LogViewWidgetItem *> LogViewWidget::items()
184 {
185     QList<LogViewWidgetItem *> items;
186 
187     QTreeWidgetItemIterator it(this);
188     while (*it) {
189         items.append(static_cast<LogViewWidgetItem *>(*it));
190         ++it;
191     }
192 
193     return items;
194 }
195 
model() const196 LogViewModel *LogViewWidget::model() const
197 {
198     return mLogViewModel;
199 }
200 
hasItemsSelected()201 bool LogViewWidget::hasItemsSelected()
202 {
203     return !selectedItems().isEmpty();
204 }
205 
firstSelectedItem()206 LogViewWidgetItem *LogViewWidget::firstSelectedItem()
207 {
208     QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::Selected);
209 
210     // Returns the first selected item or NULL is there is no item selected
211     return static_cast<LogViewWidgetItem *>(*it);
212 }
213 
lastSelectedItem()214 LogViewWidgetItem *LogViewWidget::lastSelectedItem()
215 {
216     QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::Selected);
217 
218     QTreeWidgetItem *item = nullptr;
219     while (*it) {
220         item = (*it);
221 
222         ++it;
223     }
224 
225     // Returns the last selected item or NULL is there is no item selected
226     return static_cast<LogViewWidgetItem *>(item);
227 }
228 
expandAll()229 void LogViewWidget::expandAll()
230 {
231     QTreeWidgetItemIterator it(this);
232     while (*it) {
233         expandItem(*it);
234         ++it;
235     }
236 }
237 
collapseAll()238 void LogViewWidget::collapseAll()
239 {
240     QTreeWidgetItemIterator it(this);
241     while (*it) {
242         collapseItem(*it);
243         ++it;
244     }
245 }
246 
toggleToolTip(bool enabled)247 void LogViewWidget::toggleToolTip(bool enabled)
248 {
249     qCDebug(KSYSTEMLOG) << "Toggle tool tip " << enabled;
250 
251     QTreeWidgetItemIterator it(this);
252     while (*it) {
253         auto item = static_cast<LogViewWidgetItem *>(*it);
254         item->toggleToolTip(enabled);
255 
256         ++it;
257     }
258 }
259 
scrollToNewestItem()260 void LogViewWidget::scrollToNewestItem()
261 {
262     qCDebug(KSYSTEMLOG) << "Scrolling to the newest item...";
263 
264     // Scroll to last item if requested
265     if (KSystemLogConfig::newLinesDisplayed()) {
266         LogViewWidgetItem *newestItem = findNewestItem();
267         if (newestItem) {
268             scrollToItem(newestItem);
269         }
270     }
271 }
272 
notHiddenItemCount()273 int LogViewWidget::notHiddenItemCount()
274 {
275     int count = 0;
276 
277     QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::NotHidden);
278     while (*it) {
279         count++;
280         ++it;
281     }
282 
283     return count;
284 }
285 
toggleHeader(QAction * action)286 void LogViewWidget::toggleHeader(QAction *action)
287 {
288     qCDebug(KSYSTEMLOG) << "Toggling header";
289 
290     int columnIndex = action->data().toInt();
291     if (header()->isSectionHidden(columnIndex)) {
292         header()->setSectionHidden(columnIndex, false);
293     } else {
294         header()->setSectionHidden(columnIndex, true);
295     }
296 }
297