1 #include "latexlogwidget.h"
2 #include "encoding.h"
3 #include "configmanager.h"
4 #include "minisplitter.h"
5
6 /*
7 * row heights of tables are quite large by default. Experimentally detect the
8 * optimal row height in a portable way by resizing to contents and getting
9 * the resulting row height
10 */
getOptimalRowHeight(QTableView * tableView)11 int getOptimalRowHeight(QTableView *tableView)
12 {
13 QAbstractItemModel *model = tableView->model();
14 QStandardItemModel testModel;
15 QStandardItem item("Test");
16 testModel.appendRow(&item);
17 tableView->setModel(&testModel);
18 tableView->resizeRowsToContents();
19 int height = tableView->rowHeight(0);
20 tableView->setModel(model);
21 return height;
22 }
23
LatexLogWidget(QWidget * parent)24 LatexLogWidget::LatexLogWidget(QWidget *parent) :
25 QWidget(parent), logModel(nullptr), proxyModel(nullptr), logpresent(false), filterErrorAction(nullptr), filterWarningAction(nullptr), filterBadBoxAction(nullptr)
26 {
27 logModel = new LatexLogModel(this);//needs loaded line marks
28
29 errorTable = new QTableView(this);
30 int rowHeight = getOptimalRowHeight(errorTable);
31 errorTable->verticalHeader()->setDefaultSectionSize(rowHeight);
32 QFontMetrics fm(QApplication::font());
33 errorTable->setSelectionBehavior(QAbstractItemView::SelectRows);
34 errorTable->setSelectionMode(QAbstractItemView::SingleSelection);
35 errorTable->setColumnWidth(0, UtilsUi::getFmWidth(fm, "> "));
36 errorTable->setColumnWidth(1, 20 * UtilsUi::getFmWidth(fm, "w"));
37 errorTable->setColumnWidth(2, UtilsUi::getFmWidth(fm, "WarningW"));
38 errorTable->setColumnWidth(3, UtilsUi::getFmWidth(fm, "Line WWWWW"));
39 errorTable->setColumnWidth(4, 20 * UtilsUi::getFmWidth(fm, "w"));
40 connect(errorTable, SIGNAL(clicked(const QModelIndex&)), this, SLOT(clickedOnLogModelIndex(const QModelIndex&)));
41
42 errorTable->horizontalHeader()->setStretchLastSection(true);
43 errorTable->setMinimumHeight(5 * rowHeight);
44 errorTable->setFrameShape(QFrame::NoFrame);
45 errorTable->setSortingEnabled(true);
46
47 proxyModel = new QSortFilterProxyModel(this);
48 proxyModel->setSourceModel(logModel);
49 errorTable->setModel(proxyModel);
50
51 QAction *act = new QAction(tr("&Copy"), errorTable);
52 connect(act, SIGNAL(triggered()), SLOT(copyMessage()));
53 errorTable->addAction(act);
54 act = new QAction(tr("&Copy All"), errorTable);
55 connect(act, SIGNAL(triggered()), SLOT(copyAllMessages()));
56 errorTable->addAction(act);
57 act = new QAction(tr("&Copy All With Line Numbers"), errorTable);
58 connect(act, SIGNAL(triggered()), SLOT(copyAllMessagesWithLineNumbers()));
59 errorTable->addAction(act);
60 errorTable->setContextMenuPolicy(Qt::ActionsContextMenu);
61
62 log = new LogEditor(this);
63 log->setFocusPolicy(Qt::ClickFocus);
64 log->setMinimumHeight(3 * (fm.lineSpacing() + 4));
65 log->setReadOnly(true);
66 log->setFrameShape(QFrame::NoFrame);
67 connect(log, SIGNAL(clickOnLogLine(int)), this, SLOT(gotoLogLine(int)));
68
69 QSplitter *splitter = new MiniSplitter(Qt::Vertical, this);
70 splitter->setChildrenCollapsible(false);
71 splitter->addWidget(errorTable);
72 splitter->addWidget(log);
73
74 infoLabel = new QLabel(tr("No log file available"), this);
75 infoLabel->setStyleSheet("color: black; background: #FFFBBF;");
76 infoLabel->setMargin(2);
77
78 QVBoxLayout *vLayout = new QVBoxLayout(); //contains the widgets for the normal mode (OutputTable + OutputLogTextEdit)
79 vLayout->setSpacing(0);
80 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
81 vLayout->setMargin(0);
82 #else
83 vLayout->setContentsMargins(0,0,0,0);
84 #endif
85 vLayout->addWidget(infoLabel);
86 vLayout->addWidget(splitter);
87 setLayout(vLayout);
88
89 displayTableAction = new QAction(tr("Issues"), this);
90 displayTableAction->setCheckable(true);
91 connect(displayTableAction, SIGNAL(triggered(bool)), this, SLOT(setWidgetVisibleFromAction(bool)));
92 displayLogAction = new QAction(tr("Log File"), this);
93 displayLogAction->setCheckable(true);
94 connect(displayLogAction, SIGNAL(triggered(bool)), this, SLOT(setWidgetVisibleFromAction(bool)));
95 filterErrorAction = new QAction(QIcon(":/images-ng/error.svgz"), tr("Show Error"), this);
96 filterErrorAction->setCheckable(true);
97 filterErrorAction->setChecked(true);
98 connect(filterErrorAction, SIGNAL(toggled(bool)), this, SLOT(filterChanged(bool)));
99 filterWarningAction = new QAction(QIcon(":/images-ng/warning.svgz"), tr("Show Warning"), this);
100 filterWarningAction->setCheckable(true);
101 filterWarningAction->setChecked(true);
102 connect(filterWarningAction, SIGNAL(toggled(bool)), this, SLOT(filterChanged(bool)));
103 filterBadBoxAction = new QAction(QIcon(":/images-ng/badbox.svg"), tr("Show BadBox"), this);
104 filterBadBoxAction->setCheckable(true);
105 filterBadBoxAction->setChecked(true);
106 connect(filterBadBoxAction, SIGNAL(toggled(bool)), this, SLOT(filterChanged(bool)));
107
108 // initialize visibility
109 displayTableAction->setChecked(true);
110 errorTable->setVisible(true);
111 displayLogAction->setChecked(false);
112 log->setVisible(false);
113 }
114
115
loadLogFile(const QString & logname,const QString & compiledFileName,QTextCodec * fallbackCodec)116 bool LatexLogWidget::loadLogFile(const QString &logname, const QString &compiledFileName, QTextCodec* fallbackCodec)
117 {
118 resetLog();
119 QFileInfo fi(logname);
120 if (logname.isEmpty() || !fi.exists()) {
121 setInfo(tr("Log file not found."));
122 return false;
123 }
124 if (!fi.isReadable()) {
125 setInfo(tr("Log file not readable."));
126 return false;
127 }
128
129 QFile f(logname);
130 if (f.open(QIODevice::ReadOnly)) {
131 ConfigManagerInterface *config=ConfigManagerInterface::getInstance();
132 double fileSizeLimitMB = config->getOption("LogView/WarnIfFileSizeLargerMB").toDouble();
133 UtilsUi::txsWarningState rememberChoice=static_cast<UtilsUi::txsWarningState>(config->getOption("LogView/RememberChoiceLargeFile",0).toInt());
134 if (f.size() > fileSizeLimitMB * 1024 * 1024){
135 bool result=UtilsUi::txsConfirmWarning(tr("The logfile is very large (%1 MB) are you sure you want to load it?").arg(double(f.size()) / 1024 / 1024, 0, 'f', 2),rememberChoice);
136 config->setOption("LogView/RememberChoiceLargeFile",static_cast<int>(rememberChoice));
137 if(!result)
138 return false;
139 }
140
141 QByteArray fullLog = f.readAll();
142 f.close();
143
144 int sure;
145 QTextCodec *codec = Encoding::guessEncodingBasic(fullLog, &sure);
146 if (sure < 2 || !codec) codec = fallbackCodec ? fallbackCodec : QTextCodec::codecForLocale();
147
148 log->setPlainText(codec->toUnicode(fullLog));
149
150 logModel->parseLogDocument(log->document(), compiledFileName);
151
152 logpresent = true;
153
154 // workaround to https://sourceforge.net/p/texstudio/feature-requests/622/
155 // There seems to be a bug in Qt (4.8.4) that resizeRowsToContents() does not work correctly if
156 // horizontalHeader()->setStretchLastSection(true) and the tableView has not yet been shown
157 // when iterating through the columns to determine the maximal height, everything is fine
158 // until the last column. There the calculated height is too large.
159 // As a workaround we will temporarily deactivate column stretching.
160 // Note: To reproduce, you can call the viewer via The ViewLog button. When showing the viewer
161 // by clicking the ViewTab, the widget is shown before loading (so there the bug did not appear.)
162 bool visible = errorTable->isVisible();
163 if (!visible) errorTable->horizontalHeader()->setStretchLastSection(false);
164 errorTable->resizeColumnsToContents();
165 errorTable->resizeRowsToContents();
166 if (!visible) errorTable->horizontalHeader()->setStretchLastSection(true);
167
168 selectLogEntry(0);
169 emit logLoaded();
170 return true;
171 }
172
173 setInfo(tr("Log file not readable."));
174 return false;
175 }
176
logPresent()177 bool LatexLogWidget::logPresent()
178 {
179 return logpresent;
180 }
181
resetLog()182 void LatexLogWidget::resetLog()
183 {
184 log->clear();
185 logModel->clear();
186 logpresent = false;
187 setInfo("");
188 emit logResetted();
189 }
190
logEntryNumberValid(int logEntryNumber)191 bool LatexLogWidget::logEntryNumberValid(int logEntryNumber)
192 {
193 return logEntryNumber >= 0 && logEntryNumber < logModel->count();
194 }
195
selectLogEntry(int logEntryNumber)196 void LatexLogWidget::selectLogEntry(int logEntryNumber)
197 {
198 if (!logEntryNumberValid(logEntryNumber)) return;
199 QModelIndex index = proxyModel->mapFromSource(logModel->index(logEntryNumber, 1));
200 errorTable->scrollTo(index, QAbstractItemView::PositionAtCenter);
201 //errorTable->selectRow(logEntryNumber);
202 errorTable->selectRow(index.row());
203 log->setCursorPosition(logModel->at(logEntryNumber).logline, 0);
204 }
205
copy()206 void LatexLogWidget::copy()
207 {
208 if (log->isVisible())
209 log->copy();
210 else
211 copyMessage();
212 }
213
214 // TODO what is this for?
gotoLogEntry(int logEntryNumber)215 void LatexLogWidget::gotoLogEntry(int logEntryNumber)
216 {
217 if (!logEntryNumberValid(logEntryNumber)) return;
218 //select entry in table view/log
219 //OutputTable->scrollTo(logModel->index(logEntryNumber,1),QAbstractItemView::PositionAtCenter);
220 //OutputLogTextEdit->setCursorPosition(logModel->at(logEntryNumber).logline, 0);
221 selectLogEntry(logEntryNumber);
222 //notify editor
223 emit logEntryActivated(logEntryNumber);
224 }
225
clickedOnLogModelIndex(const QModelIndex & index)226 void LatexLogWidget::clickedOnLogModelIndex(const QModelIndex &index)
227 {
228 QModelIndex idx = proxyModel->mapToSource(index);
229 gotoLogEntry(idx.row());
230 }
231
gotoLogLine(int logLine)232 void LatexLogWidget::gotoLogLine(int logLine)
233 {
234 gotoLogEntry(logModel->logLineNumberToLogEntryNumber(logLine));
235 }
236
copyMessage()237 void LatexLogWidget::copyMessage()
238 {
239 QModelIndex curMessage = proxyModel->mapToSource(errorTable->currentIndex());
240 if (!curMessage.isValid()) return;
241 curMessage = logModel->index(curMessage.row(), 3);
242 REQUIRE(QApplication::clipboard());
243 QApplication::clipboard()->setText(logModel->data(curMessage, Qt::DisplayRole).toString());
244 }
245
copyAllMessages()246 void LatexLogWidget::copyAllMessages()
247 {
248 QStringList result;
249 for (int i = 0; i < logModel->count(); i++)
250 result << logModel->data(logModel->index(i, 3), Qt::DisplayRole).toString();
251 REQUIRE(QApplication::clipboard());
252 QApplication::clipboard()->setText(result.join("\n"));
253 }
254
copyAllMessagesWithLineNumbers()255 void LatexLogWidget::copyAllMessagesWithLineNumbers()
256 {
257 QStringList result;
258 for (int i = 0; i < logModel->count(); i++)
259 result << logModel->data(logModel->index(i, 2), Qt::DisplayRole).toString() + ": " + logModel->data(logModel->index(i, 3), Qt::DisplayRole).toString();
260 REQUIRE(QApplication::clipboard());
261 QApplication::clipboard()->setText(result.join("\n"));
262 }
263
setWidgetVisibleFromAction(bool visible)264 void LatexLogWidget::setWidgetVisibleFromAction(bool visible)
265 {
266 QAction *act = qobject_cast<QAction *>(sender());
267 if (act == displayTableAction) {
268 errorTable->setVisible(visible);
269 if (!visible && !log->isVisible()) {
270 displayLogAction->setChecked(true); // fallback, one widget should always be visible
271 log->setVisible(true);
272 }
273 } else if (act == displayLogAction) {
274 log->setVisible(visible);
275 if (!visible && !errorTable->isVisible()) {
276 displayTableAction->setChecked(true); // fallback, one widget should always be visible
277 errorTable->setVisible(true);
278 }
279 }
280 }
281
setInfo(const QString & message)282 void LatexLogWidget::setInfo(const QString &message)
283 {
284 infoLabel->setText(message);
285 infoLabel->setVisible(!message.isEmpty());
286 }
287
displayActions()288 QList<QAction *> LatexLogWidget::displayActions()
289 {
290 QList<QAction *> result;
291 QAction *sep1 = new QAction(this);
292 sep1->setSeparator(true);
293 QAction *sep2 = new QAction(this);
294 sep2->setSeparator(true);
295 result << displayLogAction << displayTableAction << sep1 << filterErrorAction << filterWarningAction << filterBadBoxAction << sep2;
296 return result;
297 }
298
filterChanged(bool)299 void LatexLogWidget::filterChanged(bool )
300 {
301 QStringList lst;
302 if (filterErrorAction && filterErrorAction->isChecked())
303 lst << logModel->returnString(LT_ERROR);
304 if (filterWarningAction && filterWarningAction->isChecked())
305 lst << logModel->returnString(LT_WARNING);
306 if (filterBadBoxAction && filterBadBoxAction->isChecked())
307 lst << logModel->returnString(LT_BADBOX);
308 QString rg = lst.join("|");
309 #if QT_VERSION>=QT_VERSION_CHECK(5,12,0)
310 proxyModel->setFilterRegularExpression(rg);
311 #else
312 proxyModel->setFilterRegExp(rg);
313 #endif
314 proxyModel->setFilterKeyColumn(1);
315 }
316