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 = 2;
505
    static const int verticalSpacing = 2;
506

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

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

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

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

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

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

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

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

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

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

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

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

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

583 584
            m_trackSpin = new KIntSpinBox(0, 9999, 1, 0, this);
            m_trackSpin->setObjectName( "trackSpin" );
585
            addItem(i18nc("cd track number", "T&rack:"), m_trackSpin, trackRowLayout);
586 587
            m_trackSpin->installEventFilter(this);

588 589
            m_yearSpin = new KIntSpinBox(0, 9999, 1, 0, this );
            m_yearSpin->setObjectName( "yearSpin" );
590 591 592 593 594 595
            addItem(i18n("&Year:"), m_yearSpin, trackRowLayout);
            m_yearSpin->installEventFilter(this);

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

596 597 598 599 600 601
            trackRowLayout->setWidth();
        }
        {
            FixedHLayout *trackRowLayout = new FixedHLayout(rightColumnLayout,
                                                            horizontalSpacing);

602 603
            m_lengthBox = new KLineEdit(this);
            m_lengthBox->setObjectName( "lengthBox" );
604 605
            // addItem(i18n("Length:"), m_lengthBox, trackRowLayout);
            m_lengthBox->setMinimumWidth(fontMetrics().width("00:00") + trackRowLayout->spacing());
606
            m_lengthBox->setMaximumWidth(60);
607 608
            m_lengthBox->setAlignment(Qt::AlignRight);
            m_lengthBox->setReadOnly(true);
609
            addItem(i18n("Length:"), m_lengthBox, trackRowLayout);
610

611 612
            m_bitrateBox = new KLineEdit(this);
            m_bitrateBox->setObjectName( "bitrateBox" );
613 614
            // addItem(i18n("Bitrate:"), m_bitrateBox, trackRowLayout);
            m_bitrateBox->setMinimumWidth(fontMetrics().width("000") + trackRowLayout->spacing());
615
            m_bitrateBox->setMaximumWidth(60);
616 617
            m_bitrateBox->setAlignment(Qt::AlignRight);
            m_bitrateBox->setReadOnly(true);
618 619 620 621
            addItem(i18n("Bitrate:"), m_bitrateBox, trackRowLayout);

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

            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, "document-properties");
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
        QHBoxLayout *l = new QHBoxLayout();
784
        l->setMargin(0);
Stephan Kulow's avatar
Stephan Kulow committed
785
        layout->addItem(l);
786 787 788 789

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

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

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

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

    enableBox->hide();

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

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

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

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

    return false;
}

824 825 826 827
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////

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

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

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

848
#include "tageditor.moc"
849 850

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