Members of the KDE Community are recommended to subscribe to the kde-community mailing list at https://mail.kde.org/mailman/listinfo/kde-community to allow them to participate in important discussions and receive other important announcements

tageditor.cpp 27.8 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);
Dirk Mueller's avatar
Dirk Mueller committed
491
    show->setCheckedState(KGuiItem(i18n("Hide &Tag Editor")));
492 493
    connect(show, SIGNAL(toggled(bool)), this, SLOT(setShown(bool)));

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

626 627
    }

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

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

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

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

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

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

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

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

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

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

        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.

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

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

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

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

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

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

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

    QStringList files;

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

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

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

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

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

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

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

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

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

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

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

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

    enableBox->hide();

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

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

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

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

    return false;
}

818 819 820 821
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////

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

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

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

842
#include "tageditor.moc"
843 844

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