filerenamer.cpp 32.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/***************************************************************************
    begin                : Thu Oct 28 2004
    copyright            : (C) 2004 by Michael Pyne
                         : (c) 2003 Frerich Raabe <raabe@kde.org>
    email                : michael.pyne@kdemail.net
***************************************************************************/

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

#include <algorithm>
18

Scott Wheeler's avatar
build  
Scott Wheeler committed
19
#include <kdebug.h>
20 21 22
#include <kcombobox.h>
#include <kurl.h>
#include <kurlrequester.h>
23
#include <kiconloader.h>
24 25
#include <knuminput.h>
#include <kstandarddirs.h>
26
#include <kio/netaccess.h>
27 28 29 30
#include <kconfigbase.h>
#include <kconfig.h>
#include <kglobal.h>
#include <klineedit.h>
31
#include <klocale.h>
32
#include <kpushbutton.h>
33 34
#include <kapplication.h>
#include <kmessagebox.h>
35
#include <kvbox.h>
36

37
#include <QFile>
Laurent Montel's avatar
Laurent Montel committed
38 39 40
#include <q3hbox.h>
#include <q3vbox.h>
#include <q3scrollview.h>
41 42
#include <QObject>
#include <QTimer>
43 44 45 46 47
#include <QRegExp>
#include <QCheckBox>
#include <QDir>
#include <QLabel>
#include <QLayout>
48
#include <qsignalmapper.h>
Laurent Montel's avatar
Laurent Montel committed
49
#include <q3header.h>
50
#include <qpalette.h>
Scott Wheeler's avatar
Scott Wheeler committed
51

Laurent Montel's avatar
Laurent Montel committed
52 53 54
#include <QPixmap>
#include <Q3Frame>
#include <Q3ValueList>
55
#include <kconfiggroup.h>
56

57 58 59 60 61
#include "tag.h"
#include "filehandle.h"
#include "filerenamer.h"
#include "exampleoptions.h"
#include "playlistitem.h"
62
#include "playlist.h"
63
#include "coverinfo.h"
64

Richard Lärkäng's avatar
Richard Lärkäng committed
65
class ConfirmationDialog : public KDialog
66 67 68 69
{
public:
    ConfirmationDialog(const QMap<QString, QString> &files,
                       QWidget *parent = 0, const char *name = 0)
Richard Lärkäng's avatar
Richard Lärkäng committed
70
        : KDialog(parent)
71
    {
Richard Lärkäng's avatar
Richard Lärkäng committed
72 73 74 75 76 77 78
        setObjectName(name);
        setModal(true);
        setCaption(i18n("Warning"));
        setButtons(Ok | Cancel);

        KVBox *vbox = new KVBox(this);
        setMainWidget(vbox);
Scott Wheeler's avatar
Scott Wheeler committed
79
        KVBox *hbox = new KVBox(vbox);
80 81 82 83 84 85 86 87

        QLabel *l = new QLabel(hbox);
        l->setPixmap(SmallIcon("messagebox_warning", 32));

        l = new QLabel(i18n("You are about to rename the following files. "
                            "Are you sure you want to continue?"), hbox);
        hbox->setStretchFactor(l, 1);

Laurent Montel's avatar
Laurent Montel committed
88
        K3ListView *lv = new K3ListView(vbox);
89 90 91 92 93 94 95 96

        lv->addColumn(i18n("Original Name"));
        lv->addColumn(i18n("New Name"));

        int lvHeight = 0;

        QMap<QString, QString>::ConstIterator it = files.begin();
        for(; it != files.end(); ++it) {
97 98
            K3ListViewItem *i = it.key() != it.value()
                ? new K3ListViewItem(lv, it.key(), it.value())
Laurent Montel's avatar
Laurent Montel committed
99
                : new K3ListViewItem(lv, it.key(), i18n("No Change"));
100 101 102 103
            lvHeight += i->height();
        }

        lvHeight += lv->horizontalScrollBar()->height() + lv->header()->height();
104 105
        lv->setMinimumHeight(qMin(lvHeight, 400));
        resize(qMin(width(), 500), qMin(minimumHeight(), 400));
106 107
    }
};
108 109 110 111 112 113 114

//
// Implementation of ConfigCategoryReader
//

ConfigCategoryReader::ConfigCategoryReader() : CategoryReaderInterface(),
    m_currentItem(0)
115
{
116
    KConfigGroup config(KGlobal::config(), "FileRenamer");
117

Scott Wheeler's avatar
Scott Wheeler committed
118 119
    QList<int> categoryOrder = config.readEntry("CategoryOrder", QList<int>());
    int categoryCount[NumTypes] = { 0 }; // Keep track of each category encountered.
120

121
    // Set a default:
122

123 124
    if(categoryOrder.isEmpty())
        categoryOrder << Artist << Album << Title << Track;
125

Scott Wheeler's avatar
Scott Wheeler committed
126
    QList<int>::ConstIterator catIt = categoryOrder.constBegin();
127 128
    for(; catIt != categoryOrder.constEnd(); ++catIt)
    {
Scott Wheeler's avatar
Scott Wheeler committed
129
        int catCount = categoryCount[*catIt]++;
130 131
        TagType category = static_cast<TagType>(*catIt);
        CategoryID catId(category, catCount);
132

133 134 135
        m_options[catId] = TagRenamerOptions(catId);
        m_categoryOrder << catId;
    }
136

137
    m_folderSeparators.resize(m_categoryOrder.count() - 1, false);
138

Scott Wheeler's avatar
Scott Wheeler committed
139
    QList<int> checkedSeparators = config.readEntry("CheckedDirSeparators", QList<int>());
140

Scott Wheeler's avatar
Scott Wheeler committed
141
    QList<int>::ConstIterator it = checkedSeparators.constBegin();
142
    for(; it != checkedSeparators.constEnd(); ++it) {
Scott Wheeler's avatar
Scott Wheeler committed
143 144
        if(*it < m_folderSeparators.count())
            m_folderSeparators[*it] = true;
145
    }
146

147
    m_musicFolder = config.readEntry("MusicFolder", "${HOME}/music");
148
    m_separator = config.readEntry("Separator", " - ");
149
}
150

151 152 153
QString ConfigCategoryReader::categoryValue(TagType type) const
{
    if(!m_currentItem)
154
        return QString();
155 156 157 158 159

    Tag *tag = m_currentItem->file().tag();

    switch(type) {
    case Track:
160
        return QString::number(tag->track());
161 162 163 164 165 166 167 168 169 170 171 172

    case Year:
        return QString::number(tag->year());

    case Title:
        return tag->title();

    case Artist:
        return tag->artist();

    case Album:
        return tag->album();
173

174 175 176 177
    case Genre:
        return tag->genre();

    default:
178
        return QString();
179
    }
180
}
181

182
QString ConfigCategoryReader::prefix(const CategoryID &category) const
183
{
184
    return m_options[category].prefix();
185 186
}

187
QString ConfigCategoryReader::suffix(const CategoryID &category) const
188
{
189
    return m_options[category].suffix();
190 191
}

192
TagRenamerOptions::EmptyActions ConfigCategoryReader::emptyAction(const CategoryID &category) const
193
{
194
    return m_options[category].emptyAction();
195 196
}

197
QString ConfigCategoryReader::emptyText(const CategoryID &category) const
198
{
199 200 201
    return m_options[category].emptyText();
}

202
QList<CategoryID> ConfigCategoryReader::categoryOrder() const
203 204 205 206 207 208 209 210 211
{
    return m_categoryOrder;
}

QString ConfigCategoryReader::separator() const
{
    return m_separator;
}

Scott Wheeler's avatar
Scott Wheeler committed
212
QString ConfigCategoryReader::musicFolder() const
213
{
Scott Wheeler's avatar
Scott Wheeler committed
214
    return m_musicFolder;
215 216
}

Scott Wheeler's avatar
Scott Wheeler committed
217
int ConfigCategoryReader::trackWidth(int categoryNum) const
218
{
219
    return m_options[CategoryID(Track, categoryNum)].trackWidth();
220 221
}

Scott Wheeler's avatar
Scott Wheeler committed
222
bool ConfigCategoryReader::hasFolderSeparator(int index) const
223
{
224 225
    if(index >= m_folderSeparators.count())
        return false;
226 227 228
    return m_folderSeparators[index];
}

229
bool ConfigCategoryReader::isDisabled(const CategoryID &category) const
230 231 232 233
{
    return m_options[category].disabled();
}

234 235 236 237
//
// Implementation of FileRenamerWidget
//

238
FileRenamerWidget::FileRenamerWidget(QWidget *parent) :
Scott Wheeler's avatar
Scott Wheeler committed
239 240 241
    QWidget(parent),
    Ui::FileRenamerBase(),
    CategoryReaderInterface(),
242
    m_exampleFromFile(false)
243
{
244 245
    kDebug(65432) << k_funcinfo << endl;

Tim Beaulen's avatar
Tim Beaulen committed
246 247
    setupUi(this);

248
    QLabel *temp = new QLabel(0);
249 250 251
    QPalette palette;
    palette.setColor(m_exampleText->backgroundRole(), temp->palette().color(backgroundRole()));
    m_exampleText->setPalette(palette);
252 253
    delete temp;

David Faure's avatar
David Faure committed
254
#ifdef __GNUC__
Scott Wheeler's avatar
Scott Wheeler committed
255
    #warning Repair this.
David Faure's avatar
David Faure committed
256
#endif
Scott Wheeler's avatar
Scott Wheeler committed
257
    /* layout()->setMargin(0); */ // We'll be wrapped by KDialogBase
258

259 260 261 262 263 264 265
    // This must be created before createTagRows() is called.

    m_exampleDialog = new ExampleOptionsDialog(this);

    createTagRows();
    loadConfig();

266 267
    // Add correct text to combo box.
    m_category->clear();
Scott Wheeler's avatar
Scott Wheeler committed
268
    for(int i = StartTag; i < NumTypes; ++i) {
269
        QString category = TagRenamerOptions::tagTypeText(static_cast<TagType>(i));
270
        m_category->addItem(category);
271
    }
272 273 274 275 276 277 278

    connect(m_exampleDialog, SIGNAL(signalShown()), SLOT(exampleDialogShown()));
    connect(m_exampleDialog, SIGNAL(signalHidden()), SLOT(exampleDialogHidden()));
    connect(m_exampleDialog, SIGNAL(dataChanged()), SLOT(dataSelected()));
    connect(m_exampleDialog, SIGNAL(fileChanged(const QString &)),
            this,            SLOT(fileSelected(const QString &)));

279 280 281 282 283
    connect(m_separator, SIGNAL(textChanged(const QString &)), this, SLOT(exampleTextChanged()));
    connect(m_musicFolder, SIGNAL(textChanged(const QString &)), this, SLOT(exampleTextChanged()));
    connect(m_showExample, SIGNAL(clicked()), this, SLOT(toggleExampleDialog()));
    connect(m_insertCategory, SIGNAL(clicked()), this, SLOT(insertCategory()));

284
    exampleTextChanged();
285
}
286

287
void FileRenamerWidget::loadConfig()
288
{
289
    kDebug(65432) << k_funcinfo << endl;
Scott Wheeler's avatar
Scott Wheeler committed
290
    QList<int> checkedSeparators;
291 292
    KConfigGroup config(KGlobal::config(), "FileRenamer");

Scott Wheeler's avatar
Scott Wheeler committed
293
    for(int i = 0; i < m_rows.count(); ++i)
294 295
        m_rows[i].options = TagRenamerOptions(m_rows[i].category);

Scott Wheeler's avatar
Scott Wheeler committed
296
    checkedSeparators = config.readEntry("CheckedDirSeparators", QList<int>());
297

Stephan Kulow's avatar
Stephan Kulow committed
298

Scott Wheeler's avatar
Scott Wheeler committed
299 300 301 302
    for(QList<int>::ConstIterator it = checkedSeparators.begin();
        it != checkedSeparators.end(); ++it)
    {
        int separator = *it;
303 304
        if(separator < m_folderSwitches.count())
            m_folderSwitches[separator]->setChecked(true);
305 306
    }

David Faure's avatar
David Faure committed
307 308
    QString path = config.readEntry("MusicFolder", "${HOME}/music");
    m_musicFolder->setPath(path);
309

310
    m_separator->setItemText(m_separator->currentIndex(), config.readEntry("Separator", " - "));
311
}
312

313
void FileRenamerWidget::saveConfig()
314
{
315
    kDebug(65432) << k_funcinfo << endl;
316
    KConfigGroup config(KGlobal::config(), "FileRenamer");
Scott Wheeler's avatar
Scott Wheeler committed
317 318
    QList<int> checkedSeparators;
    QList<int> categoryOrder;
319

Scott Wheeler's avatar
Scott Wheeler committed
320 321
    for(int i = 0; i < m_rows.count(); ++i) {
        int rowId = idOfPosition(i); // Write out in GUI order, not m_rows order
322 323 324
        m_rows[rowId].options.saveConfig(m_rows[rowId].category.categoryNumber);
        categoryOrder += m_rows[rowId].category.category;
    }
325

Scott Wheeler's avatar
Scott Wheeler committed
326
    for(int i = 0; i < m_folderSwitches.count(); ++i)
327 328 329 330 331
        if(m_folderSwitches[i]->isChecked() == true)
            checkedSeparators += i;

    config.writeEntry("CheckedDirSeparators", checkedSeparators);
    config.writeEntry("CategoryOrder", categoryOrder);
David Faure's avatar
David Faure committed
332
    config.writePathEntry("MusicFolder", m_musicFolder->url().path());
333 334 335
    config.writeEntry("Separator", m_separator->currentText());

    config.sync();
336 337
}

338
FileRenamerWidget::~FileRenamerWidget()
339 340 341
{
}

Scott Wheeler's avatar
Scott Wheeler committed
342
int FileRenamerWidget::addRowCategory(TagType category)
343
{
344
    kDebug(65432) << k_funcinfo << endl;
345 346
    static QIcon up   = SmallIcon("up");
    static QIcon down = SmallIcon("down");
347 348

    // Find number of categories already of this type.
Scott Wheeler's avatar
Scott Wheeler committed
349 350
    int categoryCount = 0;
    for(int i = 0; i < m_rows.count(); ++i)
351 352 353 354 355 356 357
        if(m_rows[i].category.category == category)
            ++categoryCount;

    Row row;

    row.category = CategoryID(category, categoryCount);
    row.position = m_rows.count();
Scott Wheeler's avatar
Scott Wheeler committed
358
    int id = row.position;
359 360

    Q3HBox *frame = new Q3HBox(m_mainFrame);
361 362 363
    QPalette palette;
    palette.setColor(frame->backgroundRole(), frame->palette().color(backgroundRole()).dark(110));
    frame->setPalette(palette);
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378

    row.widget = frame;
    frame->setFrameShape(Q3Frame::Box);
    frame->setLineWidth(1);
    frame->setMargin(3);

    m_mainFrame->setStretchFactor(frame, 1);

    Q3VBox *buttons = new Q3VBox(frame);
    buttons->setFrameStyle(Q3Frame::Plain | Q3Frame::Box);
    buttons->setLineWidth(1);

    row.upButton = new KPushButton(buttons);
    row.downButton = new KPushButton(buttons);

379 380
    row.upButton->setIcon(up);
    row.downButton->setIcon(down);
381 382 383 384 385 386 387 388 389 390 391
    row.upButton->setFlat(true);
    row.downButton->setFlat(true);

    upMapper->connect(row.upButton, SIGNAL(clicked()), SLOT(map()));
    upMapper->setMapping(row.upButton, id);
    downMapper->connect(row.downButton, SIGNAL(clicked()), SLOT(map()));
    downMapper->setMapping(row.downButton, id);

    QString labelText = QString("<b>%1</b>").arg(TagRenamerOptions::tagTypeText(category));
    QLabel *label = new QLabel(labelText, frame);
    frame->setStretchFactor(label, 1);
Scott Wheeler's avatar
Scott Wheeler committed
392
    label->setAlignment(Qt::AlignCenter);
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412

    Q3VBox *options = new Q3VBox(frame);
    row.enableButton = new KPushButton(i18n("Remove"), options);
    toggleMapper->connect(row.enableButton, SIGNAL(clicked()), SLOT(map()));
    toggleMapper->setMapping(row.enableButton, id);

    row.optionsButton = new KPushButton(i18n("Options"), options);
    mapper->connect(row.optionsButton, SIGNAL(clicked()), SLOT(map()));
    mapper->setMapping(row.optionsButton, id);

    row.widget->show();
    m_rows.append(row);

    // Disable add button if there's too many rows.
    if(m_rows.count() == MAX_CATEGORIES)
        m_insertCategory->setEnabled(false);

    return id;
}

Scott Wheeler's avatar
Scott Wheeler committed
413
void FileRenamerWidget::moveSignalMappings(int oldId, int newId)
414
{
415
    kDebug(65432) << k_funcinfo << endl;
416 417 418 419 420 421
    mapper->setMapping(m_rows[oldId].optionsButton, newId);
    downMapper->setMapping(m_rows[oldId].downButton, newId);
    upMapper->setMapping(m_rows[oldId].upButton, newId);
    toggleMapper->setMapping(m_rows[oldId].enableButton, newId);
}

Scott Wheeler's avatar
Scott Wheeler committed
422
bool FileRenamerWidget::removeRow(int id)
423
{
424
    kDebug(65432) << k_funcinfo << endl;
425
    if(id >= m_rows.count()) {
Scott Wheeler's avatar
Scott Wheeler committed
426
        kWarning(65432) << "Trying to remove row, but " << id << " is out-of-range.\n";
427 428 429 430
        return false;
    }

    if(m_rows.count() == 1) {
431
        kError(65432) << "Can't remove last row of File Renamer.\n";
432 433 434 435 436 437 438 439 440 441 442
        return false;
    }

    // Remove widget.  Don't delete it since it appears QSignalMapper may still need it.
    m_rows[id].widget->deleteLater();
    m_rows[id].widget = 0;
    m_rows[id].enableButton = 0;
    m_rows[id].upButton = 0;
    m_rows[id].optionsButton = 0;
    m_rows[id].downButton = 0;

Scott Wheeler's avatar
Scott Wheeler committed
443
    int checkboxPosition = 0; // Remove first checkbox.
444 445 446 447 448 449 450 451 452 453 454 455 456

    // If not the first row, remove the checkbox before it.
    if(m_rows[id].position > 0)
        checkboxPosition = m_rows[id].position - 1;

    // The checkbox is contained within a layout widget, so the layout
    // widget is the one the needs to die.
    delete m_folderSwitches[checkboxPosition]->parent();
    m_folderSwitches.erase(&m_folderSwitches[checkboxPosition]);

    // Go through all the rows and if they have the same category and a
    // higher categoryNumber, decrement the number.  Also update the
    // position identifier.
Scott Wheeler's avatar
Scott Wheeler committed
457
    for(int i = 0; i < m_rows.count(); ++i) {
458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
        if(i == id)
            continue; // Don't mess with ourself.

        if((m_rows[id].category.category == m_rows[i].category.category) &&
           (m_rows[id].category.categoryNumber < m_rows[i].category.categoryNumber))
        {
            --m_rows[i].category.categoryNumber;
        }

        // Items are moving up.
        if(m_rows[id].position < m_rows[i].position)
            --m_rows[i].position;
    }

    // Every row after the one we delete will have a different identifier, since
    // the identifier is simply its index into m_rows.  So we need to re-do the
    // signal mappings for the affected rows.
Scott Wheeler's avatar
Scott Wheeler committed
475
    for(int i = id + 1; i < m_rows.count(); ++i)
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
        moveSignalMappings(i, i - 1);

    m_rows.erase(&m_rows[id]);

    // Make sure we update the buttons of affected rows.
    m_rows[idOfPosition(0)].upButton->setEnabled(false);
    m_rows[idOfPosition(m_rows.count() - 1)].downButton->setEnabled(false);

    // We can insert another row now, make sure GUI is updated to match.
    m_insertCategory->setEnabled(true);

    QTimer::singleShot(0, this, SLOT(exampleTextChanged()));
    return true;
}

void FileRenamerWidget::addFolderSeparatorCheckbox()
{
493
    kDebug(65432) << k_funcinfo << endl;
494
    QWidget *temp = new QWidget(m_mainFrame);
Stephan Kulow's avatar
Stephan Kulow committed
495
    QHBoxLayout *l = new QHBoxLayout(temp);
496 497 498

    QCheckBox *cb = new QCheckBox(i18n("Insert folder separator"), temp);
    m_folderSwitches.append(cb);
Scott Wheeler's avatar
Scott Wheeler committed
499
    l->addWidget(cb, 0, Qt::AlignCenter);
500 501 502 503 504 505 506 507
    cb->setChecked(false);

    connect(cb, SIGNAL(toggled(bool)),
            SLOT(exampleTextChanged()));

    temp->show();
}

508
void FileRenamerWidget::createTagRows()
509
{
510
    kDebug(65432) << k_funcinfo << endl;
511
    KConfigGroup config(KGlobal::config(), "FileRenamer");
Scott Wheeler's avatar
Scott Wheeler committed
512
    QList<int> categoryOrder = config.readEntry("CategoryOrder", QList<int>());
513

514
    if(categoryOrder.isEmpty())
515
        categoryOrder << Artist << Album << Artist << Title << Track;
516

517 518 519
    // Setup arrays.
    m_rows.reserve(categoryOrder.count());
    m_folderSwitches.reserve(categoryOrder.count() - 1);
520

521 522 523 524 525 526 527 528
    mapper       = new QSignalMapper(this);
    mapper->setObjectName("signal mapper");
    toggleMapper = new QSignalMapper(this);
    toggleMapper->setObjectName("toggle mapper");
    upMapper     = new QSignalMapper(this);
    upMapper->setObjectName("up button mapper");
    downMapper   = new QSignalMapper(this);
    downMapper->setObjectName("down button mapper");
529 530

    connect(mapper,       SIGNAL(mapped(int)), SLOT(showCategoryOption(int)));
531
    connect(toggleMapper, SIGNAL(mapped(int)), SLOT(slotRemoveRow(int)));
532 533 534
    connect(upMapper,     SIGNAL(mapped(int)), SLOT(moveItemUp(int)));
    connect(downMapper,   SIGNAL(mapped(int)), SLOT(moveItemDown(int)));

Laurent Montel's avatar
Laurent Montel committed
535
    m_mainFrame = new Q3VBox(m_mainView->viewport());
Scott Wheeler's avatar
Scott Wheeler committed
536 537 538
    m_mainFrame->setMargin(10);
    m_mainFrame->setSpacing(5);

539
    m_mainView->addChild(m_mainFrame);
Laurent Montel's avatar
Laurent Montel committed
540
    m_mainView->setResizePolicy(Q3ScrollView::AutoOneFit);
541 542

    // OK, the deal with the categoryOrder variable is that we need to create
543 544 545 546
    // the rows in the order that they were saved in (the order given by categoryOrder).
    // The signal mappers operate according to the row identifier.  To find the position of
    // a row given the identifier, use m_rows[id].position.  To find the id of a given
    // position, use idOfPosition(position).
547

Scott Wheeler's avatar
Scott Wheeler committed
548
    QList<int>::ConstIterator it = categoryOrder.constBegin();
549

550 551
    for(; it != categoryOrder.constEnd(); ++it) {
        if(*it < StartTag || *it >= NumTypes) {
552
            kError(65432) << "Invalid category encountered in file renamer configuration.\n";
553 554
            continue;
        }
555

556
        if(m_rows.count() == MAX_CATEGORIES) {
557
            kError(65432) << "Maximum number of File Renamer tags reached, bailing.\n";
558
            break;
559 560
        }

561 562 563
        TagType i = static_cast<TagType>(*it);

        addRowCategory(i);
564

565 566 567
        // Insert the directory separator checkbox if this isn't the last
        // item.

Scott Wheeler's avatar
Scott Wheeler committed
568
        QList<int>::ConstIterator dup(it);
569 570 571 572

        // Check for last item
        if(++dup != categoryOrder.constEnd())
            addFolderSeparatorCheckbox();
573
    }
574

575 576 577 578 579 580 581
    m_rows.first().upButton->setEnabled(false);
    m_rows.last().downButton->setEnabled(false);

    // If we have maximum number of categories already, don't let the user
    // add more.
    if(m_rows.count() >= MAX_CATEGORIES)
        m_insertCategory->setEnabled(false);
582 583
}

584
void FileRenamerWidget::exampleTextChanged()
585
{
586
    kDebug(65432) << k_funcinfo << endl;
587
    // Just use .mp3 as an example
588
#if 0
589
    if(m_exampleFromFile && (m_exampleFile.isEmpty() ||
590 591
                             !FileHandle(m_exampleFile).tag()->isValid()))
    {
592 593 594 595
        m_exampleText->setText(i18n("No file selected, or selected file has no tags."));
        return;
    }

596
    m_exampleText->setText(FileRenamer::fileName(*this) + ".mp3");
597
#endif
598 599
}

600
QString FileRenamerWidget::fileCategoryValue(TagType category) const
601
{
602
    kDebug(65432) << k_funcinfo << endl;
603 604
    FileHandle file(m_exampleFile);
    Tag *tag = file.tag();
605 606 607

    switch(category) {
    case Track:
608
        return QString::number(tag->track());
609 610 611 612 613

    case Year:
        return QString::number(tag->year());

    case Title:
614
        return tag->title();
615 616

    case Artist:
617
        return tag->artist();
618 619

    case Album:
620
        return tag->album();
621 622

    case Genre:
623
        return tag->genre();
624 625

    default:
626
        return QString();
627
    }
628 629
}

630
QString FileRenamerWidget::categoryValue(TagType category) const
631
{
632
    kDebug(65432) << k_funcinfo << endl;
633 634 635 636
    if(m_exampleFromFile)
        return fileCategoryValue(category);

    const ExampleOptions *example = m_exampleDialog->widget();
637

638 639
    switch (category) {
    case Track:
640
        return example->m_exampleTrack->text();
641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657

    case Year:
        return example->m_exampleYear->text();

    case Title:
        return example->m_exampleTitle->text();

    case Artist:
        return example->m_exampleArtist->text();

    case Album:
        return example->m_exampleAlbum->text();

    case Genre:
        return example->m_exampleGenre->text();

    default:
658
        return QString();
659
    }
660
}
661

662
QList<CategoryID> FileRenamerWidget::categoryOrder() const
663
{
664
    kDebug(65432) << k_funcinfo << endl;
665
    QList<CategoryID> list;
666 667

    // Iterate in GUI row order.
Scott Wheeler's avatar
Scott Wheeler committed
668 669
    for(int i = 0; i < m_rows.count(); ++i) {
        int rowId = idOfPosition(i);
670 671
        list += m_rows[rowId].category;
    }
672

673
    return list;
674 675
}

Scott Wheeler's avatar
Scott Wheeler committed
676
bool FileRenamerWidget::hasFolderSeparator(int index) const
677
{
678
    kDebug(65432) << k_funcinfo << endl;
679 680
    if(index >= m_folderSwitches.count())
        return false;
681 682
    return m_folderSwitches[index]->isChecked();
}
683

Scott Wheeler's avatar
Scott Wheeler committed
684
void FileRenamerWidget::moveItem(int id, MovementDirection direction)
685
{
686
    kDebug(65432) << k_funcinfo << endl;
687
    QWidget *l = m_rows[id].widget;
Scott Wheeler's avatar
Scott Wheeler committed
688 689 690
    int bottom = m_rows.count() - 1;
    int pos = m_rows[id].position;
    int newPos = (direction == MoveUp) ? pos - 1 : pos + 1;
691

692 693
    // Item we're moving can't go further down after this.

694 695
    if((pos == (bottom - 1) && direction == MoveDown) ||
       (pos == bottom && direction == MoveUp))
696
    {
Scott Wheeler's avatar
Scott Wheeler committed
697 698
        int idBottomRow = idOfPosition(bottom);
        int idAboveBottomRow = idOfPosition(bottom - 1);
699 700 701

        m_rows[idBottomRow].downButton->setEnabled(true);
        m_rows[idAboveBottomRow].downButton->setEnabled(false);
702 703 704 705 706
    }

    // We're moving the top item, do some button switching.

    if((pos == 0 && direction == MoveDown) || (pos == 1 && direction == MoveUp)) {
Scott Wheeler's avatar
Scott Wheeler committed
707 708
        int idTopItem = idOfPosition(0);
        int idBelowTopItem = idOfPosition(1);
709 710 711

        m_rows[idTopItem].upButton->setEnabled(true);
        m_rows[idBelowTopItem].upButton->setEnabled(false);
712 713 714 715
    }

    // This is the item we're swapping with.

Scott Wheeler's avatar
Scott Wheeler committed
716
    int idSwitchWith = idOfPosition(newPos);
717
    QWidget *w = m_rows[idSwitchWith].widget;
718 719 720

    // Update the table of widget rows.

721
    std::swap(m_rows[id].position, m_rows[idSwitchWith].position);
722 723 724 725

    // Move the item two spaces above/below its previous position.  It has to
    // be 2 spaces because of the checkbox.

Stephan Kulow's avatar
Stephan Kulow committed
726 727 728
    QBoxLayout *layout = dynamic_cast<QBoxLayout *>(m_mainFrame->layout());
    if ( layout )
        return;
David Faure's avatar
David Faure committed
729
#ifdef __GNUC__
Stephan Kulow's avatar
Stephan Kulow committed
730
#warning double check if that still works with Qt4s layout
David Faure's avatar
David Faure committed
731
#endif
732

733
    layout->removeWidget(l);
734
    layout->insertWidget(2 * newPos, l);
735 736 737 738

    // Move the top item two spaces in the opposite direction, for a similar
    // reason.

739
    layout->removeWidget(w);
740
    layout->insertWidget(2 * pos, w);
741 742 743 744 745
    layout->invalidate();

    QTimer::singleShot(0, this, SLOT(exampleTextChanged()));
}

Scott Wheeler's avatar
Scott Wheeler committed
746
int FileRenamerWidget::idOfPosition(int position) const
747
{
748
    kDebug(65432) << k_funcinfo << endl;
749
    if(position >= m_rows.count()) {
750
        kError(65432) << "Search for position " << position << " out-of-range.\n";
Scott Wheeler's avatar
Scott Wheeler committed
751
        return -1;
752 753
    }

Scott Wheeler's avatar
Scott Wheeler committed
754
    for(int i = 0; i < m_rows.count(); ++i)
755 756
        if(m_rows[i].position == position)
            return i;
757

758
    kError(65432) << "Unable to find identifier for position " << position << endl;
Scott Wheeler's avatar
Scott Wheeler committed
759
    return -1;
760 761
}

Scott Wheeler's avatar
Scott Wheeler committed
762
int FileRenamerWidget::findIdentifier(const CategoryID &category) const
763
{
764
    kDebug(65432) << k_funcinfo << endl;
Scott Wheeler's avatar
Scott Wheeler committed
765
    for(int index = 0; index < m_rows.count(); ++index)
766
        if(m_rows[index].category == category)
767 768
            return index;

769
    kError(65432) << "Unable to find match for category " <<
770 771 772 773
        TagRenamerOptions::tagTypeText(category.category) <<
        ", number " << category.categoryNumber << endl;

    return MAX_CATEGORIES;
774 775 776 777
}

void FileRenamerWidget::enableAllUpButtons()
{
778
    kDebug(65432) << k_funcinfo << endl;
Scott Wheeler's avatar
Scott Wheeler committed
779
    for(int i = 0; i < m_rows.count(); ++i)
780 781 782 783 784
        m_rows[i].upButton->setEnabled(true);
}

void FileRenamerWidget::enableAllDownButtons()
{
785
    kDebug(65432) << k_funcinfo << endl;
Scott Wheeler's avatar
Scott Wheeler committed
786
    for(int i = 0; i < m_rows.count(); ++i)
787 788 789
        m_rows[i].downButton->setEnabled(true);
}

790
void FileRenamerWidget::showCategoryOption(int id)
791
{
792
    kDebug(65432) << k_funcinfo << endl;
793
    TagOptionsDialog *dialog = new TagOptionsDialog(this, m_rows[id].options, m_rows[id].category.categoryNumber);
794 795

    if(dialog->exec() == QDialog::Accepted) {
796
        m_rows[id].options = dialog->options();
797
        exampleTextChanged();
798
    }
799 800

    delete dialog;
801 802
}

803
void FileRenamerWidget::moveItemUp(int id)
804
{
805
    kDebug(65432) << k_funcinfo << endl;
Scott Wheeler's avatar
Scott Wheeler committed
806
    moveItem(id, MoveUp);
807 808
}

809
void FileRenamerWidget::moveItemDown(int id)
810
{
811
    kDebug(65432) << k_funcinfo << endl;
Scott Wheeler's avatar
Scott Wheeler committed
812
    moveItem(id, MoveDown);
813 814
}

815
void FileRenamerWidget::toggleExampleDialog()
816
{
817
    kDebug(65432) << k_funcinfo << endl;
Stephan Kulow's avatar
Stephan Kulow committed
818
    m_exampleDialog->setHidden(!m_exampleDialog->isHidden());
819 820
}

821
void FileRenamerWidget::insertCategory()
822
{
823
    kDebug(65432) << k_funcinfo << endl;
824 825
    TagType category = TagRenamerOptions::tagFromCategoryText(m_category->currentText());
    if(category == Unknown) {
826
        kError(65432) << "Trying to add unknown category somehow.\n";
827 828 829 830 831
        return;
    }

    // We need to enable the down button of the current bottom row since it
    // can now move down.
Scott Wheeler's avatar
Scott Wheeler committed
832
    int idBottom = idOfPosition(m_rows.count() - 1);
833 834 835 836 837
    m_rows[idBottom].downButton->setEnabled(true);

    addFolderSeparatorCheckbox();

    // Identifier of new row.
Scott Wheeler's avatar
Scott Wheeler committed
838
    int id = addRowCategory(category);
839 840 841 842 843 844 845 846 847 848

    // Set its down button to be disabled.
    m_rows[id].downButton->setEnabled(false);

    m_mainFrame->layout()->invalidate();
    m_mainView->update();

    // Now update according to the code in loadConfig().
    m_rows[id].options = TagRenamerOptions(m_rows[id].category);
    exampleTextChanged();
849 850 851 852
}

void FileRenamerWidget::exampleDialogShown()
{
853
    kDebug(65432) << k_funcinfo << endl;
854 855 856 857 858
    m_showExample->setText(i18n("Hide Renamer Test Dialog"));
}

void FileRenamerWidget::exampleDialogHidden()
{
859
    kDebug(65432) << k_funcinfo << endl;
860 861 862 863 864
    m_showExample->setText(i18n("Show Renamer Test Dialog"));
}

void FileRenamerWidget::fileSelected(const QString &file)
{
865
    kDebug(65432) << k_funcinfo << endl;
866
    m_exampleFromFile = true;
867
    m_exampleFile = file;
868 869 870 871 872
    exampleTextChanged();
}

void FileRenamerWidget::dataSelected()
{
873
    kDebug(65432) << k_funcinfo << endl;
874 875 876 877 878 879
    m_exampleFromFile = false;
    exampleTextChanged();
}

QString FileRenamerWidget::separator() const
{
880
    kDebug(65432) << k_funcinfo << endl;
881 882
    return m_separator->currentText();
}
883

Scott Wheeler's avatar
Scott Wheeler committed
884
QString FileRenamerWidget::musicFolder() const
885
{
886
    kDebug(65432) << k_funcinfo << endl;
David Faure's avatar
David Faure committed
887
    return m_musicFolder->url().path();
888 889
}