1 /*
2     This file is part of Akonadi.
3 
4     SPDX-FileCopyrightText: 2009 Till Adam <adam@kde.org>
5 
6     SPDX-License-Identifier: GPL-2.0-or-later
7 */
8 
9 #include "jobtrackerwidget.h"
10 #include <QCheckBox>
11 
12 #include "jobtrackerfilterproxymodel.h"
13 #include "jobtrackermodel.h"
14 #include "jobtrackersearchwidget.h"
15 
16 #include <Akonadi/ControlGui>
17 
18 #include <QApplication>
19 #include <QClipboard>
20 #include <QFile>
21 #include <QFileDialog>
22 #include <QHeaderView>
23 #include <QMenu>
24 #include <QPushButton>
25 #include <QTreeView>
26 #include <QVBoxLayout>
27 
28 class JobTrackerWidgetPrivate
29 {
30 public:
31     JobTrackerModel *model = nullptr;
32     QTreeView *tv = nullptr;
33     JobTrackerFilterProxyModel *filterProxyModel = nullptr;
34     JobTrackerSearchWidget *searchLineEditWidget = nullptr;
35 };
36 
JobTrackerWidget(const char * name,QWidget * parent,const QString & checkboxText)37 JobTrackerWidget::JobTrackerWidget(const char *name, QWidget *parent, const QString &checkboxText)
38     : QWidget(parent)
39     , d(new JobTrackerWidgetPrivate)
40 {
41     d->model = new JobTrackerModel(name, this);
42 
43     auto layout = new QVBoxLayout(this);
44 
45     auto enableCB = new QCheckBox(this);
46     enableCB->setText(checkboxText);
47     connect(enableCB, &QAbstractButton::toggled, d->model, &JobTrackerModel::setEnabled);
48     layout->addWidget(enableCB);
49 
50     d->searchLineEditWidget = new JobTrackerSearchWidget(this);
51     layout->addWidget(d->searchLineEditWidget);
52     connect(d->searchLineEditWidget, &JobTrackerSearchWidget::searchTextChanged, this, &JobTrackerWidget::textFilterChanged);
53     connect(d->searchLineEditWidget, &JobTrackerSearchWidget::columnChanged, this, &JobTrackerWidget::searchColumnChanged);
54     connect(d->searchLineEditWidget, &JobTrackerSearchWidget::selectOnlyErrorChanged, this, &JobTrackerWidget::selectOnlyErrorChanged);
55     d->filterProxyModel = new JobTrackerFilterProxyModel(this);
56     d->filterProxyModel->setSourceModel(d->model);
57 
58     d->tv = new QTreeView(this);
59     d->tv->setModel(d->filterProxyModel);
60     d->tv->expandAll();
61     d->tv->setAlternatingRowColors(true);
62     d->tv->setContextMenuPolicy(Qt::CustomContextMenu);
63     // too slow with many jobs:
64     // tv->header()->setResizeMode( QHeaderView::ResizeToContents );
65     connect(d->model, &JobTrackerModel::modelReset, d->tv, &QTreeView::expandAll);
66     connect(d->tv, &QTreeView::customContextMenuRequested, this, &JobTrackerWidget::contextMenu);
67     layout->addWidget(d->tv);
68     d->model->setEnabled(false); // since it can be slow, default to off
69 
70     auto layout2 = new QHBoxLayout;
71     auto button = new QPushButton(QStringLiteral("Save to file..."), this);
72     connect(button, &QAbstractButton::clicked, this, &JobTrackerWidget::slotSaveToFile);
73     layout2->addWidget(button);
74     layout2->addStretch(1);
75     layout->addLayout(layout2);
76 
77     Akonadi::ControlGui::widgetNeedsAkonadi(this);
78 }
79 
80 JobTrackerWidget::~JobTrackerWidget() = default;
81 
selectOnlyErrorChanged(bool state)82 void JobTrackerWidget::selectOnlyErrorChanged(bool state)
83 {
84     d->filterProxyModel->setShowOnlyFailed(state);
85     d->tv->expandAll();
86 }
87 
searchColumnChanged(int index)88 void JobTrackerWidget::searchColumnChanged(int index)
89 {
90     d->filterProxyModel->setSearchColumn(index);
91     d->tv->expandAll();
92 }
93 
textFilterChanged(const QString & str)94 void JobTrackerWidget::textFilterChanged(const QString &str)
95 {
96     d->filterProxyModel->setFilterFixedString(str);
97     d->tv->expandAll();
98 }
99 
contextMenu(const QPoint &)100 void JobTrackerWidget::contextMenu(const QPoint & /*pos*/)
101 {
102     QMenu menu;
103     menu.addAction(QStringLiteral("Clear View"), d->model, &JobTrackerModel::resetTracker);
104     menu.addSeparator();
105     menu.addAction(QStringLiteral("Copy Info"), this, &JobTrackerWidget::copyJobInfo);
106     menu.addSeparator();
107     menu.addAction(QStringLiteral("Collapse All"), this, &JobTrackerWidget::collapseAll);
108     menu.addAction(QStringLiteral("Expand All"), this, &JobTrackerWidget::expandAll);
109 
110     menu.exec(QCursor::pos());
111 }
112 
expandAll()113 void JobTrackerWidget::expandAll()
114 {
115     d->tv->expandAll();
116 }
117 
collapseAll()118 void JobTrackerWidget::collapseAll()
119 {
120     d->tv->collapseAll();
121 }
122 
copyJobInfo()123 void JobTrackerWidget::copyJobInfo()
124 {
125     QModelIndex index = d->tv->currentIndex();
126     if (index.isValid()) {
127         const QModelIndex newIndex = d->model->index(index.row(), 6, index.parent());
128         QClipboard *cb = QApplication::clipboard();
129         cb->setText(newIndex.data().toString(), QClipboard::Clipboard);
130     }
131 }
132 
slotSaveToFile()133 void JobTrackerWidget::slotSaveToFile()
134 {
135     const QString fileName = QFileDialog::getSaveFileName(this, QString(), QString(), QString());
136     if (fileName.isEmpty()) {
137         return;
138     }
139 
140     QFile file(fileName);
141     if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
142         return;
143     }
144 
145     file.write("Job ID\t\tCreated\t\tWait Time\tJob Duration\tJob Type\t\tState\tInfo\n");
146 
147     writeRows(QModelIndex(), file, 0);
148 
149     file.close();
150 }
151 
writeRows(const QModelIndex & parent,QFile & file,int indentLevel)152 void JobTrackerWidget::writeRows(const QModelIndex &parent, QFile &file, int indentLevel)
153 {
154     for (int row = 0; row < d->model->rowCount(parent); ++row) {
155         QByteArray data;
156         for (int tabs = 0; tabs < indentLevel; ++tabs) {
157             data += '\t';
158         }
159         const int columnCount = d->model->columnCount(parent);
160         for (int column = 0; column < columnCount; ++column) {
161             const QModelIndex index = d->model->index(row, column, parent);
162             data += index.data().toByteArray();
163             if (column < columnCount - 1) {
164                 data += '\t';
165             }
166         }
167         data += '\n';
168         file.write(data);
169 
170         const QModelIndex index = d->model->index(row, 0, parent);
171         writeRows(index, file, indentLevel + 1);
172     }
173 }
174