File indexing completed on 2025-02-23 04:34:21

0001 /**
0002  * \file kid3form.cpp
0003  * GUI for kid3.
0004  *
0005  * \b Project: Kid3
0006  * \author Urs Fleisch
0007  * \date 8 Apr 2003
0008  *
0009  * Copyright (C) 2003-2024  Urs Fleisch
0010  *
0011  * This file is part of Kid3.
0012  *
0013  * Kid3 is free software; you can redistribute it and/or modify
0014  * it under the terms of the GNU General Public License as published by
0015  * the Free Software Foundation; either version 2 of the License, or
0016  * (at your option) any later version.
0017  *
0018  * Kid3 is distributed in the hope that it will be useful,
0019  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0020  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0021  * GNU General Public License for more details.
0022  *
0023  * You should have received a copy of the GNU General Public License
0024  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0025  */
0026 
0027 #include "kid3form.h"
0028 #include <QCheckBox>
0029 #include <QPushButton>
0030 #include <QToolButton>
0031 #include <QSpinBox>
0032 #include <QLayout>
0033 #include <QToolTip>
0034 #include <QStackedWidget>
0035 #include <QSplitter>
0036 #include <QDir>
0037 #include <QFrame>
0038 #include <QPixmap>
0039 #include <QComboBox>
0040 #include <QVBoxLayout>
0041 #include <QDropEvent>
0042 #include <QDragEnterEvent>
0043 #include <QLabel>
0044 #include <QGridLayout>
0045 #include <QScrollArea>
0046 #include <QUrl>
0047 #include <QApplication>
0048 #include <QMimeData>
0049 #include <QMenu>
0050 #include <QFileIconProvider>
0051 #include <QStyle>
0052 #include <QHeaderView>
0053 #include <QBuffer>
0054 #include "filesystemmodel.h"
0055 #include "frametable.h"
0056 #include "frametablemodel.h"
0057 #include "trackdata.h"
0058 #include "genres.h"
0059 #include "basemainwindow.h"
0060 #include "filelist.h"
0061 #include "framelist.h"
0062 #include "pictureframe.h"
0063 #include "configurabletreeview.h"
0064 #include "picturelabel.h"
0065 #include "fileconfig.h"
0066 #include "guiconfig.h"
0067 #include "formatconfig.h"
0068 #include "dirproxymodel.h"
0069 #include "fileproxymodel.h"
0070 #include "coretaggedfileiconprovider.h"
0071 #include "icoreplatformtools.h"
0072 #include "kid3application.h"
0073 #include "sectionactions.h"
0074 #ifdef Q_OS_MAC
0075 #include <CoreFoundation/CFURL.h>
0076 #endif
0077 
0078 /** Collapse pixmap, will be allocated in constructor */
0079 QPixmap* Kid3Form::s_collapsePixmap = nullptr;
0080 /** Expand pixmap, will be allocated in constructor */
0081 QPixmap* Kid3Form::s_expandPixmap = nullptr;
0082 
0083 namespace {
0084 
0085 /** picture for collapse pixmap */
0086 const char* const collapse_xpm[] = {
0087   "7 7 3 1",
0088   " \tc None",
0089   ".\tc #FFFFFF",
0090   "+\tc #000000",
0091   ".......",
0092   ".......",
0093   ".......",
0094   ".+++++.",
0095   ".......",
0096   ".......",
0097   "......."
0098 };
0099 
0100 /** picture for expand pixmap */
0101 const char* const expand_xpm[] = {
0102   "7 7 3 1",
0103   " \tc None",
0104   ".\tc #FFFFFF",
0105   "+\tc #000000",
0106   ".......",
0107   "...+...",
0108   "...+...",
0109   ".+++++.",
0110   "...+...",
0111   "...+...",
0112   "......."
0113 };
0114 
0115 
0116 /**
0117  * Event filter for double click on picture label.
0118  */
0119 class PictureDblClickHandler : public QObject {
0120 public:
0121   /**
0122    * Constructor.
0123    * @param app application
0124    */
0125   explicit PictureDblClickHandler(Kid3Application* app)
0126     : QObject(app), m_app(app) {}
0127 
0128   ~PictureDblClickHandler() override = default;
0129 
0130 protected:
0131   /**
0132    * Event filter function, calls Kid3Application::editOrAddPicture().
0133    *
0134    * @param obj watched object
0135    * @param event event for object
0136    *
0137    * @return true if event is filtered.
0138    */
0139   bool eventFilter(QObject* obj, QEvent* event) override;
0140 
0141 private:
0142   Q_DISABLE_COPY(PictureDblClickHandler)
0143 
0144   Kid3Application* m_app;
0145 };
0146 
0147 /**
0148  * Event filter function, calls Kid3Application::editOrAddPicture() on double click.
0149  *
0150  * @param obj watched object
0151  * @param event event for object
0152  *
0153  * @return true if event is filtered.
0154  */
0155 bool PictureDblClickHandler::eventFilter(QObject* obj, QEvent* event)
0156 {
0157   if (event->type() == QEvent::MouseButtonDblClick) {
0158     m_app->editOrAddPicture();
0159     return true;
0160   }
0161   // standard event processing
0162   return QObject::eventFilter(obj, event);
0163 }
0164 
0165 
0166 /**
0167  * File decoration provider for FileSystemModel delegating to QFileIconProvider.
0168  */
0169 class WidgetFileDecorationProvider : public AbstractFileDecorationProvider {
0170 public:
0171   QVariant headerDecoration() const override {
0172     // From QFileSystemModel:
0173     // ### TODO oh man this is ugly and doesn't even work all the way!
0174     // it is still 2 pixels off
0175     QImage pixmap(16, 1, QImage::Format_Mono);
0176     pixmap.fill(0);
0177     pixmap.setAlphaChannel(pixmap.createAlphaMask());
0178     return pixmap;
0179   }
0180 
0181   QVariant computerDecoration() const override {
0182     return m_provider.icon(QFileIconProvider::Computer);
0183   }
0184 
0185   QVariant folderDecoration() const override {
0186     return m_provider.icon(QFileIconProvider::Folder);
0187   }
0188 
0189   QVariant fileDecoration() const override {
0190     return m_provider.icon(QFileIconProvider::File);
0191   }
0192 
0193   QVariant decoration(const QFileInfo& info) const override {
0194     return m_provider.icon(info);
0195   }
0196 
0197   QString type(const QFileInfo& info) const override {
0198     return m_provider.type(info);
0199   }
0200 
0201 private:
0202   QFileIconProvider m_provider;
0203 };
0204 
0205 
0206 /**
0207  * Get the items from a combo box.
0208  *
0209  * @param comboBox combo box
0210  *
0211  * @return item texts from combo box.
0212  */
0213 QStringList getItemsFromComboBox(const QComboBox* comboBox)
0214 {
0215   QStringList lst;
0216   const int numItems = comboBox->count();
0217   lst.reserve(numItems);
0218   for (int i = 0; i < numItems; ++i) {
0219     lst += comboBox->itemText(i);
0220   }
0221   return lst;
0222 }
0223 
0224 /**
0225  * Set items in combo box and add current item if not already existing.
0226  * @param items combo box item
0227  * @param currentItem current item
0228  * @param comboBox combo box to set
0229  */
0230 void setItemsInComboBox(const QStringList& items, const QString& currentItem,
0231                         QComboBox* comboBox)
0232 {
0233   QStringList allItems = items;
0234   int idx = allItems.indexOf(currentItem);
0235   if (idx == -1) {
0236     allItems.append(currentItem);
0237     idx = allItems.size() - 1;
0238   }
0239   // Block signals on combo box while setting contents to avoid
0240   // editTextChanged() signals causing configuration changes.
0241   comboBox->blockSignals(true);
0242   if (!allItems.isEmpty()) {
0243     comboBox->clear();
0244     comboBox->addItems(allItems);
0245   }
0246   comboBox->setCurrentIndex(idx);
0247   comboBox->blockSignals(false);
0248 }
0249 
0250 }
0251 
0252 /**
0253  * Constructs an Id3Form as a child of 'parent', with the
0254  * name 'name' and widget flags set to 'f'.
0255  * @param app application
0256  * @param mainWin main window
0257  * @param parent parent widget
0258  */
0259 Kid3Form::Kid3Form(Kid3Application* app, BaseMainWindowImpl* mainWin,
0260                    QWidget* parent)
0261   : QSplitter(parent), m_pictureLabel(nullptr), m_app(app), m_mainWin(mainWin),
0262     m_iconProvider(new WidgetFileDecorationProvider)
0263 {
0264   setObjectName(QLatin1String("Kid3Form"));
0265 
0266   FOR_ALL_TAGS(tagNr) {
0267     m_tagContext[tagNr] = new Kid3FormTagContext(this, tagNr);
0268     if (tagNr != Frame::Tag_Id3v1) {
0269       m_app->getFrameList(tagNr)->setFrameEditor(m_mainWin);
0270     }
0271   }
0272 
0273   if (!s_collapsePixmap) {
0274     s_collapsePixmap = new QPixmap(const_cast<const char**>(collapse_xpm));
0275   }
0276   if (!s_expandPixmap) {
0277     s_expandPixmap = new QPixmap(const_cast<const char**>(expand_xpm));
0278   }
0279 
0280   setAcceptDrops(true);
0281   setWindowTitle(tr("Kid3"));
0282 
0283   m_leftSideWidget = new QStackedWidget(this);
0284   m_vSplitter = new QSplitter(Qt::Vertical);
0285   m_leftSideWidget->addWidget(m_vSplitter);
0286   m_fileListBox = new FileList(m_vSplitter, m_mainWin);
0287   FileProxyModel* fileProxyModel = m_app->getFileProxyModel();
0288   if (auto fsModel =
0289           qobject_cast<FileSystemModel*>(fileProxyModel->sourceModel())) {
0290     fsModel->setDecorationProvider(m_iconProvider.data());
0291   }
0292   CoreTaggedFileIconProvider* tagIconProvider = fileProxyModel->getIconProvider();
0293   tagIconProvider->setModifiedIcon(
0294         QApplication::style()->standardIcon(QStyle::SP_DriveFDIcon));
0295   int iconHeight = (((fontMetrics().height() - 1) / 16) + 1) * 16;
0296   tagIconProvider->setRequestedSize(QSize(iconHeight, iconHeight));
0297   m_fileListBox->setModel(fileProxyModel);
0298   m_fileListBox->setSelectionModel(m_app->getFileSelectionModel());
0299   m_dirListBox = new ConfigurableTreeView(m_vSplitter);
0300   m_dirListBox->setObjectName(QLatin1String("DirList"));
0301   m_dirListBox->setItemsExpandable(false);
0302   m_dirListBox->setRootIsDecorated(false);
0303   m_dirListBox->setModel(m_app->getDirProxyModel());
0304   m_dirListBox->setSelectionModel(m_app->getDirSelectionModel());
0305   m_dirListBox->setMaxNumColumns(TaggedFileSystemModel::NUM_FILESYSTEM_COLUMNS);
0306   connect(m_fileListBox, &QAbstractItemView::activated,
0307           this, &Kid3Form::fileActivated);
0308   connect(m_dirListBox, &QAbstractItemView::activated, this,
0309       &Kid3Form::dirSelected);
0310   connect(m_fileListBox, &ConfigurableTreeView::parentActivated,
0311           this, &Kid3Form::openParentDirectory);
0312   connect(m_dirListBox, &ConfigurableTreeView::parentActivated,
0313           this, &Kid3Form::openParentDirectory);
0314 
0315   connect(m_app, &Kid3Application::fileRootIndexChanged,
0316           this, &Kid3Form::setFileRootIndex);
0317   connect(m_app, &Kid3Application::dirRootIndexChanged,
0318           this, &Kid3Form::setDirRootIndex);
0319   connect(m_app, &Kid3Application::directoryOpened,
0320           this, &Kid3Form::onFirstDirectoryOpened);
0321 
0322   m_rightHalfVBox = new QWidget;
0323   auto scrollView = new QScrollArea(this);
0324   scrollView->setWidget(m_rightHalfVBox);
0325   scrollView->setWidgetResizable(true);
0326   auto rightHalfLayout = new QVBoxLayout(m_rightHalfVBox);
0327   rightHalfLayout->setSpacing(0);
0328 
0329   m_fileButton = new QToolButton(m_rightHalfVBox);
0330   m_fileButton->setIcon(*s_collapsePixmap);
0331   m_fileButton->setAutoRaise(true);
0332 #ifdef Q_OS_MAC
0333   m_fileButton->setStyleSheet(QLatin1String("border: 0;"));
0334 #endif
0335   connect(m_fileButton, &QAbstractButton::clicked, this, &Kid3Form::showHideFile);
0336   m_fileLabel = new QLabel(tr("F&ile"), m_rightHalfVBox);
0337   auto fileButtonLayout = new QHBoxLayout;
0338   fileButtonLayout->addWidget(m_fileButton);
0339   fileButtonLayout->addWidget(m_fileLabel);
0340   rightHalfLayout->addLayout(fileButtonLayout);
0341 
0342   m_fileWidget = new QWidget(m_rightHalfVBox);
0343   m_fileWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
0344   rightHalfLayout->addWidget(m_fileWidget);
0345   auto fileLayout = new QGridLayout(m_fileWidget);
0346 
0347   m_nameLabel = new QLabel(tr("Name:"), m_fileWidget);
0348   fileLayout->addWidget(m_nameLabel, 0, 0);
0349 
0350   m_nameLineEdit = new QLineEdit(m_fileWidget);
0351   connect(m_nameLineEdit, &QLineEdit::textEdited, this,
0352       &Kid3Form::nameLineEditChanged);
0353   fileLayout->addWidget(m_nameLineEdit, 0, 1, 1, 4);
0354   m_fileLabel->setBuddy(m_nameLineEdit);
0355 
0356   auto formatLabel = new QLabel(tr("Format:") + QChar(0x2191),
0357                                 m_fileWidget);
0358   fileLayout->addWidget(formatLabel, 1, 0);
0359 
0360   m_formatComboBox = new QComboBox(m_fileWidget);
0361   m_formatComboBox->setEditable(true);
0362   m_formatComboBox->setSizeAdjustPolicy(
0363         QComboBox::AdjustToMinimumContentsLengthWithIcon);
0364   m_formatComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
0365   m_formatComboBox->setToolTip(TrackDataFormatReplacer::getToolTip());
0366   connect(m_formatComboBox, &QComboBox::editTextChanged,
0367           this, &Kid3Form::onFormatEditTextChanged);
0368   m_formatFromFilenameComboBox = new QComboBox(m_fileWidget);
0369   m_formatFromFilenameComboBox->setEditable(true);
0370   m_formatFromFilenameComboBox->setSizeAdjustPolicy(
0371         QComboBox::AdjustToMinimumContentsLengthWithIcon);
0372   m_formatFromFilenameComboBox->setSizePolicy(
0373         QSizePolicy::Expanding, QSizePolicy::Fixed);
0374   m_formatFromFilenameComboBox->setToolTip(FrameFormatReplacer::getToolTip());
0375   connect(m_formatFromFilenameComboBox, &QComboBox::editTextChanged,
0376           this, &Kid3Form::onFormatFromFilenameEditTextChanged);
0377 
0378   fileLayout->addWidget(m_formatComboBox, 1, 1);
0379 
0380   setTabOrder(m_fileListBox, m_dirListBox);
0381   setTabOrder(m_dirListBox, m_nameLineEdit);
0382   setTabOrder(m_nameLineEdit, m_formatComboBox);
0383   setTabOrder(m_formatComboBox, m_formatFromFilenameComboBox);
0384 
0385   QWidget* tabWidget = m_formatFromFilenameComboBox;
0386 
0387   auto fromTagLabel = new QLabel(tr("From:"), m_fileWidget);
0388   fileLayout->addWidget(fromTagLabel, 1, 2);
0389   int column = 3;
0390   FOR_ALL_TAGS(tagNr) {
0391     if (tagNr <= Frame::Tag_2) {
0392       QString tagStr = Frame::tagNumberToString(tagNr);
0393       m_fnButton[tagNr] = new QPushButton(tr("Tag %1").arg(tagStr),
0394                                           m_fileWidget);
0395       m_fnButton[tagNr]->setToolTip(tr("Filename from Tag %1").arg(tagStr));
0396       connect(m_fnButton[tagNr], &QAbstractButton::clicked,
0397               m_app->tag(tagNr), &Kid3ApplicationTagContext::getFilenameFromTags);
0398       fileLayout->addWidget(m_fnButton[tagNr], 1, column++);
0399       setTabOrder(tabWidget, m_fnButton[tagNr]);
0400       tabWidget = m_fnButton[tagNr];
0401     } else {
0402       m_fnButton[tagNr] = nullptr;
0403     }
0404   }
0405 
0406   auto formatFromFilenameLabel = new QLabel(tr("Format:") + QChar(0x2193),
0407                                             m_fileWidget);
0408   fileLayout->addWidget(formatFromFilenameLabel, 2, 0);
0409 
0410   fileLayout->addWidget(m_formatFromFilenameComboBox, 2, 1);
0411 
0412   auto toTagLabel = new QLabel(tr("To:"), m_fileWidget);
0413   fileLayout->addWidget(toTagLabel, 2, 2);
0414   column = 3;
0415   FOR_ALL_TAGS(tagNr) {
0416     if (tagNr <= Frame::Tag_2) {
0417       QString tagStr = Frame::tagNumberToString(tagNr);
0418       m_toTagButton[tagNr] =
0419         new QPushButton(tr("Tag %1").arg(tagStr), m_fileWidget);
0420       m_toTagButton[tagNr]->setToolTip(tr("Tag %1 from Filename").arg(tagStr));
0421       connect(m_toTagButton[tagNr], &QAbstractButton::clicked,
0422               m_app->tag(tagNr), &Kid3ApplicationTagContext::getTagsFromFilename);
0423       fileLayout->addWidget(m_toTagButton[tagNr], 2, column++);
0424       setTabOrder(tabWidget, m_toTagButton[tagNr]);
0425       tabWidget = m_toTagButton[tagNr];
0426     } else {
0427       m_toTagButton[tagNr] = nullptr;
0428     }
0429   }
0430 
0431   FOR_ALL_TAGS(tagNr) {
0432     m_tagButton[tagNr] = new QToolButton(m_rightHalfVBox);
0433     m_tagButton[tagNr]->setIcon(*s_collapsePixmap);
0434     m_tagButton[tagNr]->setAutoRaise(true);
0435 #ifdef Q_OS_MAC
0436     m_tagButton[tagNr]->setStyleSheet(QLatin1String("border: 0;"));
0437 #endif
0438     connect(m_tagButton[tagNr], &QAbstractButton::clicked,
0439             tag(tagNr), &Kid3FormTagContext::showHideTag);
0440     m_tagLabel[tagNr] = new QLabel(
0441           tr("Tag &%1").arg(Frame::tagNumberToString(tagNr)), m_rightHalfVBox);
0442     auto tagButtonLayout = new QHBoxLayout;
0443     tagButtonLayout->addWidget(m_tagButton[tagNr]);
0444     tagButtonLayout->addWidget(m_tagLabel[tagNr]);
0445     rightHalfLayout->addLayout(tagButtonLayout);
0446 
0447     m_tagWidget[tagNr] = new QWidget(m_rightHalfVBox);
0448     if (tagNr == Frame::Tag_Id3v1) {
0449       m_tagWidget[tagNr]->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
0450     }
0451     rightHalfLayout->addWidget(m_tagWidget[tagNr], 100);
0452 
0453     auto idHBoxLayout = new QHBoxLayout(m_tagWidget[tagNr]);
0454     FrameTableModel* frameModel = m_app->frameModel(tagNr);
0455     frameModel->setHeadersEmpty(true);
0456     m_frameTable[tagNr] = new FrameTable(
0457           frameModel, m_app->genreModel(tagNr), m_tagWidget[tagNr]);
0458     m_frameTable[tagNr]->setSelectionModel(m_app->getFramesSelectionModel(tagNr));
0459     idHBoxLayout->addWidget(m_frameTable[tagNr], tagNr == Frame::Tag_Id3v1 ? 100 : 0);
0460     m_tagLabel[tagNr]->setBuddy(m_frameTable[tagNr]);
0461 
0462     auto buttonsVBoxLayout = new QVBoxLayout;
0463     idHBoxLayout->addLayout(buttonsVBoxLayout);
0464 
0465     if (tagNr <= Frame::Tag_2) {
0466       Frame::TagNumber otherTagNr = tagNr == Frame::Tag_1
0467           ? Frame::Tag_2
0468           : tagNr == Frame::Tag_2 ? Frame::Tag_1 : Frame::Tag_NumValues;
0469       m_id3PushButton[tagNr] =
0470         new QPushButton(tr("From Tag %1")
0471                         .arg(Frame::tagNumberToString(otherTagNr)),
0472                         m_tagWidget[tagNr]);
0473       connect(m_id3PushButton[tagNr], &QAbstractButton::clicked,
0474               m_app->tag(tagNr), &Kid3ApplicationTagContext::copyToOtherTag);
0475       buttonsVBoxLayout->addWidget(m_id3PushButton[tagNr]);
0476       setTabOrder(tabWidget, m_id3PushButton[tagNr]);
0477 
0478       auto copyPushButton = new QPushButton(tr("Copy"),
0479                                             m_tagWidget[tagNr]);
0480       connect(copyPushButton, &QAbstractButton::clicked,
0481               m_app->tag(tagNr), &Kid3ApplicationTagContext::copyTags);
0482       buttonsVBoxLayout->addWidget(copyPushButton);
0483       setTabOrder(m_id3PushButton[tagNr], copyPushButton);
0484 
0485       auto pastePushButton =
0486         new QPushButton(tr("Paste"), m_tagWidget[tagNr]);
0487       connect(pastePushButton, &QAbstractButton::clicked,
0488               m_app->tag(tagNr), &Kid3ApplicationTagContext::pasteTags);
0489       buttonsVBoxLayout->addWidget(pastePushButton);
0490       setTabOrder(copyPushButton, pastePushButton);
0491       tabWidget = pastePushButton;
0492     } else {
0493       m_id3PushButton[tagNr] = new QPushButton(tr("From"));
0494       auto menu = new QMenu(this);
0495       QAction* action = menu->addAction(tr("Filename"));
0496       connect(action, &QAction::triggered,
0497               m_app->tag(tagNr), &Kid3ApplicationTagContext::getTagsFromFilename);
0498       FOR_ALL_TAGS(fromTagNr) {
0499         if (fromTagNr != tagNr) {
0500           action = menu->addAction(
0501             tr("Tag %1").arg(Frame::tagNumberToString(fromTagNr)));
0502           QByteArray ba;
0503           ba.append(static_cast<char>(fromTagNr));
0504           ba.append(static_cast<char>(tagNr));
0505           action->setData(ba);
0506           connect(action, &QAction::triggered,
0507                   this, &Kid3Form::copyTagsActionData);
0508         }
0509       }
0510       action = menu->addAction(tr("Paste"));
0511       connect(action, &QAction::triggered,
0512               m_app->tag(tagNr), &Kid3ApplicationTagContext::pasteTags);
0513       m_id3PushButton[tagNr]->setMenu(menu);
0514       buttonsVBoxLayout->addWidget(m_id3PushButton[tagNr]);
0515       setTabOrder(tabWidget, m_id3PushButton[tagNr]);
0516 
0517       auto toButton = new QPushButton(tr("To"));
0518       menu = new QMenu(this);
0519       action = menu->addAction(tr("Filename"));
0520       connect(action, &QAction::triggered,
0521               m_app->tag(tagNr), &Kid3ApplicationTagContext::getFilenameFromTags);
0522       FOR_ALL_TAGS(fromTagNr) {
0523         if (fromTagNr != tagNr) {
0524           action = menu->addAction(
0525             tr("Tag %1").arg(Frame::tagNumberToString(fromTagNr)));
0526           QByteArray ba;
0527           ba.append(static_cast<char>(tagNr));
0528           ba.append(static_cast<char>(fromTagNr));
0529           action->setData(ba);
0530           connect(action, &QAction::triggered,
0531                   this, &Kid3Form::copyTagsActionData);
0532         }
0533       }
0534       action = menu->addAction(tr("Copy"));
0535       connect(action, &QAction::triggered,
0536               m_app->tag(tagNr), &Kid3ApplicationTagContext::copyTags);
0537       toButton->setMenu(menu);
0538       buttonsVBoxLayout->addWidget(toButton);
0539       setTabOrder(m_id3PushButton[tagNr], toButton);
0540       tabWidget = toButton;
0541     }
0542 
0543     auto removePushButton =
0544       new QPushButton(tr("Remove"), m_tagWidget[tagNr]);
0545     connect(removePushButton, &QAbstractButton::clicked,
0546             m_app->tag(tagNr), &Kid3ApplicationTagContext::removeTags);
0547     buttonsVBoxLayout->addWidget(removePushButton);
0548     setTabOrder(tabWidget, removePushButton);
0549     tabWidget = removePushButton;
0550 
0551     if (tagNr != Frame::Tag_Id3v1) {
0552       auto frameLine = new QFrame;
0553       frameLine->setFrameShape(QFrame::HLine);
0554       frameLine->setFrameShadow(QFrame::Sunken);
0555       buttonsVBoxLayout->addWidget(frameLine);
0556 
0557       auto editFramesPushButton =
0558         new QPushButton(tr("Edit..."), m_tagWidget[tagNr]);
0559       connect(editFramesPushButton, &QAbstractButton::clicked,
0560               m_app->tag(tagNr), &Kid3ApplicationTagContext::editFrame);
0561       buttonsVBoxLayout->addWidget(editFramesPushButton);
0562       setTabOrder(tabWidget, editFramesPushButton);
0563       auto framesAddPushButton =
0564         new QPushButton(tr("Add..."), m_tagWidget[tagNr]);
0565       connect(framesAddPushButton, &QAbstractButton::clicked,
0566               m_app->tag(tagNr), &Kid3ApplicationTagContext::addFrame);
0567       buttonsVBoxLayout->addWidget(framesAddPushButton);
0568       setTabOrder(editFramesPushButton, framesAddPushButton);
0569       auto deleteFramesPushButton =
0570         new QPushButton(tr("Delete"), m_tagWidget[tagNr]);
0571       connect(deleteFramesPushButton, &QAbstractButton::clicked,
0572               m_app->tag(tagNr), &Kid3ApplicationTagContext::deleteFrame);
0573       buttonsVBoxLayout->addWidget(deleteFramesPushButton);
0574       setTabOrder(framesAddPushButton, deleteFramesPushButton);
0575       tabWidget = deleteFramesPushButton;
0576     }
0577     if (tagNr == Frame::Tag_Picture) {
0578       m_pictureLabel = new PictureLabel(this);
0579       m_pictureLabel->installEventFilter(new PictureDblClickHandler(m_app));
0580       buttonsVBoxLayout->addWidget(m_pictureLabel);
0581     }
0582 
0583     buttonsVBoxLayout->addItem(
0584       new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding));
0585   }
0586 
0587   rightHalfLayout->insertStretch(-1);
0588 
0589   FOR_ALL_TAGS(tagNr) {
0590     setTabOrder(tabWidget, m_frameTable[tagNr]);
0591     tabWidget = m_frameTable[tagNr];
0592   }
0593 
0594   auto sectionActions = new SectionActions(SectionActions::Navigation,
0595                                            m_fileListBox);
0596   m_sectionActions.append(sectionActions);
0597   connect(sectionActions->previousSectionAction(), &QAction::triggered,
0598           this, [this] { setFocusPreviousTag(Frame::Tag_NumValues); });
0599   connect(sectionActions->nextSectionAction(), &QAction::triggered,
0600           this, &Kid3Form::setFocusDirList);
0601 
0602   sectionActions = new SectionActions(SectionActions::Navigation, m_dirListBox);
0603   m_sectionActions.append(sectionActions);
0604   connect(sectionActions->previousSectionAction(), &QAction::triggered,
0605           this, &Kid3Form::setFocusFileList);
0606   connect(sectionActions->nextSectionAction(), &QAction::triggered,
0607           this, &Kid3Form::setFocusFilename);
0608 
0609   sectionActions = new SectionActions(SectionActions::Navigation |
0610                                       SectionActions::Transfer,
0611                                       m_fileWidget);
0612   m_sectionActions.append(sectionActions);
0613   connect(sectionActions->previousSectionAction(), &QAction::triggered,
0614           this, &Kid3Form::setFocusDirList);
0615   connect(sectionActions->nextSectionAction(), &QAction::triggered,
0616           this, [this] { setFocusNextTag(Frame::Tag_NumValues); });
0617   connect(sectionActions->transferAction(), &QAction::triggered,
0618     m_app->tag(Frame::Tag_2), &Kid3ApplicationTagContext::getFilenameFromTags);
0619 
0620   FOR_ALL_TAGS(tagNr) {
0621     sectionActions = new SectionActions(
0622           tagNr == Frame::Tag_1 ? SectionActions::Navigation |
0623                                   SectionActions::Transfer |
0624                                   SectionActions::EditSection
0625                                 : SectionActions::Navigation |
0626                                   SectionActions::Transfer |
0627                                   SectionActions::EditSection |
0628                                   SectionActions::EditElement,
0629           m_frameTable[tagNr]);
0630     m_sectionActions.append(sectionActions);
0631     connect(sectionActions->previousSectionAction(), &QAction::triggered,
0632             this, [this, tagNr] {
0633       setFocusPreviousTag(tagNr);
0634     });
0635     connect(sectionActions->nextSectionAction(), &QAction::triggered,
0636             this, [this, tagNr] {
0637       setFocusNextTag(tagNr);
0638     });
0639 
0640     connect(sectionActions->copyAction(), &QAction::triggered,
0641             m_app->tag(tagNr), &Kid3ApplicationTagContext::copyTags);
0642     connect(sectionActions->pasteAction(), &QAction::triggered,
0643             m_app->tag(tagNr), &Kid3ApplicationTagContext::pasteTags);
0644     connect(sectionActions->removeAction(), &QAction::triggered,
0645             this, [this, tagNr] {
0646       m_app->tag(tagNr)->removeTags();
0647       setFocusTag(tagNr);
0648     });
0649 
0650     QByteArray ba;
0651     ba.append(static_cast<char>(tagNr == Frame::Tag_2 ? Frame::Tag_1
0652                                                       : Frame::Tag_2));
0653     ba.append(static_cast<char>(tagNr));
0654     sectionActions->transferAction()->setData(ba);
0655     connect(sectionActions->transferAction(), &QAction::triggered,
0656             this, &Kid3Form::copyTagsActionData);
0657 
0658     if (tagNr != Frame::Tag_1) {
0659       connect(sectionActions->editAction(), &QAction::triggered,
0660               m_app->tag(tagNr), &Kid3ApplicationTagContext::editFrame);
0661       connect(sectionActions->addAction(), &QAction::triggered,
0662               m_app->tag(tagNr), &Kid3ApplicationTagContext::addFrame);
0663       connect(sectionActions->deleteAction(), &QAction::triggered,
0664               m_app->tag(tagNr), &Kid3ApplicationTagContext::deleteFrame);
0665     }
0666   }
0667 }
0668 
0669 /**
0670  * Destructor.
0671  */
0672 Kid3Form::~Kid3Form()
0673 {
0674   m_app->removeFrameEditor(m_mainWin);
0675 }
0676 
0677 /**
0678  * Set keyboard shortcuts for section actions.
0679  * @param map map of action names to key sequences
0680  */
0681 void Kid3Form::setSectionActionShortcuts(const QMap<QString, QKeySequence>& map)
0682 {
0683   for (SectionActions* sectionActions : m_sectionActions) {
0684     sectionActions->setShortcuts(map);
0685   }
0686   m_fileListBox->setShortcuts(map);
0687   m_dirListBox->setShortcuts(map);
0688 }
0689 
0690 /**
0691  * Handle event when mouse is moved while dragging.
0692  *
0693  * @param ev drag event.
0694  */
0695 void Kid3Form::dragMoveEvent(QDragMoveEvent* ev)
0696 {
0697   if (ev->mimeData()->hasFormat(QLatin1String("text/uri-list")) ||
0698       ev->mimeData()->hasImage()) {
0699     ev->acceptProposedAction();
0700   } else {
0701     ev->ignore();
0702   }
0703 }
0704 
0705 /**
0706  * Accept drag.
0707  *
0708  * @param ev drag event.
0709  */
0710 void Kid3Form::dragEnterEvent(QDragEnterEvent* ev)
0711 {
0712   Kid3Form::dragMoveEvent(ev);
0713 }
0714 
0715 /**
0716  * Handle event when mouse leaves widget while dragging.
0717  *
0718  * @param ev drag event.
0719  */
0720 void Kid3Form::dragLeaveEvent(QDragLeaveEvent* ev)
0721 {
0722   ev->accept();
0723 }
0724 
0725 /**
0726  * Handle drop event.
0727  *
0728  * @param ev drop event.
0729  */
0730 void Kid3Form::dropEvent(QDropEvent* ev)
0731 {
0732   if (ev->mimeData()->hasImage()) {
0733     auto image = qvariant_cast<QImage>(ev->mimeData()->imageData());
0734     ev->acceptProposedAction();
0735     if (!image.isNull()) {
0736       QByteArray ba;
0737       QBuffer buffer(&ba);
0738       buffer.open(QIODevice::WriteOnly);
0739       image.save(&buffer, "JPG");
0740       PictureFrame frame;
0741       if (PictureFrame::setData(frame, ba)) {
0742         m_app->dropImage(&frame);
0743       }
0744     }
0745   } else if (ev->mimeData()->hasFormat(QLatin1String("text/uri-list"))) {
0746     QList<QUrl> urls = ev->mimeData()->urls();
0747     ev->acceptProposedAction();
0748     m_app->dropUrls(urls, ev->source() != nullptr);
0749   } else {
0750     ev->ignore();
0751   }
0752 }
0753 
0754 /**
0755  * Filename line edit is changed.
0756  * @param txt contents of line edit
0757  */
0758 void Kid3Form::nameLineEditChanged(const QString& txt)
0759 {
0760   formatLineEdit(m_nameLineEdit, txt, &FilenameFormatConfig::instance());
0761 }
0762 
0763 /**
0764  * Mark the filename as changed.
0765  * @param en true to mark as changed
0766  */
0767 void Kid3Form::markChangedFilename(bool en)
0768 {
0769   if (CoreTaggedFileIconProvider* colorProvider;
0770       en &&
0771       (colorProvider = m_app->getPlatformTools()->iconProvider()) != nullptr) {
0772     QPalette changedPalette(m_nameLabel->palette());
0773     changedPalette.setBrush(QPalette::Active, QPalette::Window,
0774                             colorProvider->colorForContext(ColorContext::Marked)
0775                             .value<QBrush>());
0776     m_nameLabel->setPalette(changedPalette);
0777   } else {
0778     m_nameLabel->setPalette(QPalette());
0779   }
0780   m_nameLabel->setAutoFillBackground(en);
0781 }
0782 
0783 /**
0784  * Format string within line edit.
0785  *
0786  * @param le   line edit
0787  * @param txt  text in line edit
0788  * @param fcfg format configuration
0789  */
0790 void Kid3Form::formatLineEdit(QLineEdit* le, const QString& txt,
0791                const FormatConfig* fcfg)
0792 {
0793   if (fcfg->formatWhileEditing()) {
0794     QString str(txt);
0795     fcfg->formatString(str);
0796     if (str != txt) {
0797       int curPos = le->cursorPosition();
0798       le->setText(str);
0799       le->setCursorPosition(curPos + str.length() - txt.length());
0800     }
0801   }
0802 }
0803 
0804 /**
0805  * Directory list box directory selected.
0806  *
0807  * @param index selected item
0808  */
0809 void Kid3Form::dirSelected(const QModelIndex& index)
0810 {
0811   if (QString dirPath = index.data(FileSystemModel::FilePathRole).toString();
0812       !dirPath.isEmpty()) {
0813     m_app->setDirUpIndex(
0814         dirPath.endsWith(QLatin1String("..")) ? index.parent() : QModelIndex());
0815     m_mainWin->updateCurrentSelection();
0816     m_mainWin->confirmedOpenDirectory({dirPath});
0817   }
0818 }
0819 
0820 /**
0821  * File list box item activated.
0822  *
0823  * @param index selected item
0824  */
0825 void Kid3Form::fileActivated(const QModelIndex& index)
0826 {
0827   if (auto fileProxyModel =
0828       qobject_cast<const FileProxyModel*>(index.model())) {
0829     if (fileProxyModel->isDir(index)) {
0830       if (QString dirPath = fileProxyModel->filePath(index);
0831           !dirPath.isEmpty()) {
0832         m_mainWin->updateCurrentSelection();
0833         m_mainWin->confirmedOpenDirectory({dirPath});
0834       }
0835     }
0836   }
0837 }
0838 
0839 /**
0840  * Open the parent directory of a model index.
0841  *
0842  * @param index current root index of item view
0843  */
0844 void Kid3Form::openParentDirectory(const QModelIndex& index)
0845 {
0846   if (index.isValid()) {
0847     if (QDir dir(index.data(FileSystemModel::FilePathRole).toString());
0848         dir.cdUp()) {
0849       QString dirPath = dir.absolutePath();
0850       if (m_dirListBox && index.model() == m_dirListBox->model()) {
0851         m_app->setDirUpIndex(index);
0852       }
0853       m_mainWin->updateCurrentSelection();
0854       m_mainWin->confirmedOpenDirectory({dirPath});
0855     }
0856   }
0857 }
0858 
0859 /**
0860  * Enable or disable controls requiring tags.
0861  * @param tagNr tag number
0862  * @param enable true to enable
0863  */
0864 void Kid3Form::enableControls(Frame::TagNumber tagNr, bool enable)
0865 {
0866   if (m_fnButton[tagNr]) {
0867     m_fnButton[tagNr]->setEnabled(enable);
0868   }
0869   if (m_toTagButton[tagNr]) {
0870     m_toTagButton[tagNr]->setEnabled(enable);
0871   }
0872   if (Frame::TagNumber otherTagNr = tagNr == Frame::Tag_1
0873         ? Frame::Tag_2
0874         : tagNr == Frame::Tag_2 ? Frame::Tag_1 : Frame::Tag_NumValues;
0875       otherTagNr < Frame::Tag_NumValues) {
0876     m_id3PushButton[otherTagNr]->setEnabled(enable);
0877   }
0878   m_tagWidget[tagNr]->setEnabled(enable);
0879   if (tagNr > Frame::Tag_2) {
0880     m_tagButton[tagNr]->setVisible(enable);
0881     m_tagLabel[tagNr]->setVisible(enable);
0882   }
0883 }
0884 
0885 /**
0886  * Display the tag format.
0887  * @param tagNr tag number
0888  * @param str string describing format, e.g. "ID3v1.1"
0889  */
0890 void Kid3Form::setTagFormat(Frame::TagNumber tagNr, const QString& str)
0891 {
0892   QString txt = tr("Tag &%1").arg(Frame::tagNumberToString(tagNr));
0893   if (!str.isEmpty()) {
0894     txt += QLatin1String(": ");
0895     txt += str;
0896   }
0897   m_tagLabel[tagNr]->setText(txt);
0898 }
0899 
0900 /**
0901  * Adjust the size of the right half box.
0902  */
0903 void Kid3Form::adjustRightHalfBoxSize()
0904 {
0905   m_rightHalfVBox->adjustSize();
0906 }
0907 
0908 /**
0909  * Hide or show file controls.
0910  *
0911  * @param hide true to hide, false to show
0912  */
0913 void Kid3Form::hideFile(bool hide)
0914 {
0915   if (hide) {
0916     m_fileWidget->hide();
0917     m_fileButton->setIcon(*s_expandPixmap);
0918   } else {
0919     m_fileWidget->show();
0920     m_fileButton->setIcon(*s_collapsePixmap);
0921   }
0922 }
0923 
0924 /**
0925  * Hide or show tag controls.
0926  * @param tagNr tag number
0927  * @param hide true to hide, false to show
0928  */
0929 void Kid3Form::hideTag(Frame::TagNumber tagNr, bool hide)
0930 {
0931   if (hide) {
0932     m_tagWidget[tagNr]->hide();
0933     m_tagButton[tagNr]->setIcon(*s_expandPixmap);
0934   } else {
0935     m_tagWidget[tagNr]->show();
0936     m_tagButton[tagNr]->setIcon(*s_collapsePixmap);
0937   }
0938 }
0939 
0940 /**
0941  * Toggle visibility of file controls.
0942  */
0943 void Kid3Form::showHideFile()
0944 {
0945   hideFile(!m_fileWidget->isHidden());
0946 }
0947 
0948 /**
0949  * Toggle visibility of tag controls.
0950  * @param tagNr tag number
0951  */
0952 void Kid3Form::showHideTag(Frame::TagNumber tagNr)
0953 {
0954   hideTag(tagNr, !m_tagWidget[tagNr]->isHidden());
0955 }
0956 
0957 /**
0958  * Set format text configuration when format edit text is changed.
0959  * @param text format text
0960  */
0961 void Kid3Form::onFormatEditTextChanged(const QString& text)
0962 {
0963   FileConfig::instance().setToFilenameFormat(text);
0964 }
0965 
0966 /**
0967  * Set format from filename text configuration when edit text is changed.
0968  * @param text format text
0969  */
0970 void Kid3Form::onFormatFromFilenameEditTextChanged(const QString& text)
0971 {
0972   FileConfig::instance().setFromFilenameFormat(text);
0973 }
0974 
0975 /**
0976  * Update sorting after directory is opened for the first time.
0977  * The sort order of the file list is not correct if it is not explicitly
0978  * sorted the first time.
0979  */
0980 void Kid3Form::onFirstDirectoryOpened()
0981 {
0982   // Only call this once.
0983   disconnect(m_app, &Kid3Application::directoryOpened,
0984              this, &Kid3Form::onFirstDirectoryOpened);
0985   const GuiConfig& guiCfg = GuiConfig::instance();
0986   m_app->getFileProxyModel()->sort(guiCfg.fileListSortColumn(),
0987                                    guiCfg.fileListSortOrder());
0988   int firstFileSectionSize =
0989       m_fileListBox->initializeColumnWidthsFromContents(-1);
0990   m_fileListBox->scrollTo(m_fileListBox->currentIndex());
0991   // The directory column often only contains "." and "..", which results
0992   // in a small size. Make it at least as wide as the corresponding
0993   // file list column.
0994   m_dirListBox->initializeColumnWidthsFromContents(firstFileSectionSize);
0995 }
0996 
0997 /**
0998  * Hide or show picture.
0999  *
1000  * @param hide true to hide, false to show
1001  */
1002 void Kid3Form::hidePicture(bool hide)
1003 {
1004   if (!m_pictureLabel)
1005     return;
1006 
1007   if (hide) {
1008     m_pictureLabel->hide();
1009   } else {
1010     m_pictureLabel->show();
1011   }
1012 }
1013 
1014 /**
1015  * Set focus on filename controls.
1016  */
1017 void Kid3Form::setFocusFilename()
1018 {
1019   if (m_fileWidget->isHidden()) {
1020     hideFile(false);
1021   }
1022   if (isFilenameEditEnabled()) {
1023     m_nameLineEdit->setFocus();
1024   } else {
1025     m_formatComboBox->setFocus();
1026   }
1027 }
1028 
1029 /**
1030  * Set focus on tag controls.
1031  * @param tagNr tag number
1032  */
1033 void Kid3Form::setFocusTag(Frame::TagNumber tagNr)
1034 {
1035   if (m_tagWidget[tagNr]->isHidden()) {
1036     hideTag(tagNr, false);
1037   }
1038   m_frameTable[tagNr]->setFocus();
1039 }
1040 
1041 /**
1042  * Set focus on next tag controls.
1043  * @param tagNr current tag, Frame::Tag_NumValues if not on tag
1044  */
1045 void Kid3Form::setFocusNextTag(Frame::TagNumber tagNr)
1046 {
1047   for (int i = tagNr == Frame::Tag_NumValues ? Frame::Tag_1
1048                                              : tagNr + 1; ; ++i) {
1049     if (i >= Frame::Tag_NumValues) {
1050       setFocusFileList();
1051       break;
1052     }
1053     if (i >= Frame::Tag_1) {
1054       if (m_tagWidget[i]->isEnabled()) {
1055         setFocusTag(static_cast<Frame::TagNumber>(i));
1056         break;
1057       }
1058     } else {
1059       break;
1060     }
1061   }
1062 }
1063 
1064 /**
1065  * Set focus on previous tag controls.
1066  * @param tagNr current tag, Frame::Tag_NumValues if not on tag
1067  */
1068 void Kid3Form::setFocusPreviousTag(Frame::TagNumber tagNr)
1069 {
1070   for (int i = tagNr - 1; ; --i) {
1071     if (i < Frame::Tag_1) {
1072       setFocusFilename();
1073       break;
1074     }
1075     if (i < Frame::Tag_NumValues) {
1076       if (m_tagWidget[i]->isEnabled()) {
1077         setFocusTag(static_cast<Frame::TagNumber>(i));
1078         break;
1079       }
1080     } else {
1081       break;
1082     }
1083   }
1084 }
1085 
1086 /**
1087  * Set focus on file list.
1088  */
1089 void Kid3Form::setFocusFileList()
1090 {
1091   m_fileListBox->setFocus();
1092 }
1093 
1094 /**
1095  * Set focus on directory list.
1096  */
1097 void Kid3Form::setFocusDirList()
1098 {
1099   m_dirListBox->setFocus();
1100 }
1101 
1102 /**
1103  * Save the local settings to the configuration.
1104  */
1105 void Kid3Form::saveConfig()
1106 {
1107   GuiConfig& guiCfg = GuiConfig::instance();
1108   FileConfig& fileCfg = FileConfig::instance();
1109   guiCfg.setSplitterSizes(sizes());
1110   guiCfg.setVSplitterSizes(m_vSplitter->sizes());
1111   fileCfg.setToFilenameFormat(m_formatComboBox->currentText());
1112   fileCfg.setToFilenameFormats(getItemsFromComboBox(m_formatComboBox));
1113   fileCfg.setFromFilenameFormat(m_formatFromFilenameComboBox->currentText());
1114   fileCfg.setFromFilenameFormats(getItemsFromComboBox(m_formatFromFilenameComboBox));
1115   if (!guiCfg.autoHideTags()) {
1116     guiCfg.setHideFile(m_fileWidget->isHidden());
1117     FOR_ALL_TAGS(tagNr) {
1118       guiCfg.setHideTag(tagNr, m_tagWidget[tagNr]->isHidden());
1119     }
1120   }
1121   saveFileAndDirListConfig();
1122 }
1123 
1124 /**
1125  * Read the local settings from the configuration.
1126  */
1127 void Kid3Form::readConfig()
1128 {
1129   const GuiConfig& guiCfg = GuiConfig::instance();
1130   const FileConfig& fileCfg = FileConfig::instance();
1131   if (!guiCfg.splitterSizes().isEmpty()) {
1132     setSizes(guiCfg.splitterSizes());
1133   } else {
1134     setSizes({307, 601});
1135   }
1136   if (!guiCfg.vSplitterSizes().isEmpty()) {
1137     m_vSplitter->setSizes(guiCfg.vSplitterSizes());
1138   } else {
1139     m_vSplitter->setSizes({451, 109});
1140   }
1141 
1142   setToFilenameFormats();
1143   setFromFilenameFormats();
1144   connect(&fileCfg, &FileConfig::toFilenameFormatsChanged,
1145           this, &Kid3Form::setToFilenameFormats, Qt::UniqueConnection);
1146   connect(&fileCfg, &FileConfig::fromFilenameFormatsChanged,
1147           this, &Kid3Form::setFromFilenameFormats, Qt::UniqueConnection);
1148 
1149   if (!guiCfg.autoHideTags()) {
1150     hideFile(guiCfg.hideFile());
1151     FOR_ALL_TAGS(tagNr) {
1152       hideTag(tagNr, guiCfg.hideTag(tagNr));
1153     }
1154   }
1155   hidePicture(guiCfg.hidePicture());
1156   readFileAndDirListConfig();
1157 }
1158 
1159 /**
1160  * Save file and directory list columns to the configuration.
1161  */
1162 void Kid3Form::saveFileAndDirListConfig()
1163 {
1164   GuiConfig& guiCfg = GuiConfig::instance();
1165   int column;
1166   Qt::SortOrder order;
1167   m_fileListBox->getSortByColumn(column, order);
1168   guiCfg.setFileListSortColumn(column);
1169   guiCfg.setFileListSortOrder(order);
1170   guiCfg.setFileListVisibleColumns(m_fileListBox->getVisibleColumns());
1171   bool customColumWidthsEnabled = m_fileListBox->areCustomColumnWidthsEnabled();
1172   guiCfg.setFileListCustomColumnWidthsEnabled(customColumWidthsEnabled);
1173   if (customColumWidthsEnabled) {
1174     guiCfg.setFileListColumnWidths(m_fileListBox->getColumnWidths());
1175   }
1176   m_dirListBox->getSortByColumn(column, order);
1177   guiCfg.setDirListSortColumn(column);
1178   guiCfg.setDirListSortOrder(order);
1179   guiCfg.setDirListVisibleColumns(m_dirListBox->getVisibleColumns());
1180   customColumWidthsEnabled = m_dirListBox->areCustomColumnWidthsEnabled();
1181   guiCfg.setDirListCustomColumnWidthsEnabled(customColumWidthsEnabled);
1182   if (customColumWidthsEnabled) {
1183     guiCfg.setDirListColumnWidths(m_dirListBox->getColumnWidths());
1184   }
1185 }
1186 
1187 /**
1188  * Set file and directory list columns from the configuration.
1189  */
1190 void Kid3Form::readFileAndDirListConfig()
1191 {
1192   const GuiConfig& guiCfg = GuiConfig::instance();
1193   m_fileListBox->sortByColumn(guiCfg.fileListSortColumn(),
1194                               guiCfg.fileListSortOrder());
1195   m_fileListBox->setVisibleColumns(guiCfg.fileListVisibleColumns());
1196   m_fileListBox->setColumnWidths(guiCfg.fileListColumnWidths());
1197   m_fileListBox->setCustomColumnWidthsEnabled(
1198         guiCfg.fileListCustomColumnWidthsEnabled());
1199   m_dirListBox->sortByColumn(guiCfg.dirListSortColumn(),
1200                              guiCfg.dirListSortOrder());
1201   m_dirListBox->setVisibleColumns(guiCfg.dirListVisibleColumns());
1202   m_dirListBox->setColumnWidths(guiCfg.dirListColumnWidths());
1203   m_dirListBox->setCustomColumnWidthsEnabled(
1204         guiCfg.dirListCustomColumnWidthsEnabled());
1205 }
1206 
1207 /**
1208  * Set items of "Format <arrow up>" combo box from file configuration.
1209  */
1210 void Kid3Form::setToFilenameFormats()
1211 {
1212   const FileConfig& fileCfg = FileConfig::instance();
1213   setItemsInComboBox(fileCfg.toFilenameFormats(), fileCfg.toFilenameFormat(),
1214                      m_formatComboBox);
1215 }
1216 
1217 /**
1218  * Set items of "Format <arrow down>" combo box from file configuration.
1219  */
1220 void Kid3Form::setFromFilenameFormats()
1221 {
1222   const FileConfig& fileCfg = FileConfig::instance();
1223   setItemsInComboBox(fileCfg.fromFilenameFormats(), fileCfg.fromFilenameFormat(),
1224                      m_formatFromFilenameComboBox);
1225 }
1226 
1227 /**
1228  * Set preview picture data.
1229  * @param data picture data, empty if no picture is available
1230  */
1231 void Kid3Form::setPictureData(const QByteArray& data)
1232 {
1233   if (m_pictureLabel) {
1234     m_pictureLabel->setData(data);
1235   }
1236 }
1237 
1238 /**
1239  * Set details info text.
1240  *
1241  * @param str detail information summary as string
1242  */
1243 void Kid3Form::setDetailInfo(const QString& str)
1244 {
1245   m_fileLabel->setText(!str.isEmpty()
1246                        ? tr("F&ile") + QLatin1String(": ") + str
1247                        : tr("F&ile"));
1248 }
1249 
1250 /**
1251  * Select all files.
1252  */
1253 void Kid3Form::selectAllFiles()
1254 {
1255   m_fileListBox->selectAll();
1256 }
1257 
1258 /**
1259  * Deselect all files.
1260  */
1261 void Kid3Form::deselectAllFiles()
1262 {
1263   m_fileListBox->clearSelection();
1264 }
1265 
1266 /**
1267  * Set the next file as the current file.
1268  *
1269  * @param select true to select the file
1270  * @param onlyTaggedFiles only consider tagged files
1271  *
1272  * @return true if a next file exists.
1273  */
1274 bool Kid3Form::nextFile(bool select, bool onlyTaggedFiles)
1275 {
1276   FrameTable* editingFrameTable = getEditingFrameTable();
1277   bool ok = m_app->nextFile(select, onlyTaggedFiles);
1278   if (ok && editingFrameTable) {
1279     editingFrameTable->edit(editingFrameTable->currentIndex());
1280   }
1281   return ok;
1282 }
1283 
1284 /**
1285  * Set the previous file as the current file.
1286  *
1287  * @param select true to select the file
1288  * @param onlyTaggedFiles only consider tagged files
1289  *
1290  * @return true if a previous file exists.
1291  */
1292 bool Kid3Form::previousFile(bool select, bool onlyTaggedFiles)
1293 {
1294   FrameTable* editingFrameTable = getEditingFrameTable();
1295   bool ok = m_app->previousFile(select, onlyTaggedFiles);
1296   if (ok && editingFrameTable) {
1297     editingFrameTable->edit(editingFrameTable->currentIndex());
1298   }
1299   return ok;
1300 }
1301 
1302 /**
1303  * Select the next tagged file as the current file.
1304  * Same as nextFile() with default arguments, provided for functor-based
1305  * connections.
1306  * @return true if a next file exists.
1307  */
1308 bool Kid3Form::selectNextTaggedFile()
1309 {
1310   return nextFile(true, true);
1311 }
1312 
1313 /**
1314  * Select the previous tagged file as the current file.
1315  * Same as previousFile() with default arguments, provided for functor-based
1316  * connections.
1317  * @return true if a previous file exists.
1318  */
1319 bool Kid3Form::selectPreviousTaggedFile()
1320 {
1321   return previousFile(true, true);
1322 }
1323 
1324 /**
1325  * Get frame table which is currently in editing state.
1326  * The returned frame table can be used to restore the editing state after
1327  * changing the current file.
1328  * @return frame table which is in editing state, 0 if none.
1329  */
1330 FrameTable* Kid3Form::getEditingFrameTable() const
1331 {
1332   if (QWidget* focusWidget = QApplication::focusWidget()) {
1333     FOR_ALL_TAGS(tagNr) {
1334       if (m_frameTable[tagNr]->getCurrentEditor() == focusWidget) {
1335         return m_frameTable[tagNr];
1336       }
1337     }
1338   }
1339   return nullptr;
1340 }
1341 
1342 /**
1343  * Set the root index of the file list.
1344  *
1345  * @param index root index of directory in file proxy model
1346  */
1347 void Kid3Form::setFileRootIndex(const QModelIndex& index)
1348 {
1349   if (index.isValid()) {
1350     m_fileListBox->setRootIndex(index);
1351     m_fileListBox->scrollTo(m_fileListBox->currentIndex());
1352   }
1353 }
1354 
1355 /**
1356  * Set the root index of the directory list.
1357  *
1358  * @param index root index of directory in directory model
1359  */
1360 void Kid3Form::setDirRootIndex(const QModelIndex& index)
1361 {
1362   if (index.isValid()) {
1363     m_dirListBox->setRootIndex(index);
1364   }
1365 }
1366 
1367 /**
1368  * Set a widget to be displayed at the left side instead of the file lists.
1369  * @param widget widget to be shown at the left side
1370  */
1371 void Kid3Form::setLeftSideWidget(QWidget* widget)
1372 {
1373   int idx = m_leftSideWidget->addWidget(widget);
1374   m_leftSideWidget->setCurrentIndex(idx);
1375 }
1376 
1377 /**
1378  * Remove widget set with setLeftSideWidget().
1379  *
1380  * The widget will not be deleted.
1381  *
1382  * @param widget widget to be removed
1383  */
1384 void Kid3Form::removeLeftSideWidget(QWidget* widget)
1385 {
1386   m_leftSideWidget->removeWidget(widget);
1387 }
1388 
1389 /**
1390  * Copy tags using QAction::data().
1391  * The source and destination tag numbers are taken from the first two bytes
1392  * in QAction::data().toByteArray() if the sender() is a QAction.
1393  */
1394 void Kid3Form::copyTagsActionData()
1395 {
1396   if (auto action = qobject_cast<QAction*>(sender())) {
1397     if (QByteArray ba = action->data().toByteArray(); ba.size() == 2) {
1398       Frame::TagNumber srcTagNr = Frame::tagNumberCast(ba.at(0));
1399       Frame::TagNumber dstTagNr = Frame::tagNumberCast(ba.at(1));
1400       if (srcTagNr != Frame::Tag_NumValues &&
1401           dstTagNr != Frame::Tag_NumValues) {
1402         m_app->copyTag(srcTagNr, dstTagNr);
1403       }
1404     }
1405   }
1406 }