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 23.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

22 23 24 25
#include <kcombobox.h>
#include <klineedit.h>
#include <knuminput.h>
#include <keditcl.h>
26
#include <kmessagebox.h>
27
#include <kconfig.h>
28 29
#include <klocale.h>
#include <kdebug.h>
30
#include <kiconloader.h>
31
#include <kactionclasses.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
#include <qeventloop.h>
40

41 42
#include <id3v1genres.h>

43 44
using namespace ActionCollection;

Scott Wheeler's avatar
Scott Wheeler committed
45 46 47 48 49 50 51 52 53 54 55 56 57
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
    {
58
	if(s.find('/') != -1)
Scott Wheeler's avatar
Scott Wheeler committed
59 60 61 62 63
	   return Invalid;
	return Acceptable;
    }
};

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

78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
class FixedHLayout : public QHBoxLayout
{
public:
    FixedHLayout(QWidget *parent, int margin = 0, int spacing = -1, const char *name = 0) :
	QHBoxLayout(parent, margin, spacing, name),
	m_width(-1) {}
    FixedHLayout(QLayout *parentLayout, int spacing = -1, const char *name = 0) :
	QHBoxLayout(parentLayout, spacing, name),
	m_width(-1) {}
    void setWidth(int w = -1)
    {
	m_width = w == -1 ? QHBoxLayout::minimumSize().width() : w;
    }
    virtual QSize minimumSize() const
    {
	QSize s = QHBoxLayout::minimumSize();
	s.setWidth(m_width);
	return s;
    }
private:
    int m_width;
};

101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
class CollectionObserver : public PlaylistObserver
{
public:
    CollectionObserver(TagEditor *parent) :
	PlaylistObserver(CollectionList::instance()),
	m_parent(parent)
    {
    }

    virtual void updateData()
    {
	if(m_parent && m_parent->m_currentPlaylist)
	    m_parent->slotSetItems(m_parent->m_currentPlaylist->selectedItems());
    }

    virtual void updateCurrent() {}

private:
    TagEditor *m_parent;
};

122 123 124 125
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

126 127
TagEditor::TagEditor(QWidget *parent, const char *name) :
    QWidget(parent, name),
128 129
    m_currentPlaylist(0),
    m_observer(0)
130
{
131
    setupActions();
132 133
    setupLayout();
    readConfig();
134
    m_dataChanged = false;
135
    m_collectionChanged = false;
136 137 138 139
}

TagEditor::~TagEditor()
{
140
    delete m_observer;
141 142 143
    saveConfig();
}

144 145 146 147 148
void TagEditor::setupObservers()
{
    m_observer = new CollectionObserver(this);
}

149 150 151 152
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

153
void TagEditor::slotSetItems(const PlaylistItemList &list)
154 155
{
    saveChangesPrompt();
156 157 158 159 160 161 162 163 164 165 166

    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 *)));
167
	connect(m_currentPlaylist, SIGNAL(destroyed()), this, SLOT(slotPlaylistRemoved()));
168 169
    }

170
    m_items = list;
171

172
    if(isVisible())
173
	slotRefresh();
174 175
}

176
void TagEditor::slotRefresh()
177
{
178
    // This method takes the list of currently selected m_items and tries to 
179 180
    // 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
181
    // check after that to see if there are other m_items and adjust accordingly.
182

183
    if(m_items.isEmpty() || !m_items.first()->file().tag()) {
184
	slotClear();
185
	setEnabled(false);
186 187 188
	return;
    }
    
189 190
    setEnabled(true);

191
    PlaylistItem *item = m_items.first();
192

193
    Tag *tag = item->file().tag();
194
	
195 196 197 198
    QFileInfo fi(item->file().absFilePath());
    if(!fi.isWritable() && m_items.count() == 1)
	setEnabled(false);

199
    m_artistNameBox->setEditText(tag->artist());
200
    m_trackNameBox->setText(tag->title());
201
    m_albumNameBox->setEditText(tag->album());
202

203
    m_fileNameBox->setText(item->file().fileInfo().fileName());
204
    new FileBoxToolTip(this, m_fileNameBox);
205
    m_bitrateBox->setText(QString::number(tag->bitrate()));
206 207
    m_lengthBox->setText(tag->lengthString());

208 209
    if(m_genreList.findIndex(tag->genre()) >= 0)
	m_genreBox->setCurrentItem(m_genreList.findIndex(tag->genre()) + 1);
210 211
    else {
	m_genreBox->setCurrentItem(0);
212
	m_genreBox->setEditText(tag->genre());
213
    }
214

215
    m_trackSpin->setValue(tag->track());
216 217 218 219 220 221 222 223 224 225 226 227
    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()) {
228 229 230 231

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

	BoxMap::Iterator boxIt = m_enableBoxes.begin();
	for(; boxIt != m_enableBoxes.end(); boxIt++) {
235 236
	    (*boxIt)->setChecked(true);
	    (*boxIt)->show();
237 238
	}
	
239 240 241
	// 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.
242
	
243
	// Also, if there are more than 50 m_items, don't scan all of them.
244
	
245 246 247 248 249 250 251 252
	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);
253 254
	}
	else {
255
	    for(; it != m_items.end(); ++it) {
256
		tag = (*it)->file().tag();
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301

		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);
		    }
302
		}
303 304
	    }
	}
305
    }
306 307
    else {
	// Clean up in the case that we are only handling one item.
308 309 310 311

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

	BoxMap::iterator boxIt = m_enableBoxes.begin();
	for(; boxIt != m_enableBoxes.end(); boxIt++) {
315 316 317 318 319
	    (*boxIt)->setChecked(true);
	    (*boxIt)->hide();
	}
    }
    m_dataChanged = false;
320 321
}

322
void TagEditor::slotClear()
323
{
324 325 326 327 328 329 330 331 332 333
    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();    
334 335
}

336
void TagEditor::slotUpdateCollection()
337
{
338 339 340 341 342 343 344 345 346 347
    if(isVisible())
	updateCollection();
    else
	m_collectionChanged = true;
}

void TagEditor::updateCollection()
{
    m_collectionChanged = false;
        
348 349 350 351 352
    CollectionList *list = CollectionList::instance();

    if(!list)
	return;
    
353 354 355 356 357 358 359 360 361
    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);
362 363 364 365 366 367
}

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

368
void TagEditor::readConfig()
369
{
370 371 372 373 374 375 376
    // combo box completion modes
	
    KConfigGroup config(KGlobal::config(), "TagEditor");
    if(m_artistNameBox && m_albumNameBox) {
	readCompletionMode(&config, m_artistNameBox, "ArtistNameBoxMode");
	readCompletionMode(&config, m_albumNameBox, "AlbumNameBoxMode");
	readCompletionMode(&config, m_genreBox, "GenreBoxMode");
377
    }
378

379 380 381 382
    bool show = config.readBoolEntry("Show", false);
    action<KToggleAction>("showEditor")->setChecked(show);
    setShown(show);

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

385 386 387
    for(TagLib::StringList::ConstIterator it = genres.begin(); it != genres.end(); ++it)
	m_genreList.append(TStringToQString((*it)));
    m_genreList.sort();
388

389 390 391
    m_genreBox->clear();
    m_genreBox->insertItem(QString::null);
    m_genreBox->insertStringList(m_genreList);
392 393 394
    m_genreBox->completionObject()->setItems(m_genreList);
}

395
void TagEditor::readCompletionMode(KConfigBase *config, KComboBox *box, const QString &key)
396 397 398 399 400
{
    KGlobalSettings::Completion mode =
	KGlobalSettings::Completion(config->readNumEntry(key, KGlobalSettings::CompletionAuto));

    box->setCompletionMode(mode);
401
}
402

403 404
void TagEditor::saveConfig()
{
405
    // combo box completion modes
406

407 408 409 410 411 412 413 414
    KConfigGroup config(KGlobal::config(), "TagEditor");

    if(m_artistNameBox && m_albumNameBox) {
	config.writeEntry("ArtistNameBoxMode", m_artistNameBox->completionMode());
	config.writeEntry("AlbumNameBoxMode", m_albumNameBox->completionMode());
	config.writeEntry("GenreBoxMode", m_genreBox->completionMode());
    }
    config.writeEntry("Show", action<KToggleAction>("showEditor")->isChecked());
415
}
416

417 418 419 420 421 422 423 424 425
void TagEditor::setupActions()
{
    KToggleAction *show = new KToggleAction(i18n("Show &Tag Editor"), "edit", 0, actions(), "showEditor");
    show->setCheckedState(i18n("Hide &Tag Editor"));
    connect(show, SIGNAL(toggled(bool)), this, SLOT(setShown(bool)));

    new KAction(i18n("&Save"), "filesave", "CTRL+t", this, SLOT(slotSave()), actions(), "saveItem");
}

426 427
void TagEditor::setupLayout()
{
428
    static const int horizontalSpacing = 12;
429
    static const int verticalSpacing = 2;
430

431
    QHBoxLayout *layout = new QHBoxLayout(this, 6, horizontalSpacing);
432 433 434 435 436 437 438 439 440 441 442 443 444 445

    //////////////////////////////////////////////////////////////////////////////
    // 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
446 447
    	
	m_artistNameBox = new KComboBox(true, this, "artistNameBox");
448
	m_artistNameBox->setCompletionMode(KGlobalSettings::CompletionAuto);
449
	addItem(i18n("&Artist name:"), m_artistNameBox, leftColumnLayout, "personal");
450

451
        m_trackNameBox = new KLineEdit(this, "trackNameBox");
452
	addItem(i18n("&Track name:"), m_trackNameBox, leftColumnLayout, "player_play");
453

454
	m_albumNameBox = new KComboBox(true, this, "albumNameBox");
455
	m_albumNameBox->setCompletionMode(KGlobalSettings::CompletionAuto);
456
	addItem(i18n("Album &name:"), m_albumNameBox, leftColumnLayout, "cdrom_unmount");
457

458
        m_genreBox = new KComboBox(true, this, "genreBox");
459
	addItem(i18n("&Genre:"), m_genreBox, leftColumnLayout, "knotify");
460 461

        // this fills the space at the bottem of the left column
Scott Wheeler's avatar
Scott Wheeler committed
462 463
        leftColumnLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum,
						  QSizePolicy::Expanding));
464 465 466 467 468
    }
    //////////////////////////////////////////////////////////////////////////////
    // put stuff in the right column
    //////////////////////////////////////////////////////////////////////////////
    { // just for organization
469 470
	
	QHBoxLayout *fileNameLayout = new QHBoxLayout(rightColumnLayout,
471
						      horizontalSpacing);
472

473
	m_fileNameBox = new KLineEdit(this, "fileNameBox");
Scott Wheeler's avatar
Scott Wheeler committed
474 475
	m_fileNameBox->setValidator(new FileNameValidator(m_fileNameBox));	

476 477
	QLabel *fileNameIcon = new QLabel(this);
	fileNameIcon->setPixmap(SmallIcon("sound"));
Scott Wheeler's avatar
Scott Wheeler committed
478 479
	QWidget *fileNameLabel = addHidden(new QLabel(m_fileNameBox, i18n("&File name:"), this));

480 481 482 483 484 485
	fileNameLayout->addWidget(addHidden(fileNameIcon));
	fileNameLayout->addWidget(fileNameLabel);
	fileNameLayout->setStretchFactor(fileNameIcon, 0);
	fileNameLayout->setStretchFactor(fileNameLabel, 0);
	fileNameLayout->insertStretch(-1, 1);
	rightColumnLayout->addWidget(addHidden(m_fileNameBox));
486 487

        { // lay out the track row
488 489
            FixedHLayout *trackRowLayout = new FixedHLayout(rightColumnLayout,
							    horizontalSpacing);
490

491
	    m_trackSpin = new KIntSpinBox(0, 255, 1, 0, 10, this, "trackSpin");
492
	    addItem(i18n("T&rack:"), m_trackSpin, trackRowLayout);
493
	    m_trackSpin->installEventFilter(this);
494

495
	    trackRowLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding,
Scott Wheeler's avatar
Scott Wheeler committed
496
						    QSizePolicy::Minimum));
497

498
	    m_yearSpin = new KIntSpinBox(0, 9999, 1, 0, 10, this, "yearSpin");
499
	    addItem(i18n("&Year:"), m_yearSpin, trackRowLayout);
500
	    m_yearSpin->installEventFilter(this);
501

502
	    trackRowLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding,
Scott Wheeler's avatar
Scott Wheeler committed
503
						    QSizePolicy::Minimum));
504

505 506 507 508 509 510 511
	    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));
512

513
	    trackRowLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding,
Scott Wheeler's avatar
Scott Wheeler committed
514
						    QSizePolicy::Minimum));
515

516 517 518 519 520 521 522
	    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));
523 524

	    trackRowLayout->setWidth();
525 526
        }

527 528
        m_commentBox = new KEdit(this, "commentBox");
	m_commentBox->setTextFormat(Qt::PlainText);
529 530 531
	addItem(i18n("&Comment:"), m_commentBox, rightColumnLayout, "edit");
    	fileNameLabel->setMinimumHeight(m_trackSpin->height());

532 533
    }

Scott Wheeler's avatar
Scott Wheeler committed
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559
    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()));
560 561 562 563
}

void TagEditor::save(const PlaylistItemList &list)
{
564
    if(!list.isEmpty() && m_dataChanged) {
565 566
	
	KApplication::setOverrideCursor(Qt::waitCursor);
567
	m_dataChanged = false;
568 569 570 571

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

	QStringList errorFiles;
572 573 574 575 576 577

	// 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();
578
	
579
	for(PlaylistItemList::ConstIterator it = list.begin(); it != end; /* Deliberatly missing */ ) {
580 581 582 583 584 585 586

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

	    kapp->eventLoop()->processEvents(QEventLoop::ExcludeUserInput);

587 588
	    PlaylistItem *item = *it;
	    
589 590 591 592 593 594
	    // 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;

595 596 597
	    QFileInfo newFile(item->file().fileInfo().dirPath() + QDir::separator() +
			      m_fileNameBox->text());
	    QFileInfo directory(item->file().fileInfo().dirPath());
598 599 600 601 602 603
	    
	    // 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
604 605
	    if(item &&
	       item->file().tag() &&
606
	       (newFile.isWritable() || (!newFile.exists() && directory.isWritable())) &&
607
	       item->file().fileInfo().isWritable())
Scott Wheeler's avatar
Scott Wheeler committed
608
	    {
609 610 611 612
		
		// If the file name in the box doesn't match the current file
		// name...
		
613 614 615
		if(list.count() == 1 &&
		   item->file().fileInfo().fileName() != newFile.fileName())
		{
616
		    
617 618
		    // Rename the file if it doesn't exist or the user says
		    // that it's ok.
619
		    
620
		    if(!newFile.exists() ||
Scott Wheeler's avatar
Scott Wheeler committed
621 622 623 624
		       KMessageBox::warningYesNo(
			   this, 
			   i18n("This file already exists.\nDo you want to replace it?"),
			   i18n("File Exists")) == KMessageBox::Yes)
625 626
		    {
			QDir currentDir;
627 628
			currentDir.rename(item->file().absFilePath(), newFile.filePath());
			item->file().setFile(newFile.filePath());
629 630
		    }
		}
631 632 633 634 635 636 637
		
		// 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.
		
638
		if(m_enableBoxes[m_artistNameBox]->isOn())
639
		    item->file().tag()->setArtist(m_artistNameBox->currentText());
640
		if(m_enableBoxes[m_trackNameBox]->isOn())
641
		    item->file().tag()->setTitle(m_trackNameBox->text());
642
		if(m_enableBoxes[m_albumNameBox]->isOn())
643
		    item->file().tag()->setAlbum(m_albumNameBox->currentText());
644 645 646
		if(m_enableBoxes[m_trackSpin]->isOn()) {
		    if(m_trackSpin->text().isEmpty())
			m_trackSpin->setValue(0);
647
		    item->file().tag()->setTrack(m_trackSpin->value());
648 649 650 651
		}
		if(m_enableBoxes[m_yearSpin]->isOn()) {
		    if(m_yearSpin->text().isEmpty())
			m_yearSpin->setValue(0);
652
		    item->file().tag()->setYear(m_yearSpin->value());
653
		}
654
		if(m_enableBoxes[m_commentBox]->isOn())
655
		    item->file().tag()->setComment(m_commentBox->text());
656
		
657
		if(m_enableBoxes[m_genreBox]->isOn())
658
		    item->file().tag()->setGenre(m_genreBox->currentText());
659
		
660
		item->file().tag()->save();
661
		
662
		item->refresh();
663
		item->playlist()->update();
664
	    }
Scott Wheeler's avatar
Scott Wheeler committed
665
	    else if(item)
666
		errorFiles.append(item->file().absFilePath());
667 668 669 670 671
	}
	
	if(!errorFiles.isEmpty())
	    KMessageBox::detailedSorry(this,
				       i18n("Could not save to specified file(s)."), 
672
				       i18n("Could Not Write to:\n") + errorFiles.join("\n"));
673
	CollectionList::instance()->dataChanged();
674
	KApplication::restoreOverrideCursor();
675 676 677 678 679
    }
}

void TagEditor::saveChangesPrompt()
{
680 681 682 683 684 685
    if(!isVisible() || !m_dataChanged || m_items.isEmpty())
	return;

    QStringList files;

    for(PlaylistItemList::Iterator it = m_items.begin(); it != m_items.end(); it++)
686
	files.append((*it)->file().absFilePath());
687 688 689 690 691 692 693 694 695 696

    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);
697 698 699
    }
}

700
void TagEditor::addItem(const QString &text, QWidget *item, QBoxLayout *layout, const QString &iconName)
701 702 703 704
{
    if(!item || !layout)
	return;

705
    QLabel *label = new QLabel(item, text, this);
706 707 708 709 710
    QLabel *iconLabel = new QLabel(item, 0, this);
    
    if(!iconName.isNull())
	iconLabel->setPixmap(SmallIcon(iconName));
    
711 712 713 714 715 716
    QCheckBox *enableBox = new QCheckBox(i18n("Enable"), this);
    enableBox->setChecked(true);

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

    if(layout->direction() == QBoxLayout::LeftToRight) {
717
    	layout->addWidget(iconLabel);
718 719 720 721 722 723
	layout->addWidget(label);
	layout->addWidget(item);
	layout->addWidget(enableBox);
    }
    else {
	QHBoxLayout *l = new QHBoxLayout(layout);
724 725
	
	l->addWidget(iconLabel);
726 727 728 729 730 731 732 733 734 735 736 737 738 739
	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)));
740
    m_enableBoxes.insert(item, enableBox);
741 742
}

743 744
void TagEditor::showEvent(QShowEvent *e)
{
745 746 747
    if(m_collectionChanged)
	updateCollection();

748
    slotRefresh();
749 750 751
    QWidget::showEvent(e);
}

752 753
bool TagEditor::eventFilter(QObject *watched, QEvent *e)
{
754 755
    QKeyEvent *ke = static_cast<QKeyEvent*>(e);
    if(watched->inherits("QSpinBox") && e->type() == QEvent::KeyRelease && ke->state() == 0)
756 757 758 759 760
	slotDataChanged();

    return false;
}

761 762 763 764
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////

765
void TagEditor::slotDataChanged(bool c)
766
{
767
    m_dataChanged = c;
768 769
}

770 771 772 773 774 775 776
void TagEditor::slotItemRemoved(PlaylistItem *item)
{
    m_items.remove(item);
    if(m_items.isEmpty())
	slotRefresh();
}

777 778 779 780 781 782 783 784
void TagEditor::slotPlaylistDestroyed(Playlist *p)
{
    if(m_currentPlaylist == p) {
	m_currentPlaylist = 0;
	slotSetItems(PlaylistItemList());
    }
}

785
#include "tageditor.moc"