tageditor.cpp 24.2 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
    m_currentPlaylist(0),
129 130
    m_observer(0),
    m_performingSave(false)
131
{
132
    setupActions();
133 134
    setupLayout();
    readConfig();
135
    m_dataChanged = false;
136
    m_collectionChanged = false;
137 138 139 140
}

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

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

150 151 152 153
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

154
void TagEditor::slotSetItems(const PlaylistItemList &list)
155
{
156 157 158
    if(m_performingSave)
	return;

159
    saveChangesPrompt();
160 161 162 163 164 165 166 167 168 169 170

    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 *)));
171
	connect(m_currentPlaylist, SIGNAL(destroyed()), this, SLOT(slotPlaylistRemoved()));
172 173
    }

174
    m_items = list;
175

176
    if(isVisible())
177
	slotRefresh();
178 179
    else
	m_collectionChanged = true;
180 181
}

182
void TagEditor::slotRefresh()
183
{
184
    // This method takes the list of currently selected m_items and tries to 
185 186
    // 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
187
    // check after that to see if there are other m_items and adjust accordingly.
188

189
    if(m_items.isEmpty() || !m_items.first()->file().tag()) {
190
	slotClear();
191
	setEnabled(false);
192 193 194
	return;
    }
    
195 196
    setEnabled(true);

197
    PlaylistItem *item = m_items.first();
198

199
    Tag *tag = item->file().tag();
200
	
201 202 203 204
    QFileInfo fi(item->file().absFilePath());
    if(!fi.isWritable() && m_items.count() == 1)
	setEnabled(false);

205
    m_artistNameBox->setEditText(tag->artist());
206
    m_trackNameBox->setText(tag->title());
207
    m_albumNameBox->setEditText(tag->album());
208

209
    m_fileNameBox->setText(item->file().fileInfo().fileName());
210
    new FileBoxToolTip(this, m_fileNameBox);
211
    m_bitrateBox->setText(QString::number(tag->bitrate()));
212 213
    m_lengthBox->setText(tag->lengthString());

214 215
    if(m_genreList.findIndex(tag->genre()) >= 0)
	m_genreBox->setCurrentItem(m_genreList.findIndex(tag->genre()) + 1);
216 217
    else {
	m_genreBox->setCurrentItem(0);
218
	m_genreBox->setEditText(tag->genre());
219
    }
220

221
    m_trackSpin->setValue(tag->track());
222 223 224 225 226 227 228 229 230 231 232 233
    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()) {
234 235 236 237

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

	BoxMap::Iterator boxIt = m_enableBoxes.begin();
	for(; boxIt != m_enableBoxes.end(); boxIt++) {
241 242
	    (*boxIt)->setChecked(true);
	    (*boxIt)->show();
243 244
	}
	
245 246 247
	// 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.
248
	
249
	// Also, if there are more than 50 m_items, don't scan all of them.
250
	
251 252 253 254 255 256 257 258
	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);
259 260
	}
	else {
261
	    for(; it != m_items.end(); ++it) {
262
		tag = (*it)->file().tag();
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 302 303 304 305 306 307

		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);
		    }
308
		}
309 310
	    }
	}
311
    }
312 313
    else {
	// Clean up in the case that we are only handling one item.
314 315 316 317

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

	BoxMap::iterator boxIt = m_enableBoxes.begin();
	for(; boxIt != m_enableBoxes.end(); boxIt++) {
321 322 323 324 325
	    (*boxIt)->setChecked(true);
	    (*boxIt)->hide();
	}
    }
    m_dataChanged = false;
326 327
}

328
void TagEditor::slotClear()
329
{
330 331 332 333 334 335 336 337 338 339
    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();    
340 341
}

342
void TagEditor::slotUpdateCollection()
343
{
344 345 346 347 348 349 350 351 352 353
    if(isVisible())
	updateCollection();
    else
	m_collectionChanged = true;
}

void TagEditor::updateCollection()
{
    m_collectionChanged = false;
        
354 355 356 357 358
    CollectionList *list = CollectionList::instance();

    if(!list)
	return;
    
359 360 361 362 363 364 365 366 367
    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);
368 369 370 371 372 373
}

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

374
void TagEditor::readConfig()
375
{
376 377 378 379 380 381 382
    // 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");
383
    }
384

385 386 387 388
    bool show = config.readBoolEntry("Show", false);
    action<KToggleAction>("showEditor")->setChecked(show);
    setShown(show);

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

391 392 393
    for(TagLib::StringList::ConstIterator it = genres.begin(); it != genres.end(); ++it)
	m_genreList.append(TStringToQString((*it)));
    m_genreList.sort();
394

395 396 397
    m_genreBox->clear();
    m_genreBox->insertItem(QString::null);
    m_genreBox->insertStringList(m_genreList);
398 399 400
    m_genreBox->completionObject()->setItems(m_genreList);
}

401
void TagEditor::readCompletionMode(KConfigBase *config, KComboBox *box, const QString &key)
402 403 404 405 406
{
    KGlobalSettings::Completion mode =
	KGlobalSettings::Completion(config->readNumEntry(key, KGlobalSettings::CompletionAuto));

    box->setCompletionMode(mode);
407
}
408

409 410
void TagEditor::saveConfig()
{
411
    // combo box completion modes
412

413 414 415 416 417 418 419 420
    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());
421
}
422

423 424 425 426 427 428 429 430 431
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");
}

432 433
void TagEditor::setupLayout()
{
434
    static const int horizontalSpacing = 12;
435
    static const int verticalSpacing = 2;
436

437
    QHBoxLayout *layout = new QHBoxLayout(this, 6, horizontalSpacing);
438 439 440 441 442 443 444 445 446 447 448 449 450 451

    //////////////////////////////////////////////////////////////////////////////
    // 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
452 453
    	
	m_artistNameBox = new KComboBox(true, this, "artistNameBox");
454
	m_artistNameBox->setCompletionMode(KGlobalSettings::CompletionAuto);
455
	addItem(i18n("&Artist name:"), m_artistNameBox, leftColumnLayout, "personal");
456

457
        m_trackNameBox = new KLineEdit(this, "trackNameBox");
458
	addItem(i18n("&Track name:"), m_trackNameBox, leftColumnLayout, "player_play");
459

460
	m_albumNameBox = new KComboBox(true, this, "albumNameBox");
461
	m_albumNameBox->setCompletionMode(KGlobalSettings::CompletionAuto);
462
	addItem(i18n("Album &name:"), m_albumNameBox, leftColumnLayout, "cdrom_unmount");
463

464
        m_genreBox = new KComboBox(true, this, "genreBox");
465
	addItem(i18n("&Genre:"), m_genreBox, leftColumnLayout, "knotify");
466 467

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

479
	m_fileNameBox = new KLineEdit(this, "fileNameBox");
Scott Wheeler's avatar
Scott Wheeler committed
480 481
	m_fileNameBox->setValidator(new FileNameValidator(m_fileNameBox));	

482 483
	QLabel *fileNameIcon = new QLabel(this);
	fileNameIcon->setPixmap(SmallIcon("sound"));
Scott Wheeler's avatar
Scott Wheeler committed
484 485
	QWidget *fileNameLabel = addHidden(new QLabel(m_fileNameBox, i18n("&File name:"), this));

486 487 488 489 490 491
	fileNameLayout->addWidget(addHidden(fileNameIcon));
	fileNameLayout->addWidget(fileNameLabel);
	fileNameLayout->setStretchFactor(fileNameIcon, 0);
	fileNameLayout->setStretchFactor(fileNameLabel, 0);
	fileNameLayout->insertStretch(-1, 1);
	rightColumnLayout->addWidget(addHidden(m_fileNameBox));
492 493

        { // lay out the track row
494 495
            FixedHLayout *trackRowLayout = new FixedHLayout(rightColumnLayout,
							    horizontalSpacing);
496

497
	    m_trackSpin = new KIntSpinBox(0, 255, 1, 0, 10, this, "trackSpin");
498
	    addItem(i18n("T&rack:"), m_trackSpin, trackRowLayout);
499
	    m_trackSpin->installEventFilter(this);
500

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

504
	    m_yearSpin = new KIntSpinBox(0, 9999, 1, 0, 10, this, "yearSpin");
505
	    addItem(i18n("&Year:"), m_yearSpin, trackRowLayout);
506
	    m_yearSpin->installEventFilter(this);
507

508
	    trackRowLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding,
Scott Wheeler's avatar
Scott Wheeler committed
509
						    QSizePolicy::Minimum));
510

511 512 513 514 515 516 517
	    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));
518

519
	    trackRowLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding,
Scott Wheeler's avatar
Scott Wheeler committed
520
						    QSizePolicy::Minimum));
521

522 523 524 525 526 527 528
	    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));
529 530

	    trackRowLayout->setWidth();
531 532
        }

533 534
        m_commentBox = new KEdit(this, "commentBox");
	m_commentBox->setTextFormat(Qt::PlainText);
535 536 537
	addItem(i18n("&Comment:"), m_commentBox, rightColumnLayout, "edit");
    	fileNameLabel->setMinimumHeight(m_trackSpin->height());

538 539
    }

Scott Wheeler's avatar
Scott Wheeler committed
540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565
    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()));
566 567 568 569
}

void TagEditor::save(const PlaylistItemList &list)
{
570
    if(!list.isEmpty() && m_dataChanged) {
571 572
	
	KApplication::setOverrideCursor(Qt::waitCursor);
573
	m_dataChanged = false;
574
	m_performingSave = true;
575 576 577 578

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

	QStringList errorFiles;
579 580 581 582 583 584

	// 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();
585
	
586
	for(PlaylistItemList::ConstIterator it = list.begin(); it != end; /* Deliberatly missing */ ) {
587 588 589 590 591 592 593

	    // 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);

594 595
	    PlaylistItem *item = *it;
	    
596 597 598 599 600 601
	    // 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;

602 603 604
	    QFileInfo newFile(item->file().fileInfo().dirPath() + QDir::separator() +
			      m_fileNameBox->text());
	    QFileInfo directory(item->file().fileInfo().dirPath());
605

606 607 608 609 610
	    // 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
611 612
	    if(item &&
	       item->file().tag() &&
613
	       item->file().fileInfo().isWritable())
Scott Wheeler's avatar
Scott Wheeler committed
614
	    {
615 616 617 618
		
		// If the file name in the box doesn't match the current file
		// name...
		
619 620 621
		if(list.count() == 1 &&
		   item->file().fileInfo().fileName() != newFile.fileName())
		{
622
		    
623 624 625 626 627 628 629 630 631 632 633 634
		    // We're definitely trying to rename a file, so if it exists
		    // but isn't writable, or it doesn't exist but its directory
		    // isn't writable, we have an error.

		    if((newFile.exists() && !newFile.isWritable()) ||
		       (!newFile.exists() && !directory.isWritable()) &&
		       item)
		    {
			errorFiles.append(newFile.filePath());
			continue;
		    }

635 636
		    // Rename the file if it doesn't exist or the user says
		    // that it's ok.
637
		    
638
		    if(!newFile.exists() ||
Scott Wheeler's avatar
Scott Wheeler committed
639 640 641 642
		       KMessageBox::warningYesNo(
			   this, 
			   i18n("This file already exists.\nDo you want to replace it?"),
			   i18n("File Exists")) == KMessageBox::Yes)
643 644
		    {
			QDir currentDir;
645 646
			currentDir.rename(item->file().absFilePath(), newFile.filePath());
			item->file().setFile(newFile.filePath());
647 648
		    }
		}
649 650 651 652 653 654 655
		
		// 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.
		
656
		if(m_enableBoxes[m_artistNameBox]->isOn())
657
		    item->file().tag()->setArtist(m_artistNameBox->currentText());
658
		if(m_enableBoxes[m_trackNameBox]->isOn())
659
		    item->file().tag()->setTitle(m_trackNameBox->text());
660
		if(m_enableBoxes[m_albumNameBox]->isOn())
661
		    item->file().tag()->setAlbum(m_albumNameBox->currentText());
662 663 664
		if(m_enableBoxes[m_trackSpin]->isOn()) {
		    if(m_trackSpin->text().isEmpty())
			m_trackSpin->setValue(0);
665
		    item->file().tag()->setTrack(m_trackSpin->value());
666 667 668 669
		}
		if(m_enableBoxes[m_yearSpin]->isOn()) {
		    if(m_yearSpin->text().isEmpty())
			m_yearSpin->setValue(0);
670
		    item->file().tag()->setYear(m_yearSpin->value());
671
		}
672
		if(m_enableBoxes[m_commentBox]->isOn())
673
		    item->file().tag()->setComment(m_commentBox->text());
674
		
675
		if(m_enableBoxes[m_genreBox]->isOn())
676
		    item->file().tag()->setGenre(m_genreBox->currentText());
677
		
678
		item->file().tag()->save();
679
		
680
		item->refresh();
681
		item->playlist()->update();
682
	    }
Scott Wheeler's avatar
Scott Wheeler committed
683
	    else if(item)
684
		errorFiles.append(item->file().absFilePath());
685 686 687 688 689
	}
	
	if(!errorFiles.isEmpty())
	    KMessageBox::detailedSorry(this,
				       i18n("Could not save to specified file(s)."), 
690
				       i18n("Could Not Write to:\n") + errorFiles.join("\n"));
691
	CollectionList::instance()->dataChanged();
692
	m_performingSave = false;
693
	KApplication::restoreOverrideCursor();
694 695 696 697 698
    }
}

void TagEditor::saveChangesPrompt()
{
699 700 701 702 703 704
    if(!isVisible() || !m_dataChanged || m_items.isEmpty())
	return;

    QStringList files;

    for(PlaylistItemList::Iterator it = m_items.begin(); it != m_items.end(); it++)
705
	files.append((*it)->file().absFilePath());
706 707 708 709 710 711 712 713 714 715

    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);
716 717 718
    }
}

719
void TagEditor::addItem(const QString &text, QWidget *item, QBoxLayout *layout, const QString &iconName)
720 721 722 723
{
    if(!item || !layout)
	return;

724
    QLabel *label = new QLabel(item, text, this);
725 726 727 728 729
    QLabel *iconLabel = new QLabel(item, 0, this);
    
    if(!iconName.isNull())
	iconLabel->setPixmap(SmallIcon(iconName));
    
730 731 732 733 734 735
    QCheckBox *enableBox = new QCheckBox(i18n("Enable"), this);
    enableBox->setChecked(true);

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

    if(layout->direction() == QBoxLayout::LeftToRight) {
736
    	layout->addWidget(iconLabel);
737 738 739 740 741 742
	layout->addWidget(label);
	layout->addWidget(item);
	layout->addWidget(enableBox);
    }
    else {
	QHBoxLayout *l = new QHBoxLayout(layout);
743 744
	
	l->addWidget(iconLabel);
745 746 747 748 749 750 751 752 753 754 755 756 757 758
	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)));
759
    m_enableBoxes.insert(item, enableBox);
760 761
}

762 763
void TagEditor::showEvent(QShowEvent *e)
{
764
    if(m_collectionChanged) {
765
	updateCollection();
766 767
	slotRefresh();
    }
768

769 770 771
    QWidget::showEvent(e);
}

772 773
bool TagEditor::eventFilter(QObject *watched, QEvent *e)
{
774 775
    QKeyEvent *ke = static_cast<QKeyEvent*>(e);
    if(watched->inherits("QSpinBox") && e->type() == QEvent::KeyRelease && ke->state() == 0)
776 777 778 779 780
	slotDataChanged();

    return false;
}

781 782 783 784
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////

785
void TagEditor::slotDataChanged(bool c)
786
{
787
    m_dataChanged = c;
788 789
}

790 791 792 793 794 795 796
void TagEditor::slotItemRemoved(PlaylistItem *item)
{
    m_items.remove(item);
    if(m_items.isEmpty())
	slotRefresh();
}

797 798 799 800 801 802 803 804
void TagEditor::slotPlaylistDestroyed(Playlist *p)
{
    if(m_currentPlaylist == p) {
	m_currentPlaylist = 0;
	slotSetItems(PlaylistItemList());
    }
}

805
#include "tageditor.moc"