1 #include <utility>
2 
3 /*
4 For general Scribus (>=1.3.2) copyright and licensing information please refer
5 to the COPYING file provided with the program. Following this notice may exist
6 a copyright and/or license notice that predates the release of Scribus 1.3.2
7 for which a new license (GPL+exception) is in place.
8 */
9 /***************************************************************************
10 						latexeditor.cpp  -  description
11 						-------------------
12 copyright            : Scribus Team
13 ***************************************************************************/
14 
15 /***************************************************************************
16 *                                                                         *
17 *   This program is free software; you can redistribute it and/or modify  *
18 *   it under the terms of the GNU General Public License as published by  *
19 *   the Free Software Foundation; either version 2 of the License, or     *
20 *   (at your option) any later version.                                   *
21 *                                                                         *
22 ***************************************************************************/
23 #include "latexeditor.h"
24 #include "latexhelpers.h"
25 #include "pageitem_latexframe.h"
26 #include "prefsmanager.h"
27 #include "ui/scmessagebox.h"
28 
29 #include <QDebug>
30 #include <QFile>
31 #include <QFrame>
32 #include <QFontComboBox>
33 #include <QLineEdit>
34 #include <QPushButton>
35 #include <QListWidget>
36 #include <QMessageBox>
37 #include <QTemporaryFile>
38 #include <cmath>
39 #include "filewatcher.h"
40 #include "util.h"
41 
LatexEditor(PageItem_LatexFrame * frame)42 LatexEditor::LatexEditor(PageItem_LatexFrame *frame): frame(frame)
43 {
44 	setupUi(this);
45 
46 	//Fill application list
47 	programComboBox->clear();
48 
49 	const QStringList configs = PrefsManager::instance().latexConfigs();
50 	for (const QString& config : configs)
51 	{
52 		QString name = LatexConfigCache::instance()->parser(config)->description();
53 		programComboBox->addItem(name, config);
54 		QString iconname = LatexConfigCache::instance()->parser(config)->icon();
55 		if (!iconname.isEmpty())
56 		{
57 			programComboBox->setItemIcon(programComboBox->count()-1, icon(config, iconname));
58 		}
59 	}
60 
61 	highlighter = new LatexHighlighter(sourceTextEdit->document());
62 	connect(buttonBox, SIGNAL(accepted()), this, SLOT(okClicked()));
63 	connect(buttonBox, SIGNAL(rejected()), this, SLOT(cancelClicked()));
64 	connect(updatePushButton, SIGNAL(clicked(bool)), this, SLOT(updateClicked(bool)));
65 	connect(revertPushButton, SIGNAL(clicked(bool)), this, SLOT(revertClicked(bool)));
66 	connect(killPushButton, SIGNAL(clicked(bool)), frame, SLOT(killProcess()));
67 	connect(externalEditorPushButton, SIGNAL(clicked(bool)), this, SLOT(extEditorClicked()));
68 	connect(dpiSpinBox, SIGNAL(editingFinished()), this, SLOT(dpiChanged()));
69 	connect(frame, SIGNAL(formulaAutoUpdate(QString,QString)), this, SLOT(formulaChanged(QString,QString)));
70 	connect(frame, SIGNAL(latexFinished()), this, SLOT(latexFinished()));
71 	connect(frame, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(stateChanged(QProcess::ProcessState)));
72 	connect(programComboBox, SIGNAL(activated(int)), this, SLOT(applicationChanged()));
73 	updateConfigFile();
74 
75 	extEditor = new QProcess();
76 	connect(extEditor, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(extEditorFinished(int,QProcess::ExitStatus)));
77 	connect(extEditor, SIGNAL(error(QProcess::ProcessError)), this, SLOT(extEditorError(QProcess::ProcessError)));
78 	extEditor->setProcessChannelMode(QProcess::MergedChannels);
79 
80 	fileWatcher = new FileWatcher(this);
81 	fileWatcher->stop();
82 	fileWatcher->setTimeOut(1500);
83 }
84 
~LatexEditor()85 LatexEditor::~LatexEditor()
86 {
87 	//IMPORTANT: Make sure no signals are emitted which
88 	// would cause crashes because the handlers access undefined memory.
89 	fileWatcher->disconnect();
90 	delete fileWatcher;
91 
92 	extEditor->disconnect();
93 	//No need to kill the editor
94 	delete extEditor;
95 
96 	QDir dir;
97 	if (!extEditorFile.isEmpty() && !dir.remove(extEditorFile)) {
98 		qCritical() << "RENDER FRAME: Failed to remove editorfile" << qPrintable(extEditorFile);
99 	}
100 
101 	buttonBox->disconnect();
102 	exitEditor();
103 	delete highlighter;
104 }
105 
changeEvent(QEvent * e)106 void LatexEditor::changeEvent(QEvent *e)
107 {
108 	if (e->type() == QEvent::LanguageChange)
109 	{
110 		retranslateUi(this);
111 		loadSettings();
112 	}
113 	else
114 		QWidget::changeEvent(e);
115 }
116 
startEditor()117 void LatexEditor::startEditor()
118 {
119 	revert();
120 	initialize();
121 	show();
122 }
123 
extEditorClicked()124 void LatexEditor::extEditorClicked()
125 {
126 	if (extEditor->state() != QProcess::NotRunning)
127 	{
128 		ScMessageBox::information(nullptr, tr("Information"),
129 		"<qt>" + tr("An editor for this frame is already running!") +
130 		"</qt>");
131 		return;
132 	}
133 
134 	QString full_command = PrefsManager::instance().latexEditorExecutable();
135 	if (full_command.isEmpty())
136 	{
137 		ScMessageBox::information(nullptr, tr("Information"),
138 		"<qt>" + tr("Please specify an editor in the preferences!") +
139 		"</qt>");
140 		return;
141 	}
142 
143 	writeExternalEditorFile(); //Don't move this command! It sets extEditorFile
144 
145 	QString editorFilePath = QString("\"%1\"").arg(extEditorFile);
146 	QString tempFilePath   = QString("\"%1\"").arg(getLongPathName(QDir::tempPath()));
147 	if (full_command.contains("%file")) {
148 		full_command.replace("%file", QDir::toNativeSeparators(editorFilePath));
149 	} else {
150 		full_command += " " + QDir::toNativeSeparators(editorFilePath);
151 	}
152 	full_command.replace("%dir", QDir::toNativeSeparators(tempFilePath));
153 
154 	extEditor->setWorkingDirectory(QDir::tempPath());
155 
156 	externalEditorPushButton->setEnabled(false);
157 	externalEditorPushButton->setText(tr("Editor running!"));
158 
159 	extEditor->start(full_command);
160 }
161 
writeExternalEditorFile()162 void LatexEditor::writeExternalEditorFile()
163 {
164 	fileWatcher->stop();
165 	fileWatcher->disconnect(); //Avoid triggering false updates
166 
167 	//First create a temp file name
168 	if (extEditorFile.isEmpty())
169 	{
170 		QTemporaryFile *editortempfile = new QTemporaryFile(QDir::tempPath() + "/scribus_temp_editor_XXXXXX");
171 		if (!editortempfile->open())
172 		{
173 			ScMessageBox::critical(nullptr, tr("Error"), "<qt>" +
174 				tr("Could not create a temporary file to run the external editor!")
175 				+ "</qt>");
176 		}
177 		extEditorFile = getLongPathName(editortempfile->fileName());
178 		editortempfile->setAutoRemove(false);
179 		editortempfile->close();
180 		delete editortempfile;
181 		fileWatcher->addFile(extEditorFile);
182 	}
183 	QFile f(extEditorFile);
184 	if(!f.open(QIODevice::WriteOnly))
185 	{
186 		qDebug()<<"Unable to open editor file in LatexEditor::writeExternalEditorFile()";
187 		return;
188 	}
189 	f.write(frame->formula().toUtf8());
190 	f.close();
191 	fileWatcher->forceScan();
192 	connect(fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(extEditorFileChanged(QString)));
193 	fileWatcher->start();
194 }
195 
loadExternalEditorFile()196 void LatexEditor::loadExternalEditorFile()
197 {
198 	QString new_formula;
199 	QFile f(extEditorFile);
200 	if(!f.open(QIODevice::ReadOnly))
201 	{
202 		qDebug()<<"Unable to open editor file in LatexEditor::loadExternalEditorFile()";
203 		return;
204 	}
205 	new_formula = QString::fromUtf8(f.readAll());
206 	f.close();
207 	if (!new_formula.isEmpty())
208 	{
209 		frame->setFormula(new_formula);
210 		sourceTextEdit->setPlainText(new_formula);
211 	}
212 	this->update();
213 }
214 
extEditorFinished(int exitCode,QProcess::ExitStatus exitStatus)215 void LatexEditor::extEditorFinished(int exitCode, QProcess::ExitStatus exitStatus)
216 {
217 	externalEditorPushButton->setEnabled(true);
218 	externalEditorPushButton->setText( tr("Run External Editor...") );
219 	if (exitCode && extEditor)
220 	{
221 		qCritical() << "RENDER FRAME: Editor failed. Output was: " <<
222 			qPrintable(QString(extEditor->readAllStandardOutput()));
223 		ScMessageBox::critical(nullptr, tr("Error"), "<qt>" +
224 			tr("Running the editor failed with exitcode %d!").arg(exitCode) +
225 			"</qt>");
226 		return;
227 	}
228 }
229 
extEditorFileChanged(const QString & filename)230 void LatexEditor::extEditorFileChanged(const QString& filename)
231 {
232 	loadExternalEditorFile();
233 	frame->rerunApplication();
234 }
235 
extEditorError(QProcess::ProcessError error)236 void LatexEditor::extEditorError(QProcess::ProcessError error)
237 {
238 	externalEditorPushButton->setEnabled(true);
239 	externalEditorPushButton->setText( tr("Run External Editor...") );
240 	ScMessageBox::critical(nullptr, tr("Error"), "<qt>" +
241 		tr("Running the editor \"%1\" failed!").
242 		arg(PrefsManager::instance().latexEditorExecutable()) +
243 		"</qt>");
244 }
245 
exitEditor()246 void LatexEditor::exitEditor()
247 {
248 	hide();
249 }
250 
revert()251 void LatexEditor::revert()
252 {
253 	sourceTextEdit->setPlainText(frame->formula());
254 }
255 
initialize()256 void LatexEditor::initialize()
257 {
258 	preambleCheckBox->setChecked(frame->usePreamble());
259 	dpiSpinBox->setValue(frame->dpi());
260 	stateChanged(frame->state());
261 	messagesTextEdit->setPlainText(frame->output());
262 	disconnect(programComboBox, SIGNAL(activated(int)), this, SLOT(applicationChanged()));
263 	int ind = programComboBox->findData(frame->configFile()); //TODO: Needs special care wrt relative filenames
264 	if (ind != -1)
265 		programComboBox->setCurrentIndex(ind);
266 	connect(programComboBox, SIGNAL(activated(int)), this, SLOT(applicationChanged()));
267 }
268 
apply(bool force)269 void LatexEditor::apply(bool force)
270 {
271 	bool changed = frame->setFormula(sourceTextEdit->toPlainText());
272 
273 	//TODO: Needs special care wrt relative filenames
274 	QString newConfig = programComboBox->itemData(programComboBox->currentIndex()).toString();
275 	if (newConfig != frame->configFile()) {
276 		changed = true;
277 		frame->setConfigFile(newConfig);
278 	}
279 
280 	if (frame->usePreamble() != preambleCheckBox->isChecked() ||
281 		frame->dpi() != dpiSpinBox->value()) {
282 		changed = true;
283 		frame->setUsePreamble(preambleCheckBox->isChecked());
284 		frame->setDpi(dpiSpinBox->value());
285 	}
286 	QString key;
287 	QString value;
288 
289 	QMapIterator<QString, XmlWidget *> i(widgetMap);
290 	while (i.hasNext()) {
291 		i.next();
292 		key = i.key();
293 		value = i.value()->toString();
294 		if (frame->editorProperties[key] != value) {
295 			changed = true;
296 			frame->editorProperties[key] = value;
297 		}
298 	}
299 
300 	if (changed || force) {
301 		frame->rerunApplication(true);
302 	}
303 }
304 
applicationChanged()305 void LatexEditor::applicationChanged()
306 {
307 	//TODO: Needs special care wrt relative filenames
308 	QString newConfig = programComboBox->itemData(programComboBox->currentIndex()).toString();
309 	if (newConfig != frame->configFile())
310 	{
311 		frame->setConfigFile(newConfig);
312 		updateConfigFile();
313 		frame->rerunApplication(true);
314 		sourceTextEdit->setPlainText(frame->formula());
315 	}
316 }
317 
dpiChanged()318 void LatexEditor::dpiChanged()
319 {
320 	apply();
321 }
322 
formulaChanged(const QString & oldText,const QString & newText)323 void LatexEditor::formulaChanged(const QString& oldText, const QString& newText)
324 {
325 	sourceTextEdit->setPlainText(newText);
326 }
327 
okClicked()328 void LatexEditor::okClicked()
329 {
330 	apply();
331 	exitEditor();
332 }
333 
cancelClicked()334 void LatexEditor::cancelClicked()
335 {
336 	revert();
337 	exitEditor();
338 }
339 
revertClicked(bool unused)340 void LatexEditor::revertClicked(bool unused)
341 {
342 	revert();
343 }
344 
updateClicked(bool unused)345 void LatexEditor::updateClicked(bool unused)
346 {
347 	apply(true);
348 }
349 
latexFinished()350 void LatexEditor::latexFinished()
351 {
352 	messagesTextEdit->setPlainText(frame->output());
353 }
354 
stateChanged(QProcess::ProcessState state)355 void LatexEditor::stateChanged(QProcess::ProcessState state)
356 {
357 	if (state == QProcess::Starting) {
358 		messagesTextEdit->setPlainText("");
359 	}
360 	QString text( tr("Status: ") );
361 	if (state == QProcess::NotRunning)
362 	{
363 		if (frame->error())
364 			text += tr("Error");
365 		else
366 			text += tr("Finished");
367 	}
368 	else
369 		text += tr("Running");
370 
371 	statusLabel->setText(text);
372 	killPushButton->setEnabled(state != QProcess::NotRunning);
373 }
374 
375 
icon(const QString & config,const QString & fn)376 QIcon LatexEditor::icon(const QString& config, const QString& fn)
377 {
378 	QFileInfo fiConfig(LatexConfigParser::absoluteFilename(config));
379 	QFileInfo fiIcon(fiConfig.path()+"/"+fn);
380 	if (fiIcon.exists() && fiIcon.isReadable())
381 		return QIcon(fiConfig.path()+"/"+fn);
382 	QIcon *tmp = IconBuffer::instance()->icon(iconFile(config), fn);
383 	if (tmp)
384 		return *tmp;
385 	return QIcon();
386 }
387 
388 
iconFile(QString config)389 QString LatexEditor::iconFile(QString config)
390 {
391 	QFileInfo fiConfig(LatexConfigParser::absoluteFilename(std::move(config)));
392 	return fiConfig.path() + "/" + fiConfig.completeBaseName() + ".tar";
393 }
394 
395 
updateConfigFile()396 void LatexEditor::updateConfigFile()
397 {
398 	QString newConfigFile = LatexConfigParser::absoluteFilename(frame->configFile());
399 	if (currentConfigFile == newConfigFile)
400 		return;
401 	currentConfigFile = newConfigFile;
402 	currentIconFile = iconFile(currentConfigFile);
403 	QFileInfo fi(currentConfigFile);
404 
405 	if (!fi.exists() || !fi.isReadable())
406 	{
407 		ScMessageBox::critical(nullptr, QObject::tr("Error"), "<qt>" +
408 				QObject::tr("Configfile %1 not found or the file is not readable").
409 				arg(currentConfigFile) + "</qt>");
410 		return;
411 	}
412 
413 	loadSettings();
414 
415 	QMapIterator<QString, XmlWidget *> i(widgetMap);
416 	while (i.hasNext())
417 	{
418 		i.next();
419 		QString key = i.key();
420 		XmlWidget *value = i.value();
421 		if (frame->editorProperties.contains(key))
422 			value->fromString(frame->editorProperties[key]);
423 	}
424 	//TODO: Needs special care wrt relative filenames
425 	highlighter->setConfig(&LatexConfigCache::instance()->parser(currentConfigFile)->highlighterRules);
426 }
427 
428 #define xmlError() qWarning() << "XML-ERROR:" << xml->lineNumber() \
429 	<< ":" << xml->columnNumber() << ":"
430 
loadSettings()431 void LatexEditor::loadSettings()
432 {
433 	while (tabWidget->count()>1)
434 	{
435 		QWidget *widget = tabWidget->widget(1);
436 		tabWidget->removeTab(1);
437 		delete widget;
438 	}
439 	widgetMap.clear();
440 
441 	QFile f(LatexConfigParser::absoluteFilename(frame->configFile()));
442 	if (!f.open(QIODevice::ReadOnly))
443 	{
444 		qDebug()<<"Unable to open config file in LatexEditor::loadSettings()";
445 		return;
446 	}
447 	I18nXmlStreamReader xml(&f);
448 	while (!xml.atEnd())
449 	{
450 		xml.readNext();
451 		if (xml.isWhitespace() || xml.isComment()) continue;
452 		if (xml.isStartElement() && xml.name() == "tab")
453 		{
454 			if (xml.attributes().value("type") == "settings")
455 			{
456 				createNewSettingsTab(&xml);
457 			} else if (xml.attributes().value("type") == "items")
458 			{
459 				createNewItemsTab(&xml);
460 			}
461 			else
462 			{
463 				qWarning() << "XML-ERROR: " << xml.lineNumber() << ":"
464 						<< xml.columnNumber() << ":" << "Unknown tab type"
465 						<< xml.attributes().value("type").toString();
466 			}
467 		}
468 	}
469 	if (xml.hasError()) {
470 		qWarning() << "XML-ERROR: " << xml.lineNumber() << ":"
471 				<< xml.columnNumber() << ":" << xml.errorString();
472 	}
473 	f.close();
474 }
475 
createNewSettingsTab(I18nXmlStreamReader * xml)476 void LatexEditor::createNewSettingsTab(I18nXmlStreamReader *xml)
477 {
478 	QStringRef tagname;
479 	QFrame *newTab = new QFrame();
480 	newTab->setFrameShape(QFrame::NoFrame);
481 	QGridLayout *layout = new QGridLayout(newTab);
482 	layout->setColumnStretch(1, 10);
483 
484 	QString title = "No Title";
485 
486 	while (!xml->atEnd()) {
487 		xml->readNext();
488 		if (xml->isWhitespace() || xml->isComment()) continue;
489 		tagname = xml->name();
490 		if (xml->isEndElement() && (tagname == "tab")) {
491 			break;
492 		}
493 		if (!xml->isStartElement()) {
494 			xmlError() << "Unexpected element (not a start element)!";
495 			continue;
496 		}
497 
498 		if (tagname == "comment") {
499 			QLabel *label = new QLabel(xml->readI18nText());
500 			int row = layout->rowCount();
501 			label->setWordWrap(true);
502 			layout->addWidget(label, row, 0, 1, 3);
503 		} else if (tagname == "title") {
504 			title = xml->readI18nText();
505 		} else {
506 			XmlWidget *widget = XmlWidget::fromXml(xml);
507 			if (dynamic_cast<QWidget *>(widget)) {
508 				QLabel *label = new QLabel(widget->description());
509 				label->setWordWrap(true);
510 				QString name = widget->name();
511 
512 				int row = layout->rowCount();
513 				layout->addWidget(label, row, 0);
514 				layout->addWidget(dynamic_cast<QWidget *>(widget), row, 1);
515 
516 				/* Commented out, because it doesn't make much sense. All
517 				   the options should be handled in the preamble. Keeping this
518 				   around as a reference for future widgets.
519 				StringPushButton *button = new StringPushButton(
520 						tr("Insert"), name);
521 				connect(button, SIGNAL(clickedWithData(QString)),
522 						this, SLOT(tagButtonClicked(QString)));
523 				layout->addWidget(button, row, 2);*/
524 
525 				if (widgetMap.contains(name)) {
526 					xmlError() << "There is already an widget with name" <<
527 						name << "!";
528 				}
529 				widgetMap[name] = widget;
530 			} else {
531 				xmlError() << "Unexpected tag" << tagname.toString() <<
532 						"in settings tab";
533 			}
534 		}
535 	}
536 	layout->setRowStretch(layout->rowCount(), 10);
537 	tabWidget->addTab(newTab, title);
538 }
539 
createNewItemsTab(I18nXmlStreamReader * xml)540 void LatexEditor::createNewItemsTab(I18nXmlStreamReader *xml)
541 {
542 	QString title = "No Title!";
543 
544 
545 	QFrame *newTab = new QFrame();
546 	newTab->setFrameShape(QFrame::NoFrame);
547 	QVBoxLayout *vLayout = new QVBoxLayout(newTab);
548 
549 	QListWidget *iconList = new QListWidget();
550 	iconList->setViewMode(QListView::IconMode);
551 	iconList->setGridSize(QSize(55, 55));
552 	iconList->setMovement(QListView::Static);
553 	iconList->setFlow(QListView::LeftToRight);
554 	iconList->setWrapping(true);
555 	iconList->setResizeMode(QListView::Adjust);
556 
557 	connect(iconList, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, SLOT(newItemSelected(QListWidgetItem *, QListWidgetItem *)));
558 	connect(iconList, SIGNAL(itemDoubleClicked (QListWidgetItem *)), this, SLOT(itemDoubleClicked(QListWidgetItem *)));
559 
560 	QHBoxLayout *hLayout = new QHBoxLayout();
561 	QLabel *statusLabel = new QLabel(tr("No item selected!"));
562 	DataPushButton *insertPushButton = new DataPushButton( tr("Insert Symbol") , iconList);
563 	connect(insertPushButton, SIGNAL(clickedWithData(QObject *)), this, SLOT(insertButtonClicked(QObject *)));
564 	hLayout->addWidget(statusLabel, 100);
565 	hLayout->addWidget(insertPushButton, 0);
566 
567 	vLayout->addWidget(iconList, 100);
568 	vLayout->addLayout(hLayout, 0);
569 
570 	QStringRef tagname;
571 	while (!xml->atEnd())
572 	{
573 		xml->readNext();
574 		if (xml->isWhitespace() || xml->isComment()) continue;
575 		tagname = xml->name();
576 		if (xml->isEndElement() && (tagname == "tab"))
577 			break;
578 		if (!xml->isStartElement())
579 		{
580 			xmlError() << "Unexpected end element "	<<tagname.toString()<<"in item tab";
581 			continue;
582 		}
583 		if (tagname == "title")
584 		{
585 			title = xml->readI18nText();
586 		}
587 		else if (tagname == "item")
588 		{
589 			QString value = xml->attributes().value("value").toString();
590 			QString img = xml->attributes().value("image").toString();
591 			QString text = xml->readI18nText();
592 
593 			QString status = value;
594 			if (text.isEmpty())
595 				text = value;
596 			else if (text != value)
597 				status = text + "(" + value +")";
598 
599 			QIcon *icon = nullptr;
600 			if (!img.isEmpty())
601 				icon = IconBuffer::instance()->icon(currentIconFile, img);
602 			QListWidgetItem *item;
603 			if (!icon)
604 				item = new QListWidgetItem(text, iconList);
605 			else
606 				item = new QListWidgetItem(*icon, "", iconList);
607 			item->setData(Qt::UserRole, value);
608 			item->setData(Qt::UserRole + 1, QVariant::fromValue((void *) statusLabel)); //UGLY
609 			item->setToolTip(text);
610 			item->setStatusTip(status);
611 		}
612 		else
613 		{
614 			xmlError() << "Unexpected tag" << tagname.toString() <<
615 				"in item tab!";
616 			continue;
617 		}
618 	}
619 	tabWidget->addTab(newTab, title);
620 }
621 
tagButtonClicked(const QString & tagname)622 void LatexEditor::tagButtonClicked(const QString& tagname)
623 {
624 	sourceTextEdit->insertPlainText("$scribus_"+tagname+"$");
625 }
626 
newItemSelected(QListWidgetItem * newItem,QListWidgetItem *)627 void LatexEditor::newItemSelected(QListWidgetItem *newItem, QListWidgetItem *)
628 {
629 	QLabel *label = (QLabel *)(newItem->data(Qt::UserRole + 1).value<void *>());
630 	label->setText(newItem->statusTip());
631 }
632 
itemDoubleClicked(QListWidgetItem * item)633 void LatexEditor::itemDoubleClicked(QListWidgetItem *item)
634 {
635 	sourceTextEdit->insertPlainText(item->data(Qt::UserRole).toString());
636 }
637 
insertButtonClicked(QObject * widget)638 void LatexEditor::insertButtonClicked(QObject *widget)
639 {
640 	QListWidget *list = qobject_cast<QListWidget*>(widget);
641 	Q_ASSERT(list);
642 	sourceTextEdit->insertPlainText(
643 		list->currentItem()->data(Qt::UserRole).toString());
644 }
645 
646 
647 class SCRIBUS_API XmlFontComboBox : public XmlWidget, public QFontComboBox
648 {
649 	public:
XmlFontComboBox(I18nXmlStreamReader * xml)650 		XmlFontComboBox(I18nXmlStreamReader *xml) : XmlWidget(xml)
651 		{
652 			fromString(m_defaultValue);
653 		}
654 
toString() const655 		QString toString() const {
656 			return currentFont().toString();
657 		}
658 
fromString(QString str)659 		void fromString(QString str) {
660 			QFont font;
661 			font.fromString(str);
662 			this->setCurrentFont(font);
663 		}
664 };
665 
666 class SCRIBUS_API XmlSpinBox : public XmlWidget, public QSpinBox
667 {
668 	public:
XmlSpinBox(I18nXmlStreamReader * xml)669 		XmlSpinBox(I18nXmlStreamReader *xml) :  XmlWidget(xml, false)
670 		{
671 			setRange(
672 				xml->attributes().value("min").toString().toInt(),
673 				xml->attributes().value("max").toString().toInt()
674 			);
675 			setSingleStep(xml->attributes().value("step").toString().toInt());
676 			setSpecialValueText(xml->attributes().value("special").toString());
677 			fromString(m_defaultValue);
678 			m_description = xml->readI18nText();
679 		}
680 
toString() const681 		QString toString() const override
682 		{
683 			if (value() == minimum() && !specialValueText().isEmpty())
684 				return specialValueText();
685 			return QString::number(value());
686 		}
687 
fromString(QString str)688 		void fromString(QString str) override
689 		{
690 			if (str == specialValueText())
691 				setValue(minimum());
692 			else
693 				setValue(str.toInt());
694 		}
695 };
696 
697 class SCRIBUS_API XmlDoubleSpinBox : public XmlWidget, public QDoubleSpinBox
698 {
699 	public:
XmlDoubleSpinBox(I18nXmlStreamReader * xml)700 		XmlDoubleSpinBox(I18nXmlStreamReader *xml) :
701 			XmlWidget(xml, false)
702 			{
703 			setRange(
704 				xml->attributes().value("min").toString().toDouble(),
705 				xml->attributes().value("max").toString().toDouble()
706 			);
707 			setSingleStep(
708 				xml->attributes().value("step").toString().toDouble());
709 			setSpecialValueText(xml->attributes().value("special").toString());
710 			fromString(m_defaultValue);
711 			m_description = xml->readI18nText();
712 		}
713 
toString() const714 		QString toString() const override
715 		{
716 			if (value() == minimum() && !specialValueText().isEmpty())
717 				return specialValueText();
718 			return QString::number(value());
719 		}
720 
fromString(QString str)721 		void fromString(QString str) override
722 		{
723 			if (str == specialValueText())
724 				setValue(minimum());
725 			else
726 				setValue(str.toDouble());
727 		}
728 };
729 
730 class SCRIBUS_API XmlLineEdit : public XmlWidget, public QLineEdit
731 {
732 	public:
XmlLineEdit(I18nXmlStreamReader * xml)733 		XmlLineEdit(I18nXmlStreamReader *xml) :  XmlWidget(xml) {
734 			fromString(m_defaultValue);
735 		}
736 
toString() const737 		QString toString() const override {
738 			return text();
739 		}
740 
fromString(QString str)741 		void fromString(QString str) override {
742 			setText(str);
743 		}
744 };
745 
746 class SCRIBUS_API XmlTextEdit : public XmlWidget, public QTextEdit
747 {
748 	public:
XmlTextEdit(I18nXmlStreamReader * xml)749 		XmlTextEdit(I18nXmlStreamReader *xml) :  XmlWidget(xml) {
750 			fromString(m_defaultValue);
751 		}
752 
toString() const753 		QString toString() const override {
754 			return toPlainText();
755 		}
756 
fromString(QString str)757 		void fromString(QString str) override {
758 			setPlainText(str);
759 		}
760 };
761 
762 class SCRIBUS_API XmlColorPicker : public XmlWidget, public QLabel
763 {
764 	public:
XmlColorPicker(I18nXmlStreamReader * xml)765 		XmlColorPicker(I18nXmlStreamReader *xml) :  XmlWidget(xml),
766 			QLabel("Color pickers are not implemented yet!")
767 		{
768 			setWordWrap(true);
769 			fromString(m_defaultValue);
770 		}
771 
toString() const772 		QString toString() const override {
773 			return "Not implemented!";
774 		}
775 
fromString(QString str)776 		void fromString(QString str) override {
777 			qDebug() << "Color pickers are not implemented yet!";
778 		}
779 };
780 
781 class SCRIBUS_API XmlComboBox : public XmlWidget, public QComboBox
782 {
783 	public:
XmlComboBox(I18nXmlStreamReader * xml)784 		XmlComboBox(I18nXmlStreamReader *xml) :  XmlWidget(xml, false)
785 		{
786 			QStringRef tagname;
787 			while (!xml->atEnd()) {
788 				xml->readNext();
789 				if (xml->isWhitespace() || xml->isComment()) continue;
790 				tagname = xml->name();
791 				if (xml->isEndElement() && (tagname == "list")) {
792 					fromString(m_defaultValue);
793 					return;
794 				}
795 				if (xml->isEndElement()) {
796 					xmlError() << "Unexpected end element" << tagname.toString();
797 					continue;
798 				}
799 				if (tagname == "title") {
800 					m_description = xml->readI18nText();
801 				} else if (tagname == "option") {
802 					QString value = xml->attributes().value("value").toString();
803 					QString text = xml->readI18nText();
804 					addItem(text, value);
805 				} else {
806 					xmlError() << "Unexpected tag" << tagname.toString() <<
807 						"in list!";
808 				}
809 			}
810 		}
811 
toString() const812 		QString toString() const override {
813 			return itemData(currentIndex()).toString();
814 		}
815 
fromString(QString str)816 		void fromString(QString str) override {
817 			setCurrentIndex(findData(str));
818 		}
819 };
820 
fromXml(I18nXmlStreamReader * xml)821 XmlWidget* XmlWidget::fromXml(I18nXmlStreamReader *xml)
822 {
823 	QStringRef tagname = xml->name();
824 	if (tagname == "font")
825 		return new XmlFontComboBox(xml);
826 	if (tagname == "spinbox")
827 	{
828 		if (xml->attributes().value("type") == "double")
829 			return new XmlDoubleSpinBox(xml);
830 		return new XmlSpinBox(xml);
831 	}
832 	if (tagname == "color")
833 		return new XmlColorPicker(xml);
834 	if (tagname == "text")
835 	{
836 		if (xml->attributes().value("type") == "long")
837 			return new XmlTextEdit(xml);
838 		return new XmlLineEdit(xml);
839 	}
840 	if (tagname == "list")
841 		return new XmlComboBox(xml);
842 	return nullptr;
843 }
844 
XmlWidget(I18nXmlStreamReader * xml,bool readDescription)845 XmlWidget::XmlWidget(I18nXmlStreamReader *xml, bool readDescription)
846 {
847 	m_name = xml->attributes().value("name").toString();
848 	m_defaultValue = xml->attributes().value("default").toString();
849 	if (readDescription)
850 		m_description = xml->readI18nText();
851 }
852 
loadFile(const QString & filename)853 void IconBuffer::loadFile(const QString& filename)
854 {
855 	if (loadedFiles.contains(filename)) return;
856 	loadedFiles << filename;
857 	file = new QFile(filename);
858 	if (!file->open(QIODevice::ReadOnly))
859 		return;
860 	basePos = 0;
861 	while (!file->atEnd())
862 	{
863 		QString name(readHeader());
864 		if (name.isEmpty()) break;
865 		if (!len) continue;
866 		icons.insert(filename + ":" + name, readData());
867 	}
868 	file->close();
869 	delete file;
870 	file = nullptr;
871 }
872 
icon(const QString & filename,const QString & name)873 QIcon *IconBuffer::icon(const QString& filename, const QString& name)
874 {
875 	loadFile(filename);
876 	QString cname = filename + ":" + name;
877 	if (icons.contains(cname))
878 		return &(icons[cname]);
879 	qWarning() << "Icon" << cname << "not found!";
880 	return nullptr;
881 }
882 
readHeader()883 QString IconBuffer::readHeader()
884 {
885 	//TODO: Error checking
886 	Q_ASSERT(file);
887 	char buffer[101];
888 	file->seek(basePos);
889 	file->read(buffer, 100);
890 	buffer[100] = 0;
891 	QString name = QString::fromLatin1(buffer);
892 	file->seek(basePos + 124);
893 	file->read(buffer, 12);
894 	buffer[12] = 0;
895 	bool ok;
896 	len = QString::fromLatin1(buffer).toInt(&ok, 8);
897 	basePos += 512;
898 	return name;
899 }
900 
readData()901 QIcon IconBuffer::readData()
902 {
903 	file->seek(basePos);
904 	QByteArray data = file->read(len);
905 	QPixmap pixmap;
906 	pixmap.loadFromData(data);
907 	basePos += static_cast<int>(ceil(len/512.0) * 512);
908 	return QIcon(pixmap);
909 }
910 
911 IconBuffer *IconBuffer::_instance = nullptr;
instance()912 IconBuffer *IconBuffer::instance()
913 {
914 	if (!_instance)
915 		_instance = new IconBuffer();
916 	return _instance;
917 }
918