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

tageditor.cpp 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
#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
#include <q3dict.h>
#include <QEvent>
#include <QKeyEvent>
#include <QHBoxLayout>
#include <QBoxLayout>
#include <QShowEvent>
#include <QVBoxLayout>
Stephan Kulow's avatar
Stephan Kulow committed
51
#include <ktoggleaction.h>
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) :
Tim Beaulen's avatar
Tim Beaulen committed
61 62 63 64
        QValidator(parent) 
    {
        setObjectName(name);
    }
Scott Wheeler's avatar
Scott Wheeler committed
65 66 67

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

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

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

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

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

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

    virtual void updateCurrent() {}

private:
    TagEditor *m_parent;
};

149 150 151 152
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

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

TagEditor::~TagEditor()
{
168
    delete m_observer;
169 170 171
    saveConfig();
}

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

177 178 179 180
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

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

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

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

    bool hadPlaylist = m_currentPlaylist != 0;

195
    saveChangesPrompt();
196 197

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

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

209
        // We can't use list here, it may not be valid
210

211
        m_items = itemPlaylist->selectedItems();
212
    }
213 214

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

220
    if(isVisible())
221
        slotRefresh();
222
    else
223
        m_collectionChanged = true;
224 225
}

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

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

239 240
    setEnabled(true);

241
    PlaylistItem *item = m_items.first();
242 243

    Q_ASSERT(item);
244

245
    Tag *tag = item->file().tag();
246

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

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

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

258
    m_bitrateBox->setText(QString::number(tag->bitrate()));
259 260
    m_lengthBox->setText(tag->lengthString());

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

268
    m_trackSpin->setValue(tag->track());
269
    m_yearSpin->setValue(tag->year());
270

271
    m_commentBox->setPlainText(tag->comment());
272

273
    // Start at the second item, since we've already processed the first.
274

275 276 277 278
    PlaylistItemList::Iterator it = m_items.begin();
    ++it;

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

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

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

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

375
void TagEditor::slotClear()
376
{
377 378 379
    m_artistNameBox->lineEdit()->clear();
    m_trackNameBox->clear();
    m_albumNameBox->lineEdit()->clear();
Stephan Kulow's avatar
Stephan Kulow committed
380
    m_genreBox->setCurrentIndex(0);
381 382 383 384 385
    m_fileNameBox->clear();
    m_trackSpin->setValue(0);
    m_yearSpin->setValue(0);
    m_lengthBox->clear();
    m_bitrateBox->clear();
386
    m_commentBox->clear();
387 388
}

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

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

401 402 403
    CollectionList *list = CollectionList::instance();

    if(!list)
404 405
        return;

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

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

418
    // Merge the list of genres found in tags with the standard ID3v1 set.
419

420
    StringHash genreHash;
421 422

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

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

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

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

432
    m_genreList = genreHash.values();
433 434
    m_genreList.sort();

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

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

445
void TagEditor::readConfig()
446
{
447
    // combo box completion modes
448

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

456
    bool show = config.readEntry("Show", false);
457
    ActionCollection::action<KToggleAction>("showEditor")->setChecked(show);
Tim Beaulen's avatar
Tim Beaulen committed
458
    setVisible(show);
459

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

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

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

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

    box->setCompletionMode(mode);
478
}
479

480 481
void TagEditor::saveConfig()
{
482
    // combo box completion modes
483

484 485 486
    KConfigGroup config(KGlobal::config(), "TagEditor");

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

494 495
void TagEditor::setupActions()
{
496
    KToggleAction *show = new KToggleAction(KIcon("edit"), i18n("Show &Tag Editor"), ActionCollection::actions(), "showEditor");
Dirk Mueller's avatar
Dirk Mueller committed
497
    show->setCheckedState(KGuiItem(i18n("Hide &Tag Editor")));
498 499
    connect(show, SIGNAL(toggled(bool)), this, SLOT(setShown(bool)));

500 501 502
    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()));
503 504
}

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

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

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

    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
531 532 533 534 535

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

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

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

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

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

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

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

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

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

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

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

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

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

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

631 632
    }

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

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

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

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

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

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

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

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

    connect(m_commentBox, SIGNAL(textChanged()),
658
            this, SLOT(slotDataChanged()));
659 660 661 662
}

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

        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.

681
            qApp->processEvents(QEventLoop::ExcludeUserInput);
682 683 684 685 686 687 688 689 690

            PlaylistItem *item = *it;

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

            ++it;

Tim Beaulen's avatar
Tim Beaulen committed
691
            QString fileName = item->file().fileInfo().path() + QDir::separator() +
692 693
                               m_fileNameBox->text();
            if(list.count() > 1)
Tim Beaulen's avatar
Tim Beaulen committed
694
                fileName = item->file().fileInfo().absoluteFilePath();
695 696 697 698 699 700 701 702 703

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

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

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

Dirk Mueller's avatar
Dirk Mueller committed
723
            if(m_enableBoxes[m_genreBox]->isChecked())
724 725 726 727 728 729 730 731 732
                tag->setGenre(m_genreBox->currentText());

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

        TagTransactionManager::instance()->commit();
        CollectionList::instance()->dataChanged();
        m_performingSave = false;
        KApplication::restoreOverrideCursor();
733 734 735 736 737
    }
}

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

    QStringList files;

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

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

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

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

768
    if(!iconName.isNull())
769 770
        iconLabel->setPixmap(SmallIcon(iconName));

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

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

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

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

790
        l->insertStretch(-1, 1);
791

792 793
        l->addWidget(enableBox);
        l->setStretchFactor(enableBox, 0);
794

795
        layout->addWidget(item);
796 797 798 799 800
    }

    enableBox->hide();

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

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

811 812 813
    QWidget::showEvent(e);
}

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

    return false;
}

823 824 825 826
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////

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

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

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

847
#include "tageditor.moc"
848 849

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