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