1 /**
2  * \file configdialogpages.cpp
3  * Pages for configuration dialog.
4  *
5  * \b Project: Kid3
6  * \author Urs Fleisch
7  * \date 17 Sep 2003
8  *
9  * Copyright (C) 2003-2018  Urs Fleisch
10  *
11  * This file is part of Kid3.
12  *
13  * Kid3 is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * Kid3 is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
25  */
26 
27 #include "configdialogpages.h"
28 #include <QLayout>
29 #include <QPushButton>
30 #include <QLabel>
31 #include <QString>
32 #include <QCheckBox>
33 #include <QComboBox>
34 #include <QLineEdit>
35 #include <QTabWidget>
36 #include <QListView>
37 #include <QSpinBox>
38 #include <QVBoxLayout>
39 #include <QHBoxLayout>
40 #include <QFormLayout>
41 #include <QGroupBox>
42 #include <QStringListModel>
43 #include <QStandardItemModel>
44 #include "formatconfig.h"
45 #include "filenameformatbox.h"
46 #include "tagformatbox.h"
47 #include "tablemodeledit.h"
48 #include "tagconfig.h"
49 #include "fileconfig.h"
50 #include "useractionsconfig.h"
51 #include "guiconfig.h"
52 #include "networkconfig.h"
53 #include "importconfig.h"
54 #include "stringlistedit.h"
55 #include "stringlisteditdialog.h"
56 #include "configtable.h"
57 #include "commandstablemodel.h"
58 #include "checkablestringlistmodel.h"
59 #include "starratingmappingsmodel.h"
60 #include "fileproxymodel.h"
61 #include "contexthelp.h"
62 #include "frame.h"
63 #include "iplatformtools.h"
64 
65 namespace {
66 
67 /**
68  * @brief Get string representation of folder patterns.
69  * @param folders folder patterns
70  * @param asteriskIfEmpty if true "*" is returned if @a folders is empty
71  * @return string representation of folder patterns.
72  */
folderPatternListToString(const QStringList & folders,bool asteriskIfEmpty=false)73 QString folderPatternListToString(const QStringList& folders,
74                                   bool asteriskIfEmpty = false)
75 {
76   QStringList patterns;
77   QChar sep = QLatin1Char(' ');
78   for (const QString& folder : folders) {
79     QString pattern = folder.trimmed();
80     if (!pattern.isEmpty()) {
81       if (pattern.contains(QLatin1Char(' '))) {
82         sep = QLatin1Char(';');
83       }
84       patterns.append(pattern); // clazy:exclude=reserve-candidates
85     }
86   }
87   patterns.removeDuplicates();
88   if (patterns.isEmpty())
89     return QLatin1String(asteriskIfEmpty ? "*" : "");
90 
91   // Keep semicolon to mark that space is not a separator.
92   if (patterns.size() == 1 && sep == QLatin1Char(';'))
93     return patterns.first() + sep;
94 
95   return patterns.join(sep);
96 }
97 
98 /**
99  * @brief Get folder patterns from string representation.
100  * @param patterns string representation of folder patterns
101  * @param asteriskIfEmpty if true an empty list is returned for "*"
102  * @return folder pattern list.
103  */
folderPatternListFromString(const QString & patterns,bool asteriskIfEmpty=false)104 QStringList folderPatternListFromString(const QString& patterns,
105                                         bool asteriskIfEmpty = false)
106 {
107   if (asteriskIfEmpty && patterns == QLatin1String("*"))
108     return QStringList();
109 
110   QStringList folders;
111   const QChar sep = patterns.contains(QLatin1Char(';'))
112       ? QLatin1Char(';') : QLatin1Char(' ');
113   const auto patternList = patterns.split(sep);
114   for (const QString& pattern : patternList) {
115     QString folder = pattern.trimmed();
116     if (!folder.isEmpty()) {
117       folders.append(folder); // clazy:exclude=reserve-candidates
118     }
119   }
120 
121   return folders;
122 }
123 
124 }
125 
126 /**
127  * Constructor.
128  */
ConfigDialogPages(IPlatformTools * platformTools,QObject * parent)129 ConfigDialogPages::ConfigDialogPages(IPlatformTools* platformTools,
130                                      QObject* parent) : QObject(parent),
131   m_platformTools(platformTools),
132   m_loadLastOpenedFileCheckBox(nullptr), m_preserveTimeCheckBox(nullptr),
133   m_markChangesCheckBox(nullptr), m_coverFileNameLineEdit(nullptr),
134   m_nameFilterComboBox(nullptr), m_includeFoldersLineEdit(nullptr),
135   m_excludeFoldersLineEdit(nullptr), m_showHiddenFilesCheckBox(nullptr),
136   m_fileTextEncodingComboBox(nullptr),
137   m_markTruncationsCheckBox(nullptr), m_textEncodingV1ComboBox(nullptr),
138   m_totalNumTracksCheckBox(nullptr), m_commentNameComboBox(nullptr),
139   m_pictureNameComboBox(nullptr), m_markOversizedPicturesCheckBox(nullptr),
140   m_maximumPictureSizeSpinBox(nullptr), m_genreNotNumericCheckBox(nullptr),
141   m_lowercaseId3ChunkCheckBox(nullptr),
142   m_markStandardViolationsCheckBox(nullptr), m_textEncodingComboBox(nullptr),
143   m_id3v2VersionComboBox(nullptr), m_trackNumberDigitsSpinBox(nullptr),
144   m_fnFormatBox(nullptr), m_tagFormatBox(nullptr),
145   m_onlyCustomGenresCheckBox(nullptr), m_genresEditModel(nullptr),
146   m_quickAccessTagsModel(nullptr), m_starRatingMappingsModel(nullptr),
147   m_trackNameComboBox(nullptr), m_playOnDoubleClickCheckBox(nullptr),
148   m_commandsTable(nullptr), m_commandsTableModel(nullptr),
149   m_browserLineEdit(nullptr), m_proxyCheckBox(nullptr),
150   m_proxyLineEdit(nullptr), m_proxyAuthenticationCheckBox(nullptr),
151   m_proxyUserNameLineEdit(nullptr), m_proxyPasswordLineEdit(nullptr),
152   m_enabledMetadataPluginsModel(nullptr), m_enabledPluginsModel(nullptr)
153 {
154 }
155 
156 /**
157  * Create page with tags settings.
158  * @return tags page.
159  */
createTagsPage()160 QWidget* ConfigDialogPages::createTagsPage()
161 {
162   QWidget* tagsPage = new QWidget;
163   auto vlayout = new QVBoxLayout(tagsPage);
164 
165   QWidget* tag1Page = new QWidget;
166   auto tag1Layout = new QVBoxLayout(tag1Page);
167   QGroupBox* v1GroupBox = new QGroupBox(tr("ID3v1"), tag1Page);
168   auto v1GroupBoxLayout = new QGridLayout(v1GroupBox);
169   m_markTruncationsCheckBox = new QCheckBox(
170         tr("&Mark truncated fields"), v1GroupBox);
171   v1GroupBoxLayout->addWidget(m_markTruncationsCheckBox, 0, 0, 1, 2);
172   QLabel* textEncodingV1Label = new QLabel(tr("Text &encoding:"), v1GroupBox);
173   m_textEncodingV1ComboBox = new QComboBox(v1GroupBox);
174   m_textEncodingV1ComboBox->addItems(TagConfig::getTextCodecNames());
175   m_textEncodingV1ComboBox->setSizePolicy(
176         QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
177   textEncodingV1Label->setBuddy(m_textEncodingV1ComboBox);
178   v1GroupBoxLayout->addWidget(textEncodingV1Label, 1, 0);
179   v1GroupBoxLayout->addWidget(m_textEncodingV1ComboBox, 1, 1);
180   tag1Layout->addWidget(v1GroupBox);
181   tag1Layout->addStretch();
182 
183   QWidget* tag2Page = new QWidget;
184   auto tag2Layout = new QHBoxLayout(tag2Page);
185   auto tag2LeftLayout = new QVBoxLayout;
186   QGroupBox* v2GroupBox = new QGroupBox(tr("ID3v2"), tag2Page);
187   auto v2GroupBoxLayout = new QGridLayout(v2GroupBox);
188   m_totalNumTracksCheckBox = new QCheckBox(
189         tr("Use &track/total number of tracks format"), v2GroupBox);
190   v2GroupBoxLayout->addWidget(m_totalNumTracksCheckBox, 0, 0, 1, 2);
191   QLabel* trackNumberDigitsLabel = new QLabel(
192         tr("Track number &digits:"), v2GroupBox);
193   m_trackNumberDigitsSpinBox = new QSpinBox(v2GroupBox);
194   m_trackNumberDigitsSpinBox->setMaximum(5);
195   m_genreNotNumericCheckBox = new QCheckBox(
196         tr("&Genre as text instead of numeric string"), v2GroupBox);
197   m_lowercaseId3ChunkCheckBox = new QCheckBox(
198         tr("&WAV files with lowercase id3 chunk"), v2GroupBox);
199   m_markStandardViolationsCheckBox =
200       new QCheckBox(tr("Mar&k standard violations"));
201   QLabel* textEncodingLabel = new QLabel(tr("Text &encoding:"), v2GroupBox);
202   m_textEncodingComboBox = new QComboBox(v2GroupBox);
203   m_textEncodingComboBox->addItems(TagConfig::getTextEncodingNames());
204   m_textEncodingComboBox->setSizePolicy(
205         QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
206   textEncodingLabel->setBuddy(m_textEncodingComboBox);
207   v2GroupBoxLayout->addWidget(m_genreNotNumericCheckBox, 1, 0, 1, 2);
208   v2GroupBoxLayout->addWidget(m_lowercaseId3ChunkCheckBox, 2, 0, 1, 2);
209   v2GroupBoxLayout->addWidget(m_markStandardViolationsCheckBox, 3, 0, 1, 2);
210   v2GroupBoxLayout->addWidget(textEncodingLabel, 4, 0);
211   v2GroupBoxLayout->addWidget(m_textEncodingComboBox, 4, 1);
212   const TagConfig& tagCfg = TagConfig::instance();
213   if (!(tagCfg.taggedFileFeatures() &
214         (TaggedFile::TF_ID3v22 | TaggedFile::TF_ID3v23 | TaggedFile::TF_ID3v24))) {
215     m_genreNotNumericCheckBox->hide();
216     textEncodingLabel->hide();
217     m_textEncodingComboBox->hide();
218   }
219   QLabel* id3v2VersionLabel = new QLabel(tr("&Version used for new tags:"),
220                                          v2GroupBox);
221   m_id3v2VersionComboBox = new QComboBox(v2GroupBox);
222   if (tagCfg.taggedFileFeatures() & TaggedFile::TF_ID3v23)
223     m_id3v2VersionComboBox->addItem(tr("ID3v2.3.0"), TagConfig::ID3v2_3_0);
224   if (tagCfg.taggedFileFeatures() & TaggedFile::TF_ID3v24)
225     m_id3v2VersionComboBox->addItem(tr("ID3v2.4.0"), TagConfig::ID3v2_4_0);
226   m_id3v2VersionComboBox->setSizePolicy(
227         QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
228   id3v2VersionLabel->setBuddy(m_id3v2VersionComboBox);
229   v2GroupBoxLayout->addWidget(id3v2VersionLabel, 5, 0);
230   v2GroupBoxLayout->addWidget(m_id3v2VersionComboBox, 5, 1);
231   if (m_id3v2VersionComboBox->count() < 2) {
232     id3v2VersionLabel->hide();
233     m_id3v2VersionComboBox->hide();
234   }
235   trackNumberDigitsLabel->setBuddy(m_trackNumberDigitsSpinBox);
236   v2GroupBoxLayout->addWidget(trackNumberDigitsLabel, 6, 0);
237   v2GroupBoxLayout->addWidget(m_trackNumberDigitsSpinBox, 6, 1);
238   tag2LeftLayout->addWidget(v2GroupBox);
239   QGroupBox* vorbisGroupBox = new QGroupBox(tr("Ogg/Vorbis"), tag2Page);
240   QLabel* commentNameLabel = new QLabel(tr("Co&mment field name:"),
241                                         vorbisGroupBox);
242   m_commentNameComboBox = new QComboBox(vorbisGroupBox);
243   QLabel* pictureNameLabel = new QLabel(tr("&Picture field name:"),
244                                         vorbisGroupBox);
245   m_pictureNameComboBox = new QComboBox(vorbisGroupBox);
246   m_commentNameComboBox->setEditable(true);
247   m_commentNameComboBox->addItems(TagConfig::getCommentNames());
248   m_commentNameComboBox->setSizePolicy(
249         QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
250   commentNameLabel->setBuddy(m_commentNameComboBox);
251   m_pictureNameComboBox->addItems(TagConfig::getPictureNames());
252   m_pictureNameComboBox->setSizePolicy(
253         QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
254   pictureNameLabel->setBuddy(m_pictureNameComboBox);
255   auto vorbisGroupBoxLayout = new QGridLayout(vorbisGroupBox);
256   vorbisGroupBoxLayout->addWidget(commentNameLabel, 0, 0);
257   vorbisGroupBoxLayout->addWidget(m_commentNameComboBox, 0, 1);
258   vorbisGroupBoxLayout->addWidget(pictureNameLabel, 1, 0);
259   vorbisGroupBoxLayout->addWidget(m_pictureNameComboBox, 1, 1);
260   vorbisGroupBox->setLayout(vorbisGroupBoxLayout);
261   tag2LeftLayout->addWidget(vorbisGroupBox);
262   if (!(tagCfg.taggedFileFeatures() & TaggedFile::TF_OggPictures)) {
263     vorbisGroupBox->hide();
264   }
265   QGroupBox* pictureGroupBox = new QGroupBox(tr("Picture"), tag2Page);
266   auto pictureGroupBoxLayout = new QHBoxLayout(pictureGroupBox);
267   m_markOversizedPicturesCheckBox =
268       new QCheckBox(tr("Mark if &larger than (bytes):"));
269   m_maximumPictureSizeSpinBox = new QSpinBox;
270   m_maximumPictureSizeSpinBox->setRange(0, INT_MAX);
271   pictureGroupBoxLayout->addWidget(m_markOversizedPicturesCheckBox);
272   pictureGroupBoxLayout->addWidget(m_maximumPictureSizeSpinBox);
273   tag2LeftLayout->addWidget(pictureGroupBox);
274   tag2LeftLayout->addStretch();
275   tag2Layout->addLayout(tag2LeftLayout);
276 
277   auto tag2RightLayout = new QVBoxLayout;
278   QGroupBox* genresGroupBox = new QGroupBox(tr("Custom &Genres"), tag2Page);
279   m_onlyCustomGenresCheckBox = new QCheckBox(tr("&Show only custom genres"),
280                                              genresGroupBox);
281   m_genresEditModel = new QStringListModel(genresGroupBox);
282   auto genresEdit = new StringListEdit(m_genresEditModel, genresGroupBox);
283   auto vbox = new QVBoxLayout;
284   vbox->addWidget(m_onlyCustomGenresCheckBox);
285   vbox->addWidget(genresEdit);
286   genresGroupBox->setLayout(vbox);
287   tag2RightLayout->addWidget(genresGroupBox);
288 
289   QGroupBox* quickAccessTagsGroupBox = new QGroupBox(tr("&Quick Access Frames"));
290   auto quickAccessTagsLayout = new QVBoxLayout(quickAccessTagsGroupBox);
291   auto quickAccessTagsListView = new QListView;
292   m_quickAccessTagsModel = new QStandardItemModel(quickAccessTagsGroupBox);
293   quickAccessTagsListView->setModel(m_quickAccessTagsModel);
294   quickAccessTagsListView->setAcceptDrops(true);
295   quickAccessTagsListView->setDragEnabled(true);
296   quickAccessTagsListView->setDragDropMode(QAbstractItemView::InternalMove);
297   quickAccessTagsListView->setDragDropOverwriteMode(false);
298   quickAccessTagsListView->setDefaultDropAction(Qt::MoveAction);
299   quickAccessTagsListView->setDropIndicatorShown(true);
300   quickAccessTagsLayout->addWidget(quickAccessTagsListView);
301   QLabel* reorderLabel =
302       new QLabel(tr("Use drag and drop to reorder the items"));
303   reorderLabel->setWordWrap(true);
304   quickAccessTagsLayout->addWidget(reorderLabel);
305   tag2RightLayout->addWidget(quickAccessTagsGroupBox);
306   tag2Layout->addLayout(tag2RightLayout);
307 
308   QWidget* tag3Page = new QWidget;
309   auto tag3Layout = new QVBoxLayout(tag3Page);
310   QGroupBox* riffGroupBox = new QGroupBox(tr("RIFF INFO"), tag3Page);
311   QLabel* trackNameLabel = new QLabel(tr("Track nu&mber field name:"),
312                                       riffGroupBox);
313   m_trackNameComboBox = new QComboBox(riffGroupBox);
314   m_trackNameComboBox->setEditable(true);
315   m_trackNameComboBox->addItems(TagConfig::getRiffTrackNames());
316   m_trackNameComboBox->setSizePolicy(
317         QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
318   trackNameLabel->setBuddy(m_trackNameComboBox);
319   auto riffGroupBoxLayout = new QGridLayout(riffGroupBox);
320   riffGroupBoxLayout->addWidget(trackNameLabel, 0, 0);
321   riffGroupBoxLayout->addWidget(m_trackNameComboBox, 0, 1);
322   riffGroupBox->setLayout(riffGroupBoxLayout);
323   tag3Layout->addWidget(riffGroupBox);
324   tag3Layout->addStretch();
325 
326   QWidget* tag1AndTag2Page = new QWidget;
327   auto tag1AndTag2Layout = new QVBoxLayout(tag1AndTag2Page);
328   QString tagFormatTitle(tr("&Tag Format"));
329   m_tagFormatBox = new TagFormatBox(tagFormatTitle, tag1AndTag2Page);
330   QGroupBox* ratingGroupBox = new QGroupBox(tr("Rating"), tag1AndTag2Page);
331   auto ratingLayout = new QVBoxLayout(ratingGroupBox);
332   m_starRatingMappingsModel = new StarRatingMappingsModel(ratingGroupBox);
333   auto ratingEdit = new TableModelEdit(m_starRatingMappingsModel);
334   ratingLayout->addWidget(ratingEdit);
335   tag1AndTag2Layout->addWidget(m_tagFormatBox);
336   tag1AndTag2Layout->addWidget(ratingGroupBox);
337 
338   auto tagsTabWidget = new QTabWidget;
339   if (tagCfg.taggedFileFeatures() & TaggedFile::TF_ID3v11) {
340     tagsTabWidget->addTab(tag1Page, tr("Tag &1"));
341   }
342   tagsTabWidget->addTab(tag2Page, tr("Tag &2"));
343   tagsTabWidget->addTab(tag3Page, tr("Tag &3"));
344   tagsTabWidget->addTab(tag1AndTag2Page, tr("All Ta&gs"));
345   tagsTabWidget->setCurrentIndex(1);
346   vlayout->addWidget(tagsTabWidget);
347   return tagsPage;
348 }
349 
350 /**
351  * Create page with files settings.
352  * @return files page.
353  */
createFilesPage()354 QWidget* ConfigDialogPages::createFilesPage()
355 {
356   QWidget* filesPage = new QWidget;
357   auto vlayout = new QVBoxLayout(filesPage);
358   auto hlayout = new QHBoxLayout;
359   auto leftLayout = new QVBoxLayout;
360   auto rightLayout = new QVBoxLayout;
361 
362   QGroupBox* startupGroupBox = new QGroupBox(tr("Startup"), filesPage);
363   m_loadLastOpenedFileCheckBox = new QCheckBox(tr("&Load last-opened files"),
364                                                startupGroupBox);
365   auto startupLayout = new QVBoxLayout;
366   startupLayout->addWidget(m_loadLastOpenedFileCheckBox);
367   startupGroupBox->setLayout(startupLayout);
368   leftLayout->addWidget(startupGroupBox);
369   QGroupBox* saveGroupBox = new QGroupBox(tr("Save"), filesPage);
370   m_preserveTimeCheckBox = new QCheckBox(tr("&Preserve file timestamp"),
371                                          saveGroupBox);
372   m_markChangesCheckBox = new QCheckBox(tr("&Mark changes"), saveGroupBox);
373   m_coverFileNameLineEdit = new QLineEdit(saveGroupBox);
374   m_fileTextEncodingComboBox = new QComboBox(saveGroupBox);
375   m_fileTextEncodingComboBox->addItems(FileConfig::getTextCodecNames());
376   m_fileTextEncodingComboBox->setSizePolicy(QSizePolicy(QSizePolicy::Expanding,
377                                                         QSizePolicy::Minimum));
378   auto formLayout = new QFormLayout;
379   formLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
380   formLayout->addRow(m_preserveTimeCheckBox);
381   formLayout->addRow(m_markChangesCheckBox);
382   formLayout->addRow(tr("F&ilename for cover:"), m_coverFileNameLineEdit);
383   formLayout->addRow(tr("Text &encoding (Export, Playlist):"),
384                      m_fileTextEncodingComboBox);
385   saveGroupBox->setLayout(formLayout);
386   leftLayout->addWidget(saveGroupBox);
387 
388   QGroupBox* fileListGroupBox = new QGroupBox(tr("File List"), filesPage);
389   QLabel* nameFilterLabel = new QLabel(tr("Filte&r:"), fileListGroupBox);
390   m_nameFilterComboBox = new QComboBox(fileListGroupBox);
391   m_nameFilterComboBox->setSizePolicy(QSizePolicy(QSizePolicy::Expanding,
392                                                   QSizePolicy::Minimum));
393   QList<QPair<QString, QString> > filters = FileProxyModel::createNameFilters();
394   for (auto it = filters.constBegin(); it != filters.constEnd(); ++it) {
395     QString nameFilter = m_platformTools->fileDialogNameFilter({*it});
396     m_nameFilterComboBox->addItem(it->first, nameFilter);
397   }
398   nameFilterLabel->setBuddy(m_nameFilterComboBox);
399   QLabel* includeFoldersLabel = new QLabel(tr("Inclu&de folders:"),
400                                            fileListGroupBox);
401   m_includeFoldersLineEdit = new QLineEdit(fileListGroupBox);
402   includeFoldersLabel->setBuddy(m_includeFoldersLineEdit);
403   QLabel* excludeFoldersLabel = new QLabel(tr("E&xclude folders:"),
404                                            fileListGroupBox);
405   m_excludeFoldersLineEdit = new QLineEdit(fileListGroupBox);
406   excludeFoldersLabel->setBuddy(m_excludeFoldersLineEdit);
407   m_showHiddenFilesCheckBox = new QCheckBox(tr("&Show hidden files"),
408                                             fileListGroupBox);
409   auto fileListGroupBoxLayout = new QGridLayout(fileListGroupBox);
410   fileListGroupBoxLayout->addWidget(nameFilterLabel, 0, 0);
411   fileListGroupBoxLayout->addWidget(m_nameFilterComboBox, 0, 1);
412   fileListGroupBoxLayout->addWidget(includeFoldersLabel, 1, 0);
413   fileListGroupBoxLayout->addWidget(m_includeFoldersLineEdit, 1, 1);
414   fileListGroupBoxLayout->addWidget(excludeFoldersLabel, 2, 0);
415   fileListGroupBoxLayout->addWidget(m_excludeFoldersLineEdit, 2, 1);
416   fileListGroupBoxLayout->addWidget(m_showHiddenFilesCheckBox, 3, 0, 1, 2);
417   rightLayout->addWidget(fileListGroupBox);
418 
419   auto formatGroupBox = new QGroupBox(tr("Format"), filesPage);
420   auto formatLayout = new QHBoxLayout(formatGroupBox);
421   auto editFormatsFromTagButton =
422           new QPushButton(tr("Filename from tag") + QLatin1String("..."));
423   connect(editFormatsFromTagButton, &QPushButton::clicked,
424           this, &ConfigDialogPages::editFormatsFromTag);
425   formatLayout->addWidget(editFormatsFromTagButton);
426   auto editFormatsToTagButton =
427           new QPushButton(tr("Tag from filename") + QLatin1String("..."));
428   connect(editFormatsToTagButton, &QPushButton::clicked,
429           this, &ConfigDialogPages::editFormatsToTag);
430   formatLayout->addWidget(editFormatsToTagButton);
431   rightLayout->addWidget(formatGroupBox);
432 
433   rightLayout->addStretch();
434 
435   hlayout->addLayout(leftLayout);
436   hlayout->addLayout(rightLayout);
437   vlayout->addLayout(hlayout);
438 
439   QString fnFormatTitle(tr("&Filename Format"));
440   m_fnFormatBox = new FilenameFormatBox(fnFormatTitle, filesPage);
441   vlayout->addWidget(m_fnFormatBox);
442   return filesPage;
443 }
444 
445 /**
446  * Open dialog to edit "Filename from tag" formats.
447  */
editFormatsFromTag()448 void ConfigDialogPages::editFormatsFromTag()
449 {
450   QWidget* parentWindow = nullptr;
451   if (auto button = qobject_cast<QPushButton*>(sender())) {
452     parentWindow = button->window();
453   }
454   StringListEditDialog dialog(
455         m_toFilenameFormats, tr("Filename from Tag"), parentWindow);
456   if (dialog.exec() == QDialog::Accepted) {
457     m_toFilenameFormats = dialog.stringList();
458   }
459 }
460 
461 /**
462  * Open dialog to edit "Filename to tag" formats.
463  */
editFormatsToTag()464 void ConfigDialogPages::editFormatsToTag()
465 {
466   QWidget* parentWindow = nullptr;
467   if (auto button = qobject_cast<QPushButton*>(sender())) {
468     parentWindow = button->window();
469   }
470   StringListEditDialog dialog(
471         m_fromFilenameFormats, tr("Tag from Filename"), parentWindow);
472   if (dialog.exec() == QDialog::Accepted) {
473     m_fromFilenameFormats = dialog.stringList();
474   }
475 }
476 
477 /**
478  * Create page with actions settings.
479  * @return actions page.
480  */
createActionsPage()481 QWidget* ConfigDialogPages::createActionsPage()
482 {
483   QWidget* actionsPage = new QWidget;
484   auto vlayout = new QVBoxLayout(actionsPage);
485   QGroupBox* browserGroupBox = new QGroupBox(tr("Browser"), actionsPage);
486   QLabel* browserLabel = new QLabel(tr("Web &browser:"), browserGroupBox);
487   m_browserLineEdit = new QLineEdit(browserGroupBox);
488   browserLabel->setBuddy(m_browserLineEdit);
489   auto hbox = new QHBoxLayout;
490   hbox->addWidget(browserLabel);
491   hbox->addWidget(m_browserLineEdit);
492   browserGroupBox->setLayout(hbox);
493   vlayout->addWidget(browserGroupBox);
494 
495   QGroupBox* commandsGroupBox = new QGroupBox(tr("Context &Menu Commands"),
496                                               actionsPage);
497   m_playOnDoubleClickCheckBox =
498       new QCheckBox(tr("&Play on double click"), commandsGroupBox);
499   m_commandsTableModel = new CommandsTableModel(commandsGroupBox);
500   m_commandsTable = new ConfigTable(m_commandsTableModel, commandsGroupBox);
501   m_commandsTable->setHorizontalResizeModes(
502     m_commandsTableModel->getHorizontalResizeModes());
503   auto commandsLayout = new QVBoxLayout;
504   commandsLayout->addWidget(m_playOnDoubleClickCheckBox);
505   commandsLayout->addWidget(m_commandsTable);
506   commandsGroupBox->setLayout(commandsLayout);
507   vlayout->addWidget(commandsGroupBox);
508   return actionsPage;
509 }
510 
511 /**
512  * Create page with network settings.
513  * @return network page.
514  */
createNetworkPage()515 QWidget* ConfigDialogPages::createNetworkPage()
516 {
517   QWidget* networkPage = new QWidget;
518   auto vlayout = new QVBoxLayout(networkPage);
519   QGroupBox* proxyGroupBox = new QGroupBox(tr("Proxy"), networkPage);
520   m_proxyCheckBox = new QCheckBox(tr("&Proxy:"), proxyGroupBox);
521   m_proxyLineEdit = new QLineEdit(proxyGroupBox);
522   m_proxyAuthenticationCheckBox = new QCheckBox(
523         tr("&Use authentication with proxy"), proxyGroupBox);
524   QLabel* proxyUserNameLabel =
525       new QLabel(tr("Proxy user &name:"), proxyGroupBox);
526   m_proxyUserNameLineEdit = new QLineEdit(proxyGroupBox);
527   proxyUserNameLabel->setBuddy(m_proxyUserNameLineEdit);
528   QLabel* proxyPasswordLabel =
529       new QLabel(tr("Proxy pass&word:"), proxyGroupBox);
530   m_proxyPasswordLineEdit = new QLineEdit(proxyGroupBox);
531   proxyPasswordLabel->setBuddy(m_proxyPasswordLineEdit);
532   m_proxyPasswordLineEdit->setEchoMode(QLineEdit::Password);
533   auto vbox = new QVBoxLayout;
534   auto proxyHbox = new QHBoxLayout;
535   proxyHbox->addWidget(m_proxyCheckBox);
536   proxyHbox->addWidget(m_proxyLineEdit);
537   vbox->addLayout(proxyHbox);
538   vbox->addWidget(m_proxyAuthenticationCheckBox);
539   auto authLayout = new QGridLayout;
540   authLayout->addWidget(proxyUserNameLabel, 0, 0);
541   authLayout->addWidget(m_proxyUserNameLineEdit, 0, 1);
542   authLayout->addWidget(proxyPasswordLabel, 1, 0);
543   authLayout->addWidget(m_proxyPasswordLineEdit, 1, 1);
544   vbox->addLayout(authLayout);
545   proxyGroupBox->setLayout(vbox);
546   vlayout->addWidget(proxyGroupBox);
547 
548   auto vspacer = new QSpacerItem(0, 0,
549                                  QSizePolicy::Minimum, QSizePolicy::Expanding);
550   vlayout->addItem(vspacer);
551   return networkPage;
552 }
553 
554 /**
555  * Create page with plugins settings.
556  * @return plugins page.
557  */
createPluginsPage()558 QWidget* ConfigDialogPages::createPluginsPage()
559 {
560   QWidget* pluginsPage = new QWidget;
561   auto vlayout = new QVBoxLayout(pluginsPage);
562   QGroupBox* metadataGroupBox = new QGroupBox(
563         tr("&Metadata Plugins && Priority"), pluginsPage);
564 
565 
566   auto metadataPluginsLayout = new QVBoxLayout(metadataGroupBox);
567   m_enabledMetadataPluginsModel =
568       new CheckableStringListModel(metadataGroupBox);
569   auto metadataEdit =
570       new StringListEdit(m_enabledMetadataPluginsModel, metadataGroupBox);
571   metadataEdit->setEditingDisabled(true);
572   metadataPluginsLayout->addWidget(metadataEdit);
573   vlayout->addWidget(metadataGroupBox);
574 
575   QGroupBox* pluginsGroupBox = new QGroupBox(tr("A&vailable Plugins"));
576   auto pluginsLayout = new QVBoxLayout(pluginsGroupBox);
577   auto pluginsListView = new QListView;
578   pluginsListView->setSelectionMode(QAbstractItemView::NoSelection);
579   m_enabledPluginsModel = new CheckableStringListModel(pluginsGroupBox);
580   pluginsListView->setModel(m_enabledPluginsModel);
581   pluginsLayout->addWidget(pluginsListView);
582   vlayout->addWidget(pluginsGroupBox);
583 
584   vlayout->addStretch();
585   vlayout->addWidget(
586         new QLabel(tr("Changes take only effect after a restart!")));
587   return pluginsPage;
588 }
589 
590 /**
591  * Set values in pages from default configuration.
592  */
setDefaultConfig()593 void ConfigDialogPages::setDefaultConfig()
594 {
595   FilenameFormatConfig fnCfg;
596   TagFormatConfig id3Cfg;
597   TagConfig tagCfg;
598   tagCfg.setAvailablePlugins(TagConfig::instance().availablePlugins());
599   tagCfg.setTaggedFileFeatures(TagConfig::instance().taggedFileFeatures());
600   tagCfg.setDefaultPluginOrder();
601   FileConfig fileCfg;
602   UserActionsConfig userActionsCfg;
603   userActionsCfg.setDefaultUserActions();
604   GuiConfig guiCfg;
605   NetworkConfig networkCfg;
606   networkCfg.setDefaultBrowser();
607   ImportConfig importCfg;
608   importCfg.setAvailablePlugins(ImportConfig::instance().availablePlugins());
609   setConfigs(fnCfg, id3Cfg, tagCfg, fileCfg, userActionsCfg, guiCfg, networkCfg,
610             importCfg);
611 }
612 
613 /**
614  * Set values in dialog from current configuration.
615  */
setConfig()616 void ConfigDialogPages::setConfig()
617 {
618   const FormatConfig& fnCfg = FilenameFormatConfig::instance();
619   const FormatConfig& id3Cfg = TagFormatConfig::instance();
620   const TagConfig& tagCfg = TagConfig::instance();
621   const FileConfig& fileCfg = FileConfig::instance();
622   const UserActionsConfig& userActionsCfg = UserActionsConfig::instance();
623   const GuiConfig& guiCfg = GuiConfig::instance();
624   const NetworkConfig& networkCfg = NetworkConfig::instance();
625   const ImportConfig& importCfg = ImportConfig::instance();
626   setConfigs(fnCfg, id3Cfg, tagCfg, fileCfg, userActionsCfg, guiCfg, networkCfg,
627             importCfg);
628 }
629 
630 /**
631  * Set values in dialog from given configurations.
632  */
setConfigs(const FormatConfig & fnCfg,const FormatConfig & id3Cfg,const TagConfig & tagCfg,const FileConfig & fileCfg,const UserActionsConfig & userActionsCfg,const GuiConfig & guiCfg,const NetworkConfig & networkCfg,const ImportConfig & importCfg)633 void ConfigDialogPages::setConfigs(
634     const FormatConfig& fnCfg, const FormatConfig& id3Cfg,
635     const TagConfig& tagCfg, const FileConfig& fileCfg,
636     const UserActionsConfig& userActionsCfg, const GuiConfig& guiCfg,
637     const NetworkConfig& networkCfg, const ImportConfig& importCfg)
638 {
639   m_fnFormatBox->fromFormatConfig(fnCfg);
640   m_tagFormatBox->fromFormatConfig(id3Cfg);
641   m_markTruncationsCheckBox->setChecked(tagCfg.markTruncations());
642   m_totalNumTracksCheckBox->setChecked(tagCfg.enableTotalNumberOfTracks());
643   m_loadLastOpenedFileCheckBox->setChecked(fileCfg.loadLastOpenedFile());
644   m_preserveTimeCheckBox->setChecked(fileCfg.preserveTime());
645   m_markChangesCheckBox->setChecked(fileCfg.markChanges());
646   m_coverFileNameLineEdit->setText(fileCfg.defaultCoverFileName());
647   m_nameFilterComboBox->setCurrentIndex(
648         m_nameFilterComboBox->findData(fileCfg.nameFilter()));
649   m_includeFoldersLineEdit->setText(
650         folderPatternListToString(fileCfg.includeFolders(), true));
651   m_excludeFoldersLineEdit->setText(
652         folderPatternListToString(fileCfg.excludeFolders(), false));
653   m_showHiddenFilesCheckBox->setChecked(fileCfg.showHiddenFiles());
654   m_fileTextEncodingComboBox->setCurrentIndex(fileCfg.textEncodingIndex());
655   m_toFilenameFormats = fileCfg.toFilenameFormats();
656   m_fromFilenameFormats = fileCfg.fromFilenameFormats();
657   m_onlyCustomGenresCheckBox->setChecked(tagCfg.onlyCustomGenres());
658   m_genresEditModel->setStringList(tagCfg.customGenres());
659   m_starRatingMappingsModel->setMappings(tagCfg.starRatingMappings());
660   quint64 frameMask = tagCfg.quickAccessFrames();
661   QList<int> frameTypes = tagCfg.quickAccessFrameOrder();
662   if (frameTypes.size() != Frame::FT_LastFrame + 1) {
663     frameTypes.clear();
664     frameTypes.reserve(Frame::FT_LastFrame - Frame::FT_FirstFrame + 1);
665     for (int i = Frame::FT_FirstFrame; i <= Frame::FT_LastFrame; ++i) {
666       frameTypes.append(i);
667     }
668   }
669   m_quickAccessTagsModel->clear();
670   const auto constFrameTypes = frameTypes;
671   for (int frameType : constFrameTypes) {
672     QStandardItem* item = new QStandardItem(
673           Frame::ExtendedType(static_cast<Frame::Type>(frameType))
674           .getTranslatedName());
675     item->setData(frameType, Qt::UserRole);
676     item->setCheckable(true);
677     item->setCheckState((frameMask & (1ULL << frameType))
678                         ? Qt::Checked : Qt::Unchecked);
679     item->setDropEnabled(false);
680     m_quickAccessTagsModel->appendRow(item);
681   }
682   m_commandsTableModel->setCommandList(userActionsCfg.contextMenuCommands());
683   int idx = m_commentNameComboBox->findText(tagCfg.commentName());
684   if (idx >= 0) {
685     m_commentNameComboBox->setCurrentIndex(idx);
686   } else {
687     m_commentNameComboBox->addItem(tagCfg.commentName());
688     m_commentNameComboBox->setCurrentIndex(m_commentNameComboBox->count() - 1);
689   }
690   m_pictureNameComboBox->setCurrentIndex(tagCfg.pictureNameIndex());
691   m_genreNotNumericCheckBox->setChecked(tagCfg.genreNotNumeric());
692   m_lowercaseId3ChunkCheckBox->setChecked(tagCfg.lowercaseId3RiffChunk());
693   m_markStandardViolationsCheckBox->setChecked(tagCfg.markStandardViolations());
694   m_textEncodingV1ComboBox->setCurrentIndex(tagCfg.textEncodingV1Index());
695   m_textEncodingComboBox->setCurrentIndex(tagCfg.textEncoding());
696   m_id3v2VersionComboBox->setCurrentIndex(
697         m_id3v2VersionComboBox->findData(tagCfg.id3v2Version()));
698   m_trackNumberDigitsSpinBox->setValue(tagCfg.trackNumberDigits());
699   m_markOversizedPicturesCheckBox->setChecked(tagCfg.markOversizedPictures());
700   m_maximumPictureSizeSpinBox->setValue(tagCfg.maximumPictureSize());
701   idx = m_trackNameComboBox->findText(tagCfg.riffTrackName());
702   if (idx >= 0) {
703     m_trackNameComboBox->setCurrentIndex(idx);
704   } else {
705     m_trackNameComboBox->addItem(tagCfg.riffTrackName());
706     m_trackNameComboBox->setCurrentIndex(m_trackNameComboBox->count() - 1);
707   }
708   m_browserLineEdit->setText(networkCfg.browser());
709   m_playOnDoubleClickCheckBox->setChecked(guiCfg.playOnDoubleClick());
710   m_proxyCheckBox->setChecked(networkCfg.useProxy());
711   m_proxyLineEdit->setText(networkCfg.proxy());
712   m_proxyAuthenticationCheckBox->setChecked(networkCfg.useProxyAuthentication());
713   m_proxyUserNameLineEdit->setText(networkCfg.proxyUserName());
714   m_proxyPasswordLineEdit->setText(networkCfg.proxyPassword());
715 
716   QStringList metadataPlugins;
717   QStringList pluginOrder = tagCfg.pluginOrder();
718   if (!pluginOrder.isEmpty()) {
719     for (int i = 0; i < pluginOrder.size(); ++i) {
720       metadataPlugins.append(QString()); // clazy:exclude=reserve-candidates
721     }
722     const auto pluginNames = tagCfg.availablePlugins();
723     for (const QString& pluginName : pluginNames) {
724       int pluginIdx = pluginOrder.indexOf(pluginName);
725       if (pluginIdx >= 0) {
726         metadataPlugins[pluginIdx] = pluginName;
727       } else {
728         metadataPlugins.append(pluginName); // clazy:exclude=reserve-candidates
729       }
730     }
731     metadataPlugins.removeAll(QString());
732   } else {
733     metadataPlugins = tagCfg.availablePlugins();
734   }
735   quint64 metadataPluginsMask = 0;
736   quint64 mask = 1;
737   QStringList disabledTagPlugins = tagCfg.disabledPlugins();
738   for (int i = 0; i < metadataPlugins.size(); ++i, mask <<= 1) {
739     if (!disabledTagPlugins.contains(metadataPlugins.at(i))) {
740       metadataPluginsMask |= mask;
741     }
742   }
743   m_enabledMetadataPluginsModel->setStringList(metadataPlugins);
744   m_enabledMetadataPluginsModel->setBitMask(metadataPluginsMask);
745 
746   QStringList importPlugins = importCfg.availablePlugins();
747   quint64 importPluginsMask = 0;
748   mask = 1;
749   QStringList disabledPlugins = importCfg.disabledPlugins();
750   for (int i = 0; i < importPlugins.size(); ++i, mask <<= 1) {
751     if (!disabledPlugins.contains(importPlugins.at(i))) {
752       importPluginsMask |= mask;
753     }
754   }
755   m_enabledPluginsModel->setStringList(importPlugins);
756   m_enabledPluginsModel->setBitMask(importPluginsMask);
757 
758   if (!guiCfg.configWindowGeometry().isEmpty()) {
759     if (QWidget* configDialog = qobject_cast<QWidget*>(parent())) {
760       configDialog->restoreGeometry(guiCfg.configWindowGeometry());
761     }
762   }
763 }
764 
765 /**
766  * Get values from dialog and store them in the current configuration.
767  */
getConfig() const768 void ConfigDialogPages::getConfig() const
769 {
770   FormatConfig& fnCfg = FilenameFormatConfig::instance();
771   FormatConfig& id3Cfg = TagFormatConfig::instance();
772   TagConfig& tagCfg = TagConfig::instance();
773   FileConfig& fileCfg = FileConfig::instance();
774   UserActionsConfig& userActionsCfg = UserActionsConfig::instance();
775   GuiConfig& guiCfg = GuiConfig::instance();
776   NetworkConfig& networkCfg = NetworkConfig::instance();
777   ImportConfig& importCfg = ImportConfig::instance();
778 
779   m_fnFormatBox->toFormatConfig(fnCfg);
780   m_tagFormatBox->toFormatConfig(id3Cfg);
781   tagCfg.setMarkTruncations(m_markTruncationsCheckBox->isChecked());
782   tagCfg.setEnableTotalNumberOfTracks(m_totalNumTracksCheckBox->isChecked());
783   fileCfg.setLoadLastOpenedFile(m_loadLastOpenedFileCheckBox->isChecked());
784   fileCfg.setPreserveTime(m_preserveTimeCheckBox->isChecked());
785   fileCfg.setMarkChanges(m_markChangesCheckBox->isChecked());
786   fileCfg.setDefaultCoverFileName(m_coverFileNameLineEdit->text());
787   fileCfg.setNameFilter(m_nameFilterComboBox->currentData().toString());
788   fileCfg.setIncludeFolders(
789         folderPatternListFromString(m_includeFoldersLineEdit->text(), true));
790   fileCfg.setExcludeFolders(
791         folderPatternListFromString(m_excludeFoldersLineEdit->text(), false));
792   fileCfg.setShowHiddenFiles(m_showHiddenFilesCheckBox->isChecked());
793   fileCfg.setTextEncodingIndex(m_fileTextEncodingComboBox->currentIndex());
794   fileCfg.setToFilenameFormats(m_toFilenameFormats);
795   fileCfg.setFromFilenameFormats(m_fromFilenameFormats);
796   tagCfg.setOnlyCustomGenres(m_onlyCustomGenresCheckBox->isChecked());
797   tagCfg.setCustomGenres(m_genresEditModel->stringList());
798   tagCfg.setStarRatingMappings(m_starRatingMappingsModel->getMappings());
799   QList<int> frameTypes;
800   bool isStandardFrameOrder = true;
801   quint64 frameMask = 0;
802   const int numQuickAccessTags = m_quickAccessTagsModel->rowCount();
803   frameTypes.reserve(numQuickAccessTags);
804   for (int row = 0; row < numQuickAccessTags; ++row) {
805     QModelIndex index = m_quickAccessTagsModel->index(row, 0);
806     int frameType = index.data(Qt::UserRole).toInt();
807     if (frameType != row) {
808       isStandardFrameOrder = false;
809     }
810     frameTypes.append(frameType);
811     if (m_quickAccessTagsModel->data(
812           index, Qt::CheckStateRole).toInt() == Qt::Checked) {
813       frameMask |= 1ULL << frameType;
814     }
815   }
816   if (isStandardFrameOrder) {
817     frameTypes.clear();
818   }
819   tagCfg.setQuickAccessFrames(frameMask);
820   tagCfg.setQuickAccessFrameOrder(frameTypes);
821   userActionsCfg.setContextMenuCommands(m_commandsTableModel->getCommandList());
822   tagCfg.setCommentName(m_commentNameComboBox->currentText());
823   tagCfg.setPictureNameIndex(m_pictureNameComboBox->currentIndex());
824   tagCfg.setGenreNotNumeric(m_genreNotNumericCheckBox->isChecked());
825   tagCfg.setLowercaseId3RiffChunk(m_lowercaseId3ChunkCheckBox->isChecked());
826   tagCfg.setMarkStandardViolations(m_markStandardViolationsCheckBox->isChecked());
827   tagCfg.setTextEncodingV1Index(m_textEncodingV1ComboBox->currentIndex());
828   tagCfg.setTextEncoding(m_textEncodingComboBox->currentIndex());
829   tagCfg.setId3v2Version(m_id3v2VersionComboBox->itemData(
830         m_id3v2VersionComboBox->currentIndex()).toInt());
831   tagCfg.setTrackNumberDigits(m_trackNumberDigitsSpinBox->value());
832   tagCfg.setMarkOversizedPictures(m_markOversizedPicturesCheckBox->isChecked());
833   tagCfg.setMaximumPictureSize(m_maximumPictureSizeSpinBox->value());
834   tagCfg.setRiffTrackName(m_trackNameComboBox->currentText());
835   networkCfg.setBrowser(m_browserLineEdit->text());
836   guiCfg.setPlayOnDoubleClick(m_playOnDoubleClickCheckBox->isChecked());
837   networkCfg.setUseProxy(m_proxyCheckBox->isChecked());
838   networkCfg.setProxy(m_proxyLineEdit->text());
839   networkCfg.setUseProxyAuthentication(m_proxyAuthenticationCheckBox->isChecked());
840   networkCfg.setProxyUserName(m_proxyUserNameLineEdit->text());
841   networkCfg.setProxyPassword(m_proxyPasswordLineEdit->text());
842 
843   QStringList pluginOrder, disabledPlugins;
844   const int numPlugins = m_enabledMetadataPluginsModel->rowCount();
845   pluginOrder.reserve(numPlugins);
846   for (int row = 0; row < numPlugins; ++row) {
847     QString pluginName =
848         m_enabledMetadataPluginsModel->index(row).data().toString();
849     pluginOrder.append(pluginName);
850     if (m_enabledMetadataPluginsModel->index(row).data(Qt::CheckStateRole)
851         .toInt() != Qt::Checked) {
852       disabledPlugins.append(pluginName); // clazy:exclude=reserve-candidates
853     }
854   }
855   tagCfg.setPluginOrder(pluginOrder);
856   tagCfg.setDisabledPlugins(disabledPlugins);
857 
858   disabledPlugins.clear();
859   for (int row = 0; row < m_enabledPluginsModel->rowCount(); ++row) {
860     if (m_enabledPluginsModel->index(row).data(Qt::CheckStateRole)
861         .toInt() != Qt::Checked) {
862       disabledPlugins.append(
863             m_enabledPluginsModel->index(row).data().toString());
864     }
865   }
866   importCfg.setDisabledPlugins(disabledPlugins);
867 
868   if (QWidget* configDialog = qobject_cast<QWidget*>(parent())) {
869     guiCfg.setConfigWindowGeometry(configDialog->saveGeometry());
870   }
871 }
872