File indexing completed on 2024-04-28 05:08:16

0001 /***************************************************************************
0002     Copyright (C) 2001-2009 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "configdialog.h"
0026 #include "field.h"
0027 #include "collection.h"
0028 #include "collectionfactory.h"
0029 #include "fetch/execexternalfetcher.h"
0030 #include "fetch/fetchmanager.h"
0031 #include "fetch/configwidget.h"
0032 #include "controller.h"
0033 #include "fetcherconfigdialog.h"
0034 #include "tellico_kernel.h"
0035 #include "utils/tellico_utils.h"
0036 #include "utils/string_utils.h"
0037 #include "config/tellico_config.h"
0038 #include "core/tellico_strings.h"
0039 #include "images/imagefactory.h"
0040 #include "gui/combobox.h"
0041 #include "gui/collectiontypecombo.h"
0042 #include "gui/previewdialog.h"
0043 #include "newstuff/manager.h"
0044 #include "fieldformat.h"
0045 #include "tellico_debug.h"
0046 
0047 #include <KLocalizedString>
0048 #include <KConfig>
0049 #include <KAcceleratorManager>
0050 #include <KColorCombo>
0051 #include <KHelpClient>
0052 #include <KRecentDirs>
0053 
0054 #ifdef ENABLE_KNEWSTUFF3
0055 #if KNEWSTUFF_VERSION < QT_VERSION_CHECK(5, 91, 0)
0056 #include <KNS3/Button>
0057 #else
0058 #include <KNSWidgets/Button>
0059 #endif
0060 #endif
0061 
0062 #include <QSpinBox>
0063 #include <QLineEdit>
0064 #include <QPushButton>
0065 #include <QSize>
0066 #include <QLayout>
0067 #include <QLabel>
0068 #include <QCheckBox>
0069 #include <QPixmap>
0070 #include <QRegularExpression>
0071 #include <QFileInfo>
0072 #include <QRadioButton>
0073 #include <QFrame>
0074 #include <QFontComboBox>
0075 #include <QGroupBox>
0076 #include <QButtonGroup>
0077 #include <QInputDialog>
0078 #include <QVBoxLayout>
0079 #include <QHBoxLayout>
0080 #include <QApplication>
0081 #include <QTimer>
0082 #include <QFileDialog>
0083 
0084 namespace {
0085   static const int CONFIG_MIN_WIDTH = 640;
0086   static const int CONFIG_MIN_HEIGHT = 420;
0087 }
0088 
0089 using Tellico::ConfigDialog;
0090 
0091 ConfigDialog::ConfigDialog(QWidget* parent_)
0092     : KPageDialog(parent_)
0093     , m_initializedPages(0)
0094     , m_modifying(false)
0095     , m_okClicked(false) {
0096   setFaceType(List);
0097   setModal(true);
0098   setWindowTitle(i18n("Configure Tellico"));
0099   setStandardButtons(QDialogButtonBox::Help |
0100                      QDialogButtonBox::Ok |
0101                      QDialogButtonBox::Apply |
0102                      QDialogButtonBox::Cancel |
0103                      QDialogButtonBox::RestoreDefaults);
0104 
0105   setupGeneralPage();
0106   setupPrintingPage();
0107   setupTemplatePage();
0108   setupFetchPage();
0109 
0110   updateGeometry();
0111   QSize s = sizeHint();
0112   resize(qMax(s.width(), CONFIG_MIN_WIDTH), qMax(s.height(), CONFIG_MIN_HEIGHT));
0113 
0114   // OK button is connected to buttonBox accepted() signal which is already connected to accept() slot
0115   connect(button(QDialogButtonBox::Apply), &QAbstractButton::clicked, this, &ConfigDialog::slotApply);
0116   connect(button(QDialogButtonBox::Help), &QAbstractButton::clicked, this, &ConfigDialog::slotHelp);
0117   connect(button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, this, &ConfigDialog::slotDefault);
0118 
0119   button(QDialogButtonBox::Ok)->setEnabled(false);
0120   button(QDialogButtonBox::Apply)->setEnabled(false);
0121   button(QDialogButtonBox::Ok)->setDefault(true);
0122   button(QDialogButtonBox::Ok)->setShortcut(Qt::CTRL | Qt::Key_Return);
0123 
0124   connect(this, &KPageDialog::currentPageChanged, this, &ConfigDialog::slotInitPage);
0125 }
0126 
0127 ConfigDialog::~ConfigDialog() {
0128   foreach(Fetch::ConfigWidget* widget, m_newStuffConfigWidgets) {
0129     widget->removed();
0130   }
0131 }
0132 
0133 void ConfigDialog::slotInitPage(KPageWidgetItem* item_) {
0134   Q_ASSERT(item_);
0135   // every page item has a frame
0136   // if the frame has no layout, then we need to initialize the itme
0137   QFrame* frame = ::qobject_cast<QFrame*>(item_->widget());
0138   Q_ASSERT(frame);
0139   if(frame->layout()) {
0140     return;
0141   }
0142 
0143   const QString name = item_->name();
0144   // these names must be kept in sync with the page names
0145   if(name == i18n("General")) {
0146     initGeneralPage(frame);
0147   } else if(name == i18n("Printing")) {
0148     initPrintingPage(frame);
0149   } else if(name == i18n("Templates")) {
0150     initTemplatePage(frame);
0151   } else if(name == i18n("Data Sources")) {
0152     initFetchPage(frame);
0153   }
0154 }
0155 
0156 void ConfigDialog::accept() {
0157   m_okClicked = true;
0158   slotApply();
0159   KPageDialog::accept();
0160   m_okClicked = false;
0161 }
0162 
0163 void ConfigDialog::slotApply() {
0164   emit signalConfigChanged();
0165   button(QDialogButtonBox::Apply)->setEnabled(false);
0166 }
0167 
0168 void ConfigDialog::slotDefault() {
0169   // only change the defaults on the active page
0170   Config::self()->useDefaults(true);
0171   const QString name = currentPage()->name();
0172   if(name == i18n("General")) {
0173     readGeneralConfig();
0174   } else if(name == i18n("Printing")) {
0175     readPrintingConfig();
0176   } else if(name == i18n("Templates")) {
0177     readTemplateConfig();
0178   }
0179   Config::self()->useDefaults(false);
0180   slotModified();
0181 }
0182 
0183 void ConfigDialog::slotHelp() {
0184   const QString name = currentPage()->name();
0185   // these names must be kept in sync with the page names
0186   if(name == i18n("General")) {
0187     KHelpClient::invokeHelp(QStringLiteral("configuration.html#general-options"));
0188   } else if(name == i18n("Printing")) {
0189     KHelpClient::invokeHelp(QStringLiteral("configuration.html#printing-options"));
0190   } else if(name == i18n("Templates")) {
0191     KHelpClient::invokeHelp(QStringLiteral("configuration.html#template-options"));
0192   } else if(name == i18n("Data Sources")) {
0193     KHelpClient::invokeHelp(QStringLiteral("configuration.html#data-sources-options"));
0194   }
0195 }
0196 
0197 bool ConfigDialog::isPageInitialized(Page page_) const {
0198   return m_initializedPages & page_;
0199 }
0200 
0201 void ConfigDialog::setupGeneralPage() {
0202   QFrame* frame = new QFrame(this);
0203   KPageWidgetItem* page = new KPageWidgetItem(frame, i18n("General"));
0204   page->setHeader(i18n("General Options"));
0205   page->setIcon(QIcon::fromTheme(QStringLiteral("tellico"), QIcon(QLatin1String(":/icons/tellico"))));
0206   addPage(page);
0207 
0208   // since this is the first page, go ahead and lay it out
0209   initGeneralPage(frame);
0210 }
0211 
0212 void ConfigDialog::initGeneralPage(QFrame* frame) {
0213   QVBoxLayout* l = new QVBoxLayout(frame);
0214 
0215   m_cbOpenLastFile = new QCheckBox(i18n("&Reopen file at startup"), frame);
0216   m_cbOpenLastFile->setWhatsThis(i18n("If checked, the file that was last open "
0217                                       "will be re-opened at program start-up."));
0218   l->addWidget(m_cbOpenLastFile);
0219   connect(m_cbOpenLastFile, &QAbstractButton::clicked, this, &ConfigDialog::slotModified);
0220 
0221   m_cbQuickFilterRegExp = new QCheckBox(i18n("&Enable regular expressions in quick filter"), frame);
0222   m_cbQuickFilterRegExp->setWhatsThis(i18n("If checked, the quick filter will "
0223                                            "interpret text as a regular expression."));
0224   l->addWidget(m_cbQuickFilterRegExp);
0225   connect(m_cbQuickFilterRegExp, &QAbstractButton::clicked, this, &ConfigDialog::slotModified);
0226 
0227   m_cbEnableWebcam = new QCheckBox(i18n("&Enable webcam for barcode scanning"), frame);
0228   m_cbEnableWebcam->setWhatsThis(i18n("If checked, the input from a webcam will be used "
0229                                       "to scan barcodes for searching."));
0230   l->addWidget(m_cbEnableWebcam);
0231   connect(m_cbEnableWebcam, &QAbstractButton::clicked, this, &ConfigDialog::slotModified);
0232 
0233   QGroupBox* imageGroupBox = new QGroupBox(i18n("Image Storage Options"), frame);
0234   l->addWidget(imageGroupBox);
0235   m_rbImageInFile = new QRadioButton(i18n("Store images in data file"), imageGroupBox);
0236   m_rbImageInAppDir = new QRadioButton(i18n("Store images in common application directory"), imageGroupBox);
0237   m_rbImageInLocalDir = new QRadioButton(i18n("Store images in directory relative to data file"), imageGroupBox);
0238   imageGroupBox->setWhatsThis(i18n("Images may be saved in the data file itself, which can "
0239                                    "cause Tellico to run slowly, stored in the Tellico "
0240                                    "application directory, or stored in a directory in the "
0241                                    "same location as the data file."));
0242   QVBoxLayout* imageGroupLayout = new QVBoxLayout(imageGroupBox);
0243   imageGroupLayout->addWidget(m_rbImageInFile);
0244   imageGroupLayout->addWidget(m_rbImageInAppDir);
0245   imageGroupLayout->addWidget(m_rbImageInLocalDir);
0246   imageGroupBox->setLayout(imageGroupLayout);
0247 
0248   QButtonGroup* imageGroup = new QButtonGroup(frame);
0249   imageGroup->addButton(m_rbImageInFile);
0250   imageGroup->addButton(m_rbImageInAppDir);
0251   imageGroup->addButton(m_rbImageInLocalDir);
0252 #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
0253   void (QButtonGroup::* buttonClicked)(int) = &QButtonGroup::buttonClicked;
0254   connect(imageGroup, buttonClicked, this, &ConfigDialog::slotModified);
0255 #else
0256   connect(imageGroup, &QButtonGroup::idClicked, this, &ConfigDialog::slotModified);
0257 #endif
0258 
0259   QGroupBox* formatGroup = new QGroupBox(i18n("Formatting Options"), frame);
0260   l->addWidget(formatGroup);
0261   QVBoxLayout* formatGroupLayout = new QVBoxLayout(formatGroup);
0262   formatGroup->setLayout(formatGroupLayout);
0263 
0264   m_cbCapitalize = new QCheckBox(i18n("Auto capitalize &titles and names"), formatGroup);
0265   m_cbCapitalize->setWhatsThis(i18n("If checked, titles and names will "
0266                                     "be automatically capitalized."));
0267   connect(m_cbCapitalize, &QAbstractButton::clicked, this, &ConfigDialog::slotModified);
0268   formatGroupLayout->addWidget(m_cbCapitalize);
0269 
0270   m_cbFormat = new QCheckBox(i18n("Auto &format titles and names"), formatGroup);
0271   m_cbFormat->setWhatsThis(i18n("If checked, titles and names will "
0272                                 "be automatically formatted."));
0273   connect(m_cbFormat, &QAbstractButton::clicked, this, &ConfigDialog::slotModified);
0274   formatGroupLayout->addWidget(m_cbFormat);
0275 
0276   QWidget* g1 = new QWidget(formatGroup);
0277   QGridLayout* g1Layout = new QGridLayout(g1);
0278   g1->setLayout(g1Layout);
0279   formatGroupLayout->addWidget(g1);
0280 
0281   QLabel* lab = new QLabel(i18n("No capitali&zation:"), g1);
0282   g1Layout->addWidget(lab, 0, 0);
0283   m_leCapitals = new QLineEdit(g1);
0284   g1Layout->addWidget(m_leCapitals, 0, 1);
0285   lab->setBuddy(m_leCapitals);
0286   QString whats = i18n("<qt>A list of words which should not be capitalized. Multiple values "
0287                        "should be separated by a semi-colon.</qt>");
0288   lab->setWhatsThis(whats);
0289   m_leCapitals->setWhatsThis(whats);
0290   connect(m_leCapitals, &QLineEdit::textChanged, this, &ConfigDialog::slotModified);
0291 
0292   lab = new QLabel(i18n("Artic&les:"), g1);
0293   g1Layout->addWidget(lab, 1, 0);
0294   m_leArticles = new QLineEdit(g1);
0295   g1Layout->addWidget(m_leArticles, 1, 1);
0296   lab->setBuddy(m_leArticles);
0297   whats = i18n("<qt>A list of words which should be considered as articles "
0298                "if they are the first word in a title. Multiple values "
0299                "should be separated by a semi-colon.</qt>");
0300   lab->setWhatsThis(whats);
0301   m_leArticles->setWhatsThis(whats);
0302   connect(m_leArticles, &QLineEdit::textChanged, this, &ConfigDialog::slotModified);
0303 
0304   lab = new QLabel(i18n("Personal suffi&xes:"), g1);
0305   g1Layout->addWidget(lab, 2, 0);
0306   m_leSuffixes = new QLineEdit(g1);
0307   g1Layout->addWidget(m_leSuffixes, 2, 1);
0308   lab->setBuddy(m_leSuffixes);
0309   whats = i18n("<qt>A list of suffixes which might be used in personal names. Multiple values "
0310                "should be separated by a semi-colon.</qt>");
0311   lab->setWhatsThis(whats);
0312   m_leSuffixes->setWhatsThis(whats);
0313   connect(m_leSuffixes, &QLineEdit::textChanged, this, &ConfigDialog::slotModified);
0314 
0315   lab = new QLabel(i18n("Surname &prefixes:"), g1);
0316   g1Layout->addWidget(lab, 3, 0);
0317   m_lePrefixes = new QLineEdit(g1);
0318   g1Layout->addWidget(m_lePrefixes, 3, 1);
0319   lab->setBuddy(m_lePrefixes);
0320   whats = i18n("<qt>A list of prefixes which might be used in surnames. Multiple values "
0321                "should be separated by a semi-colon.</qt>");
0322   lab->setWhatsThis(whats);
0323   m_lePrefixes->setWhatsThis(whats);
0324   connect(m_lePrefixes, &QLineEdit::textChanged, this, &ConfigDialog::slotModified);
0325 
0326   // stretch to fill lower area
0327   l->addStretch(1);
0328   m_initializedPages |= General;
0329   readGeneralConfig();
0330 }
0331 
0332 void ConfigDialog::setupPrintingPage() {
0333   QFrame* frame = new QFrame(this);
0334   KPageWidgetItem* page = new KPageWidgetItem(frame, i18n("Printing"));
0335   page->setHeader(i18n("Printing Options"));
0336   page->setIcon(QIcon::fromTheme(QStringLiteral("printer")));
0337   addPage(page);
0338 }
0339 
0340 void ConfigDialog::initPrintingPage(QFrame* frame) {
0341   QVBoxLayout* l = new QVBoxLayout(frame);
0342 
0343   QGroupBox* formatOptions = new QGroupBox(i18n("Formatting Options"), frame);
0344   l->addWidget(formatOptions);
0345   QVBoxLayout* formatLayout = new QVBoxLayout(formatOptions);
0346   formatOptions->setLayout(formatLayout);
0347 
0348   m_cbPrintFormatted = new QCheckBox(i18n("&Format titles and names"), formatOptions);
0349   m_cbPrintFormatted->setWhatsThis(i18n("If checked, titles and names will be automatically formatted."));
0350   connect(m_cbPrintFormatted, &QAbstractButton::clicked, this, &ConfigDialog::slotModified);
0351   formatLayout->addWidget(m_cbPrintFormatted);
0352 
0353   m_cbPrintHeaders = new QCheckBox(i18n("&Print field headers"), formatOptions);
0354   m_cbPrintHeaders->setWhatsThis(i18n("If checked, the field names will be printed as table headers."));
0355   connect(m_cbPrintHeaders, &QAbstractButton::clicked, this, &ConfigDialog::slotModified);
0356   formatLayout->addWidget(m_cbPrintHeaders);
0357 
0358   QGroupBox* groupOptions = new QGroupBox(i18n("Grouping Options"), frame);
0359   l->addWidget(groupOptions);
0360   QVBoxLayout* groupLayout = new QVBoxLayout(groupOptions);
0361   groupOptions->setLayout(groupLayout);
0362 
0363   m_cbPrintGrouped = new QCheckBox(i18n("&Group the entries"), groupOptions);
0364   m_cbPrintGrouped->setWhatsThis(i18n("If checked, the entries will be grouped by the selected field."));
0365   connect(m_cbPrintGrouped, &QAbstractButton::clicked, this, &ConfigDialog::slotModified);
0366   groupLayout->addWidget(m_cbPrintGrouped);
0367 
0368   QGroupBox* imageOptions = new QGroupBox(i18n("Image Options"), frame);
0369   l->addWidget(imageOptions);
0370 
0371   QGridLayout* gridLayout = new QGridLayout(imageOptions);
0372   imageOptions->setLayout(gridLayout);
0373 
0374   QLabel* lab = new QLabel(i18n("Maximum image &width:"), imageOptions);
0375   gridLayout->addWidget(lab, 0, 0);
0376   m_imageWidthBox = new QSpinBox(imageOptions);
0377   m_imageWidthBox->setMaximum(999);
0378   m_imageWidthBox->setMinimum(0);
0379   m_imageWidthBox->setValue(50);
0380   gridLayout->addWidget(m_imageWidthBox, 0, 1);
0381   m_imageWidthBox->setSuffix(QStringLiteral(" px"));
0382   lab->setBuddy(m_imageWidthBox);
0383   QString whats = i18n("The maximum width of the images in the printout. The aspect ratio is preserved.");
0384   lab->setWhatsThis(whats);
0385   m_imageWidthBox->setWhatsThis(whats);
0386   void (QSpinBox::* valueChanged)(int) = &QSpinBox::valueChanged;
0387   connect(m_imageWidthBox, valueChanged, this, &ConfigDialog::slotModified);
0388 
0389   lab = new QLabel(i18n("&Maximum image height:"), imageOptions);
0390   gridLayout->addWidget(lab, 1, 0);
0391   m_imageHeightBox = new QSpinBox(imageOptions);
0392   m_imageHeightBox->setMaximum(999);
0393   m_imageHeightBox->setMinimum(0);
0394   m_imageHeightBox->setValue(50);
0395   gridLayout->addWidget(m_imageHeightBox, 1, 1);
0396   m_imageHeightBox->setSuffix(QStringLiteral(" px"));
0397   lab->setBuddy(m_imageHeightBox);
0398   whats = i18n("The maximum height of the images in the printout. The aspect ratio is preserved.");
0399   lab->setWhatsThis(whats);
0400   m_imageHeightBox->setWhatsThis(whats);
0401   connect(m_imageHeightBox, valueChanged, this, &ConfigDialog::slotModified);
0402 
0403   // stretch to fill lower area
0404   l->addStretch(1);
0405   m_initializedPages |= Printing;
0406   readPrintingConfig();
0407 }
0408 
0409 void ConfigDialog::setupTemplatePage() {
0410   QFrame* frame = new QFrame(this);
0411   KPageWidgetItem* page = new KPageWidgetItem(frame, i18n("Templates"));
0412   page->setHeader(i18n("Template Options"));
0413   // odd icon, I know, matches KMail, though...
0414   page->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-theme")));
0415   addPage(page);
0416 }
0417 
0418 void ConfigDialog::initTemplatePage(QFrame* frame) {
0419   QVBoxLayout* l = new QVBoxLayout(frame);
0420 
0421   QGridLayout* gridLayout = new QGridLayout();
0422   l->addLayout(gridLayout);
0423 
0424   int row = -1;
0425   // so I can reuse an i18n string, a plain label can't have an '&'
0426   QString s = KLocalizedString::removeAcceleratorMarker(i18n("Collection &type:"));
0427   QLabel* lab = new QLabel(s, frame);
0428   gridLayout->addWidget(lab, ++row, 0);
0429   const int collType = Kernel::self()->collectionType();
0430   lab = new QLabel(CollectionFactory::nameHash().value(collType), frame);
0431   gridLayout->addWidget(lab, row, 1, 1, 2);
0432 
0433   lab = new QLabel(i18n("Template:"), frame);
0434   m_templateCombo = new GUI::ComboBox(frame);
0435   void (QComboBox::* activatedInt)(int) = &QComboBox::activated;
0436   connect(m_templateCombo, activatedInt, this, &ConfigDialog::slotModified);
0437   lab->setBuddy(m_templateCombo);
0438   QString whats = i18n("Select the template to use for the current type of collections. "
0439                        "Not all templates will use the font and color settings.");
0440   lab->setWhatsThis(whats);
0441   m_templateCombo->setWhatsThis(whats);
0442   gridLayout->addWidget(lab, ++row, 0);
0443   gridLayout->addWidget(m_templateCombo, row, 1);
0444 
0445   QPushButton* btn = new QPushButton(i18n("&Preview..."), frame);
0446   btn->setWhatsThis(i18n("Show a preview of the template"));
0447   btn->setIcon(QIcon::fromTheme(QStringLiteral("zoom-original")));
0448   gridLayout->addWidget(btn, row, 2);
0449   connect(btn, &QAbstractButton::clicked, this, &ConfigDialog::slotShowTemplatePreview);
0450 
0451   // so the button is squeezed small
0452   gridLayout->setColumnStretch(0, 10);
0453   gridLayout->setColumnStretch(1, 10);
0454 
0455   loadTemplateList();
0456 
0457 //  QLabel* l1 = new QLabel(i18n("The options below will be passed to the template, but not "
0458 //                               "all templates will use them. Some fonts and colors may be "
0459 //                               "specified directly in the template."), frame);
0460 //  l1->setTextFormat(Qt::RichText);
0461 //  l->addWidget(l1);
0462 
0463   QGroupBox* fontGroup = new QGroupBox(i18n("Font Options"), frame);
0464   l->addWidget(fontGroup);
0465 
0466   row = -1;
0467   QGridLayout* fontLayout = new QGridLayout();
0468   fontGroup->setLayout(fontLayout);
0469 
0470   lab = new QLabel(i18n("Font:"), fontGroup);
0471   fontLayout->addWidget(lab, ++row, 0);
0472   m_fontCombo = new QFontComboBox(fontGroup);
0473   fontLayout->addWidget(m_fontCombo, row, 1);
0474   connect(m_fontCombo, activatedInt, this, &ConfigDialog::slotModified);
0475   lab->setBuddy(m_fontCombo);
0476   whats = i18n("This font is passed to the template used in the Entry View.");
0477   lab->setWhatsThis(whats);
0478   m_fontCombo->setWhatsThis(whats);
0479 
0480   fontLayout->addWidget(new QLabel(i18n("Size:"), fontGroup), ++row, 0);
0481   m_fontSizeInput = new QSpinBox(fontGroup);
0482   m_fontSizeInput->setMaximum(30); // 30 is same max as konq config
0483   m_fontSizeInput->setMinimum(5);
0484   m_fontSizeInput->setSuffix(QStringLiteral("pt"));
0485   fontLayout->addWidget(m_fontSizeInput, row, 1);
0486   void (QSpinBox::* valueChangedInt)(int) = &QSpinBox::valueChanged;
0487   connect(m_fontSizeInput, valueChangedInt, this, &ConfigDialog::slotModified);
0488   lab->setBuddy(m_fontSizeInput);
0489   lab->setWhatsThis(whats);
0490   m_fontSizeInput->setWhatsThis(whats);
0491 
0492   QGroupBox* colGroup = new QGroupBox(i18n("Color Options"), frame);
0493   l->addWidget(colGroup);
0494 
0495   row = -1;
0496   QGridLayout* colLayout = new QGridLayout();
0497   colGroup->setLayout(colLayout);
0498 
0499   lab = new QLabel(i18n("Background color:"), colGroup);
0500   colLayout->addWidget(lab, ++row, 0);
0501   m_baseColorCombo = new KColorCombo(colGroup);
0502   colLayout->addWidget(m_baseColorCombo, row, 1);
0503   connect(m_baseColorCombo, activatedInt, this, &ConfigDialog::slotModified);
0504   lab->setBuddy(m_baseColorCombo);
0505   whats = i18n("This color is passed to the template used in the Entry View.");
0506   lab->setWhatsThis(whats);
0507   m_baseColorCombo->setWhatsThis(whats);
0508 
0509   lab = new QLabel(i18n("Text color:"), colGroup);
0510   colLayout->addWidget(lab, ++row, 0);
0511   m_textColorCombo = new KColorCombo(colGroup);
0512   colLayout->addWidget(m_textColorCombo, row, 1);
0513   connect(m_textColorCombo, activatedInt, this, &ConfigDialog::slotModified);
0514   lab->setBuddy(m_textColorCombo);
0515   lab->setWhatsThis(whats);
0516   m_textColorCombo->setWhatsThis(whats);
0517 
0518   lab = new QLabel(i18n("Highlight color:"), colGroup);
0519   colLayout->addWidget(lab, ++row, 0);
0520   m_highBaseColorCombo = new KColorCombo(colGroup);
0521   colLayout->addWidget(m_highBaseColorCombo, row, 1);
0522   connect(m_highBaseColorCombo, activatedInt, this, &ConfigDialog::slotModified);
0523   lab->setBuddy(m_highBaseColorCombo);
0524   lab->setWhatsThis(whats);
0525   m_highBaseColorCombo->setWhatsThis(whats);
0526 
0527   lab = new QLabel(i18n("Highlighted text color:"), colGroup);
0528   colLayout->addWidget(lab, ++row, 0);
0529   m_highTextColorCombo = new KColorCombo(colGroup);
0530   colLayout->addWidget(m_highTextColorCombo, row, 1);
0531   connect(m_highTextColorCombo, activatedInt, this, &ConfigDialog::slotModified);
0532   lab->setBuddy(m_highTextColorCombo);
0533   lab->setWhatsThis(whats);
0534   m_highTextColorCombo->setWhatsThis(whats);
0535 
0536   lab = new QLabel(i18n("Link color:"), colGroup);
0537   colLayout->addWidget(lab, ++row, 0);
0538   m_linkColorCombo = new KColorCombo(colGroup);
0539   colLayout->addWidget(m_linkColorCombo, row, 1);
0540   connect(m_linkColorCombo, activatedInt, this, &ConfigDialog::slotModified);
0541   lab->setBuddy(m_linkColorCombo);
0542   lab->setWhatsThis(whats);
0543   m_linkColorCombo->setWhatsThis(whats);
0544 
0545   QGroupBox* groupBox = new QGroupBox(i18n("Manage Templates"), frame);
0546   l->addWidget(groupBox);
0547   QVBoxLayout* vlay = new QVBoxLayout(groupBox);
0548   groupBox->setLayout(vlay);
0549 
0550   QWidget* box1 = new QWidget(groupBox);
0551   QHBoxLayout* box1HBoxLayout = new QHBoxLayout(box1);
0552   box1HBoxLayout->setMargin(0);
0553   vlay->addWidget(box1);
0554   box1HBoxLayout->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing));
0555 
0556   QPushButton* b1 = new QPushButton(i18n("Install..."), box1);
0557   box1HBoxLayout->addWidget(b1);
0558   b1->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
0559   connect(b1, &QAbstractButton::clicked, this, &ConfigDialog::slotInstallTemplate);
0560   whats = i18n("Click to install a new template directly.");
0561   b1->setWhatsThis(whats);
0562 
0563 #ifdef ENABLE_KNEWSTUFF3
0564 #if KNEWSTUFF_VERSION < QT_VERSION_CHECK(5, 91, 0)
0565   auto b2 = new KNS3::Button(i18n("Download..."), QStringLiteral("tellico-template.knsrc"), box1);
0566   connect(b2, &KNS3::Button::dialogFinished, this, &ConfigDialog::slotUpdateTemplates);
0567 #else
0568   auto b2 = new KNSWidgets::Button(i18n("Download..."), QStringLiteral("tellico-template.knsrc"), box1);
0569   connect(b2, &KNSWidgets::Button::dialogFinished, this, &ConfigDialog::slotUpdateTemplates);
0570 #endif
0571 #else
0572   QPushButton* b2 = new QPushButton(i18n("Download..."), box1);
0573   b2->setIcon(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff")));
0574   b2->setEnabled(false);
0575 #endif
0576   box1HBoxLayout->addWidget(b2);
0577   whats = i18n("Click to download additional templates.");
0578   b2->setWhatsThis(whats);
0579 
0580   QPushButton* b3 = new QPushButton(i18n("Delete..."), box1);
0581   box1HBoxLayout->addWidget(b3);
0582   b3->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
0583   connect(b3, &QAbstractButton::clicked, this, &ConfigDialog::slotDeleteTemplate);
0584   whats = i18n("Click to select and remove installed templates.");
0585   b3->setWhatsThis(whats);
0586 
0587   // stretch to fill lower area
0588   l->addStretch(1);
0589 
0590   // purely for aesthetics make all widgets line up
0591   QList<QWidget*> widgets;
0592   widgets.append(m_fontCombo);
0593   widgets.append(m_fontSizeInput);
0594   widgets.append(m_baseColorCombo);
0595   widgets.append(m_textColorCombo);
0596   widgets.append(m_highBaseColorCombo);
0597   widgets.append(m_highTextColorCombo);
0598   widgets.append(m_linkColorCombo);
0599   int w = 0;
0600   foreach(QWidget* widget, widgets) {
0601     widget->ensurePolished();
0602     w = qMax(w, widget->sizeHint().width());
0603   }
0604   foreach(QWidget* widget, widgets) {
0605     widget->setMinimumWidth(w);
0606   }
0607 
0608   KAcceleratorManager::manage(frame);
0609   m_initializedPages |= Template;
0610   readTemplateConfig();
0611 }
0612 
0613 void ConfigDialog::setupFetchPage() {
0614   QFrame* frame = new QFrame(this);
0615   KPageWidgetItem* page = new KPageWidgetItem(frame, i18n("Data Sources"));
0616   page->setHeader(i18n("Data Sources Options"));
0617   page->setIcon(QIcon::fromTheme(QStringLiteral("network-wired")));
0618   addPage(page);
0619 }
0620 
0621 void ConfigDialog::initFetchPage(QFrame* frame) {
0622   QHBoxLayout* l = new QHBoxLayout(frame);
0623 
0624   QVBoxLayout* leftLayout = new QVBoxLayout();
0625   l->addLayout(leftLayout);
0626   m_sourceListWidget = new QListWidget(frame);
0627   m_sourceListWidget->setSortingEnabled(false); // no sorting
0628   m_sourceListWidget->setSelectionMode(QAbstractItemView::SingleSelection);
0629   leftLayout->addWidget(m_sourceListWidget, 1);
0630   connect(m_sourceListWidget, &QListWidget::currentItemChanged, this, &ConfigDialog::slotSelectedSourceChanged);
0631   connect(m_sourceListWidget, &QListWidget::itemDoubleClicked, this, &ConfigDialog::slotModifySourceClicked);
0632 
0633   QWidget* hb = new QWidget(frame);
0634   QHBoxLayout* hbHBoxLayout = new QHBoxLayout(hb);
0635   hbHBoxLayout->setMargin(0);
0636   leftLayout->addWidget(hb);
0637   m_moveUpSourceBtn = new QPushButton(i18n("Move &Up"), hb);
0638   hbHBoxLayout->addWidget(m_moveUpSourceBtn);
0639   m_moveUpSourceBtn->setIcon(QIcon::fromTheme(QStringLiteral("go-up")));
0640   const QString moveTip(i18n("The order of the data sources sets the order "
0641                              "that Tellico uses when entries are automatically updated."));
0642   m_moveUpSourceBtn->setWhatsThis(moveTip);
0643   m_moveDownSourceBtn = new QPushButton(i18n("Move &Down"), hb);
0644   hbHBoxLayout->addWidget(m_moveDownSourceBtn);
0645   m_moveDownSourceBtn->setIcon(QIcon::fromTheme(QStringLiteral("go-down")));
0646   m_moveDownSourceBtn->setWhatsThis(moveTip);
0647 
0648   QWidget* hb2 = new QWidget(frame);
0649   QHBoxLayout* hb2HBoxLayout = new QHBoxLayout(hb2);
0650   hb2HBoxLayout->setMargin(0);
0651   leftLayout->addWidget(hb2);
0652   m_cbFilterSource = new QCheckBox(i18n("Filter by type:"), hb2);
0653   hb2HBoxLayout->addWidget(m_cbFilterSource);
0654   connect(m_cbFilterSource, &QAbstractButton::clicked, this, &ConfigDialog::slotSourceFilterChanged);
0655   m_sourceTypeCombo = new GUI::CollectionTypeCombo(hb2);
0656   hb2HBoxLayout->addWidget(m_sourceTypeCombo);
0657   void (QComboBox::* currentIndexChanged)(int) = &QComboBox::currentIndexChanged;
0658   connect(m_sourceTypeCombo, currentIndexChanged, this, &ConfigDialog::slotSourceFilterChanged);
0659   // we want to remove the item for a custom collection
0660   int index = m_sourceTypeCombo->findData(Data::Collection::Base);
0661   if(index > -1) {
0662     m_sourceTypeCombo->removeItem(index);
0663   }
0664   // disable until check box is checked
0665   m_sourceTypeCombo->setEnabled(false);
0666 
0667   // these icons are rather arbitrary, but seem to vaguely fit
0668   QVBoxLayout* vlay = new QVBoxLayout();
0669   l->addLayout(vlay);
0670   QPushButton* newSourceBtn = new QPushButton(i18n("&New..."), frame);
0671   newSourceBtn->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
0672   newSourceBtn->setWhatsThis(i18n("Click to add a new data source."));
0673   m_modifySourceBtn = new QPushButton(i18n("&Modify..."), frame);
0674   m_modifySourceBtn->setIcon(QIcon::fromTheme(QStringLiteral("network-wired")));
0675   m_modifySourceBtn->setWhatsThis(i18n("Click to modify the selected data source."));
0676   m_removeSourceBtn = new QPushButton(i18n("&Delete"), frame);
0677   m_removeSourceBtn->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
0678   m_removeSourceBtn->setWhatsThis(i18n("Click to delete the selected data source."));
0679 
0680   vlay->addWidget(newSourceBtn);
0681   vlay->addWidget(m_modifySourceBtn);
0682   vlay->addWidget(m_removeSourceBtn);
0683   vlay->addStretch(1);
0684 
0685   connect(newSourceBtn, &QAbstractButton::clicked, this, &ConfigDialog::slotNewSourceClicked);
0686   connect(m_modifySourceBtn, &QAbstractButton::clicked, this, &ConfigDialog::slotModifySourceClicked);
0687   connect(m_moveUpSourceBtn, &QAbstractButton::clicked, this, &ConfigDialog::slotMoveUpSourceClicked);
0688   connect(m_moveDownSourceBtn, &QAbstractButton::clicked, this, &ConfigDialog::slotMoveDownSourceClicked);
0689   connect(m_removeSourceBtn, &QAbstractButton::clicked, this, &ConfigDialog::slotRemoveSourceClicked);
0690 
0691   KAcceleratorManager::manage(frame);
0692   m_initializedPages |= Fetch;
0693   readFetchConfig();
0694 }
0695 
0696 void ConfigDialog::readGeneralConfig() {
0697   m_modifying = true;
0698 
0699   m_cbQuickFilterRegExp->setChecked(Config::quickFilterRegExp());
0700   m_cbOpenLastFile->setChecked(Config::reopenLastFile());
0701 #ifdef ENABLE_WEBCAM
0702   m_cbEnableWebcam->setChecked(Config::enableWebcam());
0703 #else
0704   m_cbEnableWebcam->setChecked(false);
0705   m_cbEnableWebcam->setEnabled(false);
0706 #endif
0707 
0708   switch(Config::imageLocation()) {
0709     case Config::ImagesInFile: m_rbImageInFile->setChecked(true); break;
0710     case Config::ImagesInAppDir: m_rbImageInAppDir->setChecked(true); break;
0711     case Config::ImagesInLocalDir: m_rbImageInLocalDir->setChecked(true); break;
0712   }
0713 
0714   bool autoCapitals = Config::autoCapitalization();
0715   m_cbCapitalize->setChecked(autoCapitals);
0716 
0717   bool autoFormat = Config::autoFormat();
0718   m_cbFormat->setChecked(autoFormat);
0719 
0720   static const QRegularExpression comma(QLatin1String("\\s*,\\s*"));
0721 
0722   m_leCapitals->setText(Config::noCapitalizationString().replace(comma, FieldFormat::delimiterString()));
0723   m_leArticles->setText(Config::articlesString().replace(comma, FieldFormat::delimiterString()));
0724   m_leSuffixes->setText(Config::nameSuffixesString().replace(comma, FieldFormat::delimiterString()));
0725   m_lePrefixes->setText(Config::surnamePrefixesString().replace(comma, FieldFormat::delimiterString()));
0726 
0727   m_modifying = false;
0728 }
0729 
0730 void ConfigDialog::readPrintingConfig() {
0731   m_modifying = true;
0732 
0733   m_cbPrintHeaders->setChecked(Config::printFieldHeaders());
0734   m_cbPrintFormatted->setChecked(Config::printFormatted());
0735   m_cbPrintGrouped->setChecked(Config::printGrouped());
0736   m_imageWidthBox->setValue(Config::maxImageWidth());
0737   m_imageHeightBox->setValue(Config::maxImageHeight());
0738 
0739   m_modifying = false;
0740 }
0741 
0742 void ConfigDialog::readTemplateConfig() {
0743   m_modifying = true;
0744 
0745   // entry template selection
0746   const int collType = Kernel::self()->collectionType();
0747   QString file = Config::templateName(collType);
0748   file.replace(QLatin1Char('_'), QLatin1Char(' '));
0749   QString fileContext = file + QLatin1String(" XSL Template");
0750   m_templateCombo->setCurrentItem(i18nc(fileContext.toUtf8().constData(), file.toUtf8().constData()));
0751 
0752   m_fontCombo->setCurrentFont(QFont(Config::templateFont(collType).family()));
0753   m_fontSizeInput->setValue(Config::templateFont(collType).pointSize());
0754   m_baseColorCombo->setColor(Config::templateBaseColor(collType));
0755   m_textColorCombo->setColor(Config::templateTextColor(collType));
0756   m_highBaseColorCombo->setColor(Config::templateHighlightedBaseColor(collType));
0757   m_highTextColorCombo->setColor(Config::templateHighlightedTextColor(collType));
0758   m_linkColorCombo->setColor(Config::templateLinkColor(collType));
0759 
0760   m_modifying = false;
0761 }
0762 
0763 void ConfigDialog::readFetchConfig() {
0764   m_modifying = true;
0765 
0766   m_sourceListWidget->clear();
0767   m_configWidgets.clear();
0768 
0769   m_sourceListWidget->setUpdatesEnabled(false);
0770   foreach(Fetch::Fetcher::Ptr fetcher, Fetch::Manager::self()->fetchers()) {
0771     Fetch::FetcherInfo info(fetcher->type(), fetcher->source(),
0772                             fetcher->updateOverwrite(), fetcher->uuid());
0773     FetcherInfoListItem* item = new FetcherInfoListItem(m_sourceListWidget, info);
0774     item->setFetcher(fetcher.data());
0775   }
0776   m_sourceListWidget->setUpdatesEnabled(true);
0777 
0778   if(m_sourceListWidget->count() == 0) {
0779     m_modifySourceBtn->setEnabled(false);
0780     m_removeSourceBtn->setEnabled(false);
0781   } else {
0782     // go ahead and select the first one
0783     m_sourceListWidget->setCurrentItem(m_sourceListWidget->item(0));
0784   }
0785 
0786   m_modifying = false;
0787   QTimer::singleShot(500, this, &ConfigDialog::slotCreateConfigWidgets);
0788 }
0789 
0790 void ConfigDialog::saveConfiguration() {
0791   if(isPageInitialized(General)) saveGeneralConfig();
0792   if(isPageInitialized(Printing)) savePrintingConfig();
0793   if(isPageInitialized(Template)) saveTemplateConfig();
0794   if(isPageInitialized(Fetch)) saveFetchConfig();
0795 }
0796 
0797 void ConfigDialog::saveGeneralConfig() {
0798   Config::setQuickFilterRegExp(m_cbQuickFilterRegExp->isChecked());
0799   Config::setEnableWebcam(m_cbEnableWebcam->isChecked());
0800 
0801   int imageLocation;
0802   if(m_rbImageInFile->isChecked()) {
0803     imageLocation = Config::ImagesInFile;
0804   } else if(m_rbImageInAppDir->isChecked()) {
0805     imageLocation = Config::ImagesInAppDir;
0806   } else {
0807     imageLocation = Config::ImagesInLocalDir;
0808   }
0809   Config::setImageLocation(imageLocation);
0810   Config::setReopenLastFile(m_cbOpenLastFile->isChecked());
0811 
0812   Config::setAutoCapitalization(m_cbCapitalize->isChecked());
0813   Config::setAutoFormat(m_cbFormat->isChecked());
0814 
0815   static const QRegularExpression semicolon(QLatin1String("\\s*;\\s*"));
0816   const QChar comma = QLatin1Char(',');
0817 
0818   Config::setNoCapitalizationString(m_leCapitals->text().replace(semicolon, comma));
0819   Config::setArticlesString(m_leArticles->text().replace(semicolon, comma));
0820   Config::setNameSuffixesString(m_leSuffixes->text().replace(semicolon, comma));
0821   Config::setSurnamePrefixesString(m_lePrefixes->text().replace(semicolon, comma));
0822 }
0823 
0824 void ConfigDialog::savePrintingConfig() {
0825   Config::setPrintFieldHeaders(m_cbPrintHeaders->isChecked());
0826   Config::setPrintFormatted(m_cbPrintFormatted->isChecked());
0827   Config::setPrintGrouped(m_cbPrintGrouped->isChecked());
0828   Config::setMaxImageWidth(m_imageWidthBox->value());
0829   Config::setMaxImageHeight(m_imageHeightBox->value());
0830 }
0831 
0832 void ConfigDialog::saveTemplateConfig() {
0833   const int collType = Kernel::self()->collectionType();
0834   if(collType == Data::Collection::Base &&
0835      Kernel::self()->URL().fileName() != i18n(Tellico::untitledFilename)) {
0836     // use a nested config group for template specific to custom collections
0837     // using the filename alone as a keyEvents
0838     const QString configGroup = QStringLiteral("Options - %1").arg(CollectionFactory::typeName(collType));
0839     KConfigGroup group(KSharedConfig::openConfig(), configGroup);
0840     KConfigGroup subGroup(&group, Kernel::self()->URL().fileName());
0841     subGroup.writeEntry(QStringLiteral("Template Name"), m_templateCombo->currentData().toString());
0842   } else {
0843     Config::setTemplateName(collType, m_templateCombo->currentData().toString());
0844   }
0845   QFont font(m_fontCombo->currentFont().family(), m_fontSizeInput->value());
0846   Config::setTemplateFont(collType, font);
0847   Config::setTemplateBaseColor(collType, m_baseColorCombo->color());
0848   Config::setTemplateTextColor(collType, m_textColorCombo->color());
0849   Config::setTemplateHighlightedBaseColor(collType, m_highBaseColorCombo->color());
0850   Config::setTemplateHighlightedTextColor(collType, m_highTextColorCombo->color());
0851   Config::setTemplateLinkColor(collType, m_linkColorCombo->color());
0852 }
0853 
0854 void ConfigDialog::saveFetchConfig() {
0855   // first, tell config widgets they got deleted
0856   foreach(Fetch::ConfigWidget* widget, m_removedConfigWidgets) {
0857     widget->removed();
0858   }
0859   m_removedConfigWidgets.clear();
0860 
0861   bool reloadFetchers = false;
0862   int count = 0; // start group numbering at 0
0863   for( ; count < m_sourceListWidget->count(); ++count) {
0864     FetcherInfoListItem* item = static_cast<FetcherInfoListItem*>(m_sourceListWidget->item(count));
0865     Fetch::ConfigWidget* cw = m_configWidgets[item];
0866     if(!cw || (!cw->shouldSave() && !item->isNewSource())) {
0867       continue;
0868     }
0869     m_newStuffConfigWidgets.removeAll(cw);
0870     QString group = QStringLiteral("Data Source %1").arg(count);
0871     // in case we later change the order, clear the group now
0872     KSharedConfig::openConfig()->deleteGroup(group);
0873     KConfigGroup configGroup(KSharedConfig::openConfig(), group);
0874     configGroup.writeEntry("Name", item->data(Qt::DisplayRole).toString());
0875     configGroup.writeEntry("Type", int(item->fetchType()));
0876     configGroup.writeEntry("UpdateOverwrite", item->updateOverwrite());
0877     configGroup.writeEntry("Uuid", item->uuid());
0878     cw->saveConfig(configGroup);
0879     item->setNewSource(false);
0880     // in case the ordering changed
0881     item->setConfigGroup(configGroup);
0882     reloadFetchers = true;
0883   }
0884   // now update total number of sources
0885   KConfigGroup sourceGroup(KSharedConfig::openConfig(), "Data Sources");
0886   sourceGroup.writeEntry("Sources Count", count);
0887   // and purge old config groups
0888   QString group = QStringLiteral("Data Source %1").arg(count);
0889   while(KSharedConfig::openConfig()->hasGroup(group)) {
0890     KSharedConfig::openConfig()->deleteGroup(group);
0891     ++count;
0892     group = QStringLiteral("Data Source %1").arg(count);
0893   }
0894 
0895   Config::self()->save();
0896 
0897   if(reloadFetchers) {
0898     Fetch::Manager::self()->loadFetchers();
0899     Controller::self()->updatedFetchers();
0900     // reload fetcher items if OK was not clicked
0901     // meaning apply was clicked
0902     if(!m_okClicked) {
0903       QString currentSource;
0904       if(m_sourceListWidget->currentItem()) {
0905         currentSource = m_sourceListWidget->currentItem()->data(Qt::DisplayRole).toString();
0906       }
0907       readFetchConfig();
0908       if(!currentSource.isEmpty()) {
0909         QList<QListWidgetItem*> items = m_sourceListWidget->findItems(currentSource, Qt::MatchExactly);
0910         if(!items.isEmpty()) {
0911           m_sourceListWidget->setCurrentItem(items.first());
0912           m_sourceListWidget->scrollToItem(items.first());
0913         }
0914       }
0915     }
0916   }
0917 }
0918 
0919 void ConfigDialog::slotModified() {
0920   if(m_modifying) {
0921     return;
0922   }
0923   button(QDialogButtonBox::Ok)->setEnabled(true);
0924   button(QDialogButtonBox::Apply)->setEnabled(true);
0925 }
0926 
0927 void ConfigDialog::slotNewSourceClicked() {
0928   FetcherConfigDialog dlg(this);
0929   if(dlg.exec() != QDialog::Accepted) {
0930     return;
0931   }
0932 
0933   Fetch::Type type = dlg.sourceType();
0934   if(type == Fetch::Unknown) {
0935     return;
0936   }
0937 
0938   Fetch::FetcherInfo info(type, dlg.sourceName(), dlg.updateOverwrite());
0939   FetcherInfoListItem* item = new FetcherInfoListItem(m_sourceListWidget, info);
0940   m_sourceListWidget->scrollToItem(item);
0941   m_sourceListWidget->setCurrentItem(item);
0942   Fetch::ConfigWidget* cw = dlg.configWidget();
0943   if(cw) {
0944     cw->setAccepted(true);
0945     cw->slotSetModified();
0946     cw->setParent(this); // keep the config widget around
0947     m_configWidgets.insert(item, cw);
0948   }
0949   m_modifySourceBtn->setEnabled(true);
0950   m_removeSourceBtn->setEnabled(true);
0951   slotModified(); // toggle apply button
0952 }
0953 
0954 void ConfigDialog::slotModifySourceClicked() {
0955   FetcherInfoListItem* item = static_cast<FetcherInfoListItem*>(m_sourceListWidget->currentItem());
0956   if(!item) {
0957     return;
0958   }
0959 
0960   Fetch::ConfigWidget* cw = nullptr;
0961   if(m_configWidgets.contains(item)) {
0962     cw = m_configWidgets[item];
0963   } else if(item->fetcher()) {
0964     // grab the config widget, taking ownership
0965     cw = item->fetcher()->configWidget(this);
0966     if(cw) { // might return null when no widget available for fetcher type
0967       m_configWidgets.insert(item, cw);
0968       // there's weird layout bug if it's not hidden
0969       cw->hide();
0970     }
0971   } else {
0972     myDebug() << "no config item fetcher!";
0973   }
0974   if(!cw) {
0975     // no config widget for this one
0976     // might be because support was compiled out
0977     myDebug() << "no config widget for source" << item->data(Qt::DisplayRole).toString();
0978     return;
0979   }
0980   FetcherConfigDialog dlg(item->data(Qt::DisplayRole).toString(), item->fetchType(), item->updateOverwrite(), cw, this);
0981 
0982   if(dlg.exec() == QDialog::Accepted) {
0983     cw->setAccepted(true); // mark to save
0984     QString newName = dlg.sourceName();
0985     if(newName != item->data(Qt::DisplayRole).toString()) {
0986       item->setData(Qt::DisplayRole, newName);
0987       cw->slotSetModified();
0988     }
0989     item->setUpdateOverwrite(dlg.updateOverwrite());
0990     slotModified(); // toggle apply button
0991   }
0992   cw->setParent(this); // keep the config widget around
0993 }
0994 
0995 void ConfigDialog::slotRemoveSourceClicked() {
0996   FetcherInfoListItem* item = static_cast<FetcherInfoListItem*>(m_sourceListWidget->currentItem());
0997   if(!item) {
0998     return;
0999   }
1000 
1001   Tellico::NewStuff::Manager::self()->removeScriptByName(item->text());
1002 
1003   Fetch::ConfigWidget* cw = m_configWidgets[item];
1004   if(cw) {
1005     m_removedConfigWidgets.append(cw);
1006     // it gets deleted by the parent
1007   }
1008   m_configWidgets.remove(item);
1009   delete item;
1010 //  m_sourceListWidget->setCurrentItem(m_sourceListWidget->currentItem());
1011   slotModified(); // toggle apply button
1012 }
1013 
1014 void ConfigDialog::slotMoveUpSourceClicked() {
1015   int row = m_sourceListWidget->currentRow();
1016   if(row < 1) {
1017     return;
1018   }
1019   QListWidgetItem* item = m_sourceListWidget->takeItem(row);
1020   m_sourceListWidget->insertItem(row-1, item);
1021   m_sourceListWidget->setCurrentItem(item);
1022   slotModified(); // toggle apply button
1023 }
1024 
1025 void ConfigDialog::slotMoveDownSourceClicked() {
1026   int row = m_sourceListWidget->currentRow();
1027   if(row > m_sourceListWidget->count()-2) {
1028     return;
1029   }
1030   QListWidgetItem* item = m_sourceListWidget->takeItem(row);
1031   m_sourceListWidget->insertItem(row+1, item);
1032   m_sourceListWidget->setCurrentItem(item);
1033   slotModified(); // toggle apply button
1034 }
1035 
1036 void ConfigDialog::slotSourceFilterChanged() {
1037   m_sourceTypeCombo->setEnabled(m_cbFilterSource->isChecked());
1038   const bool showAll = !m_sourceTypeCombo->isEnabled();
1039   const int type = m_sourceTypeCombo->currentType();
1040   for(int count = 0; count < m_sourceListWidget->count(); ++count) {
1041     FetcherInfoListItem* item = static_cast<FetcherInfoListItem*>(m_sourceListWidget->item(count));
1042     item->setHidden(!showAll && item->fetcher() && !item->fetcher()->canFetch(type));
1043   }
1044 }
1045 
1046 void ConfigDialog::slotSelectedSourceChanged(QListWidgetItem* item_) {
1047   int row = m_sourceListWidget->row(item_);
1048   m_moveUpSourceBtn->setEnabled(row > 0);
1049   m_moveDownSourceBtn->setEnabled(row < m_sourceListWidget->count()-1);
1050 }
1051 
1052 Tellico::FetcherInfoListItem* ConfigDialog::findItem(const QString& path_) const {
1053   if(path_.isEmpty()) {
1054     myDebug() << "empty path";
1055     return nullptr;
1056   }
1057 
1058   // this is a bit ugly, loop over all items, find the execexternal one
1059   // that matches the path
1060   for(int i = 0; i < m_sourceListWidget->count(); ++i) {
1061     FetcherInfoListItem* item = static_cast<FetcherInfoListItem*>(m_sourceListWidget->item(i));
1062     if(item->fetchType() != Fetch::ExecExternal) {
1063       continue;
1064     }
1065     Fetch::ExecExternalFetcher* f = dynamic_cast<Fetch::ExecExternalFetcher*>(item->fetcher());
1066     if(f && f->execPath() == path_) {
1067       return item;
1068     }
1069   }
1070   myDebug() << "no matching item found";
1071   return nullptr;
1072 }
1073 
1074 void ConfigDialog::slotShowTemplatePreview() {
1075   GUI::PreviewDialog* dlg = new GUI::PreviewDialog(this);
1076 
1077   const QString templateName = m_templateCombo->currentData().toString();
1078   dlg->setXSLTFile(templateName + QLatin1String(".xsl"));
1079 
1080   StyleOptions options;
1081   options.fontFamily = m_fontCombo->currentFont().family();
1082   options.fontSize   = m_fontSizeInput->value();
1083   options.baseColor  = m_baseColorCombo->color();
1084   options.textColor  = m_textColorCombo->color();
1085   options.highlightedTextColor = m_highTextColorCombo->color();
1086   options.highlightedBaseColor = m_highBaseColorCombo->color();
1087   options.linkColor  = m_linkColorCombo->color();
1088   dlg->setXSLTOptions(Kernel::self()->collectionType(), options);
1089 
1090   // always want to include a url to show link color too
1091   bool hasLink = false;
1092   Data::CollPtr c = CollectionFactory::collection(Kernel::self()->collectionType(), true);
1093   Data::EntryPtr e(new Data::Entry(c));
1094   foreach(Data::FieldPtr f, c->fields()) {
1095     if(f->name() == QLatin1String("title")) {
1096       e->setField(f->name(), m_templateCombo->currentText());
1097     } else if(f->type() == Data::Field::Image) {
1098       continue;
1099     } else if(f->type() == Data::Field::Choice) {
1100       e->setField(f->name(), f->allowed().front());
1101     } else if(f->type() == Data::Field::Number) {
1102       e->setField(f->name(), QStringLiteral("1"));
1103     } else if(f->type() == Data::Field::Bool) {
1104       e->setField(f->name(), QStringLiteral("true"));
1105     } else if(f->type() == Data::Field::Rating) {
1106       e->setField(f->name(), QStringLiteral("4"));
1107     } else if(f->type() == Data::Field::URL) {
1108       e->setField(f->name(), QStringLiteral("https://tellico-project.org"));
1109       hasLink = true;
1110     } else if(f->type() == Data::Field::Table) {
1111       QStringList values;
1112       bool ok;
1113       int ncols = Tellico::toUInt(f->property(QStringLiteral("columns")), &ok);
1114       ncols = qMax(ncols, 1);
1115       for(int ncol = 1; ncol <= ncols; ++ncol) {
1116         const auto prop = QStringLiteral("column%1").arg(ncol);
1117         const auto col = f->property(prop);
1118         values += col.isEmpty() ? prop : col;
1119       }
1120       e->setField(f->name(), values.join(FieldFormat::columnDelimiterString()));
1121     } else {
1122       e->setField(f->name(), f->title());
1123     }
1124   }
1125   if(!hasLink) {
1126     Data::FieldPtr f(new Data::Field(QStringLiteral("url"),
1127                                      QLatin1String("URL"),
1128                                      Data::Field::URL));
1129     f->setCategory(i18n("General"));
1130     c->addField(f);
1131     e->setField(f->name(), QStringLiteral("https://tellico-project.org"));
1132   }
1133 
1134   dlg->showEntry(e);
1135   dlg->show();
1136   // dlg gets deleted by itself
1137   // the finished() signal is connected in its constructor to delayedDestruct
1138 }
1139 
1140 void ConfigDialog::loadTemplateList() {
1141   QStringList files = Tellico::locateAllFiles(QStringLiteral("tellico/entry-templates/*.xsl"));
1142   QMap<QString, QString> templates; // a QMap will have them values sorted by key
1143   foreach(const QString& file, files) {
1144     QFileInfo fi(file);
1145     QString lfile = fi.fileName().section(QLatin1Char('.'), 0, -2);
1146     QString name = lfile;
1147     name.replace(QLatin1Char('_'), QLatin1Char(' '));
1148     QString title = i18nc((name + QLatin1String(" XSL Template")).toUtf8().constData(), name.toUtf8().constData());
1149     templates.insert(title, lfile);
1150   }
1151 
1152   QString s = m_templateCombo->currentText();
1153   m_templateCombo->clear();
1154   for(auto it2 = templates.constBegin(); it2 != templates.constEnd(); ++it2) {
1155     m_templateCombo->addItem(it2.key(), it2.value());
1156   }
1157   m_templateCombo->setCurrentItem(s);
1158 }
1159 
1160 void ConfigDialog::slotInstallTemplate() {
1161   QString filter = i18n("XSL Files") + QLatin1String(" (*.xsl)") + QLatin1String(";;");
1162   filter += i18n("Template Packages") + QLatin1String(" (*.tar.gz *.tgz)") + QLatin1String(";;");
1163   filter += i18n("All Files") + QLatin1String(" (*)");
1164 
1165   const QString fileClass(QStringLiteral(":InstallTemplate"));
1166   const QString f = QFileDialog::getOpenFileName(this, QString(), KRecentDirs::dir(fileClass), filter);
1167   if(f.isEmpty()) {
1168     return;
1169   }
1170   KRecentDirs::add(fileClass, QFileInfo(f).dir().canonicalPath());
1171 
1172   if(Tellico::NewStuff::Manager::self()->installTemplate(f)) {
1173     loadTemplateList();
1174   }
1175 }
1176 
1177 #ifdef ENABLE_KNEWSTUFF3
1178 #if KNEWSTUFF_VERSION < QT_VERSION_CHECK(5, 91, 0)
1179 void ConfigDialog::slotUpdateTemplates(const QList<KNS3::Entry>& list_) {
1180 #else
1181 void ConfigDialog::slotUpdateTemplates(const QList<KNSCore::Entry>& list_) {
1182 #endif
1183   if(!list_.isEmpty()) {
1184     loadTemplateList();
1185   }
1186 }
1187 #endif
1188 
1189 void ConfigDialog::slotDeleteTemplate() {
1190   bool ok;
1191   QString name = QInputDialog::getItem(this,
1192                                        i18n("Delete Template"),
1193                                        i18n("Select template to delete:"),
1194                                        Tellico::NewStuff::Manager::self()->userTemplates().keys(),
1195                                        0, false, &ok);
1196   if(ok && !name.isEmpty()) {
1197     Tellico::NewStuff::Manager::self()->removeTemplateByName(name);
1198     loadTemplateList();
1199   }
1200 }
1201 
1202 void ConfigDialog::slotCreateConfigWidgets() {
1203   for(int count = 0; count < m_sourceListWidget->count(); ++count) {
1204     FetcherInfoListItem* item = static_cast<FetcherInfoListItem*>(m_sourceListWidget->item(count));
1205     // only create a new config widget if we don't have one already
1206     if(!m_configWidgets.contains(item) && item->fetcher()) {
1207       Fetch::ConfigWidget* cw = item->fetcher()->configWidget(this);
1208       if(cw) { // might return 0 when no widget available for fetcher type
1209         m_configWidgets.insert(item, cw);
1210         // there's weird layout bug if it's not hidden
1211         cw->hide();
1212       }
1213     }
1214   }
1215 }