tageditor.cpp 27.4 KB
Newer Older
1 2
/***************************************************************************
    begin                : Sat Sep 7 2002
3
    copyright            : (C) 2002 - 2004 by Scott Wheeler
4
    email                : wheeler@kde.org
5 6 7 8 9 10 11 12 13 14 15
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

16 17 18 19
#include "tageditor.h"
#include "collectionlist.h"
#include "playlistitem.h"
#include "tag.h"
20
#include "actioncollection.h"
21
#include "tagtransactionmanager.h"
22

23 24 25 26
#include <kcombobox.h>
#include <klineedit.h>
#include <knuminput.h>
#include <keditcl.h>
27
#include <kmessagebox.h>
28
#include <kconfig.h>
29 30
#include <klocale.h>
#include <kdebug.h>
31
#include <kiconloader.h>
32
#include <kactionclasses.h>
33 34

#include <qlabel.h>
35
#include <qapplication.h>
36
#include <qcheckbox.h>
37 38
#include <qlayout.h>
#include <qdir.h>
Scott Wheeler's avatar
Scott Wheeler committed
39
#include <qvalidator.h>
40
#include <qtooltip.h>
41
#include <qeventloop.h>
Laurent Montel's avatar
Laurent Montel committed
42 43 44 45 46 47 48 49
#include <q3dict.h>
//Added by qt3to4:
#include <QEvent>
#include <QKeyEvent>
#include <QHBoxLayout>
#include <QBoxLayout>
#include <QShowEvent>
#include <QVBoxLayout>
50

51 52
#include <id3v1genres.h>

Scott Wheeler's avatar
Scott Wheeler committed
53 54
#undef KeyRelease

Scott Wheeler's avatar
Scott Wheeler committed
55 56 57 58
class FileNameValidator : public QValidator
{
public:
    FileNameValidator(QObject *parent, const char *name = 0) :
59
        QValidator(parent, name) {}
Scott Wheeler's avatar
Scott Wheeler committed
60 61 62

    virtual void fixup(QString &s) const
    {
63
        s.remove('/');
Scott Wheeler's avatar
Scott Wheeler committed
64 65 66 67
    }

    virtual State validate(QString &s, int &) const
    {
68 69 70
        if(s.find('/') != -1)
           return Invalid;
        return Acceptable;
Scott Wheeler's avatar
Scott Wheeler committed
71 72 73
    }
};

74
#if 0 // Qt4 porting: this needs to be in the ::event() for m_fileNameBox (i.e. it needs a KLineEdit-derived class)
75 76 77 78
class FileBoxToolTip : public QToolTip
{
public:
    FileBoxToolTip(TagEditor *editor, QWidget *widget) :
79
        QToolTip(widget), m_editor(editor) {}
80 81 82
protected:
    virtual void maybeTip(const QPoint &)
    {
83
        tip(parentWidget()->rect(), m_editor->items().first()->file().absFilePath());
84 85 86 87
    }
private:
    TagEditor *m_editor;
};
88
#endif
89

90 91 92 93
class FixedHLayout : public QHBoxLayout
{
public:
    FixedHLayout(QWidget *parent, int margin = 0, int spacing = -1, const char *name = 0) :
94 95
        QHBoxLayout(parent, margin, spacing, name),
        m_width(-1) {}
96
    FixedHLayout(QLayout *parentLayout, int spacing = -1, const char *name = 0) :
97 98
        QHBoxLayout(parentLayout, spacing, name),
        m_width(-1) {}
99 100
    void setWidth(int w = -1)
    {
101
        m_width = w == -1 ? QHBoxLayout::minimumSize().width() : w;
102 103 104
    }
    virtual QSize minimumSize() const
    {
105 106 107
        QSize s = QHBoxLayout::minimumSize();
        s.setWidth(m_width);
        return s;
108 109 110 111 112
    }
private:
    int m_width;
};

113 114 115 116
class CollectionObserver : public PlaylistObserver
{
public:
    CollectionObserver(TagEditor *parent) :
117 118
        PlaylistObserver(CollectionList::instance()),
        m_parent(parent)
119 120 121 122 123
    {
    }

    virtual void updateData()
    {
124 125
        if(m_parent && m_parent->m_currentPlaylist && m_parent->isVisible())
            m_parent->slotSetItems(m_parent->m_currentPlaylist->selectedItems());
126 127 128 129 130 131 132 133
    }

    virtual void updateCurrent() {}

private:
    TagEditor *m_parent;
};

134 135 136 137
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

138 139
TagEditor::TagEditor(QWidget *parent, const char *name) :
    QWidget(parent, name),
140
    m_currentPlaylist(0),
141 142
    m_observer(0),
    m_performingSave(false)
143
{
144
    setupActions();
145 146
    setupLayout();
    readConfig();
147
    m_dataChanged = false;
148
    m_collectionChanged = false;
149 150 151 152
}

TagEditor::~TagEditor()
{
153
    delete m_observer;
154 155 156
    saveConfig();
}

157 158 159 160 161
void TagEditor::setupObservers()
{
    m_observer = new CollectionObserver(this);
}

162 163 164 165
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

166
void TagEditor::slotSetItems(const PlaylistItemList &list)
167
{
168
    if(m_performingSave)
169
        return;
170

171 172 173 174 175
    // Store the playlist that we're setting because saveChangesPrompt
    // can delete the PlaylistItems in list.

    Playlist *itemPlaylist = 0;
    if(!list.isEmpty())
176
        itemPlaylist = list.first()->playlist();
177 178 179

    bool hadPlaylist = m_currentPlaylist != 0;

180
    saveChangesPrompt();
181 182

    if(m_currentPlaylist) {
183 184
        disconnect(m_currentPlaylist, SIGNAL(signalAboutToRemove(PlaylistItem *)),
                   this, SLOT(slotItemRemoved(PlaylistItem *)));
185 186
    }

187
    if(hadPlaylist && !m_currentPlaylist || !itemPlaylist) {
188 189
        m_currentPlaylist = 0;
        m_items.clear();
190 191
    }
    else {
192
        m_currentPlaylist = itemPlaylist;
193

194
        // We can't use list here, it may not be valid
195

196
        m_items = itemPlaylist->selectedItems();
197
    }
198 199

    if(m_currentPlaylist) {
200 201 202
        connect(m_currentPlaylist, SIGNAL(signalAboutToRemove(PlaylistItem *)),
                this, SLOT(slotItemRemoved(PlaylistItem *)));
        connect(m_currentPlaylist, SIGNAL(destroyed()), this, SLOT(slotPlaylistRemoved()));
203 204
    }

205
    if(isVisible())
206
        slotRefresh();
207
    else
208
        m_collectionChanged = true;
209 210
}

211
void TagEditor::slotRefresh()
212
{
213
    // This method takes the list of currently selected m_items and tries to
214 215
    // figure out how to show that in the tag editor.  The current strategy --
    // the most common case -- is to just process the first item.  Then we
216
    // check after that to see if there are other m_items and adjust accordingly.
217

218
    if(m_items.isEmpty() || !m_items.first()->file().tag()) {
219 220 221
        slotClear();
        setEnabled(false);
        return;
222
    }
223

224 225
    setEnabled(true);

226
    PlaylistItem *item = m_items.first();
227 228

    Q_ASSERT(item);
229

230
    Tag *tag = item->file().tag();
231

232 233
    QFileInfo fi(item->file().absFilePath());
    if(!fi.isWritable() && m_items.count() == 1)
234
        setEnabled(false);
235

236
    m_artistNameBox->setEditText(tag->artist());
237
    m_trackNameBox->setText(tag->title());
238
    m_albumNameBox->setEditText(tag->album());
239

240
    m_fileNameBox->setText(item->file().fileInfo().fileName());
241 242
    //// TODO: new FileBoxToolTip(this, m_fileNameBox);

243
    m_bitrateBox->setText(QString::number(tag->bitrate()));
244 245
    m_lengthBox->setText(tag->lengthString());

246
    if(m_genreList.findIndex(tag->genre()) >= 0)
247
        m_genreBox->setCurrentItem(m_genreList.findIndex(tag->genre()) + 1);
248
    else {
249 250
        m_genreBox->setCurrentItem(0);
        m_genreBox->setEditText(tag->genre());
251
    }
252

253
    m_trackSpin->setValue(tag->track());
254
    m_yearSpin->setValue(tag->year());
255

256
    m_commentBox->setText(tag->comment());
257

258
    // Start at the second item, since we've already processed the first.
259

260 261 262 263
    PlaylistItemList::Iterator it = m_items.begin();
    ++it;

    // If there is more than one item in the m_items that we're dealing with...
264

265
    if(it != m_items.end()) {
266

267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
        Q3ValueListIterator<QWidget *> hideIt = m_hideList.begin();
        for(; hideIt != m_hideList.end(); ++hideIt)
            (*hideIt)->hide();

        BoxMap::Iterator boxIt = m_enableBoxes.begin();
        for(; boxIt != m_enableBoxes.end(); boxIt++) {
            (*boxIt)->setChecked(true);
            (*boxIt)->show();
        }

        // Yep, this is ugly.  Loop through all of the files checking to see
        // if their fields are the same.  If so, by default, enable their
        // checkbox.

        // Also, if there are more than 50 m_items, don't scan all of them.

        if(m_items.count() > 50) {
            m_enableBoxes[m_artistNameBox]->setChecked(false);
            m_enableBoxes[m_trackNameBox]->setChecked(false);
            m_enableBoxes[m_albumNameBox]->setChecked(false);
            m_enableBoxes[m_genreBox]->setChecked(false);
            m_enableBoxes[m_trackSpin]->setChecked(false);
            m_enableBoxes[m_yearSpin]->setChecked(false);
            m_enableBoxes[m_commentBox]->setChecked(false);
        }
        else {
            for(; it != m_items.end(); ++it) {
                tag = (*it)->file().tag();

                if(tag) {

                    if(m_artistNameBox->currentText() != tag->artist() &&
                       m_enableBoxes.contains(m_artistNameBox))
                    {
                        m_artistNameBox->lineEdit()->clear();
                        m_enableBoxes[m_artistNameBox]->setChecked(false);
                    }
                    if(m_trackNameBox->text() != tag->title() &&
                       m_enableBoxes.contains(m_trackNameBox))
                    {
                        m_trackNameBox->clear();
                        m_enableBoxes[m_trackNameBox]->setChecked(false);
                    }
                    if(m_albumNameBox->currentText() != tag->album() &&
                       m_enableBoxes.contains(m_albumNameBox))
                    {
                        m_albumNameBox->lineEdit()->clear();
                        m_enableBoxes[m_albumNameBox]->setChecked(false);
                    }
                    if(m_genreBox->currentText() != tag->genre() &&
                       m_enableBoxes.contains(m_genreBox))
                    {
                        m_genreBox->lineEdit()->clear();
                        m_enableBoxes[m_genreBox]->setChecked(false);
                    }
                    if(m_trackSpin->value() != tag->track() &&
                       m_enableBoxes.contains(m_trackSpin))
                    {
                        m_trackSpin->setValue(0);
                        m_enableBoxes[m_trackSpin]->setChecked(false);
                    }
                    if(m_yearSpin->value() != tag->year() &&
                       m_enableBoxes.contains(m_yearSpin))
                    {
                        m_yearSpin->setValue(0);
                        m_enableBoxes[m_yearSpin]->setChecked(false);
                    }
                    if(m_commentBox->text() != tag->comment() &&
                       m_enableBoxes.contains(m_commentBox))
                    {
                        m_commentBox->clear();
                        m_enableBoxes[m_commentBox]->setChecked(false);
                    }
                }
            }
        }
343
    }
344
    else {
345
        // Clean up in the case that we are only handling one item.
346

347 348 349
        Q3ValueListIterator<QWidget *> showIt = m_hideList.begin();
        for(; showIt != m_hideList.end(); ++showIt)
            (*showIt)->show();
Scott Wheeler's avatar
Scott Wheeler committed
350

351 352 353 354 355
        BoxMap::iterator boxIt = m_enableBoxes.begin();
        for(; boxIt != m_enableBoxes.end(); boxIt++) {
            (*boxIt)->setChecked(true);
            (*boxIt)->hide();
        }
356 357
    }
    m_dataChanged = false;
358 359
}

360
void TagEditor::slotClear()
361
{
362 363 364 365 366 367 368 369 370
    m_artistNameBox->lineEdit()->clear();
    m_trackNameBox->clear();
    m_albumNameBox->lineEdit()->clear();
    m_genreBox->setCurrentItem(0);
    m_fileNameBox->clear();
    m_trackSpin->setValue(0);
    m_yearSpin->setValue(0);
    m_lengthBox->clear();
    m_bitrateBox->clear();
371
    m_commentBox->clear();
372 373
}

374
void TagEditor::slotUpdateCollection()
375
{
376
    if(isVisible())
377
        updateCollection();
378
    else
379
        m_collectionChanged = true;
380 381 382 383 384
}

void TagEditor::updateCollection()
{
    m_collectionChanged = false;
385

386 387 388
    CollectionList *list = CollectionList::instance();

    if(!list)
389 390
        return;

391
    QStringList artistList = list->uniqueSet(CollectionList::Artists);
392
    artistList.sort();
393 394
    m_artistNameBox->clear();
    m_artistNameBox->insertStringList(artistList);
395 396 397
    m_artistNameBox->completionObject()->setItems(artistList);

    QStringList albumList = list->uniqueSet(CollectionList::Albums);
398
    albumList.sort();
399 400
    m_albumNameBox->clear();
    m_albumNameBox->insertStringList(albumList);
401
    m_albumNameBox->completionObject()->setItems(albumList);
402

403
    // Merge the list of genres found in tags with the standard ID3v1 set.
404

405
    StringHash genreHash;
406 407

    m_genreList = list->uniqueSet(CollectionList::Genres);
408

409
    for(QStringList::ConstIterator it = m_genreList.begin(); it != m_genreList.end(); ++it)
410
        genreHash.insert(*it);
411 412 413

    TagLib::StringList genres = TagLib::ID3v1::genreList();

414
    for(TagLib::StringList::ConstIterator it = genres.begin(); it != genres.end(); ++it)
415
        genreHash.insert(TStringToQString((*it)));
416

417
    m_genreList = genreHash.values();
418 419
    m_genreList.sort();

420 421 422
    m_genreBox->clear();
    m_genreBox->insertItem(QString::null);
    m_genreBox->insertStringList(m_genreList);
423
    m_genreBox->completionObject()->setItems(m_genreList);
424 425 426 427 428 429
}

////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////

430
void TagEditor::readConfig()
431
{
432
    // combo box completion modes
433

434 435
    KConfigGroup config(KGlobal::config(), "TagEditor");
    if(m_artistNameBox && m_albumNameBox) {
436 437 438
        readCompletionMode(&config, m_artistNameBox, "ArtistNameBoxMode");
        readCompletionMode(&config, m_albumNameBox, "AlbumNameBoxMode");
        readCompletionMode(&config, m_genreBox, "GenreBoxMode");
439
    }
440

441
    bool show = config.readEntry("Show", false);
442
    ActionCollection::action<KToggleAction>("showEditor")->setChecked(show);
443 444
    setShown(show);

445
    TagLib::StringList genres = TagLib::ID3v1::genreList();
446

447
    for(TagLib::StringList::ConstIterator it = genres.begin(); it != genres.end(); ++it)
448
        m_genreList.append(TStringToQString((*it)));
449
    m_genreList.sort();
450

451 452 453
    m_genreBox->clear();
    m_genreBox->insertItem(QString::null);
    m_genreBox->insertStringList(m_genreList);
454 455 456
    m_genreBox->completionObject()->setItems(m_genreList);
}

457
void TagEditor::readCompletionMode(KConfigBase *config, KComboBox *box, const QString &key)
458 459
{
    KGlobalSettings::Completion mode =
460
        KGlobalSettings::Completion(config->readEntry(key, (int)KGlobalSettings::CompletionAuto));
461 462

    box->setCompletionMode(mode);
463
}
464

465 466
void TagEditor::saveConfig()
{
467
    // combo box completion modes
468

469 470 471
    KConfigGroup config(KGlobal::config(), "TagEditor");

    if(m_artistNameBox && m_albumNameBox) {
472 473 474
        config.writeEntry("ArtistNameBoxMode", (int)m_artistNameBox->completionMode());
        config.writeEntry("AlbumNameBoxMode", (int)m_albumNameBox->completionMode());
        config.writeEntry("GenreBoxMode", (int)m_genreBox->completionMode());
475
    }
476
    config.writeEntry("Show", ActionCollection::action<KToggleAction>("showEditor")->isChecked());
477
}
478

479 480
void TagEditor::setupActions()
{
481
    KToggleAction *show = new KToggleAction(i18n("Show &Tag Editor"), "edit", 0, ActionCollection::actions(), "showEditor");
482 483 484
    show->setCheckedState(i18n("Hide &Tag Editor"));
    connect(show, SIGNAL(toggled(bool)), this, SLOT(setShown(bool)));

485
    new KAction(i18n("&Save"), "filesave", "CTRL+t", this, SLOT(slotSave()), ActionCollection::actions(), "saveItem");
486 487
}

488 489
void TagEditor::setupLayout()
{
490
    static const int horizontalSpacing = 12;
491
    static const int verticalSpacing = 2;
492

493
    QHBoxLayout *layout = new QHBoxLayout(this, 6, horizontalSpacing);
494 495 496 497 498 499 500 501 502 503 504 505 506 507

    //////////////////////////////////////////////////////////////////////////////
    // define two columns of the bottem layout
    //////////////////////////////////////////////////////////////////////////////
    QVBoxLayout *leftColumnLayout = new QVBoxLayout(layout, verticalSpacing);
    QVBoxLayout *rightColumnLayout = new QVBoxLayout(layout, verticalSpacing);

    layout->setStretchFactor(leftColumnLayout, 2);
    layout->setStretchFactor(rightColumnLayout, 3);

    //////////////////////////////////////////////////////////////////////////////
    // put stuff in the left column -- all of the field names are class wide
    //////////////////////////////////////////////////////////////////////////////
    { // just for organization
508 509 510 511 512

        m_artistNameBox = new KComboBox( true, this );
        m_artistNameBox->setObjectName( "artistNameBox" );
        m_artistNameBox->setCompletionMode(KGlobalSettings::CompletionAuto);
        addItem(i18n("&Artist name:"), m_artistNameBox, leftColumnLayout, "personal");
513

514 515
        m_trackNameBox = new KLineEdit(this);
        m_trackNameBox->setObjectName( "trackNameBox" );
516
        addItem(i18n("&Track name:"), m_trackNameBox, leftColumnLayout, "player_play");
517

518 519 520 521
        m_albumNameBox = new KComboBox( true, this );
        m_albumNameBox->setObjectName( "albumNameBox" );
        m_albumNameBox->setCompletionMode(KGlobalSettings::CompletionAuto);
        addItem(i18n("Album &name:"), m_albumNameBox, leftColumnLayout, "cdrom_unmount");
522

Laurent Montel's avatar
Laurent Montel committed
523 524
        m_genreBox = new KComboBox( true, this );
        m_genreBox->setObjectName( "genreBox" );
525
        addItem(i18n("&Genre:"), m_genreBox, leftColumnLayout, "knotify");
526 527

        // this fills the space at the bottem of the left column
Scott Wheeler's avatar
Scott Wheeler committed
528
        leftColumnLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum,
529
                                                  QSizePolicy::Expanding));
530 531 532 533 534 535
    }
    //////////////////////////////////////////////////////////////////////////////
    // put stuff in the right column
    //////////////////////////////////////////////////////////////////////////////
    { // just for organization

536 537
        QHBoxLayout *fileNameLayout = new QHBoxLayout(rightColumnLayout,
                                                      horizontalSpacing);
Scott Wheeler's avatar
Scott Wheeler committed
538

539 540
        m_fileNameBox = new KLineEdit(this);
        m_fileNameBox->setObjectName( "fileNameBox" );
541
        m_fileNameBox->setValidator(new FileNameValidator(m_fileNameBox));
Scott Wheeler's avatar
Scott Wheeler committed
542

543 544 545 546 547 548 549 550 551 552
        QLabel *fileNameIcon = new QLabel(this);
        fileNameIcon->setPixmap(SmallIcon("sound"));
        QWidget *fileNameLabel = addHidden(new QLabel(m_fileNameBox, i18n("&File name:"), this));

        fileNameLayout->addWidget(addHidden(fileNameIcon));
        fileNameLayout->addWidget(fileNameLabel);
        fileNameLayout->setStretchFactor(fileNameIcon, 0);
        fileNameLayout->setStretchFactor(fileNameLabel, 0);
        fileNameLayout->insertStretch(-1, 1);
        rightColumnLayout->addWidget(addHidden(m_fileNameBox));
553 554

        { // lay out the track row
555
            FixedHLayout *trackRowLayout = new FixedHLayout(rightColumnLayout,
556 557
                                                            horizontalSpacing);

558 559
            m_trackSpin = new KIntSpinBox(0, 9999, 1, 0, this);
            m_trackSpin->setObjectName( "trackSpin" );
560 561 562 563 564 565
            addItem(i18n("T&rack:"), m_trackSpin, trackRowLayout);
            m_trackSpin->installEventFilter(this);

            trackRowLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding,
                                                    QSizePolicy::Minimum));

566 567
            m_yearSpin = new KIntSpinBox(0, 9999, 1, 0, this );
            m_yearSpin->setObjectName( "yearSpin" );
568 569 570 571 572 573 574
            addItem(i18n("&Year:"), m_yearSpin, trackRowLayout);
            m_yearSpin->installEventFilter(this);

            trackRowLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding,
                                                    QSizePolicy::Minimum));

            trackRowLayout->addWidget(addHidden(new QLabel(i18n("Length:"), this)));
575 576
            m_lengthBox = new KLineEdit(this);
            m_lengthBox->setObjectName( "lengthBox" );
577 578 579 580 581 582 583 584 585 586 587
            // addItem(i18n("Length:"), m_lengthBox, trackRowLayout);
            m_lengthBox->setMinimumWidth(fontMetrics().width("00:00") + trackRowLayout->spacing());
            m_lengthBox->setMaximumWidth(50);
            m_lengthBox->setAlignment(Qt::AlignRight);
            m_lengthBox->setReadOnly(true);
            trackRowLayout->addWidget(addHidden(m_lengthBox));

            trackRowLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding,
                                                    QSizePolicy::Minimum));

            trackRowLayout->addWidget(addHidden(new QLabel(i18n("Bitrate:"), this)));
588 589
            m_bitrateBox = new KLineEdit(this);
            m_bitrateBox->setObjectName( "bitrateBox" );
590 591 592 593 594 595 596 597
            // addItem(i18n("Bitrate:"), m_bitrateBox, trackRowLayout);
            m_bitrateBox->setMinimumWidth(fontMetrics().width("000") + trackRowLayout->spacing());
            m_bitrateBox->setMaximumWidth(50);
            m_bitrateBox->setAlignment(Qt::AlignRight);
            m_bitrateBox->setReadOnly(true);
            trackRowLayout->addWidget(addHidden(m_bitrateBox));

            trackRowLayout->setWidth();
598 599
        }

David Faure's avatar
David Faure committed
600 601
        m_commentBox = new KEdit(this);
        m_commentBox->setObjectName( "commentBox" );
602 603
        m_commentBox->setTextFormat(Qt::PlainText);
        addItem(i18n("&Comment:"), m_commentBox, rightColumnLayout, "edit");
604
        fileNameLabel->setMinimumHeight(m_trackSpin->height());
605

606 607
    }

Scott Wheeler's avatar
Scott Wheeler committed
608
    connect(m_artistNameBox, SIGNAL(textChanged(const QString&)),
609
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
610 611

    connect(m_trackNameBox, SIGNAL(textChanged(const QString&)),
612
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
613 614

    connect(m_albumNameBox, SIGNAL(textChanged(const QString&)),
615
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
616 617

    connect(m_genreBox, SIGNAL(activated(int)),
618
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
619 620

    connect(m_genreBox, SIGNAL(textChanged(const QString&)),
621
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
622 623

    connect(m_fileNameBox, SIGNAL(textChanged(const QString&)),
624
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
625 626

    connect(m_yearSpin, SIGNAL(valueChanged(int)),
627
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
628 629

    connect(m_trackSpin, SIGNAL(valueChanged(int)),
630
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
631 632

    connect(m_commentBox, SIGNAL(textChanged()),
633
            this, SLOT(slotDataChanged()));
634 635 636 637
}

void TagEditor::save(const PlaylistItemList &list)
{
638
    if(!list.isEmpty() && m_dataChanged) {
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655

        KApplication::setOverrideCursor(Qt::WaitCursor);
        m_dataChanged = false;
        m_performingSave = true;

        // The list variable can become corrupted if the playlist holding its
        // items dies, which is possible as we edit tags.  So we need to copy
        // the end marker.

        PlaylistItemList::ConstIterator end = list.end();

        for(PlaylistItemList::ConstIterator it = list.begin(); it != end; /* Deliberatly missing */ ) {

            // Process items before we being modifying tags, as the dynamic
            // playlists will try to modify the file we edit if the tag changes
            // due to our alterations here.

656
            qApp->processEvents(QEventLoop::ExcludeUserInput);
657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707

            PlaylistItem *item = *it;

            // The playlist can be deleted from under us if this is the last
            // item and we edit it so that it doesn't match the search, which
            // means we can't increment the iterator, so let's do it now.

            ++it;

            QString fileName = item->file().fileInfo().dirPath() + QDir::separator() +
                               m_fileNameBox->text();
            if(list.count() > 1)
                fileName = item->file().fileInfo().absFilePath();

            Tag *tag = TagTransactionManager::duplicateTag(item->file().tag(), fileName);

            // A bit more ugliness.  If there are multiple files that are
            // being modified, they each have a "enabled" checkbox that
            // says if that field is to be respected for the multiple
            // files.  We have to check to see if that is enabled before
            // each field that we write.

            if(m_enableBoxes[m_artistNameBox]->isOn())
                tag->setArtist(m_artistNameBox->currentText());
            if(m_enableBoxes[m_trackNameBox]->isOn())
                tag->setTitle(m_trackNameBox->text());
            if(m_enableBoxes[m_albumNameBox]->isOn())
                tag->setAlbum(m_albumNameBox->currentText());
            if(m_enableBoxes[m_trackSpin]->isOn()) {
                if(m_trackSpin->text().isEmpty())
                    m_trackSpin->setValue(0);
                tag->setTrack(m_trackSpin->value());
            }
            if(m_enableBoxes[m_yearSpin]->isOn()) {
                if(m_yearSpin->text().isEmpty())
                    m_yearSpin->setValue(0);
                tag->setYear(m_yearSpin->value());
            }
            if(m_enableBoxes[m_commentBox]->isOn())
                tag->setComment(m_commentBox->text());

            if(m_enableBoxes[m_genreBox]->isOn())
                tag->setGenre(m_genreBox->currentText());

            TagTransactionManager::instance()->changeTagOnItem(item, tag);
        }

        TagTransactionManager::instance()->commit();
        CollectionList::instance()->dataChanged();
        m_performingSave = false;
        KApplication::restoreOverrideCursor();
708 709 710 711 712
    }
}

void TagEditor::saveChangesPrompt()
{
713
    if(!isVisible() || !m_dataChanged || m_items.isEmpty())
714
        return;
715 716 717

    QStringList files;

Laurent Montel's avatar
Laurent Montel committed
718
    for(PlaylistItemList::Iterator it = m_items.begin(); it != m_items.end(); ++it)
719
        files.append((*it)->file().absFilePath());
720 721

    if(KMessageBox::questionYesNoList(this,
722 723 724 725 726 727
                                      i18n("Do you want to save your changes to:\n"),
                                      files,
                                      i18n("Save Changes"),
                                      KStdGuiItem::save(),
                                      KStdGuiItem::discard(),
                                      "tagEditor_showSaveChangesBox") == KMessageBox::Yes)
728
    {
729
        save(m_items);
730 731 732
    }
}

733
void TagEditor::addItem(const QString &text, QWidget *item, QBoxLayout *layout, const QString &iconName)
734 735
{
    if(!item || !layout)
736
        return;
737

738
    QLabel *label = new QLabel(item, text, this);
739
    QLabel *iconLabel = new QLabel(item, 0, this);
740

741
    if(!iconName.isNull())
742 743
        iconLabel->setPixmap(SmallIcon(iconName));

744 745 746 747 748 749
    QCheckBox *enableBox = new QCheckBox(i18n("Enable"), this);
    enableBox->setChecked(true);

    label->setMinimumHeight(enableBox->height());

    if(layout->direction() == QBoxLayout::LeftToRight) {
750
        layout->addWidget(iconLabel);
751 752 753
        layout->addWidget(label);
        layout->addWidget(item);
        layout->addWidget(enableBox);
754 755
    }
    else {
756 757 758 759 760
        QHBoxLayout *l = new QHBoxLayout(layout);

        l->addWidget(iconLabel);
        l->addWidget(label);
        l->setStretchFactor(label, 1);
761

762
        l->insertStretch(-1, 1);
763

764 765
        l->addWidget(enableBox);
        l->setStretchFactor(enableBox, 0);
766

767
        layout->addWidget(item);
768 769 770 771 772
    }

    enableBox->hide();

    connect(enableBox, SIGNAL(toggled(bool)), item, SLOT(setEnabled(bool)));
773
    m_enableBoxes.insert(item, enableBox);
774 775
}

776 777
void TagEditor::showEvent(QShowEvent *e)
{
778
    if(m_collectionChanged) {
779 780
        updateCollection();
        slotRefresh();
781
    }
782

783 784 785
    QWidget::showEvent(e);
}

786 787
bool TagEditor::eventFilter(QObject *watched, QEvent *e)
{
788 789
    QKeyEvent *ke = static_cast<QKeyEvent*>(e);
    if(watched->inherits("QSpinBox") && e->type() == QEvent::KeyRelease && ke->state() == 0)
790
        slotDataChanged();
791 792 793 794

    return false;
}

795 796 797 798
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////

799
void TagEditor::slotDataChanged(bool c)
800
{
801
    m_dataChanged = c;
802 803
}

804 805 806 807
void TagEditor::slotItemRemoved(PlaylistItem *item)
{
    m_items.remove(item);
    if(m_items.isEmpty())
808
        slotRefresh();
809 810
}

811 812 813
void TagEditor::slotPlaylistDestroyed(Playlist *p)
{
    if(m_currentPlaylist == p) {
814 815
        m_currentPlaylist = 0;
        slotSetItems(PlaylistItemList());
816 817 818
    }
}

819
#include "tageditor.moc"
820 821

// vim: set et sw=4 tw=0 sta: