tageditor.cpp 27.7 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
#include <kactioncollection.h>
#include <kconfiggroup.h>
25 26 27
#include <kcombobox.h>
#include <klineedit.h>
#include <knuminput.h>
28
#include <ktextedit.h>
29
#include <kapplication.h>
30
#include <kmessagebox.h>
31
#include <kconfig.h>
32 33
#include <klocale.h>
#include <kdebug.h>
34
#include <kiconloader.h>
35
#include <kicon.h>
36
#include <ktoggleaction.h>
37
#include <kshortcut.h>
38

39
#include <QLabel>
40
#include <QApplication>
41 42
#include <QCheckBox>
#include <QDir>
43
#include <QValidator>
44
#include <QToolTip>
45
#include <QEventLoop>
Laurent Montel's avatar
Laurent Montel committed
46 47 48
#include <QKeyEvent>
#include <QHBoxLayout>
#include <QVBoxLayout>
49

50 51
#include <id3v1genres.h>

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

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

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

    virtual State validate(QString &s, int &) const
    {
Stephan Kulow's avatar
Stephan Kulow committed
70
        if(s.contains('/'))
71 72
           return Invalid;
        return Acceptable;
Scott Wheeler's avatar
Scott Wheeler committed
73 74 75
    }
};

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

92 93 94 95
class FixedHLayout : public QHBoxLayout
{
public:
    FixedHLayout(QWidget *parent, int margin = 0, int spacing = -1, const char *name = 0) :
Stephan Kulow's avatar
Stephan Kulow committed
96 97 98 99 100 101 102
        QHBoxLayout(parent),
        m_width(-1)
    {
        setMargin( margin );
        setSpacing( spacing );
        setObjectName( name );
    }
103
    FixedHLayout(QLayout *parentLayout, int spacing = -1, const char *name = 0) :
Stephan Kulow's avatar
Stephan Kulow committed
104 105 106 107 108 109 110
        QHBoxLayout(),
        m_width(-1)
    {
        parentLayout->addItem( this );
        setSpacing( spacing );
        setObjectName( name );
    }
111 112
    void setWidth(int w = -1)
    {
113
        m_width = w == -1 ? QHBoxLayout::minimumSize().width() : w;
114 115 116
    }
    virtual QSize minimumSize() const
    {
117 118 119
        QSize s = QHBoxLayout::minimumSize();
        s.setWidth(m_width);
        return s;
120 121 122 123 124
    }
private:
    int m_width;
};

125 126 127 128
class CollectionObserver : public PlaylistObserver
{
public:
    CollectionObserver(TagEditor *parent) :
129 130
        PlaylistObserver(CollectionList::instance()),
        m_parent(parent)
131 132 133 134 135
    {
    }

    virtual void updateData()
    {
136 137
        if(m_parent && m_parent->m_currentPlaylist && m_parent->isVisible())
            m_parent->slotSetItems(m_parent->m_currentPlaylist->selectedItems());
138 139 140 141 142 143 144 145
    }

    virtual void updateCurrent() {}

private:
    TagEditor *m_parent;
};

146 147 148 149
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

Dirk Mueller's avatar
Dirk Mueller committed
150 151
TagEditor::TagEditor(QWidget *parent) :
    QWidget(parent),
152
    m_currentPlaylist(0),
153 154
    m_observer(0),
    m_performingSave(false)
155
{
156
    setupActions();
157 158
    setupLayout();
    readConfig();
159
    m_dataChanged = false;
160
    m_collectionChanged = false;
161 162 163 164
}

TagEditor::~TagEditor()
{
165
    delete m_observer;
166 167 168
    saveConfig();
}

169 170 171 172 173
void TagEditor::setupObservers()
{
    m_observer = new CollectionObserver(this);
}

174 175 176 177
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

178
void TagEditor::slotSetItems(const PlaylistItemList &list)
179
{
180
    if(m_performingSave)
181
        return;
182

183 184 185 186 187
    // Store the playlist that we're setting because saveChangesPrompt
    // can delete the PlaylistItems in list.

    Playlist *itemPlaylist = 0;
    if(!list.isEmpty())
188
        itemPlaylist = list.first()->playlist();
189 190 191

    bool hadPlaylist = m_currentPlaylist != 0;

192
    saveChangesPrompt();
193 194

    if(m_currentPlaylist) {
195 196
        disconnect(m_currentPlaylist, SIGNAL(signalAboutToRemove(PlaylistItem *)),
                   this, SLOT(slotItemRemoved(PlaylistItem *)));
197 198
    }

199
    if(hadPlaylist && !m_currentPlaylist || !itemPlaylist) {
200 201
        m_currentPlaylist = 0;
        m_items.clear();
202 203
    }
    else {
204
        m_currentPlaylist = itemPlaylist;
205

206
        // We can't use list here, it may not be valid
207

208
        m_items = itemPlaylist->selectedItems();
209
    }
210 211

    if(m_currentPlaylist) {
212 213 214
        connect(m_currentPlaylist, SIGNAL(signalAboutToRemove(PlaylistItem *)),
                this, SLOT(slotItemRemoved(PlaylistItem *)));
        connect(m_currentPlaylist, SIGNAL(destroyed()), this, SLOT(slotPlaylistRemoved()));
215 216
    }

217
    if(isVisible())
218
        slotRefresh();
219
    else
220
        m_collectionChanged = true;
221 222
}

223
void TagEditor::slotRefresh()
224
{
225
    // This method takes the list of currently selected m_items and tries to
226 227
    // 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
228
    // check after that to see if there are other m_items and adjust accordingly.
229

230
    if(m_items.isEmpty() || !m_items.first()->file().tag()) {
231 232 233
        slotClear();
        setEnabled(false);
        return;
234
    }
235

236 237
    setEnabled(true);

238
    PlaylistItem *item = m_items.first();
239 240

    Q_ASSERT(item);
241

242
    Tag *tag = item->file().tag();
243

244 245
    QFileInfo fi(item->file().absFilePath());
    if(!fi.isWritable() && m_items.count() == 1)
246
        setEnabled(false);
247

248
    m_artistNameBox->setEditText(tag->artist());
249
    m_trackNameBox->setText(tag->title());
250
    m_albumNameBox->setEditText(tag->album());
251

252
    m_fileNameBox->setText(item->file().fileInfo().fileName());
253 254
    //// TODO: new FileBoxToolTip(this, m_fileNameBox);

255
    m_bitrateBox->setText(QString::number(tag->bitrate()));
256 257
    m_lengthBox->setText(tag->lengthString());

Tim Beaulen's avatar
Tim Beaulen committed
258 259
    if(m_genreList.indexOf(tag->genre()) >= 0)
        m_genreBox->setCurrentIndex(m_genreList.indexOf(tag->genre()) + 1);
260
    else {
Stephan Kulow's avatar
Stephan Kulow committed
261
        m_genreBox->setCurrentIndex(0);
262
        m_genreBox->setEditText(tag->genre());
263
    }
264

265
    m_trackSpin->setValue(tag->track());
266
    m_yearSpin->setValue(tag->year());
267

268
    m_commentBox->setPlainText(tag->comment());
269

270
    // Start at the second item, since we've already processed the first.
271

272 273 274 275
    PlaylistItemList::Iterator it = m_items.begin();
    ++it;

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

277
    if(it != m_items.end()) {
278

279 280
        foreach(QWidget *w, m_hideList)
            w->hide();
281

282 283 284
        foreach(QCheckBox *box, m_enableBoxes) {
            box->setChecked(true);
            box->show();
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 343
        }

        // 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);
                    }
344
                    if(m_commentBox->toPlainText() != tag->comment() &&
345 346 347 348 349 350 351 352
                       m_enableBoxes.contains(m_commentBox))
                    {
                        m_commentBox->clear();
                        m_enableBoxes[m_commentBox]->setChecked(false);
                    }
                }
            }
        }
353
    }
354
    else {
355
        // Clean up in the case that we are only handling one item.
356

357 358
        foreach(QWidget *w, m_hideList)
            w->show();
Scott Wheeler's avatar
Scott Wheeler committed
359

360 361 362
        foreach(QCheckBox *box, m_enableBoxes) {
            box->setChecked(true);
            box->hide();
363
        }
364 365
    }
    m_dataChanged = false;
366 367
}

368
void TagEditor::slotClear()
369
{
370 371 372
    m_artistNameBox->lineEdit()->clear();
    m_trackNameBox->clear();
    m_albumNameBox->lineEdit()->clear();
Stephan Kulow's avatar
Stephan Kulow committed
373
    m_genreBox->setCurrentIndex(0);
374 375 376 377 378
    m_fileNameBox->clear();
    m_trackSpin->setValue(0);
    m_yearSpin->setValue(0);
    m_lengthBox->clear();
    m_bitrateBox->clear();
379
    m_commentBox->clear();
380 381
}

382
void TagEditor::slotUpdateCollection()
383
{
384
    if(isVisible())
385
        updateCollection();
386
    else
387
        m_collectionChanged = true;
388 389 390 391 392
}

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

394 395 396
    CollectionList *list = CollectionList::instance();

    if(!list)
397 398
        return;

399
    QStringList artistList = list->uniqueSet(CollectionList::Artists);
400
    artistList.sort();
401
    m_artistNameBox->clear();
Stephan Kulow's avatar
Stephan Kulow committed
402
    m_artistNameBox->addItems(artistList);
403 404 405
    m_artistNameBox->completionObject()->setItems(artistList);

    QStringList albumList = list->uniqueSet(CollectionList::Albums);
406
    albumList.sort();
407
    m_albumNameBox->clear();
Stephan Kulow's avatar
Stephan Kulow committed
408
    m_albumNameBox->addItems(albumList);
409
    m_albumNameBox->completionObject()->setItems(albumList);
410

411
    // Merge the list of genres found in tags with the standard ID3v1 set.
412

413
    StringHash genreHash;
414 415

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

417
    for(QStringList::ConstIterator it = m_genreList.begin(); it != m_genreList.end(); ++it)
418
        genreHash.insert(*it);
419 420 421

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

422
    for(TagLib::StringList::ConstIterator it = genres.begin(); it != genres.end(); ++it)
423
        genreHash.insert(TStringToQString((*it)));
424

425
    m_genreList = genreHash.values();
426 427
    m_genreList.sort();

428
    m_genreBox->clear();
429
    m_genreBox->addItem(QString());
Stephan Kulow's avatar
Stephan Kulow committed
430
    m_genreBox->addItems(m_genreList);
431
    m_genreBox->completionObject()->setItems(m_genreList);
432 433 434 435 436 437
}

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

438
void TagEditor::readConfig()
439
{
440
    // combo box completion modes
441

442 443
    KConfigGroup config(KGlobal::config(), "TagEditor");
    if(m_artistNameBox && m_albumNameBox) {
Stephan Kulow's avatar
compile  
Stephan Kulow committed
444 445 446
        readCompletionMode(config, m_artistNameBox, "ArtistNameBoxMode");
        readCompletionMode(config, m_albumNameBox, "AlbumNameBoxMode");
        readCompletionMode(config, m_genreBox, "GenreBoxMode");
447
    }
448

449
    bool show = config.readEntry("Show", false);
450
    ActionCollection::action<KToggleAction>("showEditor")->setChecked(show);
Tim Beaulen's avatar
Tim Beaulen committed
451
    setVisible(show);
452

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

455
    for(TagLib::StringList::ConstIterator it = genres.begin(); it != genres.end(); ++it)
456
        m_genreList.append(TStringToQString((*it)));
457
    m_genreList.sort();
458

459
    m_genreBox->clear();
460
    m_genreBox->addItem(QString());
Stephan Kulow's avatar
Stephan Kulow committed
461
    m_genreBox->addItems(m_genreList);
462 463 464
    m_genreBox->completionObject()->setItems(m_genreList);
}

Stephan Kulow's avatar
compile  
Stephan Kulow committed
465
void TagEditor::readCompletionMode(const KConfigGroup &config, KComboBox *box, const QString &key)
466 467
{
    KGlobalSettings::Completion mode =
Stephan Kulow's avatar
compile  
Stephan Kulow committed
468
        KGlobalSettings::Completion(config.readEntry(key, (int)KGlobalSettings::CompletionAuto));
469 470

    box->setCompletionMode(mode);
471
}
472

473 474
void TagEditor::saveConfig()
{
475
    // combo box completion modes
476

477 478 479
    KConfigGroup config(KGlobal::config(), "TagEditor");

    if(m_artistNameBox && m_albumNameBox) {
480 481 482
        config.writeEntry("ArtistNameBoxMode", (int)m_artistNameBox->completionMode());
        config.writeEntry("AlbumNameBoxMode", (int)m_albumNameBox->completionMode());
        config.writeEntry("GenreBoxMode", (int)m_genreBox->completionMode());
483
    }
484
    config.writeEntry("Show", ActionCollection::action<KToggleAction>("showEditor")->isChecked());
485
}
486

487 488
void TagEditor::setupActions()
{
489
    KToggleAction *show = new KToggleAction(KIcon("document-properties"), i18n("Show &Tag Editor"), this);
490
    ActionCollection::actions()->addAction("showEditor", show);
491 492
    connect(show, SIGNAL(toggled(bool)), this, SLOT(setShown(bool)));

493
    KAction *act = new KAction(KIcon("document-save"), i18n("&Save"), this);
494
    ActionCollection::actions()->addAction("saveItem", act);
495 496
    act->setShortcut(Qt::CTRL + Qt::Key_T);
    connect(act, SIGNAL(triggered(bool)), SLOT(slotSave()));
497 498
}

499 500
void TagEditor::setupLayout()
{
501
    static const int horizontalSpacing = 12;
502
    static const int verticalSpacing = 2;
503

Stephan Kulow's avatar
Stephan Kulow committed
504 505 506
    QHBoxLayout *layout = new QHBoxLayout(this);
    layout->setSpacing(horizontalSpacing);
    layout->setMargin(6);
507 508 509 510

    //////////////////////////////////////////////////////////////////////////////
    // define two columns of the bottem layout
    //////////////////////////////////////////////////////////////////////////////
Stephan Kulow's avatar
Stephan Kulow committed
511 512 513 514 515 516
    QVBoxLayout *leftColumnLayout = new QVBoxLayout();
    layout->addItem( leftColumnLayout );
    leftColumnLayout->setSpacing(verticalSpacing);
    QVBoxLayout *rightColumnLayout = new QVBoxLayout();
    layout->addItem( rightColumnLayout );
    rightColumnLayout->setSpacing(verticalSpacing);
517 518 519 520 521 522 523 524

    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
525 526 527 528

        m_artistNameBox = new KComboBox( true, this );
        m_artistNameBox->setObjectName( "artistNameBox" );
        m_artistNameBox->setCompletionMode(KGlobalSettings::CompletionAuto);
Pino Toscano's avatar
Pino Toscano committed
529
        addItem(i18n("&Artist name:"), m_artistNameBox, leftColumnLayout, "view-media-artist");
530

531 532
        m_trackNameBox = new KLineEdit(this);
        m_trackNameBox->setObjectName( "trackNameBox" );
533
        addItem(i18n("&Track name:"), m_trackNameBox, leftColumnLayout, "media-playback-start");
534

535 536 537
        m_albumNameBox = new KComboBox( true, this );
        m_albumNameBox->setObjectName( "albumNameBox" );
        m_albumNameBox->setCompletionMode(KGlobalSettings::CompletionAuto);
538
        addItem(i18n("Album &name:"), m_albumNameBox, leftColumnLayout, "media-optical-audio");
539

Laurent Montel's avatar
Laurent Montel committed
540 541
        m_genreBox = new KComboBox( true, this );
        m_genreBox->setObjectName( "genreBox" );
542
        addItem(i18n("&Genre:"), m_genreBox, leftColumnLayout, "help-about");
543 544

        // this fills the space at the bottem of the left column
Scott Wheeler's avatar
Scott Wheeler committed
545
        leftColumnLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum,
546
                                                  QSizePolicy::Expanding));
547 548 549 550 551 552
    }
    //////////////////////////////////////////////////////////////////////////////
    // put stuff in the right column
    //////////////////////////////////////////////////////////////////////////////
    { // just for organization

Stephan Kulow's avatar
Stephan Kulow committed
553 554 555
        QHBoxLayout *fileNameLayout = new QHBoxLayout();
	rightColumnLayout->addItem(fileNameLayout);
	fileNameLayout->setSpacing(horizontalSpacing);
Scott Wheeler's avatar
Scott Wheeler committed
556

557 558
        m_fileNameBox = new KLineEdit(this);
        m_fileNameBox->setObjectName( "fileNameBox" );
559
        m_fileNameBox->setValidator(new FileNameValidator(m_fileNameBox));
Scott Wheeler's avatar
Scott Wheeler committed
560

561
        QLabel *fileNameIcon = new QLabel(this);
Pino Toscano's avatar
Pino Toscano committed
562
        fileNameIcon->setPixmap(SmallIcon("audio-x-generic"));
Stephan Kulow's avatar
Stephan Kulow committed
563 564 565
        QLabel * tmp = new QLabel(i18n("&File name:"), this);
        tmp->setBuddy( m_fileNameBox );
        QWidget *fileNameLabel = addHidden(tmp);
566 567 568 569 570 571 572

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

        { // lay out the track row
575
            FixedHLayout *trackRowLayout = new FixedHLayout(rightColumnLayout,
576 577
                                                            horizontalSpacing);

578 579
            m_trackSpin = new KIntSpinBox(0, 9999, 1, 0, this);
            m_trackSpin->setObjectName( "trackSpin" );
580
            addItem(i18nc("cd track number", "T&rack:"), m_trackSpin, trackRowLayout);
581 582 583 584 585
            m_trackSpin->installEventFilter(this);

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

586 587
            m_yearSpin = new KIntSpinBox(0, 9999, 1, 0, this );
            m_yearSpin->setObjectName( "yearSpin" );
588 589 590 591 592 593 594
            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)));
595 596
            m_lengthBox = new KLineEdit(this);
            m_lengthBox->setObjectName( "lengthBox" );
597 598 599 600 601 602 603 604 605 606 607
            // 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)));
608 609
            m_bitrateBox = new KLineEdit(this);
            m_bitrateBox->setObjectName( "bitrateBox" );
610 611 612 613 614 615 616 617
            // 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();
618 619
        }

620
        m_commentBox = new KTextEdit(this);
David Faure's avatar
David Faure committed
621
        m_commentBox->setObjectName( "commentBox" );
622
        addItem(i18n("&Comment:"), m_commentBox, rightColumnLayout, "document-properties");
623
        fileNameLabel->setMinimumHeight(m_trackSpin->height());
624

625 626
    }

Scott Wheeler's avatar
Scott Wheeler committed
627
    connect(m_artistNameBox, SIGNAL(textChanged(const QString&)),
628
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
629 630

    connect(m_trackNameBox, SIGNAL(textChanged(const QString&)),
631
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
632 633

    connect(m_albumNameBox, SIGNAL(textChanged(const QString&)),
634
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
635 636

    connect(m_genreBox, SIGNAL(activated(int)),
637
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
638 639

    connect(m_genreBox, SIGNAL(textChanged(const QString&)),
640
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
641 642

    connect(m_fileNameBox, SIGNAL(textChanged(const QString&)),
643
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
644 645

    connect(m_yearSpin, SIGNAL(valueChanged(int)),
646
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
647 648

    connect(m_trackSpin, SIGNAL(valueChanged(int)),
649
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
650 651

    connect(m_commentBox, SIGNAL(textChanged()),
652
            this, SLOT(slotDataChanged()));
653 654 655 656
}

void TagEditor::save(const PlaylistItemList &list)
{
657
    if(!list.isEmpty() && m_dataChanged) {
658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674

        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.

675
            qApp->processEvents(QEventLoop::ExcludeUserInput);
676 677 678 679 680 681 682 683 684

            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;

Tim Beaulen's avatar
Tim Beaulen committed
685
            QString fileName = item->file().fileInfo().path() + QDir::separator() +
686 687
                               m_fileNameBox->text();
            if(list.count() > 1)
Tim Beaulen's avatar
Tim Beaulen committed
688
                fileName = item->file().fileInfo().absoluteFilePath();
689 690 691 692 693 694 695 696 697

            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.

Dirk Mueller's avatar
Dirk Mueller committed
698
            if(m_enableBoxes[m_artistNameBox]->isChecked())
699
                tag->setArtist(m_artistNameBox->currentText());
Dirk Mueller's avatar
Dirk Mueller committed
700
            if(m_enableBoxes[m_trackNameBox]->isChecked())
701
                tag->setTitle(m_trackNameBox->text());
Dirk Mueller's avatar
Dirk Mueller committed
702
            if(m_enableBoxes[m_albumNameBox]->isChecked())
703
                tag->setAlbum(m_albumNameBox->currentText());
Dirk Mueller's avatar
Dirk Mueller committed
704
            if(m_enableBoxes[m_trackSpin]->isChecked()) {
705 706 707 708
                if(m_trackSpin->text().isEmpty())
                    m_trackSpin->setValue(0);
                tag->setTrack(m_trackSpin->value());
            }
Dirk Mueller's avatar
Dirk Mueller committed
709
            if(m_enableBoxes[m_yearSpin]->isChecked()) {
710 711 712 713
                if(m_yearSpin->text().isEmpty())
                    m_yearSpin->setValue(0);
                tag->setYear(m_yearSpin->value());
            }
Dirk Mueller's avatar
Dirk Mueller committed
714
            if(m_enableBoxes[m_commentBox]->isChecked())
715
                tag->setComment(m_commentBox->toPlainText());
716

Dirk Mueller's avatar
Dirk Mueller committed
717
            if(m_enableBoxes[m_genreBox]->isChecked())
718 719 720 721 722 723 724 725 726
                tag->setGenre(m_genreBox->currentText());

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

        TagTransactionManager::instance()->commit();
        CollectionList::instance()->dataChanged();
        m_performingSave = false;
        KApplication::restoreOverrideCursor();
727 728 729 730 731
    }
}

void TagEditor::saveChangesPrompt()
{
732
    if(!isVisible() || !m_dataChanged || m_items.isEmpty())
733
        return;
734 735 736

    QStringList files;

Laurent Montel's avatar
Laurent Montel committed
737
    for(PlaylistItemList::Iterator it = m_items.begin(); it != m_items.end(); ++it)
738
        files.append((*it)->file().absFilePath());
739 740

    if(KMessageBox::questionYesNoList(this,
741 742 743
                                      i18n("Do you want to save your changes to:\n"),
                                      files,
                                      i18n("Save Changes"),
Aaron J. Seigo's avatar
build  
Aaron J. Seigo committed
744 745
                                      KStandardGuiItem::save(),
                                      KStandardGuiItem::discard(),
746
                                      "tagEditor_showSaveChangesBox") == KMessageBox::Yes)
747
    {
748
        save(m_items);
749 750 751
    }
}

752
void TagEditor::addItem(const QString &text, QWidget *item, QBoxLayout *layout, const QString &iconName)
753 754
{
    if(!item || !layout)
755
        return;
756

Stephan Kulow's avatar
Stephan Kulow committed
757 758 759 760
    QLabel *label = new QLabel(text, this);
    label->setBuddy( item );
    QLabel *iconLabel = new QLabel(0, this);
    iconLabel->setBuddy( item );
761

762
    if(!iconName.isNull())
763 764
        iconLabel->setPixmap(SmallIcon(iconName));

765 766 767 768 769 770
    QCheckBox *enableBox = new QCheckBox(i18n("Enable"), this);
    enableBox->setChecked(true);

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

    if(layout->direction() == QBoxLayout::LeftToRight) {
771
        layout->addWidget(iconLabel);
772 773 774
        layout->addWidget(label);
        layout->addWidget(item);
        layout->addWidget(enableBox);
775 776
    }
    else {
Stephan Kulow's avatar
Stephan Kulow committed
777 778
        QHBoxLayout *l = new QHBoxLayout();
        layout->addItem(l);
779 780 781 782

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

784
        l->insertStretch(-1, 1);
785

786 787
        l->addWidget(enableBox);
        l->setStretchFactor(enableBox, 0);
788

789
        layout->addWidget(item);
790 791 792 793 794
    }

    enableBox->hide();

    connect(enableBox, SIGNAL(toggled(bool)), item, SLOT(setEnabled(bool)));
795
    m_enableBoxes.insert(item, enableBox);
796 797
}

798 799
void TagEditor::showEvent(QShowEvent *e)
{
800
    if(m_collectionChanged) {
801 802
        updateCollection();
        slotRefresh();
803
    }
804

805 806 807
    QWidget::showEvent(e);
}

808 809
bool TagEditor::eventFilter(QObject *watched, QEvent *e)
{
810
    QKeyEvent *ke = static_cast<QKeyEvent*>(e);
Tim Beaulen's avatar
Tim Beaulen committed
811
    if(watched->inherits("QSpinBox") && e->type() == QEvent::KeyRelease && ke->modifiers() == 0)
812
        slotDataChanged();
813 814 815 816

    return false;
}

817 818 819 820
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////

821
void TagEditor::slotDataChanged(bool c)
822
{
823
    m_dataChanged = c;
824 825
}

826 827
void TagEditor::slotItemRemoved(PlaylistItem *item)
{
828
    m_items.removeAll(item);
829
    if(m_items.isEmpty())
830
        slotRefresh();
831 832
}

833 834 835
void TagEditor::slotPlaylistDestroyed(Playlist *p)
{
    if(m_currentPlaylist == p) {
836 837
        m_currentPlaylist = 0;
        slotSetItems(PlaylistItemList());
838 839 840
    }
}

841
#include "tageditor.moc"
842 843

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