1 /**
2  * \file GuiLog.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author John Levon
7  * \author Angus Leeming
8  * \author Jürgen Spitzmüller
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12 
13 #include <config.h>
14 
15 #include "GuiLog.h"
16 
17 #include "GuiApplication.h"
18 #include "qt_helpers.h"
19 #include "Lexer.h"
20 
21 #include "frontends/Clipboard.h"
22 
23 #include "support/docstring.h"
24 #include "support/FileName.h"
25 #include "support/gettext.h"
26 #include "support/lstrings.h"
27 
28 #include <QDesktopServices>
29 #include <QTextBrowser>
30 #include <QSyntaxHighlighter>
31 #include <QUrl>
32 #include <QClipboard>
33 
34 #include <fstream>
35 #include <sstream>
36 
37 using namespace std;
38 using namespace lyx::support;
39 
40 namespace lyx {
41 namespace frontend {
42 
43 
44 // Regular expressions needed at several places
45 // FIXME: These regexes are incomplete. It would be good if we could collect those used in LaTeX::scanLogFile
46 //        and LaTeX::scanBlgFile and re-use them here!(spitz, 2013-05-27)
47 // Information
48 QRegExp exprInfo("^(Document Class:|LaTeX Font Info:|File:|Package:|Language:|.*> INFO - |\\(|\\\\).*$");
49 // Warnings
50 QRegExp exprWarning("^(LaTeX Warning|LaTeX Font Warning|Package [\\w\\.]+ Warning|Class \\w+ Warning|Warning--|Underfull|Overfull|.*> WARN - ).*$");
51 // Errors
52 QRegExp exprError("^(!|.*---line [0-9]+ of file|.*> FATAL - |.*> ERROR - |Missing character: There is no ).*$");
53 
54 
55 /////////////////////////////////////////////////////////////////////
56 //
57 // LogHighlighter
58 //
59 ////////////////////////////////////////////////////////////////////
60 
61 class LogHighlighter : public QSyntaxHighlighter
62 {
63 public:
64 	LogHighlighter(QTextDocument * parent);
65 
66 private:
67 	void highlightBlock(QString const & text);
68 
69 private:
70 	QTextCharFormat infoFormat;
71 	QTextCharFormat warningFormat;
72 	QTextCharFormat errorFormat;
73 };
74 
75 
76 
LogHighlighter(QTextDocument * parent)77 LogHighlighter::LogHighlighter(QTextDocument * parent)
78 	: QSyntaxHighlighter(parent)
79 {
80 	infoFormat.setForeground(Qt::darkGray);
81 	warningFormat.setForeground(Qt::darkBlue);
82 	errorFormat.setForeground(Qt::red);
83 }
84 
85 
highlightBlock(QString const & text)86 void LogHighlighter::highlightBlock(QString const & text)
87 {
88 	// Info
89 	int index = exprInfo.indexIn(text);
90 	while (index >= 0) {
91 		int length = exprInfo.matchedLength();
92 		setFormat(index, length, infoFormat);
93 		index = exprInfo.indexIn(text, index + length);
94 	}
95 	// LaTeX Warning:
96 	index = exprWarning.indexIn(text);
97 	while (index >= 0) {
98 		int length = exprWarning.matchedLength();
99 		setFormat(index, length, warningFormat);
100 		index = exprWarning.indexIn(text, index + length);
101 	}
102 	// ! error
103 	index = exprError.indexIn(text);
104 	while (index >= 0) {
105 		int length = exprError.matchedLength();
106 		setFormat(index, length, errorFormat);
107 		index = exprError.indexIn(text, index + length);
108 	}
109 }
110 
111 
112 /////////////////////////////////////////////////////////////////////
113 //
114 // GuiLog
115 //
116 /////////////////////////////////////////////////////////////////////
117 
GuiLog(GuiView & lv)118 GuiLog::GuiLog(GuiView & lv)
119 	: GuiDialog(lv, "log", qt_("LaTeX Log")), type_(LatexLog)
120 {
121 	setupUi(this);
122 
123 	connect(closePB, SIGNAL(clicked()), this, SLOT(slotClose()));
124 	connect(updatePB, SIGNAL(clicked()), this, SLOT(updateContents()));
125 	connect(findPB, SIGNAL(clicked()), this, SLOT(find()));
126 	// FIXME: find via returnPressed() does not work!
127 	connect(findLE, SIGNAL(returnPressed()), this, SLOT(find()));
128 	connect(logTypeCO, SIGNAL(activated(int)),
129 		this, SLOT(typeChanged(int)));
130 
131 	bc().setPolicy(ButtonPolicy::OkCancelPolicy);
132 
133 	// set syntax highlighting
134 	highlighter = new LogHighlighter(logTB->document());
135 
136 	logTB->setReadOnly(true);
137 	logTB->setFont(guiApp->typewriterSystemFont());
138 }
139 
140 
updateContents()141 void GuiLog::updateContents()
142 {
143 	setTitle(toqstr(title()));
144 
145 	ostringstream ss;
146 	getContents(ss);
147 
148 	logTB->setPlainText(toqstr(ss.str()));
149 
150 	nextErrorPB->setEnabled(contains(exprError));
151 	nextWarningPB->setEnabled(contains(exprWarning));
152 }
153 
154 
typeChanged(int i)155 void GuiLog::typeChanged(int i)
156 {
157 	string const type =
158 		fromqstr(logTypeCO->itemData(i).toString());
159 	string ext;
160 	if (type == "latex")
161 		ext = "log";
162 	else if (type == "bibtex")
163 		ext = "blg";
164 	else if (type == "index")
165 		ext = "ilg";
166 
167 	if (!ext.empty())
168 		logfile_.changeExtension(ext);
169 
170 	updateContents();
171 }
172 
173 
find()174 void GuiLog::find()
175 {
176 	logTB->find(findLE->text());
177 }
178 
179 
on_nextErrorPB_clicked()180 void GuiLog::on_nextErrorPB_clicked()
181 {
182 	goTo(exprError);
183 }
184 
185 
on_nextWarningPB_clicked()186 void GuiLog::on_nextWarningPB_clicked()
187 {
188 	goTo(exprWarning);
189 }
190 
191 
on_openDirPB_clicked()192 void GuiLog::on_openDirPB_clicked()
193 {
194 	support::FileName dir = logfile_.onlyPath();
195 	if (!dir.exists())
196 		return;
197 	QUrl qdir(QUrl::fromLocalFile(toqstr(from_utf8(dir.absFileName()))));
198 	// Give hints in case of bugs
199 	if (!qdir.isValid()) {
200 		LYXERR0("QUrl is invalid!");
201 		return;
202 	}
203 	if (!QDesktopServices::openUrl(qdir))
204 		LYXERR0("Unable to open QUrl even though dir exists!");
205 }
206 
207 
goTo(QRegExp const & exp) const208 void GuiLog::goTo(QRegExp const & exp) const
209 {
210 	QTextCursor const newc =
211 		logTB->document()->find(exp, logTB->textCursor());
212 	logTB->setTextCursor(newc);
213 }
214 
215 
contains(QRegExp const & exp) const216 bool GuiLog::contains(QRegExp const & exp) const
217 {
218 	return !logTB->document()->find(exp, logTB->textCursor()).isNull();
219 }
220 
221 
initialiseParams(string const & data)222 bool GuiLog::initialiseParams(string const & data)
223 {
224 	istringstream is(data);
225 	Lexer lex;
226 	lex.setStream(is);
227 
228 	string logtype, logfile;
229 	lex >> logtype;
230 	if (lex) {
231 		lex.next(true);
232 		logfile = lex.getString();
233 	}
234 	if (!lex)
235 		// Parsing of the data failed.
236 		return false;
237 
238 	logTypeCO->setEnabled(logtype == "latex");
239 	logTypeCO->clear();
240 
241 	FileName log(logfile);
242 
243 	if (logtype == "latex") {
244 		type_ = LatexLog;
245 		logTypeCO->addItem(qt_("LaTeX"), toqstr(logtype));
246 		FileName tmp = log;
247 		tmp.changeExtension("blg");
248 		if (tmp.exists()) {
249 			if (support::contains(tmp.fileContents("UTF-8"), from_ascii("This is Biber")))
250 				logTypeCO->addItem(qt_("Biber"), QString("bibtex"));
251 			else
252 				logTypeCO->addItem(qt_("BibTeX"), QString("bibtex"));
253 		}
254 		tmp.changeExtension("ilg");
255 		if (tmp.exists())
256 			logTypeCO->addItem(qt_("Index"), QString("index"));
257 	// FIXME: not sure "literate" still works.
258 	} else if (logtype == "literate") {
259 		type_ = LiterateLog;
260 		logTypeCO->addItem(qt_("Literate"), toqstr(logtype));
261 	} else if (logtype == "lyx2lyx") {
262 		type_ = Lyx2lyxLog;
263 		logTypeCO->addItem(qt_("LyX2LyX"), toqstr(logtype));
264 	} else if (logtype == "vc") {
265 		type_ = VCLog;
266 		logTypeCO->addItem(qt_("Version Control"), toqstr(logtype));
267 	} else
268 		return false;
269 
270 	logfile_ = log;
271 
272 	updateContents();
273 
274 	return true;
275 }
276 
277 
clearParams()278 void GuiLog::clearParams()
279 {
280 	logfile_.erase();
281 }
282 
283 
title() const284 docstring GuiLog::title() const
285 {
286 	switch (type_) {
287 	case LatexLog:
288 		return _("LaTeX Log");
289 	case LiterateLog:
290 		return _("Literate Programming Build Log");
291 	case Lyx2lyxLog:
292 		return _("lyx2lyx Error Log");
293 	case VCLog:
294 		return _("Version Control Log");
295 	default:
296 		return docstring();
297 	}
298 }
299 
300 
getContents(ostream & ss) const301 void GuiLog::getContents(ostream & ss) const
302 {
303 	ifstream in(logfile_.toFilesystemEncoding().c_str());
304 
305 	bool success = false;
306 
307 	// FIXME UNICODE
308 	// Our caller interprets the file contents as UTF8, but is that
309 	// correct?
310 	// spitz: No it isn't (generally). The log file encoding depends on the TeX
311 	// _output_ encoding (T1 etc.). We should account for that. See #10728.
312 	if (in) {
313 		ss << in.rdbuf();
314 		success = ss.good();
315 	}
316 
317 	if (success)
318 		return;
319 
320 	switch (type_) {
321 	case LatexLog:
322 		ss << to_utf8(_("Log file not found."));
323 		break;
324 	case LiterateLog:
325 		ss << to_utf8(_("No literate programming build log file found."));
326 		break;
327 	case Lyx2lyxLog:
328 		ss << to_utf8(_("No lyx2lyx error log file found."));
329 		break;
330 	case VCLog:
331 		ss << to_utf8(_("No version control log file found."));
332 		break;
333 	}
334 }
335 
336 
createGuiLog(GuiView & lv)337 Dialog * createGuiLog(GuiView & lv) { return new GuiLog(lv); }
338 
339 
340 } // namespace frontend
341 } // namespace lyx
342 
343 #include "moc_GuiLog.cpp"
344