1 /*
2  * Cantata
3  *
4  * Copyright (c) 2011-2020 Craig Drummond <craig.p.drummond@gmail.com>
5  *
6  * ----
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more detailexampleSong.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; see the file COPYING.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23 
24 #include "albumdetailsdialog.h"
25 #include "audiocddevice.h"
26 #include "models/musiclibraryitemsong.h"
27 #include "support/messagebox.h"
28 #include "support/inputdialog.h"
29 #include "models/devicesmodel.h"
30 #include "models/mpdlibrarymodel.h"
31 #include "cdalbum.h"
32 #include "widgets/icons.h"
33 #include "gui/coverdialog.h"
34 #include "widgets/basicitemdelegate.h"
35 #include "support/lineedit.h"
36 #include <QMenu>
37 #include <QStyledItemDelegate>
38 #include <QMouseEvent>
39 #include <QSpinBox>
40 #include <algorithm>
41 
42 enum Columns {
43     COL_TRACK,
44     COL_ARTIST,
45     COL_TITLE
46 };
47 
48 class EditorDelegate : public BasicItemDelegate
49 {
50 public:
EditorDelegate(QObject * parent=0)51     EditorDelegate(QObject *parent=0) : BasicItemDelegate(parent) { }
52 
createEditor(QWidget * parent,const QStyleOptionViewItem & option,const QModelIndex & index) const53     QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
54         Q_UNUSED(option)
55         if (COL_TRACK==index.column()) {
56             QSpinBox *editor = new QSpinBox(parent);
57             editor->setMinimum(0);
58             editor->setMaximum(500);
59             return editor;
60         } else {
61             parent->setProperty("cantata-delegate", true);
62             return new LineEdit(parent);
63         }
64     }
65 
sizeHint(const QStyleOptionViewItem & option,const QModelIndex & index) const66     QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const {
67         return QStyledItemDelegate::sizeHint(option, index)+QSize(0, 4);
68     }
69 
setEditorData(QWidget * editor,const QModelIndex & index) const70     void setEditorData(QWidget *editor, const QModelIndex &index) const {
71         if (COL_TRACK==index.column()) {
72             static_cast<QSpinBox*>(editor)->setValue(index.model()->data(index, Qt::EditRole).toInt());
73         } else {
74             static_cast<LineEdit*>(editor)->setText(index.model()->data(index, Qt::EditRole).toString());
75         }
76     }
setModelData(QWidget * editor,QAbstractItemModel * model,const QModelIndex & index) const77     void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const {
78         if (COL_TRACK==index.column()) {
79             model->setData(index, static_cast<QSpinBox*>(editor)->value(), Qt::EditRole);
80         } else {
81             model->setData(index, static_cast<LineEdit*>(editor)->text().trimmed(), Qt::EditRole);
82         }
83     }
84 
updateEditorGeometry(QWidget * editor,const QStyleOptionViewItem & option,const QModelIndex & index) const85     void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const {
86         Q_UNUSED(index)
87         editor->setGeometry(option.rect);
88     }
89 };
90 
91 static int iCount=0;
92 
instanceCount()93 int AlbumDetailsDialog::instanceCount()
94 {
95     return iCount;
96 }
97 
AlbumDetailsDialog(QWidget * parent)98 AlbumDetailsDialog::AlbumDetailsDialog(QWidget *parent)
99     : Dialog(parent, "AlbumDetailsDialog")
100     , pressed(false)
101 {
102     iCount++;
103     setButtons(User1|Ok|Cancel);
104     setCaption(tr("Audio CD"));
105     setAttribute(Qt::WA_DeleteOnClose);
106 
107     QWidget *mainWidet = new QWidget(this);
108     setupUi(mainWidet);
109     setMainWidget(mainWidet);
110 
111     QSet<QString> artists;
112     QSet<QString> albumArtists;
113     QSet<QString> composers;
114     QSet<QString> albums;
115     QSet<QString> genres;
116     MpdLibraryModel::self()->getDetails(artists, albumArtists, composers, albums, genres);
117 
118     QStringList strings=albumArtists.toList();
119     strings.sort();
120     artist->clear();
121     artist->insertItems(0, strings);
122 
123     strings=composers.toList();
124     strings.sort();
125     composer->clear();
126     composer->insertItems(0, strings);
127 
128     strings=albums.toList();
129     strings.sort();
130     title->clear();
131     title->insertItems(0, strings);
132 
133     strings=genres.toList();
134     strings.sort();
135     genre->clear();
136     genre->insertItems(0, strings);
137 
138     QMenu *toolsMenu=new QMenu(this);
139     toolsMenu->addAction(tr("Apply \"Various Artists\" Workaround"), this, SLOT(applyVa()));
140     toolsMenu->addAction(tr("Revert \"Various Artists\" Workaround"), this, SLOT(revertVa()));
141     toolsMenu->addAction(tr("Capitalize"), this, SLOT(capitalise()));
142     toolsMenu->addAction(tr("Adjust Track Numbers"), this, SLOT(adjustTrackNumbers()));
143     setButtonMenu(User1, toolsMenu, InstantPopup);
144     setButtonGuiItem(User1, GuiItem(tr("Tools"), "tools-wizard"));
145     connect(singleArtist, SIGNAL(toggled(bool)), SLOT(hideArtistColumn(bool)));
146     resize(600, 600);
147 
148     int size=fontMetrics().height()*5;
149     cover->setMinimumSize(size, size);
150     cover->setMaximumSize(size, size);
151     setCover();
152     cover->installEventFilter(this);
153     tracks->setItemDelegate(new EditorDelegate(tracks));
154 }
155 
~AlbumDetailsDialog()156 AlbumDetailsDialog::~AlbumDetailsDialog()
157 {
158     iCount--;
159 }
160 
show(AudioCdDevice * dev)161 void AlbumDetailsDialog::show(AudioCdDevice *dev)
162 {
163     udi=dev->id();
164     artist->setText(dev->albumArtist());
165     composer->setText(dev->albumComposer());
166     title->setText(dev->albumName());
167     genre->setText(dev->albumGenre());
168     disc->setValue(dev->albumDisc());
169     year->setValue(dev->albumYear());
170     tracks->clear();
171     QSet<QString> artists;
172     artists.insert(dev->albumArtist());
173 
174     QList<Song> songs;
175     for (const MusicLibraryItem *i: dev->childItems()) {
176         songs.append(static_cast<const MusicLibraryItemSong *>(i)->song());
177     }
178     std::sort(songs.begin(), songs.end());
179 
180     for (const Song &s: songs) {
181         QTreeWidgetItem *item=new QTreeWidgetItem(tracks);
182         update(item, s);
183         artists.insert(s.artist);
184         item->setFlags(item->flags()|Qt::ItemIsEditable);
185     }
186 
187     singleArtist->setChecked(1==artists.count());
188     coverImage=dev->cover();
189     if (!coverImage.img.isNull()) {
190         cover->setPixmap(QPixmap::fromImage(coverImage.img.scaled(cover->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)));
191     }
192     Dialog::show();
193 }
194 
slotButtonClicked(int button)195 void AlbumDetailsDialog::slotButtonClicked(int button)
196 {
197     switch (button) {
198     case Ok: {
199         Device *dev=DevicesModel::self()->device(udi);
200         if (dev && Device::AudioCd==dev->devType()) {
201             CdAlbum cdAlbum=getAlbum();
202             for(int i=0; i<tracks->topLevelItemCount(); ++i) {
203                 cdAlbum.tracks.append(toSong(tracks->topLevelItem(i), cdAlbum));
204             }
205             static_cast<AudioCdDevice *>(dev)->setDetails(cdAlbum);
206             static_cast<AudioCdDevice *>(dev)->setCover(coverImage);
207         }
208         accept();
209         break;
210     }
211     case Cancel:
212         reject();
213         break;
214     default:
215         break;
216     }
217 
218     if (Ok==button) {
219         accept();
220     }
221 
222     Dialog::slotButtonClicked(button);
223 }
224 
hideArtistColumn(bool hide)225 void AlbumDetailsDialog::hideArtistColumn(bool hide)
226 {
227     tracks->header()->setSectionHidden(COL_ARTIST, hide);
228 }
229 
applyVa()230 void AlbumDetailsDialog::applyVa()
231 {
232     if (MessageBox::No==MessageBox::questionYesNo(this, tr("Apply \"Various Artists\" workaround?")+
233                                                         QLatin1String("\n\n")+
234                                                         tr("This will set 'Album artist' and 'Artist' to "
235                                                              "\"Various Artists\", and set 'Title' to "
236                                                              "\"TrackArtist - TrackTitle\""), tr("Apply \"Various Artists\" Workaround"),
237                                                   StdGuiItem::apply(), StdGuiItem::cancel())) {
238         return;
239     }
240 
241     CdAlbum album=getAlbum();
242     for(int i=0; i<tracks->topLevelItemCount(); ++i) {
243         QTreeWidgetItem *itm=tracks->topLevelItem(i);
244         Song s=toSong(itm, album);
245         if (s.fixVariousArtists()) {
246             update(itm, s);
247         }
248     }
249 }
250 
revertVa()251 void AlbumDetailsDialog::revertVa()
252 {
253     if (MessageBox::No==MessageBox::questionYesNo(this, tr("Revert \"Various Artists\" workaround?")+
254                                                         QLatin1String("\n\n")+
255                                                         tr("Where the 'Album artist' is the same as 'Artist' "
256                                                              "and the 'Title' is of the format \"TrackArtist - TrackTitle\", "
257                                                              "'Artist' will be taken from 'Title' and 'Title' itself will be "
258                                                              "set to just the title. e.g. \n"
259                                                              "If 'Title' is \"Wibble - Wobble\", then 'Artist' will be set to "
260                                                              "\"Wibble\" and 'Title' will be set to \"Wobble\""), tr("Revert \"Various Artists\" Workaround"),
261                                                   GuiItem(tr("Revert")), StdGuiItem::cancel())) {
262         return;
263     }
264 
265     CdAlbum album=getAlbum();
266     for(int i=0; i<tracks->topLevelItemCount(); ++i) {
267         QTreeWidgetItem *itm=tracks->topLevelItem(i);
268         Song s=toSong(itm, album);
269         if (s.revertVariousArtists()) {
270             update(itm, s);
271         }
272     }
273 }
274 
capitalise()275 void AlbumDetailsDialog::capitalise()
276 {
277     if (MessageBox::No==MessageBox::questionYesNo(this, tr("Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'?"),
278                                                   tr("Capitalize"), GuiItem(tr("Capitalize")), StdGuiItem::cancel())) {
279         return;
280     }
281 
282     CdAlbum album=getAlbum();
283     for(int i=0; i<tracks->topLevelItemCount(); ++i) {
284         QTreeWidgetItem *itm=tracks->topLevelItem(i);
285         Song s=toSong(itm, album);
286         if (s.capitalise()) {
287             update(itm, s);
288         }
289     }
290 }
291 
adjustTrackNumbers()292 void AlbumDetailsDialog::adjustTrackNumbers()
293 {
294     bool ok=false;
295     int adj=InputDialog::getInteger(tr("Adjust Track Numbers"), tr("Adjust track number by:"), 0, -500, 500, 1, 10, &ok, this);
296 
297     if (!ok || 0==adj) {
298         return;
299     }
300 
301     CdAlbum album=getAlbum();
302     for(int i=0; i<tracks->topLevelItemCount(); ++i) {
303         QTreeWidgetItem *itm=tracks->topLevelItem(i);
304         Song s=toSong(itm, album);
305         s.track+=adj;
306         update(itm, s);
307     }
308 }
309 
310 enum Roles {
311     Role_Id = Qt::UserRole,
312     Role_File,
313     Role_Time
314 };
315 
toSong(QTreeWidgetItem * i,const CdAlbum & album)316 Song AlbumDetailsDialog::toSong(QTreeWidgetItem *i, const CdAlbum &album)
317 {
318     Song s;
319     s.albumartist=album.artist;
320     s.album=album.name;
321     s.genres[0]=album.genre;
322     s.year=album.year;
323     s.disc=album.disc;
324     s.artist=singleArtist->isChecked() ? s.albumartist : i->text(COL_ARTIST);
325     if (!album.composer.isEmpty()) {
326         s.setComposer(album.composer);
327     }
328     s.title=i->text(COL_TITLE);
329     s.track=i->text(COL_TRACK).toInt();
330     s.id=i->data(0, Role_Id).toInt();
331     s.file=i->data(0, Role_File).toString();
332     s.time=i->data(0, Role_Time).toInt();
333     s.fillEmptyFields();
334     return s;
335 }
336 
getAlbum() const337 CdAlbum AlbumDetailsDialog::getAlbum() const
338 {
339     CdAlbum cdAlbum;
340     cdAlbum.artist=artist->text().trimmed();
341     cdAlbum.composer=composer->text().trimmed();
342     cdAlbum.name=title->text().trimmed();
343     cdAlbum.disc=disc->value();
344     cdAlbum.year=year->value();
345     cdAlbum.genre=genre->text().trimmed();
346     if (cdAlbum.artist.isEmpty()) {
347         cdAlbum.artist=Song::unknown();
348     }
349     if (cdAlbum.name.isEmpty()) {
350         cdAlbum.name=Song::unknown();
351     }
352     return cdAlbum;
353 }
354 
update(QTreeWidgetItem * i,const Song & s)355 void AlbumDetailsDialog::update(QTreeWidgetItem *i, const Song &s)
356 {
357     i->setText(COL_TRACK, QString::number(s.track));
358     i->setText(COL_ARTIST, s.artist);
359     i->setText(COL_TITLE, s.title);
360     i->setData(0, Role_Id, s.id);
361     i->setData(0, Role_File, s.file);
362     i->setData(0, Role_Time, s.time);
363 }
364 
setCover()365 void AlbumDetailsDialog::setCover()
366 {
367     int iconSize=cover->size().width()<=128 ? 128 : 256;
368     cover->setPixmap(Icons::self()->albumIcon(iconSize).pixmap(iconSize, iconSize).scaled(cover->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
369 }
370 
eventFilter(QObject * object,QEvent * event)371 bool AlbumDetailsDialog::eventFilter(QObject *object, QEvent *event)
372 {
373     switch(event->type()) {
374     case QEvent::MouseButtonPress:
375         if (Qt::LeftButton==static_cast<QMouseEvent *>(event)->button()) {
376             pressed=true;
377         }
378         break;
379     case QEvent::MouseButtonRelease:
380         if (pressed && Qt::LeftButton==static_cast<QMouseEvent *>(event)->button()) {
381             if (0==CoverDialog::instanceCount()) {
382                 CoverDialog *dlg=new CoverDialog(this);
383                 connect(dlg, SIGNAL(selectedCover(QImage, QString)), this, SLOT(coverSelected(QImage, QString)));
384                 Song s;
385                 s.file=AudioCdDevice::coverUrl(udi);
386                 s.artist=artist->text().trimmed();
387                 s.album=title->text().trimmed();
388                 s.type=Song::Cdda;
389                 dlg->show(s, coverImage);
390             }
391         }
392         pressed=false;
393         break;
394     default:
395         break;
396     }
397     return QObject::eventFilter(object, event);
398 }
399 
coverSelected(const QImage & img,const QString & fileName)400 void AlbumDetailsDialog::coverSelected(const QImage &img, const QString &fileName)
401 {
402     coverImage=Covers::Image(img, fileName);
403     if (!coverImage.img.isNull()) {
404         cover->setPixmap(QPixmap::fromImage(coverImage.img.scaled(cover->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)));
405     }
406 }
407 
408 #include "moc_albumdetailsdialog.cpp"
409