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 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) :
96 97
        QHBoxLayout(parent, margin, spacing, name),
        m_width(-1) {}
98
    FixedHLayout(QLayout *parentLayout, int spacing = -1, const char *name = 0) :
99 100
        QHBoxLayout(parentLayout, spacing, name),
        m_width(-1) {}
101 102
    void setWidth(int w = -1)
    {
103
        m_width = w == -1 ? QHBoxLayout::minimumSize().width() : w;
104 105 106
    }
    virtual QSize minimumSize() const
    {
107 108 109
        QSize s = QHBoxLayout::minimumSize();
        s.setWidth(m_width);
        return s;
110 111 112 113 114
    }
private:
    int m_width;
};

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

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

    virtual void updateCurrent() {}

private:
    TagEditor *m_parent;
};

136 137 138 139
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

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

TagEditor::~TagEditor()
{
155
    delete m_observer;
156 157 158
    saveConfig();
}

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

164 165 166 167
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

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

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

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

    bool hadPlaylist = m_currentPlaylist != 0;

182
    saveChangesPrompt();
183 184

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

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

196
        // We can't use list here, it may not be valid
197

198
        m_items = itemPlaylist->selectedItems();
199
    }
200 201

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

207
    if(isVisible())
208
        slotRefresh();
209
    else
210
        m_collectionChanged = true;
211 212
}

213
void TagEditor::slotRefresh()
214
{
215
    // This method takes the list of currently selected m_items and tries to
216 217
    // 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
218
    // check after that to see if there are other m_items and adjust accordingly.
219

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

226 227
    setEnabled(true);

228
    PlaylistItem *item = m_items.first();
229 230

    Q_ASSERT(item);
231

232
    Tag *tag = item->file().tag();
233

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

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

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

245
    m_bitrateBox->setText(QString::number(tag->bitrate()));
246 247
    m_lengthBox->setText(tag->lengthString());

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

255
    m_trackSpin->setValue(tag->track());
256
    m_yearSpin->setValue(tag->year());
257

258
    m_commentBox->setText(tag->comment());
259

260
    // Start at the second item, since we've already processed the first.
261

262 263 264 265
    PlaylistItemList::Iterator it = m_items.begin();
    ++it;

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

267
    if(it != m_items.end()) {
268

269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
        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);
                    }
                }
            }
        }
345
    }
346
    else {
347
        // Clean up in the case that we are only handling one item.
348

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

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

362
void TagEditor::slotClear()
363
{
364 365 366 367 368 369 370 371 372
    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();
373
    m_commentBox->clear();
374 375
}

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

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

388 389 390
    CollectionList *list = CollectionList::instance();

    if(!list)
391 392
        return;

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

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

405
    // Merge the list of genres found in tags with the standard ID3v1 set.
406

407
    StringHash genreHash;
408 409

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

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

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

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

419
    m_genreList = genreHash.values();
420 421
    m_genreList.sort();

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

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

432
void TagEditor::readConfig()
433
{
434
    // combo box completion modes
435

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

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

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

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

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

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

    box->setCompletionMode(mode);
465
}
466

467 468
void TagEditor::saveConfig()
{
469
    // combo box completion modes
470

471 472 473
    KConfigGroup config(KGlobal::config(), "TagEditor");

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

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

487 488 489
    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()));
490 491
}

492 493
void TagEditor::setupLayout()
{
494
    static const int horizontalSpacing = 12;
495
    static const int verticalSpacing = 2;
496

Stephan Kulow's avatar
Stephan Kulow committed
497 498 499
    QHBoxLayout *layout = new QHBoxLayout(this);
    layout->setSpacing(horizontalSpacing);
    layout->setMargin(6);
500 501 502 503

    //////////////////////////////////////////////////////////////////////////////
    // define two columns of the bottem layout
    //////////////////////////////////////////////////////////////////////////////
Stephan Kulow's avatar
Stephan Kulow committed
504 505 506 507 508 509
    QVBoxLayout *leftColumnLayout = new QVBoxLayout();
    layout->addItem( leftColumnLayout );
    leftColumnLayout->setSpacing(verticalSpacing);
    QVBoxLayout *rightColumnLayout = new QVBoxLayout();
    layout->addItem( rightColumnLayout );
    rightColumnLayout->setSpacing(verticalSpacing);
510 511 512 513 514 515 516 517

    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
518 519 520 521 522

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

524 525
        m_trackNameBox = new KLineEdit(this);
        m_trackNameBox->setObjectName( "trackNameBox" );
526
        addItem(i18n("&Track name:"), m_trackNameBox, leftColumnLayout, "player_play");
527

528 529 530 531
        m_albumNameBox = new KComboBox( true, this );
        m_albumNameBox->setObjectName( "albumNameBox" );
        m_albumNameBox->setCompletionMode(KGlobalSettings::CompletionAuto);
        addItem(i18n("Album &name:"), m_albumNameBox, leftColumnLayout, "cdrom_unmount");
532

Laurent Montel's avatar
Laurent Montel committed
533 534
        m_genreBox = new KComboBox( true, this );
        m_genreBox->setObjectName( "genreBox" );
535
        addItem(i18n("&Genre:"), m_genreBox, leftColumnLayout, "knotify");
536 537

        // this fills the space at the bottem of the left column
Scott Wheeler's avatar
Scott Wheeler committed
538
        leftColumnLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum,
539
                                                  QSizePolicy::Expanding));
540 541 542 543 544 545
    }
    //////////////////////////////////////////////////////////////////////////////
    // put stuff in the right column
    //////////////////////////////////////////////////////////////////////////////
    { // just for organization

546 547
        QHBoxLayout *fileNameLayout = new QHBoxLayout(rightColumnLayout,
                                                      horizontalSpacing);
Scott Wheeler's avatar
Scott Wheeler committed
548

549 550
        m_fileNameBox = new KLineEdit(this);
        m_fileNameBox->setObjectName( "fileNameBox" );
551
        m_fileNameBox->setValidator(new FileNameValidator(m_fileNameBox));
Scott Wheeler's avatar
Scott Wheeler committed
552

553 554
        QLabel *fileNameIcon = new QLabel(this);
        fileNameIcon->setPixmap(SmallIcon("sound"));
Stephan Kulow's avatar
Stephan Kulow committed
555 556 557
        QLabel * tmp = new QLabel(i18n("&File name:"), this);
        tmp->setBuddy( m_fileNameBox );
        QWidget *fileNameLabel = addHidden(tmp);
558 559 560 561 562 563 564

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

        { // lay out the track row
567
            FixedHLayout *trackRowLayout = new FixedHLayout(rightColumnLayout,
568 569
                                                            horizontalSpacing);

570 571
            m_trackSpin = new KIntSpinBox(0, 9999, 1, 0, this);
            m_trackSpin->setObjectName( "trackSpin" );
572 573 574 575 576 577
            addItem(i18n("T&rack:"), m_trackSpin, trackRowLayout);
            m_trackSpin->installEventFilter(this);

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

578 579
            m_yearSpin = new KIntSpinBox(0, 9999, 1, 0, this );
            m_yearSpin->setObjectName( "yearSpin" );
580 581 582 583 584 585 586
            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)));
587 588
            m_lengthBox = new KLineEdit(this);
            m_lengthBox->setObjectName( "lengthBox" );
589 590 591 592 593 594 595 596 597 598 599
            // 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)));
600 601
            m_bitrateBox = new KLineEdit(this);
            m_bitrateBox->setObjectName( "bitrateBox" );
602 603 604 605 606 607 608 609
            // 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();
610 611
        }

David Faure's avatar
David Faure committed
612 613
        m_commentBox = new KEdit(this);
        m_commentBox->setObjectName( "commentBox" );
614 615
        m_commentBox->setTextFormat(Qt::PlainText);
        addItem(i18n("&Comment:"), m_commentBox, rightColumnLayout, "edit");
616
        fileNameLabel->setMinimumHeight(m_trackSpin->height());
617

618 619
    }

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

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

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

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

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

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

    connect(m_yearSpin, SIGNAL(valueChanged(int)),
639
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
640 641

    connect(m_trackSpin, SIGNAL(valueChanged(int)),
642
            this, SLOT(slotDataChanged()));
Scott Wheeler's avatar
Scott Wheeler committed
643 644

    connect(m_commentBox, SIGNAL(textChanged()),
645
            this, SLOT(slotDataChanged()));
646 647 648 649
}

void TagEditor::save(const PlaylistItemList &list)
{
650
    if(!list.isEmpty() && m_dataChanged) {
651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667

        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.

668
            qApp->processEvents(QEventLoop::ExcludeUserInput);
669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719

            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();
720 721 722 723 724
    }
}

void TagEditor::saveChangesPrompt()
{
725
    if(!isVisible() || !m_dataChanged || m_items.isEmpty())
726
        return;
727 728 729

    QStringList files;

Laurent Montel's avatar
Laurent Montel committed
730
    for(PlaylistItemList::Iterator it = m_items.begin(); it != m_items.end(); ++it)
731
        files.append((*it)->file().absFilePath());
732 733

    if(KMessageBox::questionYesNoList(this,
734 735 736 737 738 739
                                      i18n("Do you want to save your changes to:\n"),
                                      files,
                                      i18n("Save Changes"),
                                      KStdGuiItem::save(),
                                      KStdGuiItem::discard(),
                                      "tagEditor_showSaveChangesBox") == KMessageBox::Yes)
740
    {
741
        save(m_items);
742 743 744
    }
}

745
void TagEditor::addItem(const QString &text, QWidget *item, QBoxLayout *layout, const QString &iconName)
746 747
{
    if(!item || !layout)
748
        return;
749

Stephan Kulow's avatar
Stephan Kulow committed
750 751 752 753
    QLabel *label = new QLabel(text, this);
    label->setBuddy( item );
    QLabel *iconLabel = new QLabel(0, this);
    iconLabel->setBuddy( item );
754

755
    if(!iconName.isNull())
756 757
        iconLabel->setPixmap(SmallIcon(iconName));

758 759 760 761 762 763
    QCheckBox *enableBox = new QCheckBox(i18n("Enable"), this);
    enableBox->setChecked(true);

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

    if(layout->direction() == QBoxLayout::LeftToRight) {
764
        layout->addWidget(iconLabel);
765 766 767
        layout->addWidget(label);
        layout->addWidget(item);
        layout->addWidget(enableBox);
768 769
    }
    else {
770 771 772 773 774
        QHBoxLayout *l = new QHBoxLayout(layout);

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

776
        l->insertStretch(-1, 1);
777

778 779
        l->addWidget(enableBox);
        l->setStretchFactor(enableBox, 0);
780

781
        layout->addWidget(item);
782 783 784 785 786
    }

    enableBox->hide();

    connect(enableBox, SIGNAL(toggled(bool)), item, SLOT(setEnabled(bool)));
787
    m_enableBoxes.insert(item, enableBox);
788 789
}

790 791
void TagEditor::showEvent(QShowEvent *e)
{
792
    if(m_collectionChanged) {
793 794
        updateCollection();
        slotRefresh();
795
    }
796

797 798 799
    QWidget::showEvent(e);
}

800 801
bool TagEditor::eventFilter(QObject *watched, QEvent *e)
{
802 803
    QKeyEvent *ke = static_cast<QKeyEvent*>(e);
    if(watched->inherits("QSpinBox") && e->type() == QEvent::KeyRelease && ke->state() == 0)
804
        slotDataChanged();
805 806 807 808

    return false;
}

809 810 811 812
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////

813
void TagEditor::slotDataChanged(bool c)
814
{
815
    m_dataChanged = c;
816 817
}

818 819 820 821
void TagEditor::slotItemRemoved(PlaylistItem *item)
{
    m_items.remove(item);
    if(m_items.isEmpty())
822
        slotRefresh();
823 824
}

825 826 827
void TagEditor::slotPlaylistDestroyed(Playlist *p)
{
    if(m_currentPlaylist == p) {
828 829
        m_currentPlaylist = 0;
        slotSetItems(PlaylistItemList());
830 831 832
    }
}

833
#include "tageditor.moc"
834 835

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