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