tageditor.cpp 27.9 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
#include <kcombobox.h>
#include <klineedit.h>
#include <knuminput.h>
26
#include <ktextedit.h>
27
#include <kmessagebox.h>
28
#include <kconfig.h>
29 30
#include <klocale.h>
#include <kdebug.h>
31
#include <kiconloader.h>
32 33
#include <kicon.h>
#include <kshortcut.h>
34

35

36
#include <QLabel>
37
#include <qapplication.h>
38 39 40
#include <QCheckBox>
#include <QLayout>
#include <QDir>
Scott Wheeler's avatar
Scott Wheeler committed
41
#include <qvalidator.h>
42
#include <QToolTip>
43
#include <qeventloop.h>
Laurent Montel's avatar
Laurent Montel committed
44 45 46 47 48 49 50 51
#include <q3dict.h>
//Added by qt3to4:
#include <QEvent>
#include <QKeyEvent>
#include <QHBoxLayout>
#include <QBoxLayout>
#include <QShowEvent>
#include <QVBoxLayout>
Stephan Kulow's avatar
Stephan Kulow committed
52
#include <ktoggleaction.h>
53

54 55
#include <id3v1genres.h>

Scott Wheeler's avatar
Scott Wheeler committed
56 57
#undef KeyRelease

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

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

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

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

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

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

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

    virtual void updateCurrent() {}

private:
    TagEditor *m_parent;
};

147 148 149 150
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

151 152
TagEditor::TagEditor(QWidget *parent, const char *name) :
    QWidget(parent, name),
153
    m_currentPlaylist(0),
154 155
    m_observer(0),
    m_performingSave(false)
156
{
157
    setupActions();
158 159
    setupLayout();
    readConfig();
160
    m_dataChanged = false;
161
    m_collectionChanged = false;
162 163 164 165
}

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

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

175 176 177 178
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

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

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

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

    bool hadPlaylist = m_currentPlaylist != 0;

193
    saveChangesPrompt();
194 195

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

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

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

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

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

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

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

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

237 238
    setEnabled(true);

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

    Q_ASSERT(item);
242

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

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

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

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

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

259
    if(m_genreList.findIndex(tag->genre()) >= 0)
260
        m_genreBox->setCurrentItem(m_genreList.findIndex(tag->genre()) + 1);
261
    else {
262 263
        m_genreBox->setCurrentItem(0);
        m_genreBox->setEditText(tag->genre());
264
    }
265

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

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

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

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

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

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

360 361 362
        Q3ValueListIterator<QWidget *> showIt = m_hideList.begin();
        for(; showIt != m_hideList.end(); ++showIt)
            (*showIt)->show();
Scott Wheeler's avatar
Scott Wheeler committed
363

364 365 366 367 368
        BoxMap::iterator boxIt = m_enableBoxes.begin();
        for(; boxIt != m_enableBoxes.end(); boxIt++) {
            (*boxIt)->setChecked(true);
            (*boxIt)->hide();
        }
369 370
    }
    m_dataChanged = false;
371 372
}

373
void TagEditor::slotClear()
374
{
375 376 377 378 379 380 381 382 383
    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();
384
    m_commentBox->clear();
385 386
}

387
void TagEditor::slotUpdateCollection()
388
{
389
    if(isVisible())
390
        updateCollection();
391
    else
392
        m_collectionChanged = true;
393 394 395 396 397
}

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

399 400 401
    CollectionList *list = CollectionList::instance();

    if(!list)
402 403
        return;

404
    QStringList artistList = list->uniqueSet(CollectionList::Artists);
405
    artistList.sort();
406
    m_artistNameBox->clear();
Stephan Kulow's avatar
Stephan Kulow committed
407
    m_artistNameBox->addItems(artistList);
408 409 410
    m_artistNameBox->completionObject()->setItems(artistList);

    QStringList albumList = list->uniqueSet(CollectionList::Albums);
411
    albumList.sort();
412
    m_albumNameBox->clear();
Stephan Kulow's avatar
Stephan Kulow committed
413
    m_albumNameBox->addItems(albumList);
414
    m_albumNameBox->completionObject()->setItems(albumList);
415

416
    // Merge the list of genres found in tags with the standard ID3v1 set.
417

418
    StringHash genreHash;
419 420

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

422
    for(QStringList::ConstIterator it = m_genreList.begin(); it != m_genreList.end(); ++it)
423
        genreHash.insert(*it);
424 425 426

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

427
    for(TagLib::StringList::ConstIterator it = genres.begin(); it != genres.end(); ++it)
428
        genreHash.insert(TStringToQString((*it)));
429

430
    m_genreList = genreHash.values();
431 432
    m_genreList.sort();

433
    m_genreBox->clear();
434
    m_genreBox->addItem(QString::null);
Stephan Kulow's avatar
Stephan Kulow committed
435
    m_genreBox->addItems(m_genreList);
436
    m_genreBox->completionObject()->setItems(m_genreList);
437 438 439 440 441 442
}

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

443
void TagEditor::readConfig()
444
{
445
    // combo box completion modes
446

447 448
    KConfigGroup config(KGlobal::config(), "TagEditor");
    if(m_artistNameBox && m_albumNameBox) {
449 450 451
        readCompletionMode(&config, m_artistNameBox, "ArtistNameBoxMode");
        readCompletionMode(&config, m_albumNameBox, "AlbumNameBoxMode");
        readCompletionMode(&config, m_genreBox, "GenreBoxMode");
452
    }
453

454
    bool show = config.readEntry("Show", false);
455
    ActionCollection::action<KToggleAction>("showEditor")->setChecked(show);
456 457
    setShown(show);

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

460
    for(TagLib::StringList::ConstIterator it = genres.begin(); it != genres.end(); ++it)
461
        m_genreList.append(TStringToQString((*it)));
462
    m_genreList.sort();
463

464
    m_genreBox->clear();
465
    m_genreBox->addItem(QString::null);
Stephan Kulow's avatar
Stephan Kulow committed
466
    m_genreBox->addItems(m_genreList);
467 468 469
    m_genreBox->completionObject()->setItems(m_genreList);
}

470
void TagEditor::readCompletionMode(KConfigBase *config, KComboBox *box, const QString &key)
471 472
{
    KGlobalSettings::Completion mode =
473
        KGlobalSettings::Completion(config->readEntry(key, (int)KGlobalSettings::CompletionAuto));
474 475

    box->setCompletionMode(mode);
476
}
477

478 479
void TagEditor::saveConfig()
{
480
    // combo box completion modes
481

482 483 484
    KConfigGroup config(KGlobal::config(), "TagEditor");

    if(m_artistNameBox && m_albumNameBox) {
485 486 487
        config.writeEntry("ArtistNameBoxMode", (int)m_artistNameBox->completionMode());
        config.writeEntry("AlbumNameBoxMode", (int)m_albumNameBox->completionMode());
        config.writeEntry("GenreBoxMode", (int)m_genreBox->completionMode());
488
    }
489
    config.writeEntry("Show", ActionCollection::action<KToggleAction>("showEditor")->isChecked());
490
}
491

492 493
void TagEditor::setupActions()
{
494
    KToggleAction *show = new KToggleAction(KIcon("edit"), i18n("Show &Tag Editor"), ActionCollection::actions(), "showEditor");
495 496 497
    show->setCheckedState(i18n("Hide &Tag Editor"));
    connect(show, SIGNAL(toggled(bool)), this, SLOT(setShown(bool)));

498 499 500
    KAction *act = new KAction(KIcon("filesave"), i18n("&Save"), ActionCollection::actions(), "saveItem");
    act->setShortcut(Qt::CTRL + Qt::Key_T);
    connect(act, SIGNAL(triggered(bool)), SLOT(slotSave()));
501 502
}

503 504
void TagEditor::setupLayout()
{
505
    static const int horizontalSpacing = 12;
506
    static const int verticalSpacing = 2;
507

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

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

    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
529 530 531 532 533

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

535 536
        m_trackNameBox = new KLineEdit(this);
        m_trackNameBox->setObjectName( "trackNameBox" );
537
        addItem(i18n("&Track name:"), m_trackNameBox, leftColumnLayout, "player_play");
538

539 540 541 542
        m_albumNameBox = new KComboBox( true, this );
        m_albumNameBox->setObjectName( "albumNameBox" );
        m_albumNameBox->setCompletionMode(KGlobalSettings::CompletionAuto);
        addItem(i18n("Album &name:"), m_albumNameBox, leftColumnLayout, "cdrom_unmount");
543

Laurent Montel's avatar
Laurent Montel committed
544 545
        m_genreBox = new KComboBox( true, this );
        m_genreBox->setObjectName( "genreBox" );
546
        addItem(i18n("&Genre:"), m_genreBox, leftColumnLayout, "knotify");
547 548

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

Stephan Kulow's avatar
Stephan Kulow committed
557 558 559
        QHBoxLayout *fileNameLayout = new QHBoxLayout();
	rightColumnLayout->addItem(fileNameLayout);
	fileNameLayout->setSpacing(horizontalSpacing);
Scott Wheeler's avatar
Scott Wheeler committed
560

561 562
        m_fileNameBox = new KLineEdit(this);
        m_fileNameBox->setObjectName( "fileNameBox" );
563
        m_fileNameBox->setValidator(new FileNameValidator(m_fileNameBox));
Scott Wheeler's avatar
Scott Wheeler committed
564

565 566
        QLabel *fileNameIcon = new QLabel(this);
        fileNameIcon->setPixmap(SmallIcon("sound"));
Stephan Kulow's avatar
Stephan Kulow committed
567 568 569
        QLabel * tmp = new QLabel(i18n("&File name:"), this);
        tmp->setBuddy( m_fileNameBox );
        QWidget *fileNameLabel = addHidden(tmp);
570 571 572 573 574 575 576

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

        { // lay out the track row
579
            FixedHLayout *trackRowLayout = new FixedHLayout(rightColumnLayout,
580 581
                                                            horizontalSpacing);

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

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

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

624
        m_commentBox = new KTextEdit(this);
David Faure's avatar
David Faure committed
625
        m_commentBox->setObjectName( "commentBox" );
626
        addItem(i18n("&Comment:"), m_commentBox, rightColumnLayout, "edit");
627
        fileNameLabel->setMinimumHeight(m_trackSpin->height());
628

629 630
    }

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

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

    connect(m_albumNameBox, SIGNAL(textChanged(const QString&)),
638
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
639 640

    connect(m_genreBox, SIGNAL(activated(int)),
641
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
642 643

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

    connect(m_fileNameBox, SIGNAL(textChanged(const QString&)),
647
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
648 649

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

    connect(m_trackSpin, SIGNAL(valueChanged(int)),
653
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
654 655

    connect(m_commentBox, SIGNAL(textChanged()),
656
            this, SLOT(slotDataChanged()));
657 658 659 660
}

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

        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.

679
            qApp->processEvents(QEventLoop::ExcludeUserInput);
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 708 709 710 711 712 713 714 715 716 717 718

            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())
719
                tag->setComment(m_commentBox->toPlainText());
720 721 722 723 724 725 726 727 728 729 730

            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();
731 732 733 734 735
    }
}

void TagEditor::saveChangesPrompt()
{
736
    if(!isVisible() || !m_dataChanged || m_items.isEmpty())
737
        return;
738 739 740

    QStringList files;

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

    if(KMessageBox::questionYesNoList(this,
745 746 747 748 749 750
                                      i18n("Do you want to save your changes to:\n"),
                                      files,
                                      i18n("Save Changes"),
                                      KStdGuiItem::save(),
                                      KStdGuiItem::discard(),
                                      "tagEditor_showSaveChangesBox") == KMessageBox::Yes)
751
    {
752
        save(m_items);
753 754 755
    }
}

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

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

766
    if(!iconName.isNull())
767 768
        iconLabel->setPixmap(SmallIcon(iconName));

769 770 771 772 773 774
    QCheckBox *enableBox = new QCheckBox(i18n("Enable"), this);
    enableBox->setChecked(true);

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

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

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

788
        l->insertStretch(-1, 1);
789

790 791
        l->addWidget(enableBox);
        l->setStretchFactor(enableBox, 0);
792

793
        layout->addWidget(item);
794 795 796 797 798
    }

    enableBox->hide();

    connect(enableBox, SIGNAL(toggled(bool)), item, SLOT(setEnabled(bool)));
799
    m_enableBoxes.insert(item, enableBox);
800 801
}

802 803
void TagEditor::showEvent(QShowEvent *e)
{
804
    if(m_collectionChanged) {
805 806
        updateCollection();
        slotRefresh();
807
    }
808

809 810 811
    QWidget::showEvent(e);
}

812 813
bool TagEditor::eventFilter(QObject *watched, QEvent *e)
{
814 815
    QKeyEvent *ke = static_cast<QKeyEvent*>(e);
    if(watched->inherits("QSpinBox") && e->type() == QEvent::KeyRelease && ke->state() == 0)
816
        slotDataChanged();
817 818 819 820

    return false;
}

821 822 823 824
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////

825
void TagEditor::slotDataChanged(bool c)
826
{
827
    m_dataChanged = c;
828 829
}

830 831 832 833
void TagEditor::slotItemRemoved(PlaylistItem *item)
{
    m_items.remove(item);
    if(m_items.isEmpty())
834
        slotRefresh();
835 836
}

837 838 839
void TagEditor::slotPlaylistDestroyed(Playlist *p)
{
    if(m_currentPlaylist == p) {
840 841
        m_currentPlaylist = 0;
        slotSetItems(PlaylistItemList());
842 843 844
    }
}

845
#include "tageditor.moc"
846 847

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