File indexing completed on 2024-05-05 05:00:08

0001 /*
0002     SPDX-FileCopyrightText: 2005 Ivor Hewitt <ivor@ivor.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 // Own
0008 #include "filteropts.h"
0009 
0010 // Qt
0011 #include <QRegularExpression>
0012 #include <QTextStream>
0013 #include <QDBusConnection>
0014 #include <QDBusMessage>
0015 #include <QCheckBox>
0016 #include <QLabel>
0017 #include <QTreeView>
0018 #include <QWhatsThis>
0019 #include <QVBoxLayout>
0020 #include <QHBoxLayout>
0021 #include <QListWidget>
0022 #include <QPushButton>
0023 #include <QUrl>
0024 #include <QFileDialog>
0025 
0026 // KDE
0027 #include <kaboutdata.h>
0028 #include <kconfig.h>
0029 #include <KLocalizedString>
0030 #include <KPluginFactory>
0031 #include <klistwidgetsearchline.h>
0032 #include <klineedit.h>
0033 #include <kpluralhandlingspinbox.h>
0034 #include <KConfigGroup>
0035 #include <KSharedConfig>
0036 
0037 KCMFilter::KCMFilter(QObject *parent, const KPluginMetaData &md, const QVariantList &)
0038     : KCModule(parent, md),
0039       mGroupname(QStringLiteral("Filter Settings")),
0040       mSelCount(0),
0041       mOriginalString(QString())
0042 {
0043     mConfig = KSharedConfig::openConfig(QStringLiteral("khtmlrc"), KConfig::NoGlobals);
0044     setButtons(Default | Apply | Help);
0045 
0046     QVBoxLayout *topLayout = new QVBoxLayout(widget());
0047 
0048     mEnableCheck = new QCheckBox(i18n("Enable filters"), widget());
0049     topLayout->addWidget(mEnableCheck);
0050 
0051     mKillCheck = new QCheckBox(i18n("Hide filtered images"), widget());
0052     topLayout->addWidget(mKillCheck);
0053 
0054     mFilterWidget = new QTabWidget(widget());
0055     topLayout->addWidget(mFilterWidget);
0056 
0057     QWidget *container = new QWidget(mFilterWidget);
0058     mFilterWidget->addTab(container, i18n("Manual Filter"));
0059 
0060     QVBoxLayout *vbox = new QVBoxLayout;
0061 
0062     mListBox = new QListWidget;
0063     mListBox->setSelectionMode(QListWidget::ExtendedSelection);
0064 
0065     // If the filter list were sensitive to ordering, then we would need to
0066     // preserve the order of items inserted or arranged by the user (and the
0067     // GUI would need "Move Up" and "Move Down" buttons to reorder the filters).
0068     // However, now the filters are applied in an unpredictable order because
0069     // of the new hashed matching algorithm.  So the list can stay sorted.
0070     mListBox->setSortingEnabled(true);
0071 
0072     QWidget *searchBox = new QWidget;
0073     QHBoxLayout *searchBoxHBoxLayout = new QHBoxLayout(searchBox);
0074     searchBoxHBoxLayout->setContentsMargins(0, 0, 0, 0);
0075     searchBoxHBoxLayout->setSpacing(-1);
0076     new QLabel(i18n("Search:"), searchBox);
0077 
0078     mSearchLine = new KListWidgetSearchLine(searchBox, mListBox);
0079 
0080     vbox->addWidget(searchBox);
0081 
0082     vbox->addWidget(mListBox);
0083 
0084     QLabel *exprLabel = new QLabel(i18n("<qt>Filter expression (e.g. <tt>http://www.example.com/ad/*</tt>, <a href=\"filterhelp\">more information</a>):"), widget());
0085     connect(exprLabel, &QLabel::linkActivated, this, &KCMFilter::slotInfoLinkActivated);
0086     vbox->addWidget(exprLabel);
0087 
0088     mString = new KLineEdit;
0089     vbox->addWidget(mString);
0090 
0091     QWidget *buttonBox = new QWidget;
0092     QHBoxLayout *buttonBoxHBoxLayout = new QHBoxLayout(buttonBox);
0093     buttonBoxHBoxLayout->setContentsMargins(0, 0, 0, 0);
0094     vbox->addWidget(buttonBox);
0095 
0096     container->setLayout(vbox);
0097 
0098     /** tab for automatic filter lists */
0099     container = new QWidget(mFilterWidget);
0100     mFilterWidget->addTab(container, i18n("Automatic Filter"));
0101     QGridLayout *grid = new QGridLayout;
0102     grid->setColumnStretch(2, 1);
0103     container->setLayout(grid);
0104 
0105     mAutomaticFilterList = new QTreeView(container);
0106     mAutomaticFilterList->setModel(&mAutomaticFilterModel);
0107     grid->addWidget(mAutomaticFilterList, 0, 0, 1, 3);
0108 
0109     QLabel *label = new QLabel(i18n("Automatic update interval:"), container);
0110     grid->addWidget(label, 1, 0);
0111     mRefreshFreqSpinBox = new KPluralHandlingSpinBox(container);
0112     grid->addWidget(mRefreshFreqSpinBox, 1, 1);
0113     mRefreshFreqSpinBox->setRange(1, 365);
0114     mRefreshFreqSpinBox->setSuffix(ki18np(" day", " days"));
0115 
0116     /** connect signals and slots */
0117 #if QT_VERSION_MAJOR < 6
0118     connect(&mAutomaticFilterModel, &AutomaticFilterModel::changed, this, QOverload<bool>::of(&KCModule::changed));
0119 #else
0120     connect(&mAutomaticFilterModel, &AutomaticFilterModel::changed, this, [this](bool changed){setNeedsSave(changed);});
0121 #endif
0122     connect(mRefreshFreqSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), this, &KCMFilter::spinBoxChanged);
0123 
0124     mInsertButton = new QPushButton(QIcon::fromTheme(QStringLiteral("list-add")), i18n("Insert"), buttonBox);
0125     buttonBoxHBoxLayout->addWidget(mInsertButton);
0126     connect(mInsertButton, &QAbstractButton::clicked, this, &KCMFilter::insertFilter);
0127     mUpdateButton = new QPushButton(QIcon::fromTheme(QStringLiteral("document-edit")), i18n("Update"), buttonBox);
0128     buttonBoxHBoxLayout->addWidget(mUpdateButton);
0129     connect(mUpdateButton, &QAbstractButton::clicked, this, &KCMFilter::updateFilter);
0130     mRemoveButton = new QPushButton(QIcon::fromTheme(QStringLiteral("list-remove")), i18n("Remove"), buttonBox);
0131     buttonBoxHBoxLayout->addWidget(mRemoveButton);
0132     connect(mRemoveButton, &QAbstractButton::clicked, this, &KCMFilter::removeFilter);
0133 
0134     mImportButton = new QPushButton(QIcon::fromTheme(QStringLiteral("document-import")), i18n("Import..."), buttonBox);
0135     buttonBoxHBoxLayout->addWidget(mImportButton);
0136     connect(mImportButton, &QAbstractButton::clicked, this, &KCMFilter::importFilters);
0137     mExportButton = new QPushButton(QIcon::fromTheme(QStringLiteral("document-export")), i18n("Export..."), buttonBox);
0138     buttonBoxHBoxLayout->addWidget(mExportButton);
0139     connect(mExportButton, &QAbstractButton::clicked, this, &KCMFilter::exportFilters);
0140 
0141     QWidget *impexpBox = new QWidget;
0142     QHBoxLayout *impexpBoxHBoxLayout = new QHBoxLayout(impexpBox);
0143     impexpBoxHBoxLayout->setContentsMargins(0, 0, 0, 0);
0144     QLabel *impexpLabel = new QLabel(i18n("<qt>More information on "
0145                                           "<a href=\"importhelp\">import format</a>, "
0146                                           "<a href=\"exporthelp\">export format</a>"), impexpBox);
0147     connect(impexpLabel, &QLabel::linkActivated, this, &KCMFilter::slotInfoLinkActivated);
0148     impexpBoxHBoxLayout->addWidget(impexpLabel);
0149     vbox->addWidget(impexpBox, 0, Qt::AlignRight);
0150 
0151     connect(mEnableCheck, &QAbstractButton::toggled, this, &KCMFilter::slotEnableChecked);
0152     connect(mKillCheck, &QAbstractButton::clicked, this, &KCMFilter::slotKillChecked);
0153     connect(mListBox, &QListWidget::itemSelectionChanged, this, &KCMFilter::slotItemSelected);
0154     connect(mString, &QLineEdit::textChanged, this, &KCMFilter::updateButton);
0155     /*
0156      * Whats this items
0157      */
0158     mEnableCheck->setToolTip(i18n("Enable or disable AdBlocK filters. When enabled, a set of URL expressions "
0159                                     "should be defined in the filter list for blocking to take effect."));
0160     mKillCheck->setToolTip(i18n("When enabled blocked images will be removed from the page completely, "
0161                                   "otherwise a placeholder 'blocked' image will be used."));
0162 
0163     // The list is no longer sensitive to order, because of the new hashed
0164     // matching.  So this tooltip doesn't imply that.
0165     //
0166     // FIXME: blocking of frames is not currently implemented by KHTML
0167     mListBox->setToolTip(i18n("This is the list of URL filters that will be applied to all embedded "
0168                                 "images and media objects."));
0169     //                              "images, objects and frames.") );
0170 
0171     mString->setToolTip(i18n("<qt><p>Enter an expression to filter. Filters can be defined as either:"
0172                                "<ul><li>a shell-style wildcard, e.g. <tt>http://www.example.com/ads*</tt>, the wildcards <tt>*?[]</tt> may be used</li>"
0173                                "<li>a full regular expression by surrounding the string with '<tt>/</tt>', e.g. <tt>/\\/(ad|banner)\\./</tt></li></ul>"
0174                                "<p>Any filter string can be preceded by '<tt>@@</tt>' to whitelist (allow) any matching URL, "
0175                                "which takes priority over any blacklist (blocking) filter."));
0176 }
0177 
0178 KCMFilter::~KCMFilter()
0179 {
0180 }
0181 
0182 void KCMFilter::slotInfoLinkActivated(const QString &url)
0183 {
0184     if (url == QLatin1String("filterhelp")) {
0185         QWhatsThis::showText(QCursor::pos(), mString->toolTip());
0186     } else if (url == QLatin1String("importhelp"))
0187         QWhatsThis::showText(QCursor::pos(), i18n("<qt><p>The filter import format is a plain text file. "
0188                              "Blank lines, comment lines starting with '<tt>!</tt>' "
0189                              "and the header line <tt>[AdBlock]</tt> are ignored. "
0190                              "Any other line is added as a filter expression."));
0191     else if (url == QLatin1String("exporthelp"))
0192         QWhatsThis::showText(QCursor::pos(), i18n("<qt><p>The filter export format is a plain text file. "
0193                              "The file begins with a header line <tt>[AdBlock]</tt>, then all of "
0194                              "the filters follow each on a separate line."));
0195 }
0196 
0197 void KCMFilter::slotKillChecked()
0198 {
0199     setNeedsSave(true);
0200 }
0201 
0202 void KCMFilter::slotEnableChecked()
0203 {
0204     updateButton();
0205     setNeedsSave(true);
0206 }
0207 
0208 void KCMFilter::slotItemSelected()
0209 {
0210     int currentId = -1;
0211     int i;
0212     for (i = 0, mSelCount = 0; i < mListBox->count() && mSelCount < 2; ++i) {
0213         if (mListBox->item(i)->isSelected()) {
0214             currentId = i;
0215             mSelCount++;
0216         }
0217     }
0218 
0219     if (currentId >= 0) {
0220         mOriginalString = mListBox->item(currentId)->text();
0221         mString->setText(mOriginalString);
0222         mString->setFocus(Qt::OtherFocusReason);
0223     }
0224     updateButton();
0225 }
0226 
0227 void KCMFilter::updateButton()
0228 {
0229     bool state = mEnableCheck->isChecked();
0230     bool expressionIsNotEmpty = !mString->text().isEmpty();
0231     bool filterEdited = expressionIsNotEmpty && mOriginalString != mString->text();
0232 
0233     mInsertButton->setEnabled(state && expressionIsNotEmpty && filterEdited);
0234     mUpdateButton->setEnabled(state && (mSelCount == 1) && expressionIsNotEmpty && filterEdited);
0235     mRemoveButton->setEnabled(state && (mSelCount > 0));
0236     mImportButton->setEnabled(state);
0237     mExportButton->setEnabled(state && mListBox->count() > 0);
0238 
0239     mListBox->setEnabled(state);
0240     mString->setEnabled(state);
0241     mKillCheck->setEnabled(state);
0242 
0243     if (filterEdited) {
0244         if (mSelCount == 1 && mUpdateButton->isEnabled()) {
0245             mUpdateButton->setDefault(true);
0246         } else if (mInsertButton->isEnabled()) {
0247             mInsertButton->setDefault(true);
0248         }
0249     } else {
0250         mInsertButton->setDefault(false);
0251         mUpdateButton->setDefault(false);
0252     }
0253 
0254     mAutomaticFilterList->setEnabled(state);
0255     mRefreshFreqSpinBox->setEnabled(state);
0256 }
0257 
0258 void KCMFilter::importFilters()
0259 {
0260     QString inFile = QFileDialog::getOpenFileName(widget(), i18n("Import Filters"));
0261     if (!inFile.isEmpty()) {
0262         QFile f(inFile);
0263         if (f.open(QIODevice::ReadOnly)) {
0264             QTextStream ts(&f);
0265             QStringList paths;
0266             QString line;
0267             while (!ts.atEnd()) {
0268                 line = ts.readLine();
0269                 if (line.isEmpty() || line.compare(QLatin1String("[adblock]"), Qt::CaseInsensitive) == 0) {
0270                     continue;
0271                 }
0272 
0273                 // Treat leading ! as filter comment, otherwise check expressions
0274                 // are valid.
0275                 if (!line.startsWith(QLatin1String("!"))) { //krazy:exclude=doublequote_chars
0276                     if (line.length() > 2 && line[0] == '/' && line[line.length() - 1] == '/') {
0277                         QString inside = line.mid(1, line.length() - 2);
0278                         QRegularExpression rx(inside);
0279                         if (!rx.isValid()) {
0280                             continue;
0281                         }
0282                     } else {
0283                         QRegularExpression rx(QRegularExpression::wildcardToRegularExpression(line));
0284                         if (!rx.isValid()) {
0285                             continue;
0286                         }
0287                     }
0288 
0289                     if (mListBox->findItems(line, Qt::MatchCaseSensitive | Qt::MatchExactly).isEmpty()) {
0290                         paths.append(line);
0291                     }
0292                 }
0293             }
0294             f.close();
0295 
0296             mListBox->addItems(paths);
0297             setNeedsSave(true);
0298         }
0299     }
0300 }
0301 
0302 void KCMFilter::exportFilters()
0303 {
0304     QString outFile = QFileDialog::getSaveFileName(widget(), i18n("Export Filters"));
0305     if (!outFile.isEmpty()) {
0306 
0307         QFile f(outFile);
0308         if (f.open(QIODevice::WriteOnly)) {
0309             QTextStream ts(&f);
0310 #if QT_VERSION_MAJOR < 6
0311             ts.setCodec("UTF-8");
0312 #endif
0313             ts << "[AdBlock]" << Qt::endl;
0314 
0315             int nbLine =  mListBox->count();
0316             for (int i = 0; i < nbLine; ++i) {
0317                 ts << mListBox->item(i)->text() << Qt::endl;
0318             }
0319 
0320             f.close();
0321         }
0322     }
0323 }
0324 
0325 void KCMFilter::defaults()
0326 {
0327     mAutomaticFilterModel.defaults();
0328 
0329     mListBox->clear();
0330     mEnableCheck->setChecked(false);
0331     mKillCheck->setChecked(false);
0332     mString->clear();
0333     updateButton();
0334 
0335 #if QT_VERSION_MAJOR > 5
0336     setRepresentsDefaults(true);
0337 #endif
0338 }
0339 
0340 void KCMFilter::save()
0341 {
0342     KConfigGroup cg(mConfig, mGroupname);
0343     cg.deleteGroup();
0344     cg = KConfigGroup(mConfig, mGroupname);
0345 
0346     cg.writeEntry("Enabled", mEnableCheck->isChecked());
0347     cg.writeEntry("Shrink", mKillCheck->isChecked());
0348 
0349     int i;
0350     for (i = 0; i < mListBox->count(); ++i) {
0351         QString key = "Filter-" + QString::number(i);
0352         cg.writeEntry(key, mListBox->item(i)->text());
0353     }
0354     cg.writeEntry("Count", mListBox->count());
0355 
0356     mAutomaticFilterModel.save(cg);
0357     cg.writeEntry("HTMLFilterListMaxAgeDays", mRefreshFreqSpinBox->value());
0358 
0359     cg.sync();
0360 
0361     QDBusMessage message =
0362         QDBusMessage::createSignal(QStringLiteral("/KonqMain"), QStringLiteral("org.kde.Konqueror.Main"), QStringLiteral("reparseConfiguration"));
0363     QDBusConnection::sessionBus().send(message);
0364 
0365     KCModule::save();
0366 }
0367 
0368 void KCMFilter::load()
0369 {
0370     QStringList paths;
0371 
0372     KConfigGroup cg(mConfig, mGroupname);
0373     mAutomaticFilterModel.load(cg);
0374     mAutomaticFilterList->resizeColumnToContents(0);
0375     int refreshFreq = cg.readEntry("HTMLFilterListMaxAgeDays", 7);
0376     mRefreshFreqSpinBox->setValue(refreshFreq < 1 ? 1 : refreshFreq);
0377 
0378     mEnableCheck->setChecked(cg.readEntry("Enabled", false));
0379     mKillCheck->setChecked(cg.readEntry("Shrink", false));
0380 
0381     QMap<QString, QString> entryMap = cg.entryMap();
0382     QMap<QString, QString>::ConstIterator it;
0383     int num = cg.readEntry("Count", 0);
0384     for (int i = 0; i < num; ++i) {
0385         QString key = "Filter-" + QString::number(i);
0386         it = entryMap.constFind(key);
0387         if (it != entryMap.constEnd()) {
0388             paths.append(it.value());
0389         }
0390     }
0391 
0392     mListBox->addItems(paths);
0393     updateButton();
0394     KCModule::load();
0395 }
0396 
0397 void KCMFilter::insertFilter()
0398 {
0399     QString newFilter = mString->text();
0400 
0401     if (!newFilter.isEmpty() && mListBox->findItems(newFilter, Qt::MatchCaseSensitive | Qt::MatchExactly).isEmpty()) {
0402         mListBox->clearSelection();
0403         mListBox->addItem(newFilter);
0404 
0405         // The next line assumed that the new item would be added at the end
0406         // of the list, but that may not be the case if sorting is enabled.
0407         // So we search again to locate the just-added item.
0408         //int id = mListBox->count()-1;
0409         QListWidgetItem *newItem = mListBox->findItems(newFilter, Qt::MatchCaseSensitive | Qt::MatchExactly).first();
0410         if (newItem != nullptr) {
0411             int id = mListBox->row(newItem);
0412 
0413             mListBox->item(id)->setSelected(true);
0414             mListBox->setCurrentRow(id);
0415         }
0416 
0417         updateButton();
0418         setNeedsSave(true);
0419     }
0420 }
0421 
0422 void KCMFilter::removeFilter()
0423 {
0424     for (int i = mListBox->count(); i >= 0; --i) {
0425         if (mListBox->item(i) && mListBox->item(i)->isSelected()) {
0426             delete mListBox->takeItem(i);
0427         }
0428     }
0429     mString->clear();
0430     setNeedsSave(true);
0431     updateButton();
0432 }
0433 
0434 void KCMFilter::updateFilter()
0435 {
0436     if (!mString->text().isEmpty()) {
0437         int index = mListBox->currentRow();
0438         if (index >= 0) {
0439             mListBox->item(index)->setText(mString->text());
0440             setNeedsSave(true);
0441         }
0442     }
0443     updateButton();
0444 }
0445 
0446 void KCMFilter::spinBoxChanged(int)
0447 {
0448     setNeedsSave(true);
0449 }
0450 
0451 AutomaticFilterModel::AutomaticFilterModel(QObject *parent)
0452     : QAbstractItemModel(parent),
0453       mGroupname(QStringLiteral("Filter Settings"))
0454 {
0455     //mConfig = KSharedConfig::openConfig("khtmlrc", KConfig::NoGlobals);
0456     mConfig = KSharedConfig::openConfig(QStringLiteral("khtmlrc"), KConfig::IncludeGlobals);
0457 }
0458 
0459 void AutomaticFilterModel::load(KConfigGroup &cg)
0460 {
0461     beginResetModel();
0462     mFilters.clear();
0463     const int maxNumFilters = 1024;
0464     const bool defaultHTMLFilterListEnabled = false;
0465 
0466     for (int numFilters = 1; numFilters < maxNumFilters; ++numFilters) {
0467         struct FilterConfig filterConfig;
0468         filterConfig.filterName = cg.readEntry(QStringLiteral("HTMLFilterListName-") + QString::number(numFilters), "");
0469         if (filterConfig.filterName == QLatin1String("")) {
0470             break;
0471         }
0472 
0473         filterConfig.enableFilter = cg.readEntry(QStringLiteral("HTMLFilterListEnabled-") + QString::number(numFilters), defaultHTMLFilterListEnabled);
0474         filterConfig.filterURL = cg.readEntry(QStringLiteral("HTMLFilterListURL-") + QString::number(numFilters), "");
0475         filterConfig.filterLocalFilename = cg.readEntry(QStringLiteral("HTMLFilterListLocalFilename-") + QString::number(numFilters), "");
0476 
0477         mFilters << filterConfig;
0478     }
0479     endResetModel();
0480 }
0481 
0482 void AutomaticFilterModel::save(KConfigGroup &cg)
0483 {
0484     for (int i = mFilters.count() - 1; i >= 0; --i) {
0485         cg.writeEntry(QStringLiteral("HTMLFilterListLocalFilename-") + QString::number(i + 1), mFilters[i].filterLocalFilename);
0486         cg.writeEntry(QStringLiteral("HTMLFilterListURL-") + QString::number(i + 1), mFilters[i].filterURL);
0487         cg.writeEntry(QStringLiteral("HTMLFilterListName-") + QString::number(i + 1), mFilters[i].filterName);
0488         cg.writeEntry(QStringLiteral("HTMLFilterListEnabled-") + QString::number(i + 1), mFilters[i].enableFilter);
0489     }
0490 }
0491 
0492 void AutomaticFilterModel::defaults()
0493 {
0494     mConfig = KSharedConfig::openConfig(QStringLiteral("khtmlrc"), KConfig::IncludeGlobals);
0495     KConfigGroup cg(mConfig, mGroupname);
0496     load(cg);
0497 }
0498 
0499 QModelIndex AutomaticFilterModel::index(int row, int column, const QModelIndex & /*parent*/) const
0500 {
0501     return createIndex(row, column, (void *)nullptr);
0502 }
0503 
0504 QModelIndex AutomaticFilterModel::parent(const QModelIndex & /*index*/) const
0505 {
0506     return QModelIndex();
0507 }
0508 
0509 bool AutomaticFilterModel::hasChildren(const QModelIndex &parent) const
0510 {
0511     return parent == QModelIndex();
0512 }
0513 
0514 int AutomaticFilterModel::rowCount(const QModelIndex & /*parent*/) const
0515 {
0516     return mFilters.count();
0517 }
0518 
0519 int AutomaticFilterModel::columnCount(const QModelIndex & /*parent*/) const
0520 {
0521     return 2;
0522 }
0523 
0524 QVariant AutomaticFilterModel::data(const QModelIndex &index, int role) const
0525 {
0526     if (!index.isValid()) {
0527         return QVariant();
0528     }
0529 
0530     if (role == Qt::DisplayRole && index.row() < mFilters.count())
0531         switch (index.column()) {
0532         case 0: return QVariant(mFilters[index.row()].filterName);
0533         case 1: return QVariant(mFilters[index.row()].filterURL);
0534         default: return QVariant("?");
0535         }
0536     else if (role == Qt::CheckStateRole && index.column() == 0 && index.row() < mFilters.count()) {
0537         return mFilters[index.row()].enableFilter ? Qt::Checked : Qt::Unchecked;
0538     } else {
0539         return QVariant();
0540     }
0541 
0542 }
0543 
0544 bool AutomaticFilterModel::setData(const QModelIndex &index, const QVariant &value, int role)
0545 {
0546     if (role == Qt::CheckStateRole && index.column() == 0 && index.row() < mFilters.count()) {
0547         mFilters[index.row()].enableFilter = static_cast<Qt::CheckState>(value.toInt()) == Qt::Checked;
0548         emit dataChanged(index, index);
0549         emit changed(true);
0550         return true;
0551     }
0552 
0553     return false;
0554 }
0555 
0556 QVariant AutomaticFilterModel::headerData(int section, Qt::Orientation orientation, int role) const
0557 {
0558     if (role != Qt::DisplayRole || orientation != Qt::Horizontal) {
0559         return QVariant();
0560     }
0561 
0562     switch (section) {
0563     case 0: return QVariant(i18n("Name"));
0564     case 1: return QVariant(i18n("URL"));
0565     default: return QVariant("?");
0566     }
0567 }
0568 
0569 Qt::ItemFlags AutomaticFilterModel::flags(const QModelIndex &index) const
0570 {
0571     Qt::ItemFlags rc = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
0572     if (index.column() == 0) {
0573         rc |= Qt::ItemIsUserCheckable;
0574     }
0575     return rc;
0576 }