/***************************************************** vim:set ts=4 sw=4 sts=4:
  KControl module for KTTSD configuration and Job Management
  -------------------
  Copyright 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org>
  Copyright 2004-2005 by Gary Cramblitt <garycramblitt@comcast.net>
  Copyright 2009 by Jeremy Whiting <jpwhiting@kde.org>
  -------------------
  Original author: José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org>
  Current Maintainer: Jeremy Whiting <jpwhiting@kde.org>

  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.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ***************************************************************************/

// Note to programmers.  There is a subtle difference between a plugIn name and a
// synthesizer name.  The latter is a translated name, for example, "Festival Interactivo",
// while the former is alway an English name, example "Festival Interactive".

// KCMKttsMgr includes.
#include "kcmkttsmgr.h"
#include "kcmkttsmgr.moc"

// C++ includes.
#include <math.h>

// Qt includes.
#include <QtGui/QWidget>
#include <QtGui/QTabWidget>
#include <QtGui/QCheckBox>
#include <QtGui/QLayout>
#include <QtGui/QRadioButton>
#include <QtGui/QSlider>
#include <QtGui/QLabel>
#include <QtGui/QTreeWidget>
#include <QtGui/QHeaderView>
#include <QtCore/QTextStream>
#include <QtGui/QMenu>

// KDE includes.
#include <kparts/componentfactory.h>
#include <klineedit.h>
#include <kurlrequester.h>
#include <kicon.h>
#include <kstandarddirs.h>
#include <kaboutdata.h>
#include <kconfig.h>
#include <knuminput.h>
#include <kcombobox.h>
#include <kinputdialog.h>
#include <kmessagebox.h>
#include <kfiledialog.h>
#include <ktoolinvocation.h>
#include <kdialog.h>
#include <kspeech.h>
#include <kpluginfactory.h>
#include <kpluginloader.h>

// KTTS includes.
#include "talkercode.h"
#include "filterconf.h"
#include "selecttalkerdlg.h"
#include "selectlanguagedlg.h"
#include "utils.h"
#include "kttsjobmgr.h"

// Some constants.
// Defaults set when clicking Defaults button.
const bool autostartMgrCheckBoxValue = true;
const bool autoexitMgrCheckBoxValue = true;


// Make this a plug in.
K_PLUGIN_FACTORY (KCMKttsMgrFactory, registerPlugin<KCMKttsMgr>();)
K_EXPORT_PLUGIN (KCMKttsMgrFactory ("jovie"))


// ----------------------------------------------------------------------------

FilterListModel::FilterListModel (FilterList filters, QObject *parent)
        : QAbstractListModel (parent), m_filters (filters)
{
}

int FilterListModel::rowCount (const QModelIndex &parent) const
{
    if (!parent.isValid())
        return m_filters.count();
    else
        return 0;
}

int FilterListModel::columnCount (const QModelIndex &parent) const
{
    Q_UNUSED (parent);
    return 2;
}

QModelIndex FilterListModel::index (int row, int column, const QModelIndex &parent) const
{
    if (!parent.isValid())
        return createIndex (row, column, 0);
    else
        return QModelIndex();
}

QModelIndex FilterListModel::parent (const QModelIndex &index) const
{
    Q_UNUSED (index);
    return QModelIndex();
}

QVariant FilterListModel::data (const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (index.row() < 0 || index.row() >= m_filters.count())
        return QVariant();

    if (index.column() < 0 || index.column() >= 2)
        return QVariant();

    if (role == Qt::DisplayRole || role == Qt::EditRole)
        switch (index.column()) {
        case 0:
            return QVariant();
            break;
        case 1:
            return m_filters.at (index.row()).userFilterName;
            break;
        }

    if (role == Qt::CheckStateRole)
        switch (index.column()) {
        case 0:
            if (m_filters.at (index.row()).enabled)
                return Qt::Checked;
            else
                return Qt::Unchecked;
            break;
        case 1:
            return QVariant();
            break;
        }

    return QVariant();
}

Qt::ItemFlags FilterListModel::flags (const QModelIndex &index) const
{
    if (!index.isValid())
        return Qt::ItemIsEnabled;

    switch (index.column()) {
    case 0:
        return QAbstractItemModel::flags (index) | Qt::ItemIsEnabled |
               Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
        break;
    case 1:
        return QAbstractItemModel::flags (index) | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
        break;
    }
    return QAbstractItemModel::flags (index) | Qt::ItemIsEnabled;
}

QVariant FilterListModel::headerData (int section, Qt::Orientation orientation, int role) const
{
    if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
        switch (section) {
        case 0:
            return QLatin1String( "" );
        case 1:
            return i18n ("Filter");
        };
    return QVariant();
}

bool FilterListModel::removeRow (int row, const QModelIndex & parent)
{
    beginRemoveRows (parent, row, row);
    m_filters.removeAt (row);
    endRemoveRows();
    return true;
}

FilterItem FilterListModel::getRow (int row) const
{
    if (row < 0 || row >= rowCount()) return FilterItem();
    return m_filters[row];
}

bool FilterListModel::appendRow (FilterItem& filter)
{
    beginInsertRows (QModelIndex(), m_filters.count(), m_filters.count());
    m_filters.append (filter);
    endInsertRows();
    return true;
}

bool FilterListModel::updateRow (int row, FilterItem& filter)
{
    m_filters.replace (row, filter);
    emit dataChanged (index (row, 0, QModelIndex()), index (row, columnCount() - 1, QModelIndex()));
    return true;
}

bool FilterListModel::swap (int i, int j)
{
    m_filters.swap (i, j);
    emit dataChanged (index (i, 0, QModelIndex()), index (j, columnCount() - 1, QModelIndex()));
    return true;
}

void FilterListModel::clear()
{
    m_filters.clear();
    emit reset();
}

// ----------------------------------------------------------------------------

/**
* Constructor.
*/
KCMKttsMgr::KCMKttsMgr (QWidget *parent, const QVariantList &) :
        KCModule (KCMKttsMgrFactory::componentData(), parent/*, name*/),
        m_kspeech (0)
{

    // kDebug() << "KCMKttsMgr constructor running.";

    // Initialize some variables.
    m_config = 0;
    m_jobMgrWidget = 0;
    m_configDlg = 0;
    m_changed = false;
    m_suppressConfigChanged = false;

    // Add the KTTS Manager widget
    setupUi (this);

    // Connect Views to Models and set row selection mode.
    talkersView->setModel (&m_talkerListModel);
    filtersView->setModel (&m_filterListModel);
    talkersView->setSelectionBehavior (QAbstractItemView::SelectRows);
    filtersView->setSelectionBehavior (QAbstractItemView::SelectRows);
    talkersView->setRootIsDecorated (false);
    filtersView->setRootIsDecorated (false);
    talkersView->setItemsExpandable (false);
    filtersView->setItemsExpandable (false);

    // Give buttons icons.
    // Talkers tab.
    higherTalkerPriorityButton->setIcon (KIcon ( QLatin1String( "go-up" )));
    lowerTalkerPriorityButton->setIcon (KIcon ( QLatin1String( "go-down" )));
    removeTalkerButton->setIcon (KIcon ( QLatin1String( "user-trash" )));
    configureTalkerButton->setIcon (KIcon ( QLatin1String( "configure" )));

    // Filters tab.
    higherFilterPriorityButton->setIcon (KIcon ( QLatin1String( "go-up" )));
    lowerFilterPriorityButton->setIcon (KIcon ( QLatin1String( "go-down" )));
    removeFilterButton->setIcon (KIcon ( QLatin1String( "user-trash" )));
    configureFilterButton->setIcon (KIcon ( QLatin1String( "configure" )));

    // Object for the KTTSD configuration.
    m_config = new KConfig (QLatin1String( "kttsdrc" ));

    // Connect the signals from the KCMKtssMgrWidget to this class.

    // General tab.
    connect (enableJovieCheckBox, SIGNAL (toggled(bool)),
             SLOT (slotEnableJovie_toggled(bool)));

    // Talker tab.
    connect (addTalkerButton, SIGNAL (clicked()),
             this, SLOT (slotAddTalkerButton_clicked()));
    connect (higherTalkerPriorityButton, SIGNAL (clicked()),
             this, SLOT (slotHigherTalkerPriorityButton_clicked()));
    connect (lowerTalkerPriorityButton, SIGNAL (clicked()),
             this, SLOT (slotLowerTalkerPriorityButton_clicked()));
    connect (removeTalkerButton, SIGNAL (clicked()),
             this, SLOT (slotRemoveTalkerButton_clicked()));
    connect (configureTalkerButton, SIGNAL (clicked()),
             this, SLOT (slotConfigureTalkerButton_clicked()));
    connect (talkersView, SIGNAL (clicked(QModelIndex)),
             this, SLOT (updateTalkerButtons()));

    // Filter tab.
    connect (addFilterButton, SIGNAL (clicked()),
             this, SLOT (slotAddFilterButton_clicked()));
    connect (higherFilterPriorityButton, SIGNAL (clicked()),
             this, SLOT (slotHigherFilterPriorityButton_clicked()));
    connect (lowerFilterPriorityButton, SIGNAL (clicked()),
             this, SLOT (slotLowerFilterPriorityButton_clicked()));
    connect (removeFilterButton, SIGNAL (clicked()),
             this, SLOT (slotRemoveFilterButton_clicked()));
    connect (configureFilterButton, SIGNAL (clicked()),
             this, SLOT (slotConfigureFilterButton_clicked()));
    connect (filtersView, SIGNAL (clicked(QModelIndex)),
             this, SLOT (updateFilterButtons()));
    connect (filtersView, SIGNAL (clicked(QModelIndex)),
             this, SLOT (slotFilterListView_clicked(QModelIndex)));


    // Others.
    connect (mainTab, SIGNAL (currentChanged(int)),
             this, SLOT (slotTabChanged()));

    // See if Jovie is already running, and if so, create jobs tab.
    if (QDBusConnection::sessionBus().interface()->isServiceRegistered (QLatin1String( "org.kde.jovie" )))
        jovieStarted();
    else
        // Start Jovie if check box is checked.
        slotEnableJovie_toggled (enableJovieCheckBox->isChecked());

    // Adjust view column sizes.
    // TODO: To work properly, this needs to be done after the widgets are shown.
    // Possibly in Resize event?
    for (int i = 0; i < m_filterListModel.columnCount(); ++i)
        filtersView->resizeColumnToContents (i);
    for (int i = 0; i < m_talkerListModel.columnCount(); ++i)
        talkersView->resizeColumnToContents (i);

    // Switch to Talkers tab if none configured,
    // otherwise switch to Jobs tab if it is active.
    if (m_talkerListModel.rowCount() == 0)
        mainTab->setCurrentIndex (wpTalkers);
    else if (enableJovieCheckBox->isChecked())
        mainTab->setCurrentIndex (wpJobs);
}

/**
* Destructor.
*/
KCMKttsMgr::~KCMKttsMgr()
{
    // kDebug() << "KCMKttsMgr::~KCMKttsMgr: Running";
    delete m_config;
}

/**
* This method is invoked whenever the module should read its
* configuration (most of the times from a config file) and update the
* user interface. This happens when the user clicks the "Reset" button in
* the control center, to undo all of his changes and restore the currently
* valid settings. NOTE that this is not called after the modules is loaded,
* so you probably want to call this method in the constructor.
*/
void KCMKttsMgr::load()
{
    // kDebug() << "KCMKttsMgr::load: Running";

    m_changed = false;
    // Don't emit changed() signal while loading.
    m_suppressConfigChanged = true;

    // Set the group general for the configuration of kttsd itself (no plug ins)
    KConfigGroup generalConfig (m_config, "General");

    // Overall settings.
    enableJovieCheckBox->setChecked (generalConfig.readEntry ("EnableJovie",
                                     enableJovieCheckBox->isChecked()));

    // Last filter ID.  Used to generate a new ID for an added filter.
    m_lastFilterID = 0;

    // Load existing Talkers into Talker List.
    m_talkerListModel.loadTalkerCodesFromConfig (m_config);

    // Last talker ID.  Used to generate a new ID for an added talker.
    //m_lastTalkerID = m_talkerListModel.highestTalkerId();

    // Dictionary mapping languages to language codes.
    m_languagesToCodes.clear();
    for (int i = 0; i < m_talkerListModel.rowCount(); ++i) {
        QString language = m_talkerListModel.getRow (i).language();
        m_languagesToCodes[language] = language;
    }

    // Iterate thru the possible plug ins getting their language support codes.
    //for(int i=0; i < offers.count() ; ++i)
    //{
    //    QString synthName = offers[i]->name();
    //    QStringList languageCodes = offers[i]->property("X-KDE-Languages").toStringList();
    //    // Add language codes to the language-to-language code map.
    //    QStringList::ConstIterator endLanguages(languageCodes.constEnd());
    //    for( QStringList::ConstIterator it = languageCodes.constBegin(); it != endLanguages; ++it )
    //    {
    //        QString language = TalkerCode::languageCodeToLanguage(*it);
    //        m_languagesToCodes[language] = *it;
    //    }

    //    // All plugins support "Other".
    //    // TODO: Eventually, this should not be necessary, since all plugins will know
    //    // the languages they support and report them in call to getSupportedLanguages().
    //    if (!languageCodes.contains("other")) languageCodes.append("other");

    //    // Add supported language codes to synthesizer-to-language map.
    //    m_synthToLangMap[synthName] = languageCodes;
    //}

    // Add "Other" language.
    //m_languagesToCodes[i18nc("Other language", "Other")] = "other";

    // Load Filters.
    m_filterListModel.clear();
    QStringList filterIDsList = generalConfig.readEntry ("FilterIDs", QStringList());
    // kDebug() << "KCMKttsMgr::load: FilterIDs = " << filterIDsList;
    if (!filterIDsList.isEmpty()) {
        QStringList::ConstIterator itEnd = filterIDsList.constEnd();
        for (QStringList::ConstIterator it = filterIDsList.constBegin(); it != itEnd; ++it) {
            QString filterID = *it;
            // kDebug() << "KCMKttsMgr::load: filterID = " << filterID;
            KConfigGroup filterConfig (m_config, QLatin1String( "Filter_" ) + filterID);
            QString desktopEntryName = filterConfig.readEntry ("DesktopEntryName", QString());
            // If a DesktopEntryName is not in the config file, it was configured before
            // we started using them, when we stored translated plugin names instead.
            // Try to convert the translated plugin name to a DesktopEntryName.
            // DesktopEntryNames are better because user can change their desktop language
            // and DesktopEntryName won't change.
            QString filterPlugInName;
            filterPlugInName = FilterDesktopEntryNameToName (desktopEntryName);
            if (!filterPlugInName.isEmpty()) {
                FilterItem fi;
                fi.id = filterID;
                fi.plugInName = filterPlugInName;
                fi.desktopEntryName = desktopEntryName;
                fi.userFilterName = filterConfig.readEntry ("UserFilterName", filterPlugInName);
                fi.multiInstance = filterConfig.readEntry ("MultiInstance", false);
                fi.enabled = filterConfig.readEntry ("Enabled", false);
                // Determine if this filter is a Sentence Boundary Detector (SBD).
                m_filterListModel.appendRow (fi);
                if (filterID.toInt() > m_lastFilterID) m_lastFilterID = filterID.toInt();
            }
        }
    }

    // Add at least one unchecked instance of each available filter plugin if there is
    // not already at least one instance and the filter can autoconfig itself.
    //offers = KServiceTypeTrader::self()->query("KTTSD/FilterPlugin");
    //for (int i=0; i < offers.count() ; ++i)
    //{
    //    QString filterPlugInName = offers[i]->name();
    //    QString desktopEntryName = FilterNameToDesktopEntryName(filterPlugInName);
    //    if (!desktopEntryName.isEmpty() && (countFilterPlugins(filterPlugInName) == 0))
    //    {
    //        // Must load plugin to determine if it supports multiple instances
    //        // and to see if it can autoconfigure itself.
    //        KttsFilterConf* filterPlugIn = loadFilterPlugin(desktopEntryName);
    //        if (filterPlugIn)
    //        {
    //            ++m_lastFilterID;
    //            QString filterID = QString::number(m_lastFilterID);
    //            QString groupName = "Filter_" + filterID;
    //            filterPlugIn->load(m_config, groupName);
    //            QString userFilterName = filterPlugIn->userPlugInName();
    //            if (!userFilterName.isEmpty())
    //            {
    //                kDebug() << "KCMKttsMgr::load: auto-configuring filter " << userFilterName;
    //                bool multiInstance = filterPlugIn->supportsMultiInstance();
    //                FilterItem fi;
    //                fi.id = filterID;
    //                fi.userFilterName = userFilterName;
    //                fi.plugInName = filterPlugInName;
    //                fi.desktopEntryName = desktopEntryName;
    //                fi.enabled = true;
    //                fi.multiInstance = multiInstance;
    //                // Determine if plugin is an SBD filter.
    //                bool isSbd = filterPlugIn->isSBD();
    //                if (isSbd)
    //                    m_sbdFilterListModel.appendRow(fi);
    //                else
    //                    m_filterListModel.appendRow(fi);
    //                KConfigGroup filterConfig(m_config, groupName);
    //                filterPlugIn->save(m_config, groupName);
    //                filterConfig.writeEntry("DesktopEntryName", desktopEntryName);
    //                filterConfig.writeEntry("UserFilterName", userFilterName);
    //                filterConfig.writeEntry("Enabled", isSbd);
    //                filterConfig.writeEntry("MultiInstance", multiInstance);
    //                filterConfig.writeEntry("IsSBD", isSbd);
    //                filterIDsList.append(filterID);
    //            } else m_lastFilterID--;
    //        } else
    //            kDebug() << "KCMKttsMgr::load: Unable to load filter plugin " << filterPlugInName
    //                << " DesktopEntryName " << desktopEntryName << endl;
    //        delete filterPlugIn;
    //    }
    //}
    // Rewrite list of FilterIDs in case we added any.
    QString filterIDs = filterIDsList.join (QLatin1String( "," ));
    generalConfig.writeEntry ("FilterIDs", filterIDs);
    m_config->sync();

    // Update controls based on new states.
    updateTalkerButtons();
    updateFilterButtons();

    m_changed = false;
    m_suppressConfigChanged = false;
}

/**
* This function gets called when the user wants to save the settings in
* the user interface, updating the config files or wherever the
* configuration is stored. The method is called when the user clicks "Apply"
* or "Ok".
*/
void KCMKttsMgr::save()
{
    // kDebug() << "KCMKttsMgr::save: Running";
    m_changed = false;

    // Clean up config.
    m_config->deleteGroup ("General", 0);

    // Set the group general for the configuration of kttsd itself (no plug ins)
    KConfigGroup generalConfig (m_config, "General");

    // Uncheck and disable KTTSD checkbox if no Talkers are configured.
    // Enable checkbox if at least one Talker is configured.
    bool enableJovieWasToggled = false;
    //if (m_talkerListModel.rowCount() == 0)
    //{
    //enableKttsdWasToggled = enableKttsdCheckBox->isChecked();
    //enableKttsdCheckBox->setChecked(false);
    //enableKttsdCheckBox->setEnabled(false);
    // Might as well zero LastTalkerID as well.
    //m_lastTalkerID = 0;
    //}
    //else
    enableJovieCheckBox->setEnabled (true);

    generalConfig.writeEntry ("EnableKttsd", enableJovieCheckBox->isChecked());

    // Get ordered list of all talker IDs.
    QList<TalkerCode> talkers;
    QStringList talkerIDsList;
    KConfigGroup talkerGroup (m_config, "Talkers");
    talkerGroup.deleteGroup();
    for (int i = 0; i < m_talkerListModel.rowCount(); ++i) {
        TalkerCode talker = m_talkerListModel.getRow (i);
        talkers << talker;
        talkerGroup.writeEntry (talker.name(), talker.getTalkerCode());
        talkerIDsList << talker.name();
    }

    QString talkerIDs = talkerIDsList.join (QLatin1String( "," ));
    generalConfig.writeEntry ("TalkerIDs", talkerIDs);

    // Erase obsolete Talker_nn sections.
    QStringList groupList = m_config->groupList();
    int groupListCount = groupList.count();
    for (int groupNdx = 0; groupNdx < groupListCount; ++groupNdx) {
        QString groupName = groupList[groupNdx];
        if (groupName.left (7) == QLatin1String( "Talker_" )) {
            QString groupTalkerID = groupName.mid (7);
            if (!talkerIDsList.contains (groupTalkerID))
                m_config->deleteGroup (groupName, 0);
        }
    }

    // Get ordered list of all filter IDs.  Record enabled state of each filter.
    QStringList filterIDsList;
    for (int i = 0; i < m_filterListModel.rowCount(); ++i) {
        FilterItem fi = m_filterListModel.getRow (i);
        filterIDsList.append (fi.id);
        KConfigGroup filterConfig (m_config, QLatin1String( "Filter_" ) + fi.id);
        filterConfig.writeEntry ("Enabled", fi.enabled);
    }
    QString filterIDs = filterIDsList.join (QLatin1String( "," ));
    generalConfig.writeEntry ("FilterIDs", filterIDs);

    // Erase obsolete Filter_nn sections.
    for (int groupNdx = 0; groupNdx < groupListCount; ++groupNdx) {
        QString groupName = groupList[groupNdx];
        if (groupName.left (7) == QLatin1String( "Filter_" )) {
            QString groupFilterID = groupName.mid (7);
            if (!filterIDsList.contains (groupFilterID))
                m_config->deleteGroup (groupName, 0);
        }
    }

    m_config->sync();

    // apply changes in the jobs page if it exists
    if (m_jobMgrWidget) {
        m_jobMgrWidget->save();
    }

    // If we automatically unchecked the Enable KTTSD checkbox, stop KTTSD.
    if (enableJovieWasToggled)
        slotEnableJovie_toggled (false);
    else {
        // If Jovie is running, reinitialize it.
        if (m_kspeech) {
            kDebug() << "Restarting Jovie";
            m_kspeech->reinit();
        }
    }
}

void KCMKttsMgr::slotTabChanged()
{
    // TODO: Commentting this out to avoid a crash.  It seems there is a bug in
    // KDialog.  The workaround is to call setDefaultButton(), but that method
    // is not available to a KCModule.  Uncomment this when bug is fixed.
    // setButtons(buttons());
    int currentPageIndex = mainTab->currentIndex();
    if (currentPageIndex == wpJobs) {
        if (m_changed) {
            KMessageBox::information (this,
                                      i18n ("You have made changes to the configuration but have not saved them yet.  "
                                            "Click Apply to save the changes or Cancel to abandon the changes."));
        }
    }
}

/**
* This function is called to set the settings in the module to sensible
* default values. It gets called when hitting the "Default" button. The
* default values should probably be the same as the ones the application
* uses when started without a config file.
*/
void KCMKttsMgr::defaults()
{
    // kDebug() << "Running: KCMKttsMgr::defaults: Running";

    int currentPageIndex = mainTab->currentIndex();
    // configChanged();
}

/**
* This is a static method which gets called to realize the modules settings
* during the startup of KDE. NOTE that most modules do not implement this
* method, but modules like the keyboard and mouse modules, which directly
* interact with the X-server, need this method. As this method is static,
* it can avoid to create an instance of the user interface, which is often
* not needed in this case.
*/
void KCMKttsMgr::init()
{
    // kDebug() << "KCMKttsMgr::init: Running";
}

/**
* This function returns the small quickhelp.
* That is displayed in the sidebar in the KControl
*/
QString KCMKttsMgr::quickHelp() const
{
    // kDebug() << "KCMKttsMgr::quickHelp: Running";
    return i18n (
               "<h1>Text-to-Speech</h1>"
               "<p>This is the configuration for the text-to-speech D-Bus service</p>"
               "<p>This allows other applications to access text-to-speech resources</p>"
               "<p>Be sure to configure a default language for the language you are using as this will be the language used by most of the applications</p>");
}

const KAboutData* KCMKttsMgr::aboutData() const
{
    KAboutData *about =
        new KAboutData (I18N_NOOP ("jovie"), 0, ki18n ("KCMKttsMgr"),
                        0, KLocalizedString(), KAboutData::License_GPL,
                        ki18n ("(c) 2010, Jeremy Whiting"));

    about->addAuthor (ki18n ("José Pablo Ezequiel Fernández"), ki18n ("Author") , "pupeno@kde.org");
    about->addAuthor (ki18n ("Gary Cramblitt"), ki18n ("Maintainer") , "garycramblitt@comcast.net");
    about->addAuthor (ki18n ("Olaf Schmidt"), ki18n ("Contributor"), "ojschmidt@kde.org");
    about->addAuthor (ki18n ("Paul Giannaros"), ki18n ("Contributor"), "ceruleanblaze@gmail.com");

    return about;
}

/**
 * Loads the configuration plug in for a named filter plug in.
 * @param plugInName       DesktopEntryName of the plugin.
 * @return                 Pointer to the configuration plugin for the Filter.
 */
KttsFilterConf* KCMKttsMgr::loadFilterPlugin (const QString& plugInName)
{
    // kDebug() << "KCMKttsMgr::loadPlugin: Running";

    // Find the plugin.
    KService::List offers = KServiceTypeTrader::self()->query (QLatin1String( "Jovie/FilterPlugin" ),
                            QString (QLatin1String( "DesktopEntryName == '%1'" )).arg (plugInName));

    if (offers.count() == 1) {
        // When the entry is found, load the plug in
        // First create a factory for the library
        KLibFactory *factory = KLibLoader::self()->factory (QLatin1String( offers[0]->library().toLatin1() ));
        if (factory) {
            // If the factory is created successfully, instantiate the KttsFilterConf class for the
            // specific plug in to get the plug in configuration object.
            int errorNo = 0;
            KttsFilterConf *plugIn =
                KLibLoader::createInstance<KttsFilterConf> (
                    QLatin1String( offers[0]->library().toLatin1() ), NULL, QStringList ( QLatin1String( offers[0]->library().toLatin1() )),
                    &errorNo);
            if (plugIn) {
                // If everything went ok, return the plug in pointer.
                return plugIn;
            } else {
                // Something went wrong, returning null.
                kDebug() << "KCMKttsMgr::loadFilterPlugin: Unable to instantiate KttsFilterConf class for plugin " << plugInName << " error: " << errorNo;
                return NULL;
            }
        } else {
            // Something went wrong, returning null.
            kDebug() << "KCMKttsMgr::loadFilterPlugin: Unable to create Factory object for plugin " << plugInName;
            return NULL;
        }
    }
    // The plug in was not found (unexpected behaviour, returns null).
    kDebug() << "KCMKttsMgr::loadFilterPlugin: KTrader did not return an offer for plugin " << plugInName;
    return NULL;
}

/**
 * Add a talker.
 */
void KCMKttsMgr::slotAddTalkerButton_clicked()
{
    QPointer<AddTalker> dlg = new AddTalker (this);
    if (dlg->exec() == QDialog::Accepted) {
        TalkerCode code = dlg->getTalkerCode();

        // Add to list of Talkers.
        m_talkerListModel.appendRow (code);

        // Make sure visible.
        const QModelIndex modelIndex = m_talkerListModel.index (m_talkerListModel.rowCount() - 1,
                                       0, QModelIndex());
        talkersView->scrollTo (modelIndex);

        // Select the new item, update buttons.
        talkersView->setCurrentIndex (modelIndex);
        updateTalkerButtons();

        // Inform Control Center that change has been made.
        configChanged();
    }
    delete dlg;

    kDebug() << "KCMKttsMgr::addTalker: done.";
}

void KCMKttsMgr::slotAddFilterButton_clicked()
{
    addFilter();
}

/**
* Add a filter.
*/
void KCMKttsMgr::addFilter()
{
    QTreeView* lView = filtersView;
    FilterListModel* model = qobject_cast<FilterListModel *> (lView->model());

    // Build a list of filters that support multiple instances and let user choose.
    QStringList filterPlugInNames;
    for (int i = 0; i < model->rowCount(); ++i) {
        FilterItem fi = model->getRow (i);
        if (fi.multiInstance) {
            if (!filterPlugInNames.contains (fi.plugInName))
                filterPlugInNames.append (fi.plugInName);
        }
    }
    // Append those available plugins not yet in the list at all.
    KService::List offers = KServiceTypeTrader::self()->query (QLatin1String( "Jovie/FilterPlugin" ));
    for (int i = 0; i < offers.count() ; ++i) {
        QString filterPlugInName = offers[i]->name();
        if (countFilterPlugins (filterPlugInName) == 0) {
            QString desktopEntryName = FilterNameToDesktopEntryName (filterPlugInName);
            KttsFilterConf* filterConf = loadFilterPlugin (desktopEntryName);
            if (filterConf) {
                filterPlugInNames.append (filterPlugInName);
                delete filterConf;
            }
        }
    }

    // If no choice (shouldn't happen), bail out.
    // kDebug() << "KCMKttsMgr::addFilter: filterPluginNames = " << filterPlugInNames;
    if (filterPlugInNames.count() == 0) return;

    // If exactly one choice, skip selection dialog, otherwise display list to user to select from.
    bool okChosen = false;
    QString filterPlugInName;
    if (filterPlugInNames.count() > 1) {
        filterPlugInName = KInputDialog::getItem (
                               i18n ("Select Filter"),
                               i18n ("Filter"),
                               filterPlugInNames,
                               0,
                               false,
                               &okChosen,
                               this);
        if (!okChosen) return;
    } else
        filterPlugInName = filterPlugInNames[0];

    // kDebug() << "KCMKttsMgr::addFilter: filterPlugInName = " << filterPlugInName;

    // Assign a new Filter ID for the filter.  Wraps around to 1.
    QString filterID = QString::number (m_lastFilterID + 1);

    // Erase extraneous Filter configuration entries that might be there.
    m_config->deleteGroup (QLatin1String ("Filter_") + filterID, 0);
    m_config->sync();

    // Get DesktopEntryName from the translated name.
    QString desktopEntryName = FilterNameToDesktopEntryName (filterPlugInName);
    // This shouldn't happen, but just in case.
    if (desktopEntryName.isEmpty()) return;

    // Load the plugin.
    m_loadedFilterPlugIn = loadFilterPlugin (desktopEntryName);
    if (!m_loadedFilterPlugIn) return;

    // Permit plugin to autoconfigure itself.
    m_loadedFilterPlugIn->load (m_config, QLatin1String ("Filter_") + filterID);

    // Display configuration dialog for user to configure the plugin.
    configureFilter();

    // Did user Cancel?
    if (!m_loadedFilterPlugIn) {
        delete m_configDlg;
        m_configDlg = 0;
        return;
    }

    // Get user's name for Filter.
    QString userFilterName = m_loadedFilterPlugIn->userPlugInName();

    // If user properly configured the plugin, save its configuration.
    if (!userFilterName.isEmpty()) {
        // Let plugin save its configuration.
        m_loadedFilterPlugIn->save (m_config, QLatin1String ("Filter_" ) + filterID);

        // Record last Filter ID used for next add.
        m_lastFilterID = filterID.toInt();

        // Determine if filter supports multiple instances.
        bool multiInstance = m_loadedFilterPlugIn->supportsMultiInstance();

        // Record configuration data.  Note, might as well do this now.
        KConfigGroup filterConfig (m_config, QLatin1String ("Filter_" ) + filterID);
        filterConfig.writeEntry ("DesktopEntryName", desktopEntryName);
        filterConfig.writeEntry ("UserFilterName", userFilterName);
        filterConfig.writeEntry ("MultiInstance", multiInstance);
        filterConfig.writeEntry ("Enabled", true);
        m_config->sync();

        // Add listview item.
        FilterItem fi;
        fi.id = filterID;
        fi.plugInName = filterPlugInName;
        fi.userFilterName = userFilterName;
        fi.desktopEntryName = desktopEntryName;
        fi.multiInstance = multiInstance;
        fi.enabled = true;
        model->appendRow (fi);

        // Make sure visible.
        QModelIndex modelIndex = model->index (model->rowCount() - 1, 0, QModelIndex());
        lView->scrollTo (modelIndex);

        // Select the new item, update buttons.
        lView->setCurrentIndex (modelIndex);
        updateFilterButtons();

        // Inform Control Center that change has been made.
        configChanged();
    }

    // Don't need plugin in memory anymore.
    delete m_loadedFilterPlugIn;
    m_loadedFilterPlugIn = 0;
    delete m_configDlg;
    m_configDlg = 0;

    // kDebug() << "KCMKttsMgr::addFilter: done.";
}

/**
* Remove talker.
*/
void KCMKttsMgr::slotRemoveTalkerButton_clicked()
{
    // kDebug() << "KCMKttsMgr::removeTalker: Running";

    // Get the selected talker.
    QModelIndex modelIndex = talkersView->currentIndex();
    if (!modelIndex.isValid()) return;

    // Delete the talker from configuration file?
    QString talkerID = m_talkerListModel.getRow (modelIndex.row()).name();
    m_config->deleteGroup (QLatin1String ("Talker_") + talkerID, 0);

    // Delete the talker from the list of Talkers.
    m_talkerListModel.removeRow (modelIndex.row());

    updateTalkerButtons();

    // Emit configuration changed.
    configChanged();
}

void KCMKttsMgr::slotRemoveFilterButton_clicked()
{
    removeFilter();
}

/**
* Remove filter.
*/
void KCMKttsMgr::removeFilter()
{
    // kDebug() << "KCMKttsMgr::removeFilter: Running";

    FilterListModel* model;
    QTreeView* lView = filtersView;
    model = qobject_cast<FilterListModel *> (lView->model());
    QModelIndex modelIndex = lView->currentIndex();
    if (!modelIndex.isValid()) return;
    QString filterID = model->getRow (modelIndex.row()).id;
    // Delete the filter from list view.
    model->removeRow (modelIndex.row());
    updateFilterButtons();

    // Delete the filter from the configuration file?
    kDebug() << "KCMKttsMgr::removeFilter: removing FilterID = " << filterID << " from config file.";
    m_config->deleteGroup (QLatin1String ("Filter_") + filterID, 0);

    // Emit configuration changed.
    configChanged();
}

void KCMKttsMgr::slotHigherTalkerPriorityButton_clicked()
{
    QModelIndex modelIndex = talkersView->currentIndex();
    if (!modelIndex.isValid()) return;
    m_talkerListModel.swap (modelIndex.row(), modelIndex.row() - 1);
    modelIndex = m_talkerListModel.index (modelIndex.row() - 1, 0, QModelIndex());
    talkersView->scrollTo (modelIndex);
    talkersView->setCurrentIndex (modelIndex);
    updateTalkerButtons();
    configChanged();
}

void KCMKttsMgr::slotHigherFilterPriorityButton_clicked()
{
    QModelIndex modelIndex = filtersView->currentIndex();
    if (!modelIndex.isValid()) return;
    m_filterListModel.swap (modelIndex.row(), modelIndex.row() - 1);
    modelIndex = m_filterListModel.index (modelIndex.row() - 1, 0, QModelIndex());
    filtersView->scrollTo (modelIndex);
    filtersView->setCurrentIndex (modelIndex);
    updateFilterButtons();
    configChanged();
}

void KCMKttsMgr::slotLowerTalkerPriorityButton_clicked()
{
    QModelIndex modelIndex = talkersView->currentIndex();
    if (!modelIndex.isValid()) return;
    m_talkerListModel.swap (modelIndex.row(), modelIndex.row() + 1);
    modelIndex = m_talkerListModel.index (modelIndex.row() + 1, 0, QModelIndex());
    talkersView->scrollTo (modelIndex);
    talkersView->setCurrentIndex (modelIndex);
    updateTalkerButtons();
    configChanged();
}

void KCMKttsMgr::slotLowerFilterPriorityButton_clicked()
{
    QModelIndex modelIndex = filtersView->currentIndex();
    if (!modelIndex.isValid()) return;
    m_filterListModel.swap (modelIndex.row(), modelIndex.row() + 1);
    modelIndex = m_filterListModel.index (modelIndex.row() + 1, 0, QModelIndex());
    filtersView->scrollTo (modelIndex);
    filtersView->setCurrentIndex (modelIndex);
    updateFilterButtons();
    configChanged();
}

/**
* Update the status of the Talker buttons.
*/
void KCMKttsMgr::updateTalkerButtons()
{
    // kDebug() << "KCMKttsMgr::updateTalkerButtons: Running";
    QModelIndex modelIndex = talkersView->currentIndex();
    if (modelIndex.isValid()) {
        removeTalkerButton->setEnabled (true);
        configureTalkerButton->setEnabled (true);
        higherTalkerPriorityButton->setEnabled (modelIndex.row() != 0);
        lowerTalkerPriorityButton->setEnabled (modelIndex.row() < (m_talkerListModel.rowCount() - 1));
    } else {
        removeTalkerButton->setEnabled (false);
        configureTalkerButton->setEnabled (false);
        higherTalkerPriorityButton->setEnabled (false);
        lowerTalkerPriorityButton->setEnabled (false);
    }
    // kDebug() << "KCMKttsMgr::updateTalkerButtons: Exiting";
}

/**
* Update the status of the normal Filter buttons.
*/
void KCMKttsMgr::updateFilterButtons()
{
    // kDebug() << "KCMKttsMgr::updateFilterButtons: Running";
    QModelIndex modelIndex = filtersView->currentIndex();
    if (modelIndex.isValid()) {
        removeFilterButton->setEnabled (true);
        configureFilterButton->setEnabled (true);
        higherFilterPriorityButton->setEnabled (modelIndex.row() != 0);
        lowerFilterPriorityButton->setEnabled (modelIndex.row() < (m_filterListModel.rowCount() - 1));
    } else {
        removeFilterButton->setEnabled (false);
        configureFilterButton->setEnabled (false);
        higherFilterPriorityButton->setEnabled (false);
        lowerFilterPriorityButton->setEnabled (false);
    }
    // kDebug() << "KCMKttsMgr::updateFilterButtons: Exiting";
}

/**
* This signal is emitted whenever user checks/unchecks the Enable TTS System check box.
*/
void KCMKttsMgr::slotEnableJovie_toggled (bool)
{
    // Prevent re-entrancy.
    static bool reenter;
    if (reenter) return;
    reenter = true;
    // See if Jovie is running.
    bool kttsdRunning = (QDBusConnection::sessionBus().interface()->isServiceRegistered (QLatin1String( "org.kde.jovie" )));

    // kDebug() << "KCMKttsMgr::slotEnableKttsd_toggled: kttsdRunning = " << kttsdRunning;
    // If Enable Jovie check box is checked and it is not running, then start Jovie.
    if (enableJovieCheckBox->isChecked()) {
        if (!kttsdRunning) {
            // kDebug() << "KCMKttsMgr::slotEnableKttsd_toggled:: Starting Jovie";
            QString error;
            if (KToolInvocation::startServiceByDesktopName (QLatin1String( "jovie" ), QStringList(), &error)) {
                kDebug() << "Starting Jovie failed with message " << error;
                enableJovieCheckBox->setChecked (false);

            } else {
                configChanged();
                jovieStarted();
            }
        }
    } else
        // If check box is not checked and it is running, then stop Jovie.
    {
        if (kttsdRunning) {
            // kDebug() << "KCMKttsMgr::slotEnableKttsd_toggled:: Stopping Jovie";
            if (!m_kspeech)
                m_kspeech = new OrgKdeKSpeechInterface (QLatin1String( "org.kde.jovie" ), QLatin1String( "/KSpeech" ), QDBusConnection::sessionBus());
            m_kspeech->kttsdExit();
            delete m_kspeech;
            m_kspeech = 0;
            configChanged();
        }
    }
    reenter = false;
}

/**
* This slot is called whenever KTTSD starts or restarts.
*/
void KCMKttsMgr::jovieStarted()
{
    // kDebug() << "KCMKttsMgr::jovieStarted: Running";
    bool kttsdLoaded = (m_jobMgrWidget != 0);
    // Load Job Manager Part library.
    if (!kttsdLoaded) {
        m_jobMgrWidget = new KttsJobMgr (this);
        if (m_jobMgrWidget) {
            connect (m_jobMgrWidget, SIGNAL (configChanged()), this, SLOT (configChanged()));
            // Add the Job Manager part as a new tab.
            mainTab->addTab (m_jobMgrWidget, i18n ("Jobs"));
            kttsdLoaded = true;
        } else
            kDebug() << "KCMKttsMgr::jovieStarted: Could not create kttsjobmgr part.";
    }
    // Check/Uncheck the Enable KTTSD check box.
    if (kttsdLoaded) {
        enableJovieCheckBox->setChecked (true);
        m_kspeech = new OrgKdeKSpeechInterface (QLatin1String( "org.kde.jovie" ), QLatin1String( "/KSpeech" ), QDBusConnection::sessionBus());
        m_kspeech->setParent (this);
        m_kspeech->setApplicationName (QLatin1String( "KCMKttsMgr" ));
        m_kspeech->setIsSystemManager (true);
        // Connect KTTSD DBUS signals to our slots.
        connect (m_kspeech, SIGNAL (kttsdStarted()),
                 this, SLOT (jovieStarted()));
        connect (m_kspeech, SIGNAL (kttsdExiting()),
                 this, SLOT (jovieExiting()));
        connect (QDBusConnection::sessionBus().interface(), SIGNAL (serviceUnregistered(QString)),
                 this, SLOT (slotServiceUnregistered(QString)));
        connect (QDBusConnection::sessionBus().interface(), SIGNAL (serviceOwnerChanged(QString,QString,QString)),
                 this, SLOT (slotServiceOwnerChanged(QString,QString,QString)));

        kttsdVersion->setText (i18n ("Jovie Version: %1", m_kspeech->version()));

    } else {
        enableJovieCheckBox->setChecked (false);
        delete m_kspeech;
        m_kspeech = 0;
    }
}

void KCMKttsMgr::slotServiceUnregistered (const QString &service)
{
    if (service == QLatin1String ("org.kde.jovie")) {
        jovieExiting();
    }

}

void KCMKttsMgr::slotServiceOwnerChanged (const QString &service, const QString &, const QString &newOwner)
{
    if (service == QLatin1String ("org.kde.jovie") && newOwner.isEmpty()) {
        jovieExiting();
    }
}

/**
* This slot is called whenever KTTSD is about to exit.
*/
void KCMKttsMgr::jovieExiting()
{
    // kDebug() << "KCMKttsMgr::kttsdExiting: Running";
    if (m_jobMgrWidget) {
        mainTab->removeTab (wpJobs);
        delete m_jobMgrWidget;
        m_jobMgrWidget = 0;
    }

    enableJovieCheckBox->setChecked (false);
    disconnect (QDBusConnection::sessionBus().interface(), 0, this, 0);
    delete m_kspeech;
    m_kspeech = 0;
    kttsdVersion->setText (i18n ("Jovie not running"));
}

/**
* User has requested display of talker configuration dialog.
*/
void KCMKttsMgr::slotConfigureTalkerButton_clicked()
{
    // Get highlighted Talker from ListView.
    QModelIndex modelIndex = talkersView->currentIndex();

    if (!modelIndex.isValid())
        return;

    TalkerCode tc = m_talkerListModel.getRow(modelIndex.row());

    QPointer<AddTalker> dlg = new AddTalker (this);
    dlg->setTalkerCode(tc);
    if (dlg->exec() == QDialog::Accepted) {
        TalkerCode code = dlg->getTalkerCode();

        // Change the existing talker code to this new code.
        m_talkerListModel.updateRow(modelIndex.row(), code);

        configChanged();
    }
}

void KCMKttsMgr::slotConfigureFilterButton_clicked()
{
    configureFilterItem();
}

/**
 * User has requested display of filter configuration dialog.
 */
void KCMKttsMgr::configureFilterItem()
{
    // Get highlighted plugin from Filter ListView and load into memory.
    QTreeView* lView;
    FilterListModel* model;
    lView = filtersView;
    model = &m_filterListModel;
    QModelIndex modelIndex = lView->currentIndex();
    if (!modelIndex.isValid()) return;
    FilterItem fi = model->getRow (modelIndex.row());
    QString filterID = fi.id;
    QString filterPlugInName = fi.plugInName;
    QString desktopEntryName = fi.desktopEntryName;
    if (desktopEntryName.isEmpty()) return;
    m_loadedFilterPlugIn = loadFilterPlugin (desktopEntryName);
    if (!m_loadedFilterPlugIn) return;
    // kDebug() << "KCMKttsMgr::slot_configureFilter: plugin for " << filterPlugInName << " loaded successfully.";

    // Tell plugin to load its configuration.
    // kDebug() << "KCMKttsMgr::slot_configureFilter: about to call plugin load() method with Filter ID = " << filterID;
    m_loadedFilterPlugIn->load (m_config, QLatin1String ("Filter_") + filterID);

    // Display configuration dialog.
    configureFilter();

    // Did user Cancel?
    if (!m_loadedFilterPlugIn) {
        delete m_configDlg;
        m_configDlg = 0;
        return;
    }

    // Get user's name for the plugin.
    QString userFilterName = m_loadedFilterPlugIn->userPlugInName();

    // If user properly configured the plugin, save the configuration.
    if (!userFilterName.isEmpty()) {
        // Let plugin save its configuration.
        m_loadedFilterPlugIn->save (m_config, QLatin1String ("Filter_") + filterID);

        // Save configuration.
        KConfigGroup filterConfig (m_config, QLatin1String ("Filter_") + filterID);
        filterConfig.writeEntry ("DesktopEntryName", desktopEntryName);
        filterConfig.writeEntry ("UserFilterName", userFilterName);
        filterConfig.writeEntry ("Enabled", true);
        filterConfig.writeEntry ("MultiInstance", m_loadedFilterPlugIn->supportsMultiInstance());

        m_config->sync();

        // Update display.
        FilterItem fi;
        fi.id = filterID;
        fi.desktopEntryName = desktopEntryName;
        fi.userFilterName = userFilterName;
        fi.enabled = true;
        fi.multiInstance = m_loadedFilterPlugIn->supportsMultiInstance();
        model->updateRow (modelIndex.row(), fi);
        // Inform Control Center that configuration has changed.
        configChanged();
    }

    delete m_configDlg;
    m_configDlg = 0;
}

/**
* Display filter configuration dialog.  The plugin is assumed already loaded into
* memory referenced by m_loadedFilterPlugIn.
*/
void KCMKttsMgr::configureFilter()
{
    if (!m_loadedFilterPlugIn) return;
    m_configDlg = new KDialog (this);
    m_configDlg->setCaption (i18n ("Filter Configuration"));
    m_configDlg->setButtons (KDialog::Help | KDialog::Default | KDialog::Ok | KDialog::Cancel);
    m_configDlg->setDefaultButton (KDialog::Cancel);
    m_loadedFilterPlugIn->setMinimumSize (m_loadedFilterPlugIn->minimumSizeHint());
    m_loadedFilterPlugIn->show();
    m_configDlg->setMainWidget (m_loadedFilterPlugIn);
    m_configDlg->setHelp (QLatin1String( "configure-filter" ), QLatin1String( "jovie" ));
    m_configDlg->enableButtonOk (false);
    connect (m_loadedFilterPlugIn, SIGNAL (changed(bool)), this, SLOT (slotConfigFilterDlg_ConfigChanged()));
    connect (m_configDlg, SIGNAL (defaultClicked()), this, SLOT (slotConfigFilterDlg_DefaultClicked()));
    connect (m_configDlg, SIGNAL (cancelClicked()), this, SLOT (slotConfigFilterDlg_CancelClicked()));
    // Display the dialog.
    m_configDlg->exec();
}

/**
* Count number of configured Filters with the specified plugin name.
*/
int KCMKttsMgr::countFilterPlugins (const QString& filterPlugInName)
{
    int cnt = 0;
    for (int i = 0; i < m_filterListModel.rowCount(); ++i) {
        FilterItem fi = m_filterListModel.getRow (i);
        if (fi.plugInName == filterPlugInName) ++cnt;
    }
    return cnt;
}

void KCMKttsMgr::slotConfigTalkerDlg_ConfigChanged()
{
    //m_configDlg->enableButtonOk(!m_loadedTalkerPlugIn->getTalkerCode().isEmpty());
}

void KCMKttsMgr::slotConfigFilterDlg_ConfigChanged()
{
    m_configDlg->enableButtonOk (!m_loadedFilterPlugIn->userPlugInName().isEmpty());
}

void KCMKttsMgr::slotConfigTalkerDlg_DefaultClicked()
{
    //m_loadedTalkerPlugIn->defaults();
}

void KCMKttsMgr::slotConfigFilterDlg_DefaultClicked()
{
    m_loadedFilterPlugIn->defaults();
}

void KCMKttsMgr::slotConfigTalkerDlg_CancelClicked()
{
    //delete m_loadedTalkerPlugIn;
    //m_loadedTalkerPlugIn = 0;
}

void KCMKttsMgr::slotConfigFilterDlg_CancelClicked()
{
    delete m_loadedFilterPlugIn;
    m_loadedFilterPlugIn = 0;
}

/**
 * Uses KTrader to convert a translated Filter Plugin Name to DesktopEntryName.
 * @param name                   The translated plugin name.  From Name= line in .desktop file.
 * @return                       DesktopEntryName.  The name of the .desktop file (less .desktop).
 *                               QString() if not found.
 */
QString KCMKttsMgr::FilterNameToDesktopEntryName (const QString& name)
{
    if (name.isEmpty()) return QString();
    const KService::List  offers =  KServiceTypeTrader::self()->query (QLatin1String( "Jovie/FilterPlugin" ));
    for (int ndx = 0; ndx < offers.count(); ++ndx)
        if (offers[ndx]->name() == name)
            return offers[ndx]->desktopEntryName();
    return QString();
}

/**
 * Uses KTrader to convert a DesktopEntryName into a translated Filter Plugin Name.
 * @param desktopEntryName       The DesktopEntryName.
 * @return                       The translated Name of the plugin, from Name= line in .desktop file.
 */
QString KCMKttsMgr::FilterDesktopEntryNameToName (const QString& desktopEntryName)
{
    if (desktopEntryName.isEmpty()) return QString();
    KService::List offers = KServiceTypeTrader::self()->query (QLatin1String( "Jovie/FilterPlugin" ),
                            QString (QLatin1String( "DesktopEntryName == '%1'" )).arg (desktopEntryName));

    if (offers.count() == 1)
        return offers[0]->name();
    else
        return QString();
}


void KCMKttsMgr::slotFilterListView_clicked (const QModelIndex & index)
{
    if (!index.isValid()) return;
    if (index.column() != 0) return;
    if (index.row() < 0 || index.row() >= m_filterListModel.rowCount()) return;
    FilterItem fi = m_filterListModel.getRow (index.row());
    fi.enabled = !fi.enabled;
    m_filterListModel.updateRow (index.row(), fi);
    configChanged();
}
