1 /**
2  * \file kid3form.cpp
3  * GUI for kid3.
4  *
5  * \b Project: Kid3
6  * \author Urs Fleisch
7  * \date 8 Apr 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 "kid3form.h"
28 #include <QCheckBox>
29 #include <QPushButton>
30 #include <QToolButton>
31 #include <QSpinBox>
32 #include <QLayout>
33 #include <QToolTip>
34 #include <QStackedWidget>
35 #include <QSplitter>
36 #include <QDir>
37 #include <QFrame>
38 #include <QPixmap>
39 #include <QComboBox>
40 #include <QVBoxLayout>
41 #include <QDropEvent>
42 #include <QDragEnterEvent>
43 #include <QLabel>
44 #include <QGridLayout>
45 #include <QScrollArea>
46 #include <QUrl>
47 #include <QApplication>
48 #include <QMimeData>
49 #include <QMenu>
50 #include <QFileIconProvider>
51 #include <QStyle>
52 #include <QHeaderView>
53 #include <QBuffer>
54 #include "filesystemmodel.h"
55 #include "frametable.h"
56 #include "frametablemodel.h"
57 #include "trackdata.h"
58 #include "genres.h"
59 #include "basemainwindow.h"
60 #include "filelist.h"
61 #include "framelist.h"
62 #include "pictureframe.h"
63 #include "configurabletreeview.h"
64 #include "picturelabel.h"
65 #include "fileconfig.h"
66 #include "guiconfig.h"
67 #include "formatconfig.h"
68 #include "dirproxymodel.h"
69 #include "fileproxymodel.h"
70 #include "coretaggedfileiconprovider.h"
71 #include "icoreplatformtools.h"
72 #include "kid3application.h"
73 #include "sectionactions.h"
74 #ifdef Q_OS_MAC
75 #include <CoreFoundation/CFURL.h>
76 #endif
77 
78 /** Collapse pixmap, will be allocated in constructor */
79 QPixmap* Kid3Form::s_collapsePixmap = nullptr;
80 /** Expand pixmap, will be allocated in constructor */
81 QPixmap* Kid3Form::s_expandPixmap = nullptr;
82 
83 namespace {
84 
85 /** picture for collapse pixmap */
86 const char* const collapse_xpm[] = {
87   "7 7 3 1",
88   " \tc None",
89   ".\tc #FFFFFF",
90   "+\tc #000000",
91   ".......",
92   ".......",
93   ".......",
94   ".+++++.",
95   ".......",
96   ".......",
97   "......."
98 };
99 
100 /** picture for expand pixmap */
101 const char* const expand_xpm[] = {
102   "7 7 3 1",
103   " \tc None",
104   ".\tc #FFFFFF",
105   "+\tc #000000",
106   ".......",
107   "...+...",
108   "...+...",
109   ".+++++.",
110   "...+...",
111   "...+...",
112   "......."
113 };
114 
115 
116 /**
117  * Event filter for double click on picture label.
118  */
119 class PictureDblClickHandler : public QObject {
120 public:
121   /**
122    * Constructor.
123    * @param app application
124    */
PictureDblClickHandler(Kid3Application * app)125   explicit PictureDblClickHandler(Kid3Application* app)
126     : QObject(app), m_app(app) {}
127   virtual ~PictureDblClickHandler() override = default;
128 
129 protected:
130   /**
131    * Event filter function, calls Kid3Application::editOrAddPicture().
132    *
133    * @param obj watched object
134    * @param event event for object
135    *
136    * @return true if event is filtered.
137    */
138   virtual bool eventFilter(QObject* obj, QEvent* event) override;
139 
140 private:
141   Q_DISABLE_COPY(PictureDblClickHandler)
142 
143   Kid3Application* m_app;
144 };
145 
146 /**
147  * Event filter function, calls Kid3Application::editOrAddPicture() on double click.
148  *
149  * @param obj watched object
150  * @param event event for object
151  *
152  * @return true if event is filtered.
153  */
eventFilter(QObject * obj,QEvent * event)154 bool PictureDblClickHandler::eventFilter(QObject* obj, QEvent* event)
155 {
156   if (event->type() == QEvent::MouseButtonDblClick) {
157     m_app->editOrAddPicture();
158     return true;
159   } else {
160     // standard event processing
161     return QObject::eventFilter(obj, event);
162   }
163 }
164 
165 
166 /**
167  * File decoration provider for FileSystemModel delegating to QFileIconProvider.
168  */
169 class WidgetFileDecorationProvider : public AbstractFileDecorationProvider {
170 public:
headerDecoration() const171   virtual QVariant headerDecoration() const override {
172     // From QFileSystemModel:
173     // ### TODO oh man this is ugly and doesn't even work all the way!
174     // it is still 2 pixels off
175     QImage pixmap(16, 1, QImage::Format_Mono);
176     pixmap.fill(0);
177     pixmap.setAlphaChannel(pixmap.createAlphaMask());
178     return pixmap;
179   }
180 
computerDecoration() const181   virtual QVariant computerDecoration() const override {
182     return m_provider.icon(QFileIconProvider::Computer);
183   }
184 
folderDecoration() const185   virtual QVariant folderDecoration() const override {
186     return m_provider.icon(QFileIconProvider::Folder);
187   }
188 
fileDecoration() const189   virtual QVariant fileDecoration() const override {
190     return m_provider.icon(QFileIconProvider::File);
191   }
192 
decoration(const QFileInfo & info) const193   virtual QVariant decoration(const QFileInfo& info) const override {
194     return m_provider.icon(info);
195   }
196 
type(const QFileInfo & info) const197   virtual QString type(const QFileInfo& info) const override {
198     return m_provider.type(info);
199   }
200 
201 private:
202   QFileIconProvider m_provider;
203 };
204 
205 
206 /**
207  * Get the items from a combo box.
208  *
209  * @param comboBox combo box
210  *
211  * @return item texts from combo box.
212  */
getItemsFromComboBox(const QComboBox * comboBox)213 QStringList getItemsFromComboBox(const QComboBox* comboBox)
214 {
215   QStringList lst;
216   const int numItems = comboBox->count();
217   lst.reserve(numItems);
218   for (int i = 0; i < numItems; ++i) {
219     lst += comboBox->itemText(i);
220   }
221   return lst;
222 }
223 
224 /**
225  * Set items in combo box and add current item if not already existing.
226  * @param items combo box item
227  * @param currentItem current item
228  * @param comboBox combo box to set
229  */
setItemsInComboBox(const QStringList & items,const QString & currentItem,QComboBox * comboBox)230 void setItemsInComboBox(const QStringList& items, const QString& currentItem,
231                         QComboBox* comboBox)
232 {
233   QStringList allItems = items;
234   int idx = allItems.indexOf(currentItem);
235   if (idx == -1) {
236     allItems.append(currentItem);
237     idx = allItems.size() - 1;
238   }
239   // Block signals on combo box while setting contents to avoid
240   // editTextChanged() signals causing configuration changes.
241   comboBox->blockSignals(true);
242   if (!allItems.isEmpty()) {
243     comboBox->clear();
244     comboBox->addItems(allItems);
245   }
246   comboBox->setCurrentIndex(idx);
247   comboBox->blockSignals(false);
248 }
249 
250 }
251 
252 /**
253  * Constructs an Id3Form as a child of 'parent', with the
254  * name 'name' and widget flags set to 'f'.
255  * @param app application
256  * @param parent parent widget
257  */
Kid3Form(Kid3Application * app,BaseMainWindowImpl * mainWin,QWidget * parent)258 Kid3Form::Kid3Form(Kid3Application* app, BaseMainWindowImpl* mainWin,
259                    QWidget* parent)
260   : QSplitter(parent), m_pictureLabel(nullptr), m_app(app), m_mainWin(mainWin),
261     m_iconProvider(new WidgetFileDecorationProvider)
262 {
263   setObjectName(QLatin1String("Kid3Form"));
264 
265   FOR_ALL_TAGS(tagNr) {
266     m_tagContext[tagNr] = new Kid3FormTagContext(this, tagNr);
267     if (tagNr != Frame::Tag_Id3v1) {
268       m_app->getFrameList(tagNr)->setFrameEditor(m_mainWin);
269     }
270   }
271 
272   if (!s_collapsePixmap) {
273     s_collapsePixmap = new QPixmap(const_cast<const char**>(collapse_xpm));
274   }
275   if (!s_expandPixmap) {
276     s_expandPixmap = new QPixmap(const_cast<const char**>(expand_xpm));
277   }
278 
279   setAcceptDrops(true);
280   setWindowTitle(tr("Kid3"));
281 
282   m_leftSideWidget = new QStackedWidget(this);
283   m_vSplitter = new QSplitter(Qt::Vertical);
284   m_leftSideWidget->addWidget(m_vSplitter);
285   m_fileListBox = new FileList(m_vSplitter, m_mainWin);
286   FileProxyModel* fileProxyModel = m_app->getFileProxyModel();
287   if (FileSystemModel* fsModel =
288           qobject_cast<FileSystemModel*>(fileProxyModel->sourceModel())) {
289     fsModel->setDecorationProvider(m_iconProvider.data());
290   }
291   CoreTaggedFileIconProvider* tagIconProvider = fileProxyModel->getIconProvider();
292   tagIconProvider->setModifiedIcon(
293         QApplication::style()->standardIcon(QStyle::SP_DriveFDIcon));
294   int iconHeight = (((fontMetrics().height() - 1) / 16) + 1) * 16;
295   tagIconProvider->setRequestedSize(QSize(iconHeight, iconHeight));
296   m_fileListBox->setModel(fileProxyModel);
297   m_fileListBox->setSelectionModel(m_app->getFileSelectionModel());
298   m_dirListBox = new ConfigurableTreeView(m_vSplitter);
299   m_dirListBox->setObjectName(QLatin1String("DirList"));
300   m_dirListBox->setItemsExpandable(false);
301   m_dirListBox->setRootIsDecorated(false);
302   m_dirListBox->setModel(m_app->getDirProxyModel());
303   m_dirListBox->setSelectionModel(m_app->getDirSelectionModel());
304   connect(m_fileListBox, &QAbstractItemView::activated,
305           this, &Kid3Form::fileActivated);
306   connect(m_dirListBox, &QAbstractItemView::activated, this,
307       &Kid3Form::dirSelected);
308   connect(m_fileListBox, &ConfigurableTreeView::parentActivated,
309           this, &Kid3Form::openParentDirectory);
310   connect(m_dirListBox, &ConfigurableTreeView::parentActivated,
311           this, &Kid3Form::openParentDirectory);
312 
313   connect(m_app, &Kid3Application::fileRootIndexChanged,
314           this, &Kid3Form::setFileRootIndex);
315   connect(m_app, &Kid3Application::dirRootIndexChanged,
316           this, &Kid3Form::setDirRootIndex);
317   connect(m_app, &Kid3Application::directoryOpened,
318           this, &Kid3Form::onFirstDirectoryOpened);
319 
320   m_rightHalfVBox = new QWidget;
321   auto scrollView = new QScrollArea(this);
322   scrollView->setWidget(m_rightHalfVBox);
323   scrollView->setWidgetResizable(true);
324   auto rightHalfLayout = new QVBoxLayout(m_rightHalfVBox);
325   rightHalfLayout->setSpacing(0);
326 
327   m_fileButton = new QToolButton(m_rightHalfVBox);
328   m_fileButton->setIcon(*s_collapsePixmap);
329   m_fileButton->setAutoRaise(true);
330 #ifdef Q_OS_MAC
331   m_fileButton->setStyleSheet(QLatin1String("border: 0;"));
332 #endif
333   connect(m_fileButton, &QAbstractButton::clicked, this, &Kid3Form::showHideFile);
334   m_fileLabel = new QLabel(tr("F&ile"), m_rightHalfVBox);
335   auto fileButtonLayout = new QHBoxLayout;
336   fileButtonLayout->addWidget(m_fileButton);
337   fileButtonLayout->addWidget(m_fileLabel);
338   rightHalfLayout->addLayout(fileButtonLayout);
339 
340   m_fileWidget = new QWidget(m_rightHalfVBox);
341   m_fileWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
342   rightHalfLayout->addWidget(m_fileWidget);
343   auto fileLayout = new QGridLayout(m_fileWidget);
344 
345   m_nameLabel = new QLabel(tr("Name:"), m_fileWidget);
346   fileLayout->addWidget(m_nameLabel, 0, 0);
347 
348   m_nameLineEdit = new QLineEdit(m_fileWidget);
349   connect(m_nameLineEdit, &QLineEdit::textEdited, this,
350       &Kid3Form::nameLineEditChanged);
351   fileLayout->addWidget(m_nameLineEdit, 0, 1, 1, 4);
352   m_fileLabel->setBuddy(m_nameLineEdit);
353 
354   QLabel* formatLabel = new QLabel(tr("Format:") + QChar(0x2191),
355                                    m_fileWidget);
356   fileLayout->addWidget(formatLabel, 1, 0);
357 
358   m_formatComboBox = new QComboBox(m_fileWidget);
359   m_formatComboBox->setEditable(true);
360   m_formatComboBox->setSizeAdjustPolicy(
361         QComboBox::AdjustToMinimumContentsLengthWithIcon);
362   m_formatComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
363   m_formatComboBox->setToolTip(TrackDataFormatReplacer::getToolTip());
364   connect(m_formatComboBox, &QComboBox::editTextChanged,
365           this, &Kid3Form::onFormatEditTextChanged);
366   m_formatFromFilenameComboBox = new QComboBox(m_fileWidget);
367   m_formatFromFilenameComboBox->setEditable(true);
368   m_formatFromFilenameComboBox->setSizeAdjustPolicy(
369         QComboBox::AdjustToMinimumContentsLengthWithIcon);
370   m_formatFromFilenameComboBox->setSizePolicy(
371         QSizePolicy::Expanding, QSizePolicy::Fixed);
372   m_formatFromFilenameComboBox->setToolTip(FrameFormatReplacer::getToolTip());
373   connect(m_formatFromFilenameComboBox, &QComboBox::editTextChanged,
374           this, &Kid3Form::onFormatFromFilenameEditTextChanged);
375 
376   fileLayout->addWidget(m_formatComboBox, 1, 1);
377 
378   setTabOrder(m_fileListBox, m_dirListBox);
379   setTabOrder(m_dirListBox, m_nameLineEdit);
380   setTabOrder(m_nameLineEdit, m_formatComboBox);
381   setTabOrder(m_formatComboBox, m_formatFromFilenameComboBox);
382 
383   QWidget* tabWidget = m_formatFromFilenameComboBox;
384 
385   QLabel* fromTagLabel = new QLabel(tr("From:"), m_fileWidget);
386   fileLayout->addWidget(fromTagLabel, 1, 2);
387   int column = 3;
388   FOR_ALL_TAGS(tagNr) {
389     if (tagNr <= Frame::Tag_2) {
390       QString tagStr = Frame::tagNumberToString(tagNr);
391       m_fnButton[tagNr] = new QPushButton(tr("Tag %1").arg(tagStr),
392                                           m_fileWidget);
393       m_fnButton[tagNr]->setToolTip(tr("Filename from Tag %1").arg(tagStr));
394       connect(m_fnButton[tagNr], &QAbstractButton::clicked,
395               m_app->tag(tagNr), &Kid3ApplicationTagContext::getFilenameFromTags);
396       fileLayout->addWidget(m_fnButton[tagNr], 1, column++);
397       setTabOrder(tabWidget, m_fnButton[tagNr]);
398       tabWidget = m_fnButton[tagNr];
399     } else {
400       m_fnButton[tagNr] = nullptr;
401     }
402   }
403 
404   QLabel* formatFromFilenameLabel = new QLabel(tr("Format:") + QChar(0x2193),
405                                                m_fileWidget);
406   fileLayout->addWidget(formatFromFilenameLabel, 2, 0);
407 
408   fileLayout->addWidget(m_formatFromFilenameComboBox, 2, 1);
409 
410   QLabel* toTagLabel = new QLabel(tr("To:"), m_fileWidget);
411   fileLayout->addWidget(toTagLabel, 2, 2);
412   column = 3;
413   FOR_ALL_TAGS(tagNr) {
414     if (tagNr <= Frame::Tag_2) {
415       QString tagStr = Frame::tagNumberToString(tagNr);
416       m_toTagButton[tagNr] =
417         new QPushButton(tr("Tag %1").arg(tagStr), m_fileWidget);
418       m_toTagButton[tagNr]->setToolTip(tr("Tag %1 from Filename").arg(tagStr));
419       connect(m_toTagButton[tagNr], &QAbstractButton::clicked,
420               m_app->tag(tagNr), &Kid3ApplicationTagContext::getTagsFromFilename);
421       fileLayout->addWidget(m_toTagButton[tagNr], 2, column++);
422       setTabOrder(tabWidget, m_toTagButton[tagNr]);
423       tabWidget = m_toTagButton[tagNr];
424     } else {
425       m_toTagButton[tagNr] = nullptr;
426     }
427   }
428 
429   FOR_ALL_TAGS(tagNr) {
430     m_tagButton[tagNr] = new QToolButton(m_rightHalfVBox);
431     m_tagButton[tagNr]->setIcon(*s_collapsePixmap);
432     m_tagButton[tagNr]->setAutoRaise(true);
433 #ifdef Q_OS_MAC
434     m_tagButton[tagNr]->setStyleSheet(QLatin1String("border: 0;"));
435 #endif
436     connect(m_tagButton[tagNr], &QAbstractButton::clicked,
437             tag(tagNr), &Kid3FormTagContext::showHideTag);
438     m_tagLabel[tagNr] = new QLabel(
439           tr("Tag &%1").arg(Frame::tagNumberToString(tagNr)), m_rightHalfVBox);
440     auto tagButtonLayout = new QHBoxLayout;
441     tagButtonLayout->addWidget(m_tagButton[tagNr]);
442     tagButtonLayout->addWidget(m_tagLabel[tagNr]);
443     rightHalfLayout->addLayout(tagButtonLayout);
444 
445     m_tagWidget[tagNr] = new QWidget(m_rightHalfVBox);
446     if (tagNr == Frame::Tag_Id3v1) {
447       m_tagWidget[tagNr]->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
448     }
449     rightHalfLayout->addWidget(m_tagWidget[tagNr], 100);
450 
451     auto idHBoxLayout = new QHBoxLayout(m_tagWidget[tagNr]);
452     FrameTableModel* frameModel = m_app->frameModel(tagNr);
453     frameModel->setHeadersEmpty(true);
454     m_frameTable[tagNr] = new FrameTable(
455           frameModel, m_app->genreModel(tagNr), m_tagWidget[tagNr]);
456     m_frameTable[tagNr]->setSelectionModel(m_app->getFramesSelectionModel(tagNr));
457     idHBoxLayout->addWidget(m_frameTable[tagNr], tagNr == Frame::Tag_Id3v1 ? 100 : 0);
458     m_tagLabel[tagNr]->setBuddy(m_frameTable[tagNr]);
459 
460     auto buttonsVBoxLayout = new QVBoxLayout;
461     idHBoxLayout->addLayout(buttonsVBoxLayout);
462 
463     if (tagNr <= Frame::Tag_2) {
464       Frame::TagNumber otherTagNr = tagNr == Frame::Tag_1
465           ? Frame::Tag_2
466           : tagNr == Frame::Tag_2 ? Frame::Tag_1 : Frame::Tag_NumValues;
467       m_id3PushButton[tagNr] =
468         new QPushButton(tr("From Tag %1")
469                         .arg(Frame::tagNumberToString(otherTagNr)),
470                         m_tagWidget[tagNr]);
471       connect(m_id3PushButton[tagNr], &QAbstractButton::clicked,
472               m_app->tag(tagNr), &Kid3ApplicationTagContext::copyToOtherTag);
473       buttonsVBoxLayout->addWidget(m_id3PushButton[tagNr]);
474       setTabOrder(tabWidget, m_id3PushButton[tagNr]);
475 
476       QPushButton* copyPushButton = new QPushButton(tr("Copy"),
477                                                     m_tagWidget[tagNr]);
478       connect(copyPushButton, &QAbstractButton::clicked,
479               m_app->tag(tagNr), &Kid3ApplicationTagContext::copyTags);
480       buttonsVBoxLayout->addWidget(copyPushButton);
481       setTabOrder(m_id3PushButton[tagNr], copyPushButton);
482 
483       QPushButton* pastePushButton =
484         new QPushButton(tr("Paste"), m_tagWidget[tagNr]);
485       connect(pastePushButton, &QAbstractButton::clicked,
486               m_app->tag(tagNr), &Kid3ApplicationTagContext::pasteTags);
487       buttonsVBoxLayout->addWidget(pastePushButton);
488       setTabOrder(copyPushButton, pastePushButton);
489       tabWidget = pastePushButton;
490     } else {
491       m_id3PushButton[tagNr] = new QPushButton(tr("From"));
492       auto menu = new QMenu(this);
493       QAction* action = menu->addAction(tr("Filename"));
494       connect(action, &QAction::triggered,
495               m_app->tag(tagNr), &Kid3ApplicationTagContext::getTagsFromFilename);
496       FOR_ALL_TAGS(fromTagNr) {
497         if (fromTagNr != tagNr) {
498           action = menu->addAction(
499             tr("Tag %1").arg(Frame::tagNumberToString(fromTagNr)));
500           QByteArray ba;
501           ba.append(static_cast<char>(fromTagNr));
502           ba.append(static_cast<char>(tagNr));
503           action->setData(ba);
504           connect(action, &QAction::triggered,
505                   this, &Kid3Form::copyTagsActionData);
506         }
507       }
508       action = menu->addAction(tr("Paste"));
509       connect(action, &QAction::triggered,
510               m_app->tag(tagNr), &Kid3ApplicationTagContext::pasteTags);
511       m_id3PushButton[tagNr]->setMenu(menu);
512       buttonsVBoxLayout->addWidget(m_id3PushButton[tagNr]);
513       setTabOrder(tabWidget, m_id3PushButton[tagNr]);
514 
515       QPushButton* toButton = new QPushButton(tr("To"));
516       menu = new QMenu(this);
517       action = menu->addAction(tr("Filename"));
518       connect(action, &QAction::triggered,
519               m_app->tag(tagNr), &Kid3ApplicationTagContext::getFilenameFromTags);
520       FOR_ALL_TAGS(fromTagNr) {
521         if (fromTagNr != tagNr) {
522           action = menu->addAction(
523             tr("Tag %1").arg(Frame::tagNumberToString(fromTagNr)));
524           QByteArray ba;
525           ba.append(static_cast<char>(tagNr));
526           ba.append(static_cast<char>(fromTagNr));
527           action->setData(ba);
528           connect(action, &QAction::triggered,
529                   this, &Kid3Form::copyTagsActionData);
530         }
531       }
532       action = menu->addAction(tr("Copy"));
533       connect(action, &QAction::triggered,
534               m_app->tag(tagNr), &Kid3ApplicationTagContext::copyTags);
535       toButton->setMenu(menu);
536       buttonsVBoxLayout->addWidget(toButton);
537       setTabOrder(m_id3PushButton[tagNr], toButton);
538       tabWidget = toButton;
539     }
540 
541     QPushButton* removePushButton =
542       new QPushButton(tr("Remove"), m_tagWidget[tagNr]);
543     connect(removePushButton, &QAbstractButton::clicked,
544             m_app->tag(tagNr), &Kid3ApplicationTagContext::removeTags);
545     buttonsVBoxLayout->addWidget(removePushButton);
546     setTabOrder(tabWidget, removePushButton);
547     tabWidget = removePushButton;
548 
549     if (tagNr != Frame::Tag_Id3v1) {
550       QFrame* frameLine = new QFrame;
551       frameLine->setFrameShape(QFrame::HLine);
552       frameLine->setFrameShadow(QFrame::Sunken);
553       buttonsVBoxLayout->addWidget(frameLine);
554 
555       QPushButton* editFramesPushButton =
556         new QPushButton(tr("Edit..."), m_tagWidget[tagNr]);
557       connect(editFramesPushButton, &QAbstractButton::clicked,
558               m_app->tag(tagNr), &Kid3ApplicationTagContext::editFrame);
559       buttonsVBoxLayout->addWidget(editFramesPushButton);
560       setTabOrder(tabWidget, editFramesPushButton);
561       QPushButton* framesAddPushButton =
562         new QPushButton(tr("Add..."), m_tagWidget[tagNr]);
563       connect(framesAddPushButton, &QAbstractButton::clicked,
564               m_app->tag(tagNr), &Kid3ApplicationTagContext::addFrame);
565       buttonsVBoxLayout->addWidget(framesAddPushButton);
566       setTabOrder(editFramesPushButton, framesAddPushButton);
567       QPushButton* deleteFramesPushButton =
568         new QPushButton(tr("Delete"), m_tagWidget[tagNr]);
569       connect(deleteFramesPushButton, &QAbstractButton::clicked,
570               m_app->tag(tagNr), &Kid3ApplicationTagContext::deleteFrame);
571       buttonsVBoxLayout->addWidget(deleteFramesPushButton);
572       setTabOrder(framesAddPushButton, deleteFramesPushButton);
573       tabWidget = deleteFramesPushButton;
574     }
575     if (tagNr == Frame::Tag_Picture) {
576       m_pictureLabel = new PictureLabel(this);
577       m_pictureLabel->installEventFilter(new PictureDblClickHandler(m_app));
578       buttonsVBoxLayout->addWidget(m_pictureLabel);
579     }
580 
581     buttonsVBoxLayout->addItem(
582       new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding));
583   }
584 
585   rightHalfLayout->insertStretch(-1);
586 
587   FOR_ALL_TAGS(tagNr) {
588     setTabOrder(tabWidget, m_frameTable[tagNr]);
589     tabWidget = m_frameTable[tagNr];
590   }
591 
592   auto sectionActions = new SectionActions(SectionActions::Navigation,
593                                            m_fileListBox);
594   m_sectionActions.append(sectionActions);
595   connect(sectionActions->previousSectionAction(), &QAction::triggered,
596           this, [this]() { setFocusPreviousTag(Frame::Tag_NumValues); });
597   connect(sectionActions->nextSectionAction(), &QAction::triggered,
598           this, &Kid3Form::setFocusDirList);
599 
600   sectionActions = new SectionActions(SectionActions::Navigation, m_dirListBox);
601   m_sectionActions.append(sectionActions);
602   connect(sectionActions->previousSectionAction(), &QAction::triggered,
603           this, &Kid3Form::setFocusFileList);
604   connect(sectionActions->nextSectionAction(), &QAction::triggered,
605           this, &Kid3Form::setFocusFilename);
606 
607   sectionActions = new SectionActions(SectionActions::Navigation |
608                                       SectionActions::Transfer,
609                                       m_fileWidget);
610   m_sectionActions.append(sectionActions);
611   connect(sectionActions->previousSectionAction(), &QAction::triggered,
612           this, &Kid3Form::setFocusDirList);
613   connect(sectionActions->nextSectionAction(), &QAction::triggered,
614           this, [this]() { setFocusNextTag(Frame::Tag_NumValues); });
615   connect(sectionActions->transferAction(), &QAction::triggered,
616     m_app->tag(Frame::Tag_2), &Kid3ApplicationTagContext::getFilenameFromTags);
617 
618   FOR_ALL_TAGS(tagNr) {
619     sectionActions = new SectionActions(
620           tagNr == Frame::Tag_1 ? SectionActions::Navigation |
621                                   SectionActions::Transfer |
622                                   SectionActions::EditSection
623                                 : SectionActions::Navigation |
624                                   SectionActions::Transfer |
625                                   SectionActions::EditSection |
626                                   SectionActions::EditElement,
627           m_frameTable[tagNr]);
628     m_sectionActions.append(sectionActions);
629     connect(sectionActions->previousSectionAction(), &QAction::triggered,
630             this, [this, tagNr]() {
631       setFocusPreviousTag(tagNr);
632     });
633     connect(sectionActions->nextSectionAction(), &QAction::triggered,
634             this, [this, tagNr]() {
635       setFocusNextTag(tagNr);
636     });
637 
638     connect(sectionActions->copyAction(), &QAction::triggered,
639             m_app->tag(tagNr), &Kid3ApplicationTagContext::copyTags);
640     connect(sectionActions->pasteAction(), &QAction::triggered,
641             m_app->tag(tagNr), &Kid3ApplicationTagContext::pasteTags);
642     connect(sectionActions->removeAction(), &QAction::triggered,
643             this, [this, tagNr]() {
644       m_app->tag(tagNr)->removeTags();
645       setFocusTag(tagNr);
646     });
647 
648     QByteArray ba;
649     ba.append(static_cast<char>(tagNr == Frame::Tag_2 ? Frame::Tag_1
650                                                       : Frame::Tag_2));
651     ba.append(static_cast<char>(tagNr));
652     sectionActions->transferAction()->setData(ba);
653     connect(sectionActions->transferAction(), &QAction::triggered,
654             this, &Kid3Form::copyTagsActionData);
655 
656     if (tagNr != Frame::Tag_1) {
657       connect(sectionActions->editAction(), &QAction::triggered,
658               m_app->tag(tagNr), &Kid3ApplicationTagContext::editFrame);
659       connect(sectionActions->addAction(), &QAction::triggered,
660               m_app->tag(tagNr), &Kid3ApplicationTagContext::addFrame);
661       connect(sectionActions->deleteAction(), &QAction::triggered,
662               m_app->tag(tagNr), &Kid3ApplicationTagContext::deleteFrame);
663     }
664   }
665 }
666 
667 /**
668  * Destructor.
669  */
~Kid3Form()670 Kid3Form::~Kid3Form()
671 {
672   m_app->removeFrameEditor(m_mainWin);
673 }
674 
675 /**
676  * Set keyboard shortcuts for section actions.
677  * @param map map of action names to key sequences
678  */
setSectionActionShortcuts(const QMap<QString,QKeySequence> & map)679 void Kid3Form::setSectionActionShortcuts(const QMap<QString, QKeySequence>& map)
680 {
681   for (SectionActions* sectionActions : m_sectionActions) {
682     sectionActions->setShortcuts(map);
683   }
684   m_fileListBox->setShortcuts(map);
685   m_dirListBox->setShortcuts(map);
686 }
687 
688 /**
689  * Handle event when mouse is moved while dragging.
690  *
691  * @param ev drag event.
692  */
dragMoveEvent(QDragMoveEvent * ev)693 void Kid3Form::dragMoveEvent(QDragMoveEvent* ev)
694 {
695   if (ev->mimeData()->hasFormat(QLatin1String("text/uri-list")) ||
696       ev->mimeData()->hasImage()) {
697     ev->acceptProposedAction();
698   } else {
699     ev->ignore();
700   }
701 }
702 
703 /**
704  * Accept drag.
705  *
706  * @param ev drag event.
707  */
dragEnterEvent(QDragEnterEvent * ev)708 void Kid3Form::dragEnterEvent(QDragEnterEvent* ev)
709 {
710   Kid3Form::dragMoveEvent(ev);
711 }
712 
713 /**
714  * Handle event when mouse leaves widget while dragging.
715  *
716  * @param ev drag event.
717  */
dragLeaveEvent(QDragLeaveEvent * ev)718 void Kid3Form::dragLeaveEvent(QDragLeaveEvent* ev)
719 {
720   ev->accept();
721 }
722 
723 /**
724  * Handle drop event.
725  *
726  * @param ev drop event.
727  */
dropEvent(QDropEvent * ev)728 void Kid3Form::dropEvent(QDropEvent* ev)
729 {
730   if (ev->mimeData()->hasImage()) {
731     QImage image = qvariant_cast<QImage>(ev->mimeData()->imageData());
732     ev->acceptProposedAction();
733     if (!image.isNull()) {
734       QByteArray ba;
735       QBuffer buffer(&ba);
736       buffer.open(QIODevice::WriteOnly);
737       image.save(&buffer, "JPG");
738       PictureFrame frame;
739       if (PictureFrame::setData(frame, ba)) {
740         m_app->dropImage(&frame);
741       }
742     }
743   } else if (ev->mimeData()->hasFormat(QLatin1String("text/uri-list"))) {
744     QList<QUrl> urls = ev->mimeData()->urls();
745     ev->acceptProposedAction();
746     m_app->dropUrls(urls, ev->source() != nullptr);
747   } else {
748     ev->ignore();
749   }
750 }
751 
752 /**
753  * Filename line edit is changed.
754  * @param txt contents of line edit
755  */
nameLineEditChanged(const QString & txt)756 void Kid3Form::nameLineEditChanged(const QString& txt)
757 {
758   formatLineEdit(m_nameLineEdit, txt, &FilenameFormatConfig::instance());
759 }
760 
761 /**
762  * Mark the filename as changed.
763  * @param en true to mark as changed
764  */
markChangedFilename(bool en)765 void Kid3Form::markChangedFilename(bool en)
766 {
767   CoreTaggedFileIconProvider* colorProvider;
768   if (en &&
769       (colorProvider = m_app->getPlatformTools()->iconProvider()) != nullptr) {
770     QPalette changedPalette(m_nameLabel->palette());
771     changedPalette.setBrush(QPalette::Active, QPalette::Window,
772                             colorProvider->colorForContext(ColorContext::Marked)
773                             .value<QBrush>());
774     m_nameLabel->setPalette(changedPalette);
775   } else {
776     m_nameLabel->setPalette(QPalette());
777   }
778   m_nameLabel->setAutoFillBackground(en);
779 }
780 
781 /**
782  * Format string within line edit.
783  *
784  * @param le   line edit
785  * @param txt  text in line edit
786  * @param fcfg format configuration
787  */
formatLineEdit(QLineEdit * le,const QString & txt,const FormatConfig * fcfg)788 void Kid3Form::formatLineEdit(QLineEdit* le, const QString& txt,
789                const FormatConfig* fcfg)
790 {
791   if (fcfg->formatWhileEditing()) {
792     QString str(txt);
793     fcfg->formatString(str);
794     if (str != txt) {
795       int curPos = le->cursorPosition();
796       le->setText(str);
797       le->setCursorPosition(curPos + str.length() - txt.length());
798     }
799   }
800 }
801 
802 /**
803  * Directory list box directory selected.
804  *
805  * @param index selected item
806  */
dirSelected(const QModelIndex & index)807 void Kid3Form::dirSelected(const QModelIndex& index)
808 {
809   QString dirPath = index.data(FileSystemModel::FilePathRole).toString();
810   if (!dirPath.isEmpty()) {
811     m_app->setDirUpIndex(
812         dirPath.endsWith(QLatin1String("..")) ? index.parent() : QModelIndex());
813     m_mainWin->updateCurrentSelection();
814     m_mainWin->confirmedOpenDirectory({dirPath});
815   }
816 }
817 
818 /**
819  * File list box item activated.
820  *
821  * @param index selected item
822  */
fileActivated(const QModelIndex & index)823 void Kid3Form::fileActivated(const QModelIndex& index)
824 {
825   if (const FileProxyModel* fileProxyModel =
826       qobject_cast<const FileProxyModel*>(index.model())) {
827     if (fileProxyModel->isDir(index)) {
828       QString dirPath = fileProxyModel->filePath(index);
829       if (!dirPath.isEmpty()) {
830         m_mainWin->updateCurrentSelection();
831         m_mainWin->confirmedOpenDirectory({dirPath});
832       }
833     }
834   }
835 }
836 
837 /**
838  * Open the parent directory of a model index.
839  *
840  * @param index current root index of item view
841  */
openParentDirectory(const QModelIndex & index)842 void Kid3Form::openParentDirectory(const QModelIndex& index)
843 {
844   if (index.isValid()) {
845     QDir dir(index.data(FileSystemModel::FilePathRole).toString());
846     if (dir.cdUp()) {
847       QString dirPath = dir.absolutePath();
848       if (m_dirListBox && index.model() == m_dirListBox->model()) {
849         m_app->setDirUpIndex(index);
850       }
851       m_mainWin->updateCurrentSelection();
852       m_mainWin->confirmedOpenDirectory({dirPath});
853     }
854   }
855 }
856 
857 /**
858  * Enable or disable controls requiring tags.
859  * @param tagNr tag number
860  * @param enable true to enable
861  */
enableControls(Frame::TagNumber tagNr,bool enable)862 void Kid3Form::enableControls(Frame::TagNumber tagNr, bool enable)
863 {
864   if (m_fnButton[tagNr]) {
865     m_fnButton[tagNr]->setEnabled(enable);
866   }
867   if (m_toTagButton[tagNr]) {
868     m_toTagButton[tagNr]->setEnabled(enable);
869   }
870   Frame::TagNumber otherTagNr = tagNr == Frame::Tag_1
871       ? Frame::Tag_2
872       : tagNr == Frame::Tag_2 ? Frame::Tag_1 : Frame::Tag_NumValues;
873   if (otherTagNr < Frame::Tag_NumValues) {
874     m_id3PushButton[otherTagNr]->setEnabled(enable);
875   }
876   m_tagWidget[tagNr]->setEnabled(enable);
877   if (tagNr > Frame::Tag_2) {
878     m_tagButton[tagNr]->setVisible(enable);
879     m_tagLabel[tagNr]->setVisible(enable);
880   }
881 }
882 
883 /**
884  * Display the tag format.
885  * @param tagNr tag number
886  * @param str string describing format, e.g. "ID3v1.1"
887  */
setTagFormat(Frame::TagNumber tagNr,const QString & str)888 void Kid3Form::setTagFormat(Frame::TagNumber tagNr, const QString& str)
889 {
890   QString txt = tr("Tag &%1").arg(Frame::tagNumberToString(tagNr));
891   if (!str.isEmpty()) {
892     txt += QLatin1String(": ");
893     txt += str;
894   }
895   m_tagLabel[tagNr]->setText(txt);
896 }
897 
898 /**
899  * Adjust the size of the right half box.
900  */
adjustRightHalfBoxSize()901 void Kid3Form::adjustRightHalfBoxSize()
902 {
903   m_rightHalfVBox->adjustSize();
904 }
905 
906 /**
907  * Hide or show file controls.
908  *
909  * @param hide true to hide, false to show
910  */
hideFile(bool hide)911 void Kid3Form::hideFile(bool hide)
912 {
913   if (hide) {
914     m_fileWidget->hide();
915     m_fileButton->setIcon(*s_expandPixmap);
916   } else {
917     m_fileWidget->show();
918     m_fileButton->setIcon(*s_collapsePixmap);
919   }
920 }
921 
922 /**
923  * Hide or show tag controls.
924  * @param tagNr tag number
925  * @param hide true to hide, false to show
926  */
hideTag(Frame::TagNumber tagNr,bool hide)927 void Kid3Form::hideTag(Frame::TagNumber tagNr, bool hide)
928 {
929   if (hide) {
930     m_tagWidget[tagNr]->hide();
931     m_tagButton[tagNr]->setIcon(*s_expandPixmap);
932   } else {
933     m_tagWidget[tagNr]->show();
934     m_tagButton[tagNr]->setIcon(*s_collapsePixmap);
935   }
936 }
937 
938 /**
939  * Toggle visibility of file controls.
940  */
showHideFile()941 void Kid3Form::showHideFile()
942 {
943   hideFile(!m_fileWidget->isHidden());
944 }
945 
946 /**
947  * Toggle visibility of tag controls.
948  * @param tagNr tag number
949  */
showHideTag(Frame::TagNumber tagNr)950 void Kid3Form::showHideTag(Frame::TagNumber tagNr)
951 {
952   hideTag(tagNr, !m_tagWidget[tagNr]->isHidden());
953 }
954 
955 /**
956  * Set format text configuration when format edit text is changed.
957  * @param text format text
958  */
onFormatEditTextChanged(const QString & text)959 void Kid3Form::onFormatEditTextChanged(const QString& text)
960 {
961   FileConfig::instance().setToFilenameFormat(text);
962 }
963 
964 /**
965  * Set format from filename text configuration when edit text is changed.
966  * @param text format text
967  */
onFormatFromFilenameEditTextChanged(const QString & text)968 void Kid3Form::onFormatFromFilenameEditTextChanged(const QString& text)
969 {
970   FileConfig::instance().setFromFilenameFormat(text);
971 }
972 
973 /**
974  * Update sorting after directory is opened for the first time.
975  * The sort order of the file list is not correct if it is not explicitly
976  * sorted the first time.
977  */
onFirstDirectoryOpened()978 void Kid3Form::onFirstDirectoryOpened()
979 {
980   // Only call this once.
981   disconnect(m_app, &Kid3Application::directoryOpened,
982              this, &Kid3Form::onFirstDirectoryOpened);
983   const GuiConfig& guiCfg = GuiConfig::instance();
984   m_app->getFileProxyModel()->sort(guiCfg.fileListSortColumn(),
985                                    guiCfg.fileListSortOrder());
986   int firstFileSectionSize =
987       m_fileListBox->initializeColumnWidthsFromContents(-1);
988   m_fileListBox->scrollTo(m_fileListBox->currentIndex());
989   // The directory column often only contains "." and "..", which results
990   // in a small size. Make it at least as wide as the corresponding
991   // file list column.
992   m_dirListBox->initializeColumnWidthsFromContents(firstFileSectionSize);
993 }
994 
995 /**
996  * Hide or show picture.
997  *
998  * @param hide true to hide, false to show
999  */
hidePicture(bool hide)1000 void Kid3Form::hidePicture(bool hide)
1001 {
1002   if (!m_pictureLabel)
1003     return;
1004 
1005   if (hide) {
1006     m_pictureLabel->hide();
1007   } else {
1008     m_pictureLabel->show();
1009   }
1010 }
1011 
1012 /**
1013  * Set focus on filename controls.
1014  */
setFocusFilename()1015 void Kid3Form::setFocusFilename()
1016 {
1017   if (m_fileWidget->isHidden()) {
1018     hideFile(false);
1019   }
1020   if (isFilenameEditEnabled()) {
1021     m_nameLineEdit->setFocus();
1022   } else {
1023     m_formatComboBox->setFocus();
1024   }
1025 }
1026 
1027 /**
1028  * Set focus on tag controls.
1029  * @param tagNr tag number
1030  */
setFocusTag(Frame::TagNumber tagNr)1031 void Kid3Form::setFocusTag(Frame::TagNumber tagNr)
1032 {
1033   if (m_tagWidget[tagNr]->isHidden()) {
1034     hideTag(tagNr, false);
1035   }
1036   m_frameTable[tagNr]->setFocus();
1037 }
1038 
1039 /**
1040  * Set focus on next tag controls.
1041  * @param tagNr current tag, Frame::Tag_NumValues if not on tag
1042  */
setFocusNextTag(Frame::TagNumber tagNr)1043 void Kid3Form::setFocusNextTag(Frame::TagNumber tagNr)
1044 {
1045   for (int i = tagNr == Frame::Tag_NumValues ? Frame::Tag_1
1046                                              : tagNr + 1; ; ++i) {
1047     if (i >= Frame::Tag_NumValues) {
1048       setFocusFileList();
1049       break;
1050     } else if (i >= Frame::Tag_1) {
1051       if (m_tagWidget[i]->isEnabled()) {
1052         setFocusTag(static_cast<Frame::TagNumber>(i));
1053         break;
1054       }
1055     } else {
1056       break;
1057     }
1058   }
1059 }
1060 
1061 /**
1062  * Set focus on previous tag controls.
1063  * @param tagNr current tag, Frame::Tag_NumValues if not on tag
1064  */
setFocusPreviousTag(Frame::TagNumber tagNr)1065 void Kid3Form::setFocusPreviousTag(Frame::TagNumber tagNr)
1066 {
1067   for (int i = tagNr - 1; ; --i) {
1068     if (i < Frame::Tag_1) {
1069       setFocusFilename();
1070       break;
1071     } else if (i < Frame::Tag_NumValues) {
1072       if (m_tagWidget[i]->isEnabled()) {
1073         setFocusTag(static_cast<Frame::TagNumber>(i));
1074         break;
1075       }
1076     } else {
1077       break;
1078     }
1079   }
1080 }
1081 
1082 /**
1083  * Set focus on file list.
1084  */
setFocusFileList()1085 void Kid3Form::setFocusFileList()
1086 {
1087   m_fileListBox->setFocus();
1088 }
1089 
1090 /**
1091  * Set focus on directory list.
1092  */
setFocusDirList()1093 void Kid3Form::setFocusDirList()
1094 {
1095   m_dirListBox->setFocus();
1096 }
1097 
1098 /**
1099  * Save the local settings to the configuration.
1100  */
saveConfig()1101 void Kid3Form::saveConfig()
1102 {
1103   GuiConfig& guiCfg = GuiConfig::instance();
1104   FileConfig& fileCfg = FileConfig::instance();
1105   guiCfg.setSplitterSizes(sizes());
1106   guiCfg.setVSplitterSizes(m_vSplitter->sizes());
1107   fileCfg.setToFilenameFormat(m_formatComboBox->currentText());
1108   fileCfg.setToFilenameFormats(getItemsFromComboBox(m_formatComboBox));
1109   fileCfg.setFromFilenameFormat(m_formatFromFilenameComboBox->currentText());
1110   fileCfg.setFromFilenameFormats(getItemsFromComboBox(m_formatFromFilenameComboBox));
1111   if (!guiCfg.autoHideTags()) {
1112     guiCfg.setHideFile(m_fileWidget->isHidden());
1113     FOR_ALL_TAGS(tagNr) {
1114       guiCfg.setHideTag(tagNr, m_tagWidget[tagNr]->isHidden());
1115     }
1116   }
1117   int column;
1118   Qt::SortOrder order;
1119   m_fileListBox->getSortByColumn(column, order);
1120   guiCfg.setFileListSortColumn(column);
1121   guiCfg.setFileListSortOrder(order);
1122   guiCfg.setFileListVisibleColumns(m_fileListBox->getVisibleColumns());
1123   bool customColumWidthsEnabled = m_fileListBox->areCustomColumnWidthsEnabled();
1124   guiCfg.setFileListCustomColumnWidthsEnabled(customColumWidthsEnabled);
1125   if (customColumWidthsEnabled) {
1126     guiCfg.setFileListColumnWidths(m_fileListBox->getColumnWidths());
1127   }
1128   m_dirListBox->getSortByColumn(column, order);
1129   guiCfg.setDirListSortColumn(column);
1130   guiCfg.setDirListSortOrder(order);
1131   guiCfg.setDirListVisibleColumns(m_dirListBox->getVisibleColumns());
1132   customColumWidthsEnabled = m_dirListBox->areCustomColumnWidthsEnabled();
1133   guiCfg.setDirListCustomColumnWidthsEnabled(customColumWidthsEnabled);
1134   if (customColumWidthsEnabled) {
1135     guiCfg.setDirListColumnWidths(m_dirListBox->getColumnWidths());
1136   }
1137 }
1138 
1139 /**
1140  * Read the local settings from the configuration.
1141  */
readConfig()1142 void Kid3Form::readConfig()
1143 {
1144   const GuiConfig& guiCfg = GuiConfig::instance();
1145   const FileConfig& fileCfg = FileConfig::instance();
1146   if (!guiCfg.splitterSizes().isEmpty()) {
1147     setSizes(guiCfg.splitterSizes());
1148   } else {
1149     setSizes({307, 601});
1150   }
1151   if (!guiCfg.vSplitterSizes().isEmpty()) {
1152     m_vSplitter->setSizes(guiCfg.vSplitterSizes());
1153   } else {
1154     m_vSplitter->setSizes({451, 109});
1155   }
1156 
1157   setToFilenameFormats();
1158   setFromFilenameFormats();
1159   connect(&fileCfg, &FileConfig::toFilenameFormatsChanged,
1160           this, &Kid3Form::setToFilenameFormats, Qt::UniqueConnection);
1161   connect(&fileCfg, &FileConfig::fromFilenameFormatsChanged,
1162           this, &Kid3Form::setFromFilenameFormats, Qt::UniqueConnection);
1163 
1164   if (!guiCfg.autoHideTags()) {
1165     hideFile(guiCfg.hideFile());
1166     FOR_ALL_TAGS(tagNr) {
1167       hideTag(tagNr, guiCfg.hideTag(tagNr));
1168     }
1169   }
1170   hidePicture(guiCfg.hidePicture());
1171   m_fileListBox->sortByColumn(guiCfg.fileListSortColumn(),
1172                               guiCfg.fileListSortOrder());
1173   m_fileListBox->setVisibleColumns(guiCfg.fileListVisibleColumns());
1174   m_fileListBox->setColumnWidths(guiCfg.fileListColumnWidths());
1175   m_fileListBox->setCustomColumnWidthsEnabled(
1176         guiCfg.fileListCustomColumnWidthsEnabled());
1177   m_dirListBox->sortByColumn(guiCfg.dirListSortColumn(),
1178                              guiCfg.dirListSortOrder());
1179   m_dirListBox->setVisibleColumns(guiCfg.dirListVisibleColumns());
1180   m_dirListBox->setColumnWidths(guiCfg.dirListColumnWidths());
1181   m_dirListBox->setCustomColumnWidthsEnabled(
1182         guiCfg.dirListCustomColumnWidthsEnabled());
1183 }
1184 
1185 /**
1186  * Set items of "Format <arrow up>" combo box from file configuration.
1187  */
setToFilenameFormats()1188 void Kid3Form::setToFilenameFormats()
1189 {
1190   const FileConfig& fileCfg = FileConfig::instance();
1191   setItemsInComboBox(fileCfg.toFilenameFormats(), fileCfg.toFilenameFormat(),
1192                      m_formatComboBox);
1193 }
1194 
1195 /**
1196  * Set items of "Format <arrow down>" combo box from file configuration.
1197  */
setFromFilenameFormats()1198 void Kid3Form::setFromFilenameFormats()
1199 {
1200   const FileConfig& fileCfg = FileConfig::instance();
1201   setItemsInComboBox(fileCfg.fromFilenameFormats(), fileCfg.fromFilenameFormat(),
1202                      m_formatFromFilenameComboBox);
1203 }
1204 
1205 /**
1206  * Set preview picture data.
1207  * @param data picture data, empty if no picture is available
1208  */
setPictureData(const QByteArray & data)1209 void Kid3Form::setPictureData(const QByteArray& data)
1210 {
1211   if (m_pictureLabel) {
1212     m_pictureLabel->setData(data);
1213   }
1214 }
1215 
1216 /**
1217  * Set details info text.
1218  *
1219  * @param str detail information summary as string
1220  */
setDetailInfo(const QString & str)1221 void Kid3Form::setDetailInfo(const QString& str)
1222 {
1223   m_fileLabel->setText(!str.isEmpty()
1224                        ? tr("F&ile") + QLatin1String(": ") + str
1225                        : tr("F&ile"));
1226 }
1227 
1228 /**
1229  * Select all files.
1230  */
selectAllFiles()1231 void Kid3Form::selectAllFiles()
1232 {
1233   m_fileListBox->selectAll();
1234 }
1235 
1236 /**
1237  * Deselect all files.
1238  */
deselectAllFiles()1239 void Kid3Form::deselectAllFiles()
1240 {
1241   m_fileListBox->clearSelection();
1242 }
1243 
1244 /**
1245  * Set the next file as the current file.
1246  *
1247  * @param select true to select the file
1248  * @param onlyTaggedFiles only consider tagged files
1249  *
1250  * @return true if a next file exists.
1251  */
nextFile(bool select,bool onlyTaggedFiles)1252 bool Kid3Form::nextFile(bool select, bool onlyTaggedFiles)
1253 {
1254   FrameTable* editingFrameTable = getEditingFrameTable();
1255   bool ok = m_app->nextFile(select, onlyTaggedFiles);
1256   if (ok && editingFrameTable) {
1257     editingFrameTable->edit(editingFrameTable->currentIndex());
1258   }
1259   return ok;
1260 }
1261 
1262 /**
1263  * Set the previous file as the current file.
1264  *
1265  * @param select true to select the file
1266  * @param onlyTaggedFiles only consider tagged files
1267  *
1268  * @return true if a previous file exists.
1269  */
previousFile(bool select,bool onlyTaggedFiles)1270 bool Kid3Form::previousFile(bool select, bool onlyTaggedFiles)
1271 {
1272   FrameTable* editingFrameTable = getEditingFrameTable();
1273   bool ok = m_app->previousFile(select, onlyTaggedFiles);
1274   if (ok && editingFrameTable) {
1275     editingFrameTable->edit(editingFrameTable->currentIndex());
1276   }
1277   return ok;
1278 }
1279 
1280 /**
1281  * Select the next tagged file as the current file.
1282  * Same as nextFile() with default arguments, provided for functor-based
1283  * connections.
1284  * @return true if a next file exists.
1285  */
selectNextTaggedFile()1286 bool Kid3Form::selectNextTaggedFile()
1287 {
1288   return nextFile(true, true);
1289 }
1290 
1291 /**
1292  * Select the previous tagged file as the current file.
1293  * Same as previousFile() with default arguments, provided for functor-based
1294  * connections.
1295  * @return true if a previous file exists.
1296  */
selectPreviousTaggedFile()1297 bool Kid3Form::selectPreviousTaggedFile()
1298 {
1299   return previousFile(true, true);
1300 }
1301 
1302 /**
1303  * Get frame table which is currently in editing state.
1304  * The returned frame table can be used to restore the editing state after
1305  * changing the current file.
1306  * @return frame table which is in editing state, 0 if none.
1307  */
getEditingFrameTable() const1308 FrameTable* Kid3Form::getEditingFrameTable() const
1309 {
1310   if (QWidget* focusWidget = QApplication::focusWidget()) {
1311     FOR_ALL_TAGS(tagNr) {
1312       if (m_frameTable[tagNr]->getCurrentEditor() == focusWidget) {
1313         return m_frameTable[tagNr];
1314       }
1315     }
1316   }
1317   return nullptr;
1318 }
1319 
1320 /**
1321  * Set the root index of the file list.
1322  *
1323  * @param index root index of directory in file proxy model
1324  */
setFileRootIndex(const QModelIndex & index)1325 void Kid3Form::setFileRootIndex(const QModelIndex& index)
1326 {
1327   if (index.isValid()) {
1328     m_fileListBox->setRootIndex(index);
1329     m_fileListBox->scrollTo(m_fileListBox->currentIndex());
1330   }
1331 }
1332 
1333 /**
1334  * Set the root index of the directory list.
1335  *
1336  * @param index root index of directory in directory model
1337  */
setDirRootIndex(const QModelIndex & index)1338 void Kid3Form::setDirRootIndex(const QModelIndex& index)
1339 {
1340   if (index.isValid()) {
1341     m_dirListBox->setRootIndex(index);
1342   }
1343 }
1344 
1345 /**
1346  * Set a widget to be displayed at the left side instead of the file lists.
1347  * @param widget widget to be shown at the left side
1348  */
setLeftSideWidget(QWidget * widget)1349 void Kid3Form::setLeftSideWidget(QWidget* widget)
1350 {
1351   int idx = m_leftSideWidget->addWidget(widget);
1352   m_leftSideWidget->setCurrentIndex(idx);
1353 }
1354 
1355 /**
1356  * Remove widget set with setLeftSideWidget().
1357  *
1358  * The widget will not be deleted.
1359  *
1360  * @param widget widget to be removed
1361  */
removeLeftSideWidget(QWidget * widget)1362 void Kid3Form::removeLeftSideWidget(QWidget* widget)
1363 {
1364   m_leftSideWidget->removeWidget(widget);
1365 }
1366 
1367 /**
1368  * Copy tags using QAction::data().
1369  * The source and destination tag numbers are taken from the first two bytes
1370  * in QAction::data().toByteArray() if the sender() is a QAction.
1371  */
copyTagsActionData()1372 void Kid3Form::copyTagsActionData()
1373 {
1374   if (auto action = qobject_cast<QAction*>(sender())) {
1375     QByteArray ba = action->data().toByteArray();
1376     if (ba.size() == 2) {
1377       Frame::TagNumber srcTagNr = Frame::tagNumberCast(ba.at(0));
1378       Frame::TagNumber dstTagNr = Frame::tagNumberCast(ba.at(1));
1379       if (srcTagNr != Frame::Tag_NumValues &&
1380           dstTagNr != Frame::Tag_NumValues) {
1381         m_app->copyTag(srcTagNr, dstTagNr);
1382       }
1383     }
1384   }
1385 }
1386