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

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

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

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

23 24
#include <kactioncollection.h>
#include <kconfiggroup.h>
25 26 27
#include <kcombobox.h>
#include <klineedit.h>
#include <knuminput.h>
28
#include <ktextedit.h>
29
#include <kapplication.h>
30
#include <kmessagebox.h>
31
#include <kconfig.h>
32 33
#include <klocale.h>
#include <kdebug.h>
34
#include <kiconloader.h>
35
#include <kicon.h>
36
#include <ktoggleaction.h>
37
#include <kshortcut.h>
38

39
#include <QLabel>
40
#include <QApplication>
41 42
#include <QCheckBox>
#include <QDir>
43
#include <QValidator>
44
#include <QToolTip>
45
#include <QEventLoop>
Laurent Montel's avatar
Laurent Montel committed
46 47 48
#include <QKeyEvent>
#include <QHBoxLayout>
#include <QVBoxLayout>
49
#include <QSizePolicy>
50

51 52
#include <id3v1genres.h>

Scott Wheeler's avatar
Scott Wheeler committed
53 54
#undef KeyRelease

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

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

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

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

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

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

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

    virtual void updateCurrent() {}

private:
    TagEditor *m_parent;
};

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

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

    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
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
    }

Matthias Fuchs's avatar
Matthias Fuchs committed
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
        foreach(QWidget *w, m_hideList)
            w->hide();
284

285 286 287
        foreach(QCheckBox *box, m_enableBoxes) {
            box->setChecked(true);
            box->show();
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
        }

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

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

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

                if(tag) {

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

360 361
        foreach(QWidget *w, m_hideList)
            w->show();
Scott Wheeler's avatar
Scott Wheeler committed
362

363 364 365
        foreach(QCheckBox *box, m_enableBoxes) {
            box->setChecked(true);
            box->hide();
366
        }
367 368
    }
    m_dataChanged = false;
369 370
}

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

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

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

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

    if(!list)
400 401
        return;

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

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

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

416
    StringHash genreHash;
417 418

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

Laurent Montel's avatar
Laurent Montel committed
420
    for(QStringList::ConstIterator it = m_genreList.constBegin(); it != m_genreList.constEnd(); ++it)
421
        genreHash.insert(*it);
422 423 424

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

Laurent Montel's avatar
Laurent Montel committed
425
    for(TagLib::StringList::Iterator it = genres.begin(); it != genres.end(); ++it)
426
        genreHash.insert(TStringToQString((*it)));
427

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

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

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

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

445 446
    KConfigGroup config(KGlobal::config(), "TagEditor");
    if(m_artistNameBox && m_albumNameBox) {
Stephan Kulow's avatar
compile  
Stephan Kulow committed
447 448 449
        readCompletionMode(config, m_artistNameBox, "ArtistNameBoxMode");
        readCompletionMode(config, m_albumNameBox, "AlbumNameBoxMode");
        readCompletionMode(config, m_genreBox, "GenreBoxMode");
450
    }
451

452
    bool show = config.readEntry("Show", false);
453
    ActionCollection::action<KToggleAction>("showEditor")->setChecked(show);
Tim Beaulen's avatar
Tim Beaulen committed
454
    setVisible(show);
455

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

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

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

Stephan Kulow's avatar
compile  
Stephan Kulow committed
468
void TagEditor::readCompletionMode(const KConfigGroup &config, KComboBox *box, const QString &key)
469 470
{
    KGlobalSettings::Completion mode =
Stephan Kulow's avatar
compile  
Stephan Kulow committed
471
        KGlobalSettings::Completion(config.readEntry(key, (int)KGlobalSettings::CompletionAuto));
472 473

    box->setCompletionMode(mode);
474
}
475

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

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

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

490 491
void TagEditor::setupActions()
{
492
    KToggleAction *show = new KToggleAction(KIcon("document-properties"), i18n("Show &Tag Editor"), this);
493
    ActionCollection::actions()->addAction("showEditor", show);
494 495
    connect(show, SIGNAL(toggled(bool)), this, SLOT(setShown(bool)));

496
    KAction *act = new KAction(KIcon("document-save"), i18n("&Save"), this);
497
    ActionCollection::actions()->addAction("saveItem", act);
498 499
    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

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

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

538 539 540
        m_albumNameBox = new KComboBox( true, this );
        m_albumNameBox->setObjectName( "albumNameBox" );
        m_albumNameBox->setCompletionMode(KGlobalSettings::CompletionAuto);
541
        addItem(i18n("Album &name:"), m_albumNameBox, leftColumnLayout, "media-optical-audio");
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, "help-about");
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
        QHBoxLayout *fileNameLayout = new QHBoxLayout();
557 558
        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
        QLabel *fileNameIcon = new QLabel(this);
Pino Toscano's avatar
Pino Toscano committed
565
        fileNameIcon->setPixmap(SmallIcon("audio-x-generic"));
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
            addItem(i18nc("cd track number", "T&rack:"), m_trackSpin, trackRowLayout);
584 585 586 587 588
            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
        }

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

628 629
    }

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

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

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

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

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

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

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

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

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

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

        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.

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

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

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

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

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

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

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

    QStringList files;

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

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

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

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

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

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

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

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

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

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

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

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

    enableBox->hide();

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

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

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

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

    return false;
}

820 821 822 823
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////

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

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

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

844
#include "tageditor.moc"
845 846

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