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

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

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

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

#include <qlabel.h>
37
#include <qapplication.h>
38
#include <qcheckbox.h>
39 40
#include <qlayout.h>
#include <qdir.h>
Scott Wheeler's avatar
Scott Wheeler committed
41
#include <qvalidator.h>
42
#include <qtooltip.h>
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>
52

53 54
#include <id3v1genres.h>

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

Scott Wheeler's avatar
Scott Wheeler committed
57 58 59 60
class FileNameValidator : public QValidator
{
public:
    FileNameValidator(QObject *parent, const char *name = 0) :
61
        QValidator(parent, 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
////////////////////////////////////////////////////////////////////////////////

150 151
TagEditor::TagEditor(QWidget *parent, const char *name) :
    QWidget(parent, name),
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());

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

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

268
    m_commentBox->setText(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 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 347 348 349 350 351 352 353 354
        Q3ValueListIterator<QWidget *> hideIt = m_hideList.begin();
        for(; hideIt != m_hideList.end(); ++hideIt)
            (*hideIt)->hide();

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

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

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

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

                if(tag) {

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

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

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

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

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

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

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

    if(!list)
401 402
        return;

403
    QStringList artistList = list->uniqueSet(CollectionList::Artists);
404
    artistList.sort();
405 406
    m_artistNameBox->clear();
    m_artistNameBox->insertStringList(artistList);
407 408 409
    m_artistNameBox->completionObject()->setItems(artistList);

    QStringList albumList = list->uniqueSet(CollectionList::Albums);
410
    albumList.sort();
411 412
    m_albumNameBox->clear();
    m_albumNameBox->insertStringList(albumList);
413
    m_albumNameBox->completionObject()->setItems(albumList);
414

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

417
    StringHash genreHash;
418 419

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

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

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

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

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

432 433 434
    m_genreBox->clear();
    m_genreBox->insertItem(QString::null);
    m_genreBox->insertStringList(m_genreList);
435
    m_genreBox->completionObject()->setItems(m_genreList);
436 437 438 439 440 441
}

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

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

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

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

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

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

463 464 465
    m_genreBox->clear();
    m_genreBox->insertItem(QString::null);
    m_genreBox->insertStringList(m_genreList);
466 467 468
    m_genreBox->completionObject()->setItems(m_genreList);
}

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

    box->setCompletionMode(mode);
475
}
476

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

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

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

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

497 498 499
    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()));
500 501
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

David Faure's avatar
David Faure committed
623 624
        m_commentBox = new KEdit(this);
        m_commentBox->setObjectName( "commentBox" );
625 626
        m_commentBox->setTextFormat(Qt::PlainText);
        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 719 720 721 722 723 724 725 726 727 728 729 730

            PlaylistItem *item = *it;

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

            ++it;

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

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

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

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

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

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

        TagTransactionManager::instance()->commit();
        CollectionList::instance()->dataChanged();
        m_performingSave = false;
        KApplication::restoreOverrideCursor();
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: