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