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 20.6 KB
Newer Older
1 2 3 4
/***************************************************************************
                          tageditor.cpp  -  description
                             -------------------
    begin                : Sat Sep 7 2002
5
    copyright            : (C) 2002, 2003 by Scott Wheeler
6
    email                : wheeler@kde.org
7 8 9 10 11 12 13 14 15 16 17
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

18 19 20 21 22
#include "tageditor.h"
#include "collectionlist.h"
#include "playlistitem.h"
#include "tag.h"

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

#include <qlabel.h>
34
#include <qcheckbox.h>
35 36
#include <qlayout.h>
#include <qdir.h>
Scott Wheeler's avatar
Scott Wheeler committed
37
#include <qvalidator.h>
38
#include <qtooltip.h>
39

40 41
#include <id3v1genres.h>

Scott Wheeler's avatar
Scott Wheeler committed
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
class FileNameValidator : public QValidator
{
public:
    FileNameValidator(QObject *parent, const char *name = 0) :
	QValidator(parent, name) {}

    virtual void fixup(QString &s) const
    {
	s.remove('/');
    }

    virtual State validate(QString &s, int &) const
    {
	if(s.find('/' != -1))
	   return Invalid;
	return Acceptable;
    }
};

61 62 63 64
class FileBoxToolTip : public QToolTip
{
public:
    FileBoxToolTip(TagEditor *editor, QWidget *widget) :
65
	QToolTip(widget), m_editor(editor) {}
66 67 68
protected:
    virtual void maybeTip(const QPoint &)
    {
69
	tip(parentWidget()->rect(), m_editor->items().first()->file().absFilePath());
70 71 72 73 74
    }
private:
    TagEditor *m_editor;
};

75 76 77 78
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

79 80
TagEditor::TagEditor(QWidget *parent, const char *name) : QWidget(parent, name),
							  m_currentPlaylist(0)
81 82 83
{
    setupLayout();
    readConfig();
84
    m_dataChanged = false;
85 86 87 88 89 90 91 92 93 94 95
}

TagEditor::~TagEditor()
{
    saveConfig();
}

////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

96
void TagEditor::slotSetItems(const PlaylistItemList &list)
97 98
{
    saveChangesPrompt();
99 100 101 102 103 104 105 106 107 108 109 110 111

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

    m_currentPlaylist = list.isEmpty() ? 0 : static_cast<Playlist *>(list.first()->listView());

    if(m_currentPlaylist) {
	connect(m_currentPlaylist, SIGNAL(signalAboutToRemove(PlaylistItem *)),
		this, SLOT(slotItemRemoved(PlaylistItem *)));
    }

112
    m_items = list;
113

114
    if(isVisible())
115
	slotRefresh();
116 117
}

118
void TagEditor::slotRefresh()
119
{
120
    // This method takes the list of currently selected m_items and tries to 
121 122
    // 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
123
    // check after that to see if there are other m_items and adjust accordingly.
124

125
    if(m_items.isEmpty() || !m_items.first()->file().tag()) {
126
	slotClear();
127
	setEnabled(false);
128 129 130
	return;
    }
    
131 132
    setEnabled(true);

133
    PlaylistItem *item = m_items.first();
134

135
    Tag *tag = item->file().tag();
136
	
137
    m_artistNameBox->setEditText(tag->artist());
138
    m_trackNameBox->setText(tag->title());
139
    m_albumNameBox->setEditText(tag->album());
140

141
    m_fileNameBox->setText(item->file().fileInfo().fileName());
142
    new FileBoxToolTip(this, m_fileNameBox);
143
    m_bitrateBox->setText(QString::number(tag->bitrate()));
144 145
    m_lengthBox->setText(tag->lengthString());

146 147
    if(m_genreList.findIndex(tag->genre()) >= 0)
	m_genreBox->setCurrentItem(m_genreList.findIndex(tag->genre()) + 1);
148 149
    else {
	m_genreBox->setCurrentItem(0);
150
	m_genreBox->setEditText(tag->genre());
151
    }
152

153
    m_trackSpin->setValue(tag->track());
154 155 156 157 158 159 160 161 162 163 164 165
    m_yearSpin->setValue(tag->year());
    
    m_commentBox->setText(tag->comment());
    
    // Start at the second item, since we've already processed the first.
    
    PlaylistItemList::Iterator it = m_items.begin();
    ++it;

    // If there is more than one item in the m_items that we're dealing with...
    
    if(it != m_items.end()) {
166 167 168 169

	QValueListIterator<QWidget *> hideIt = m_hideList.begin();
	for(; hideIt != m_hideList.end(); ++hideIt)
	    (*hideIt)->hide();
Scott Wheeler's avatar
Scott Wheeler committed
170 171 172

	BoxMap::Iterator boxIt = m_enableBoxes.begin();
	for(; boxIt != m_enableBoxes.end(); boxIt++) {
173 174
	    (*boxIt)->setChecked(true);
	    (*boxIt)->show();
175 176
	}
	
177 178 179
	// 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.
180
	
181
	// Also, if there are more than 50 m_items, don't scan all of them.
182
	
183 184 185 186 187 188 189 190
	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);
191 192
	}
	else {
193
	    for(; it != m_items.end(); ++it) {
194
		tag = (*it)->file().tag();
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239

		if(tag) {

		    if(m_artistNameBox->currentText() != tag->artist() &&
		       m_enableBoxes.contains(m_artistNameBox))
		    {
			m_artistNameBox->lineEdit()->clear();
			m_enableBoxes[m_artistNameBox]->setChecked(false);
		    }
		    if(m_trackNameBox->text() != tag->title() &&
		       m_enableBoxes.contains(m_trackNameBox))
		    {
			m_trackNameBox->clear();
			m_enableBoxes[m_trackNameBox]->setChecked(false);
		    }
		    if(m_albumNameBox->currentText() != tag->album() &&
		       m_enableBoxes.contains(m_albumNameBox))
		    {
			m_albumNameBox->lineEdit()->clear();
			m_enableBoxes[m_albumNameBox]->setChecked(false);
		    }
		    if(m_genreBox->currentText() != tag->genre() &&
		       m_enableBoxes.contains(m_genreBox))
		    {
			m_genreBox->lineEdit()->clear();
			m_enableBoxes[m_genreBox]->setChecked(false);
		    }		
		    if(m_trackSpin->value() != tag->track() &&
		       m_enableBoxes.contains(m_trackSpin))
		    {
			m_trackSpin->setValue(0);
			m_enableBoxes[m_trackSpin]->setChecked(false);
		    }		
		    if(m_yearSpin->value() != tag->year() &&
		       m_enableBoxes.contains(m_yearSpin))
		    {
			m_yearSpin->setValue(0);
			m_enableBoxes[m_yearSpin]->setChecked(false);
		    }
		    if(m_commentBox->text() != tag->comment() &&
		       m_enableBoxes.contains(m_commentBox))
		    {
			m_commentBox->clear();
			m_enableBoxes[m_commentBox]->setChecked(false);
		    }
240
		}
241 242
	    }
	}
243
    }
244 245
    else {
	// Clean up in the case that we are only handling one item.
246 247 248 249

	QValueListIterator<QWidget *> showIt = m_hideList.begin();
	for(; showIt != m_hideList.end(); ++showIt)
	    (*showIt)->show();
Scott Wheeler's avatar
Scott Wheeler committed
250 251 252

	BoxMap::iterator boxIt = m_enableBoxes.begin();
	for(; boxIt != m_enableBoxes.end(); boxIt++) {
253 254 255 256 257
	    (*boxIt)->setChecked(true);
	    (*boxIt)->hide();
	}
    }
    m_dataChanged = false;
258 259
}

260
void TagEditor::slotClear()
261
{
262 263 264 265 266 267 268 269 270 271
    m_artistNameBox->lineEdit()->clear();
    m_trackNameBox->clear();
    m_albumNameBox->lineEdit()->clear();
    m_genreBox->setCurrentItem(0);
    m_fileNameBox->clear();
    m_trackSpin->setValue(0);
    m_yearSpin->setValue(0);
    m_lengthBox->clear();
    m_bitrateBox->clear();
    m_commentBox->clear();    
272 273
}

274
void TagEditor::slotUpdateCollection()
275
{
276 277 278 279 280
    CollectionList *list = CollectionList::instance();

    if(!list)
	return;
    
281 282 283 284 285 286 287 288 289
    QStringList artistList = list->uniqueSet(CollectionList::Artists);
    m_artistNameBox->listBox()->clear();
    m_artistNameBox->listBox()->insertStringList(artistList);
    m_artistNameBox->completionObject()->setItems(artistList);

    QStringList albumList = list->uniqueSet(CollectionList::Albums);
    m_albumNameBox->listBox()->clear();
    m_albumNameBox->listBox()->insertStringList(albumList);
    m_albumNameBox->completionObject()->setItems(albumList);
290 291 292 293 294 295
}

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

296
void TagEditor::readConfig()
297
{
298 299
    KConfig *config = KGlobal::config();
    { // combo box completion modes
300 301
	KConfigGroupSaver saver(config, "TagEditor");
	if(m_artistNameBox && m_albumNameBox) {
302 303 304
	    readCompletionMode(config, m_artistNameBox, "ArtistNameBoxMode");
	    readCompletionMode(config, m_albumNameBox, "AlbumNameBoxMode");
	    readCompletionMode(config, m_genreBox, "GenreBoxMode");
305 306
        }
    }
307

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

310 311 312
    for(TagLib::StringList::ConstIterator it = genres.begin(); it != genres.end(); ++it)
	m_genreList.append(TStringToQString((*it)));
    m_genreList.sort();
313

314 315 316
    m_genreBox->clear();
    m_genreBox->insertItem(QString::null);
    m_genreBox->insertStringList(m_genreList);
317 318 319 320 321 322 323 324 325
    m_genreBox->completionObject()->setItems(m_genreList);
}

void TagEditor::readCompletionMode(KConfig *config, KComboBox *box, const QString &key)
{
    KGlobalSettings::Completion mode =
	KGlobalSettings::Completion(config->readNumEntry(key, KGlobalSettings::CompletionAuto));

    box->setCompletionMode(mode);
326
}
327

328 329 330 331 332
void TagEditor::saveConfig()
{
    KConfig *config = KGlobal::config();
    { // combo box completion modes
        KConfigGroupSaver saver(config, "TagEditor");
333 334 335
        if(m_artistNameBox && m_albumNameBox) {
	    config->writeEntry("ArtistNameBoxMode", m_artistNameBox->completionMode());
	    config->writeEntry("AlbumNameBoxMode", m_albumNameBox->completionMode());
336
	    config->writeEntry("GenreBoxMode", m_genreBox->completionMode());
337 338
        }
    }
339

340
}
341

342 343
void TagEditor::setupLayout()
{
344
    static const int horizontalSpacing = 12;
345
    static const int verticalSpacing = 2;
346

347
    QHBoxLayout *layout = new QHBoxLayout(this, 6, horizontalSpacing);
348 349 350 351 352 353 354 355 356 357 358 359 360 361

    //////////////////////////////////////////////////////////////////////////////
    // define two columns of the bottem layout
    //////////////////////////////////////////////////////////////////////////////
    QVBoxLayout *leftColumnLayout = new QVBoxLayout(layout, verticalSpacing);
    QVBoxLayout *rightColumnLayout = new QVBoxLayout(layout, verticalSpacing);

    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
362 363
    	
	m_artistNameBox = new KComboBox(true, this, "artistNameBox");
364
	m_artistNameBox->setCompletionMode(KGlobalSettings::CompletionAuto);
365
	addItem(i18n("&Artist name:"), m_artistNameBox, leftColumnLayout, "personal");
366

367
        m_trackNameBox = new KLineEdit(this, "trackNameBox");
368
	addItem(i18n("&Track name:"), m_trackNameBox, leftColumnLayout, "player_play");
369

370
	m_albumNameBox = new KComboBox(true, this, "albumNameBox");
371
	m_albumNameBox->setCompletionMode(KGlobalSettings::CompletionAuto);
372
	addItem(i18n("Album &name:"), m_albumNameBox, leftColumnLayout, "cdrom_unmount");
373

374
        m_genreBox = new KComboBox(true, this, "genreBox");
375
	addItem(i18n("&Genre:"), m_genreBox, leftColumnLayout, "knotify");
376 377

        // this fills the space at the bottem of the left column
Scott Wheeler's avatar
Scott Wheeler committed
378 379
        leftColumnLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum,
						  QSizePolicy::Expanding));
380 381 382 383 384
    }
    //////////////////////////////////////////////////////////////////////////////
    // put stuff in the right column
    //////////////////////////////////////////////////////////////////////////////
    { // just for organization
385 386 387
	
	QHBoxLayout *fileNameLayout = new QHBoxLayout(rightColumnLayout,
							horizontalSpacing);
388

389
	m_fileNameBox = new KLineEdit(this, "fileNameBox");
Scott Wheeler's avatar
Scott Wheeler committed
390 391
	m_fileNameBox->setValidator(new FileNameValidator(m_fileNameBox));	

392 393
	QLabel *fileNameIcon = new QLabel(this);
	fileNameIcon->setPixmap(SmallIcon("sound"));
Scott Wheeler's avatar
Scott Wheeler committed
394 395
	QWidget *fileNameLabel = addHidden(new QLabel(m_fileNameBox, i18n("&File name:"), this));

396 397 398 399 400 401
	fileNameLayout->addWidget(addHidden(fileNameIcon));
	fileNameLayout->addWidget(fileNameLabel);
	fileNameLayout->setStretchFactor(fileNameIcon, 0);
	fileNameLayout->setStretchFactor(fileNameLabel, 0);
	fileNameLayout->insertStretch(-1, 1);
	rightColumnLayout->addWidget(addHidden(m_fileNameBox));
402 403

        { // lay out the track row
Scott Wheeler's avatar
Scott Wheeler committed
404 405
            QHBoxLayout *trackRowLayout = new QHBoxLayout(rightColumnLayout,
							  horizontalSpacing);
406

407
	    m_trackSpin = new KIntSpinBox(0, 255, 1, 0, 10, this, "trackSpin");
408
	    addItem(i18n("T&rack:"), m_trackSpin, trackRowLayout);
409
	    m_trackSpin->installEventFilter(this);
410

411
	    trackRowLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding,
Scott Wheeler's avatar
Scott Wheeler committed
412
						    QSizePolicy::Minimum));
413

414
	    m_yearSpin = new KIntSpinBox(0, 9999, 1, 0, 10, this, "yearSpin");
415
	    addItem(i18n("&Year:"), m_yearSpin, trackRowLayout);
416
	    m_yearSpin->installEventFilter(this);
417

418
	    trackRowLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding,
Scott Wheeler's avatar
Scott Wheeler committed
419
						    QSizePolicy::Minimum));
420

421 422 423 424 425 426 427
	    trackRowLayout->addWidget(addHidden(new QLabel(i18n("Length:"), this)));
	    m_lengthBox = new KLineEdit(this, "lengthBox");
	    // addItem(i18n("Length:"), m_lengthBox, trackRowLayout);
	    m_lengthBox->setMaximumWidth(50);
	    m_lengthBox->setAlignment(Qt::AlignRight);
	    m_lengthBox->setReadOnly(true);
	    trackRowLayout->addWidget(addHidden(m_lengthBox));
428

429
	    trackRowLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding,
Scott Wheeler's avatar
Scott Wheeler committed
430
						    QSizePolicy::Minimum));
431

432 433 434 435 436 437 438
	    trackRowLayout->addWidget(addHidden(new QLabel(i18n("Bitrate:"), this)));
	    m_bitrateBox = new KLineEdit(this, "bitrateBox");
	    // addItem(i18n("Bitrate:"), m_bitrateBox, trackRowLayout);
	    m_bitrateBox->setMaximumWidth(50);
	    m_bitrateBox->setAlignment(Qt::AlignRight);
	    m_bitrateBox->setReadOnly(true);
	    trackRowLayout->addWidget(addHidden(m_bitrateBox));
439 440
        }

441 442
        m_commentBox = new KEdit(this, "commentBox");
	m_commentBox->setTextFormat(Qt::PlainText);
443 444 445
	addItem(i18n("&Comment:"), m_commentBox, rightColumnLayout, "edit");
    	fileNameLabel->setMinimumHeight(m_trackSpin->height());

446 447
    }

Scott Wheeler's avatar
Scott Wheeler committed
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
    connect(m_artistNameBox, SIGNAL(textChanged(const QString&)),
	    this, SLOT(slotDataChanged()));

    connect(m_trackNameBox, SIGNAL(textChanged(const QString&)),
	    this, SLOT(slotDataChanged()));

    connect(m_albumNameBox, SIGNAL(textChanged(const QString&)),
	    this, SLOT(slotDataChanged()));

    connect(m_genreBox, SIGNAL(activated(int)),
	    this, SLOT(slotDataChanged()));

    connect(m_genreBox, SIGNAL(textChanged(const QString&)),
	    this, SLOT(slotDataChanged()));

    connect(m_fileNameBox, SIGNAL(textChanged(const QString&)),
	    this, SLOT(slotDataChanged()));

    connect(m_yearSpin, SIGNAL(valueChanged(int)),
	    this, SLOT(slotDataChanged()));

    connect(m_trackSpin, SIGNAL(valueChanged(int)),
	    this, SLOT(slotDataChanged()));

    connect(m_commentBox, SIGNAL(textChanged()),
	    this, SLOT(slotDataChanged()));
474 475 476 477
}

void TagEditor::save(const PlaylistItemList &list)
{
478
    if(!list.isEmpty() && m_dataChanged) {
479 480 481 482 483 484 485
	
	KApplication::setOverrideCursor(Qt::waitCursor);

	// To keep track of the files that don't cooperate...

	QStringList errorFiles;
	
486
	for(PlaylistItemList::ConstIterator it = list.begin(); it != list.end(); ++it) {
487 488
	    PlaylistItem *item = *it;
	    
489 490 491
	    QFileInfo newFile(item->file().fileInfo().dirPath() + QDir::separator() +
			      m_fileNameBox->text());
	    QFileInfo directory(item->file().fileInfo().dirPath());
492 493 494 495 496 497
	    
	    // If (the new file is writable or the new file doesn't exist and
	    // it's directory is writable) and the old file is writable...  
	    // If not we'll append it to errorFiles to tell the user which
	    // files we couldn't write to.
	    
Scott Wheeler's avatar
Scott Wheeler committed
498 499
	    if(item &&
	       item->file().tag() &&
500
	       (newFile.isWritable() || (!newFile.exists() && directory.isWritable())) &&
501
	       item->file().fileInfo().isWritable())
Scott Wheeler's avatar
Scott Wheeler committed
502
	    {
503 504 505 506
		
		// If the file name in the box doesn't match the current file
		// name...
		
507 508 509
		if(list.count() == 1 &&
		   item->file().fileInfo().fileName() != newFile.fileName())
		{
510
		    
511 512
		    // Rename the file if it doesn't exist or the user says
		    // that it's ok.
513
		    
514
		    if(!newFile.exists() ||
Scott Wheeler's avatar
Scott Wheeler committed
515 516 517 518
		       KMessageBox::warningYesNo(
			   this, 
			   i18n("This file already exists.\nDo you want to replace it?"),
			   i18n("File Exists")) == KMessageBox::Yes)
519 520
		    {
			QDir currentDir;
521 522
			currentDir.rename(item->file().absFilePath(), newFile.filePath());
			item->file().setFile(newFile.filePath());
523 524
		    }
		}
525 526 527 528 529 530 531
		
		// 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.
		
532
		if(m_enableBoxes[m_artistNameBox]->isOn())
533
		    item->file().tag()->setArtist(m_artistNameBox->currentText());
534
		if(m_enableBoxes[m_trackNameBox]->isOn())
535
		    item->file().tag()->setTitle(m_trackNameBox->text());
536
		if(m_enableBoxes[m_albumNameBox]->isOn())
537
		    item->file().tag()->setAlbum(m_albumNameBox->currentText());
538
		if(m_enableBoxes[m_trackSpin]->isOn())
539
		    item->file().tag()->setTrack(m_trackSpin->value());
540
		if(m_enableBoxes[m_yearSpin]->isOn())
541
		    item->file().tag()->setYear(m_yearSpin->value());
542
		if(m_enableBoxes[m_commentBox]->isOn())
543
		    item->file().tag()->setComment(m_commentBox->text());
544
		
545
		if(m_enableBoxes[m_genreBox]->isOn())
546
		    item->file().tag()->setGenre(m_genreBox->currentText());
547
		
548
		item->file().tag()->save();
549
		
550
		item->refresh();
551
	    }
Scott Wheeler's avatar
Scott Wheeler committed
552
	    else if(item)
553
		errorFiles.append(item->file().absFilePath());
554 555

	    kapp->processEvents();
556 557 558 559 560
	}
	
	if(!errorFiles.isEmpty())
	    KMessageBox::detailedSorry(this,
				       i18n("Could not save to specified file(s)."), 
561
				       i18n("Could Not Write to:\n") + errorFiles.join("\n"));
562
	m_dataChanged = false;
563 564

	KApplication::restoreOverrideCursor();
565 566 567 568 569
    }
}

void TagEditor::saveChangesPrompt()
{
570 571 572 573 574 575
    if(!isVisible() || !m_dataChanged || m_items.isEmpty())
	return;

    QStringList files;

    for(PlaylistItemList::Iterator it = m_items.begin(); it != m_items.end(); it++)
576
	files.append((*it)->file().absFilePath());
577 578 579 580 581 582 583 584 585 586

    if(KMessageBox::questionYesNoList(this,
				      i18n("Do you want to save your changes to:\n"), 
				      files, 
				      i18n("Save Changes"),
				      KStdGuiItem::yes(),
				      KStdGuiItem::no(),
				      "tagEditor_showSaveChangesBox") == KMessageBox::Yes)
    {
	save(m_items);
587 588 589
    }
}

590
void TagEditor::addItem(const QString &text, QWidget *item, QBoxLayout *layout, const QString &iconName)
591 592 593 594
{
    if(!item || !layout)
	return;

595
    QLabel *label = new QLabel(item, text, this);
596 597 598 599 600
    QLabel *iconLabel = new QLabel(item, 0, this);
    
    if(!iconName.isNull())
	iconLabel->setPixmap(SmallIcon(iconName));
    
601 602 603 604 605 606
    QCheckBox *enableBox = new QCheckBox(i18n("Enable"), this);
    enableBox->setChecked(true);

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

    if(layout->direction() == QBoxLayout::LeftToRight) {
607
    	layout->addWidget(iconLabel);
608 609 610 611 612 613
	layout->addWidget(label);
	layout->addWidget(item);
	layout->addWidget(enableBox);
    }
    else {
	QHBoxLayout *l = new QHBoxLayout(layout);
614 615
	
	l->addWidget(iconLabel);
616 617 618 619 620 621 622 623 624 625 626 627 628 629
	l->addWidget(label);
	l->setStretchFactor(label, 1);

	l->insertStretch(-1, 1);

	l->addWidget(enableBox);
	l->setStretchFactor(enableBox, 0);

	layout->addWidget(item);
    }

    enableBox->hide();

    connect(enableBox, SIGNAL(toggled(bool)), item, SLOT(setEnabled(bool)));
630
    m_enableBoxes.insert(item, enableBox);
631 632
}

633 634
void TagEditor::showEvent(QShowEvent *e)
{
635
    slotRefresh();
636 637 638
    QWidget::showEvent(e);
}

639 640 641 642 643 644 645 646
bool TagEditor::eventFilter(QObject *watched, QEvent *e)
{
    if(watched->inherits("QSpinBox") && e->type() == QEvent::KeyRelease)
	slotDataChanged();

    return false;
}

647 648 649 650
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////

651
void TagEditor::slotDataChanged(bool c)
652
{
653
    m_dataChanged = c;
654 655
}

656 657 658 659 660 661 662
void TagEditor::slotItemRemoved(PlaylistItem *item)
{
    m_items.remove(item);
    if(m_items.isEmpty())
	slotRefresh();
}

663
#include "tageditor.moc"