Adblock Plugin patch, part I

Jonathan Marten jjm2 at keelhaul.demon.co.uk
Fri Jul 10 17:31:59 BST 2009


As suggested... this is the first phase of the changes that I've been working 
on for the adblock plugin, the "Blockable items" dialogue and UI.  It's a big 
patch because there are lots of internal code changes, notably needing to 
save 'elements' as a member of AdBlock and moving ListViewItem to the .cpp 
file (it's only used there).  But the user-visible changes are:

IFRAMEs can now be blocked in addition to OBJECT, EMBED, SCRIPT and images.

Some text strings changed for (IMHO) clearer meaning: "Show Elements" -> "Show 
Blockable Elements", "Configure" -> "
Configure Filters", "Disable..." -> "No 
blocking...", "Show it" -> "View item".

Hook added for displaying the blocking filter in effect as an item tootip.  
Needs support from KHTML to get that information (I'm working on that...).

Added menu options "Filter same host", "Filter same domain" (the latter very 
useful for randomised "stats" hosts such as 2o7.net).

Added "Configure Filters" button to dialogue to go straight to the filter 
settings KCM.

The "Blockable items" dialogue now works this way.  Clicking or 
double-clicking (depending on the global KDE setting) an item in the list 
copies its URL to the "New filter" box and enables the "Add filter" button.  
The "Filter..." and "Whitelist" menu entries do the same, modifying the URL 
string appropriately.  Clicking "Add filter" adds the filter to the KHTML 
settings, and any items in the list now blocked turn red; "Add Filter" is 
disabled again until the filter is edited.  Only "Close" or "Configure 
Filters" will close the dialogue.

Comments invited; if there are no objections then I'll commit and work on the 
rest...

Regards,
  Jonathan

-------------------------------------------------------------------------
Index: extragear/base/konq-plugins/adblock/adblock.cpp
===================================================================
--- extragear/base/konq-plugins/adblock/adblock.cpp	(revision 990526)
+++ extragear/base/konq-plugins/adblock/adblock.cpp	(working copy)
@@ -60,7 +60,7 @@
 
 AdBlock::AdBlock(QObject *parent, const QVariantList & /*args*/) :
     Plugin(parent),
-    m_label(0), m_menu(0)
+    m_label(0), m_menu(0), m_elements(0)
 {
     m_part = dynamic_cast<KHTMLPart *>(parent);
     if(!m_part)
@@ -71,31 +71,29 @@
     m_menu = new KActionMenu(KIcon( "preferences-web-browser-adblock" ), 
i18n("Adblock"),
                            actionCollection() );
     actionCollection()->addAction( "action adblock", m_menu );
-     m_menu->setDelayed( false );
+    m_menu->setDelayed( false );
 
-    QAction *a = actionCollection()->addAction(  "configure");
-    a->setText(i18n("Configure..."));
-    connect(a, SIGNAL(triggered()), this, SLOT(showKCModule()));
+    QAction *a = actionCollection()->addAction(  "show_elements");
+    a->setText(i18n("Show Blockable Elements..."));
+    connect(a, SIGNAL(triggered()), this, SLOT(slotConfigure()));
     m_menu->addAction(a);
 
-
-    a = actionCollection()->addAction(  "show_elements");
-    a->setText(i18n("Show Elements..."));
-    connect(a, SIGNAL(triggered()), this, SLOT(slotConfigure()));
+    a = actionCollection()->addAction(  "configure");
+    a->setText(i18n("Configure Filters..."));
+    connect(a, SIGNAL(triggered()), this, SLOT(showKCModule()));
     m_menu->addAction(a);
 
     a = actionCollection()->addAction( "separator" );
     a->setSeparator( true );
     m_menu->addAction(a);
 
-
     a = actionCollection()->addAction(  "disable_for_this_page");
-    a->setText(i18n("Disable for this page"));
+    a->setText(i18n("No blocking for this page"));
     connect(a, SIGNAL(triggered()), this, SLOT(slotDisableForThisPage()));
     m_menu->addAction(a);
 
     a = actionCollection()->addAction(  "disable_for_this_site");
-    a->setText(i18n("Disable for this site"));
+    a->setText(i18n("No blocking for this site"));
     connect(a, SIGNAL(triggered()), this, SLOT(slotDisableForThisSite()));
     m_menu->addAction(a);
 
@@ -113,7 +111,9 @@
     }
     m_label = 0;
     delete m_menu;
-    m_menu=0;
+    m_menu = 0;
+    delete m_elements;
+    m_elements = 0;
 }
 
 void AdBlock::initLabel()
@@ -142,21 +142,28 @@
 }
 
 
-void AdBlock::slotDisableForThisPage()
+void AdBlock::disableForUrl(KUrl url)
 {
+    url.setQuery(QString());
+    url.setRef(QString());
+
     KHTMLSettings *settings = const_cast<KHTMLSettings 
*>(m_part->settings());
-    settings->addAdFilter("@@"+m_part->toplevelURL().url());
+    settings->addAdFilter("@@"+url.url());
 }
 
+void AdBlock::slotDisableForThisPage()
+{
+    disableForUrl(m_part->toplevelURL().url());
+}
+
 void AdBlock::slotDisableForThisSite()
 {
-    KHTMLSettings *settings = const_cast<KHTMLSettings 
*>(m_part->settings());
-    QString url = m_part->toplevelURL().url();
-    url = url.remove( "http://" );
-    url = url.section( '/', 0,0 );
-    settings->addAdFilter("@@http://"+url);
+    KUrl u(m_part->toplevelURL().url());
+    u.setPath("/*");
+    disableForUrl(u);
 }
 
+
 void AdBlock::slotConfigure()
 {
     if (!m_part->settings()->isAdFilterEnabled())
@@ -168,11 +175,12 @@
 	return;
     }
 
-    AdElementList elements;
-    fillBlockableElements(elements);
+    m_elements = new AdElementList;
+    fillBlockableElements();
 
-    AdBlockDlg *dlg = new AdBlockDlg(m_part->widget(), elements, m_part);
+    AdBlockDlg *dlg = new AdBlockDlg(m_part->widget(), m_elements, m_part);
     connect(dlg, SIGNAL( notEmptyFilter(const QString&) ), this, SLOT( 
addAdFilter(const QString&) ));
+    connect(dlg, SIGNAL( configureFilters() ), this, SLOT( showKCModule() ));
     dlg->exec();
     delete dlg;
 }
@@ -191,17 +199,18 @@
     m_menu->menu()->exec(QCursor::pos());
 }
 
-void AdBlock::fillBlockableElements(AdElementList &elements)
+void AdBlock::fillBlockableElements()
 {
-    fillWithHtmlTag(elements, "script", "src", i18n( "script" ));
-    fillWithHtmlTag(elements, "embed" , "src", i18n( "object" ));
-    fillWithHtmlTag(elements, "object", "src", i18n( "object" ));
-    fillWithImages(elements);
+    fillWithHtmlTag("script", "src", i18n( "script" ));
+    fillWithHtmlTag("embed" , "src", i18n( "object" ));
+    fillWithHtmlTag("object", "src", i18n( "object" ));
+    fillWithHtmlTag("iframe", "src", i18n( "frame" ));
+    fillWithImages();
 
     const KHTMLSettings *settings = m_part->settings();
 
     AdElementList::iterator it;
-    for ( it = elements.begin(); it != elements.end(); ++it )
+    for ( it = m_elements->begin(); it != m_elements->end(); ++it )
     {
 	AdElement &element = (*it);
         if (settings->isAdFiltered( element.url() ))
@@ -211,7 +220,7 @@
     }
 }
 
-void AdBlock::fillWithImages(AdElementList &elements)
+void AdBlock::fillWithImages()
 {
     HTMLDocument htmlDoc = m_part->htmlDocument();
 
@@ -227,14 +236,13 @@
 	if (!url.isEmpty() && url != m_part->baseURL().url())
 	{
 	    AdElement element(url, i18n( "image" ), "IMG", false, image);
-	    if (!elements.contains( element ))
-		elements.append( element);
+	    if (!m_elements->contains( element ))
+		m_elements->append( element);
 	}
     }
 }
 
-void AdBlock::fillWithHtmlTag(AdElementList &elements,
-			      const DOMString &tagName,
+void AdBlock::fillWithHtmlTag(const DOMString &tagName,
 			      const DOMString &attrName,
 			      const QString &category)
 {
@@ -253,9 +261,9 @@
 	QString url = doc.completeURL(src).string();
 	if (!url.isEmpty() && url != m_part->baseURL().url())
 	{
-	    AdElement element(url, tagName.string(), category, false, attr);
-	    if (!elements.contains( element ))
-		elements.append( element);
+	    AdElement element(url, category, tagName.string().toUpper(), false, 
attr);
+	    if (!m_elements->contains( element ))
+		m_elements->append( element);
 	}
     }
 }
@@ -265,6 +273,13 @@
     //FIXME hackish
     KHTMLSettings *settings = const_cast<KHTMLSettings 
*>(m_part->settings());
     settings->addAdFilter(url);
+
+    AdElementList::iterator it;
+    for ( it = m_elements->begin(); it != m_elements->end(); ++it )
+    {
+	AdElement &element = (*it);
+        if (settings->isAdFiltered(element.url())) element.setBlocked(true);
+    }
 }
 
 // ----------------------------------------------------------------------------
Index: extragear/base/konq-plugins/adblock/adblockdialog.cpp
===================================================================
--- extragear/base/konq-plugins/adblock/adblockdialog.cpp	(revision 990526)
+++ extragear/base/konq-plugins/adblock/adblockdialog.cpp	(working copy)
@@ -26,10 +26,10 @@
 #include <klocale.h>
 #include <KTreeWidgetSearchLine>
 #include <KRun>
+#include <KStandardGuiItem>
 
 #include <qcursor.h>
 #include <qlabel.h>
-#include <KVBox>
 #include <qlineedit.h>
 #include <qcolor.h>
 #include <qfont.h>
@@ -37,66 +37,146 @@
 #include <QTreeWidget>
 #include <QApplication>
 #include <QClipboard>
+#include <QVBoxLayout>
 
-AdBlockDlg::AdBlockDlg(QWidget *parent, AdElementList &elements, 
KHTMLPart*part) :
+// ----------------------------------------------------------------------------
+
+class ListViewItem : public QTreeWidgetItem
+{
+public:
+    ListViewItem(QTreeWidget *listView, const QStringList& lst,const 
AdElement *element)
+        : QTreeWidgetItem( listView, lst),
+          m_element(element),
+          m_blocked(false){};
+
+    bool isBlocked() const { return (m_blocked); };
+    void setBlocked(bool blocked);
+    void setBlockedBy(const QString &reason);
+    void setNode( const DOM::Node& node );
+    DOM::Node node()const { return (m_node); }
+    const AdElement *element() const { return (m_element); }
+
+private:
+    const AdElement *m_element;
+    bool m_blocked;
+    DOM::Node m_node;
+};
+
+
+void ListViewItem::setBlocked(bool blocked)
+{
+    m_blocked = blocked;
+    setData ( 0,Qt::TextColorRole, (blocked ? Qt::red : Qt::black));
+    QFont itemFont =  font(0);
+    itemFont.setItalic( blocked );
+    itemFont.setBold( blocked );
+    setData( 0, Qt::FontRole, itemFont );
+}
+
+void ListViewItem::setBlockedBy(const QString &reason)
+{
+    setToolTip( 0, reason );
+}
+
+
+void ListViewItem::setNode( const DOM::Node& node )
+{
+    m_node = node;
+}
+
+// ----------------------------------------------------------------------------
+
+AdBlockDlg::AdBlockDlg(QWidget *parent, const AdElementList *elements, 
KHTMLPart*part) :
     KDialog( parent ), m_part( part )
 {
     setModal( true );
-    setCaption( i18n("Adblock dialog") );
-    setButtons( KDialog::Ok | KDialog::Cancel );
-    setDefaultButton(KDialog::Ok);
+    setCaption( i18n("Blockable items on this page") );
+    setButtons( KDialog::User1 | KDialog::User2 | KDialog::Close );
+    setDefaultButton( KDialog::User2 );
+    setButtonText( KDialog::User1, i18n("Configure Filters...") );
+    setButtonText( KDialog::User2, i18n("Add filter") );
+    setButtonIcon( KDialog::User2, KStandardGuiItem::add().icon() );
 
+    QWidget *page = new QWidget(this);
+    setMainWidget(page);
 
+    QVBoxLayout *layout = new QVBoxLayout(page);
+    layout->setMargin(0);
 
-    KVBox *page = new KVBox(this);
-    setMainWidget(page);
-    m_label1 = new QLabel( i18n("All blockable items in this page:"), page);
+    QLabel *l = new QLabel( i18n("Search:"), page);
+    layout->addWidget(l);
 
     KTreeWidgetSearchLine* searchLine = new KTreeWidgetSearchLine( page );
+    layout->addWidget(searchLine);
+    l->setBuddy(searchLine);
 
+    l = new QLabel( i18n("Blockable items:"), page);
+    layout->addWidget(l);
+
     m_list = new QTreeWidget(page);
     m_list->setAllColumnsShowFocus( true );
+    layout->addWidget(m_list);
+    l->setBuddy(m_list);
 
-    searchLine->setTreeWidget( m_list );
-
     QStringList lstHeader;
-    lstHeader<<i18n("Source")<<i18n("Category")<<i18n("Node Name");
+    lstHeader<<i18n("Source")<<i18n("Category")<<i18n("Tag");
     m_list->setHeaderLabels(lstHeader);
 
     m_list->setColumnWidth(0, 600);
     m_list->setColumnWidth(1, 90);
-    m_list->setColumnWidth(2, 90);
+    m_list->setColumnWidth(2, 65);
 
     m_list->setRootIsDecorated( false );
-    AdElementList::iterator it;
-    for ( it = elements.begin(); it != elements.end(); ++it )
+
+    AdElementList::const_iterator it;
+    for ( it = elements->constBegin(); it != elements->constEnd(); ++it )
     {
-	AdElement &element = (*it);
+	const AdElement &element = (*it);
 
         QStringList lst;
-        lst<<element.url()<<  element.category()<<element.type();
+        lst << element.url() <<  element.category() << element.type();
 
-        ListViewItem *item = new ListViewItem( m_list,lst );
-	item->setBlocked(element.isBlocked());
+        ListViewItem *item = new ListViewItem( m_list, lst, &element );
+	item->setBlocked( element.isBlocked() );
         item->setNode( element.node() );
+        // TODO: get blocking filter and setBlockedBy() appropriately
     }
 
-    m_label2 = new QLabel( i18n("New filter (use * as a wildcard):"), page);
+    searchLine->setTreeWidget( m_list );
 
+    layout->addSpacing(KDialog::spacingHint());
+
+    l = new QLabel( i18n("New filter (can use *?[] wildcards, prefix with @@ 
for white list):"), page);
+    layout->addWidget(l);
+
     m_filter = new QLineEdit( page);
+    layout->addWidget(m_filter);
+    connect(m_filter,SIGNAL(textChanged(const QString 
&)),SLOT(filterTextChanged(const QString &)));
+    l->setBuddy(m_filter);
+    filterTextChanged(QString::null);
 
-    connect(this, SIGNAL( okClicked() ), this, SLOT( validateFilter() ));
-    connect(m_list, SIGNAL( itemDoubleClicked(QTreeWidgetItem *, int )), 
this, SLOT(updateFilter(QTreeWidgetItem *)) );
+    connect(this, SIGNAL( user1Clicked() ), this, SLOT( 
slotConfigureFilters() ));
+    connect(this, SIGNAL( user2Clicked() ), this, SLOT( slotAddFilter() ));
 
+    // Use itemActivated() signal instead of itemDoubleClicked() to honour
+    // the KDE single/double click setting, and allow keyboard navigation.
+    // Activating a item just copies its URL to the "new filter" box and
+    // sets focus to there, it doesn't add the filter immediately.
+    //connect(m_list, SIGNAL( itemDoubleClicked(QTreeWidgetItem *, int )), 
this, SLOT(updateFilter(QTreeWidgetItem *)) );
+    connect(m_list, SIGNAL( itemActivated(QTreeWidgetItem *, int )), this, 
SLOT(updateFilter(QTreeWidgetItem *)) );
+
     m_menu = new KMenu(this);
     m_menu->addAction(i18n("Filter this item"), this, SLOT(filterItem()));
     m_menu->addAction(i18n("Filter all items at same path"), this, 
SLOT(filterPath()));
+    m_menu->addAction(i18n("Filter all items from same host"), this, 
SLOT(filterHost()));
+    m_menu->addAction(i18n("Filter all items from same domain"), this, 
SLOT(filterDomain()));
+    m_menu->addSeparator();
     m_menu->addAction( i18n( "Add this item to white list" ), this, SLOT( 
addWhiteList() ) );
     m_menu->addSeparator();
     m_menu->addAction( i18n( "Copy Link Address" ),  this,  SLOT( 
copyLinkAddress() ) );
     //comment for the moment
     //m_menu->addAction( i18n( "Highlight Element" ), this, SLOT( 
highLightElement() ) );
-    m_menu->addAction( i18n( "Show it" ), this, SLOT( showElement() ) );
+    m_menu->addAction( i18n( "View item" ), this, SLOT( showElement() ) );
     m_list->setContextMenuPolicy( Qt::CustomContextMenu );
     connect( m_list, SIGNAL( customContextMenuRequested( const QPoint & ) ),
            this, SLOT( showContextMenu(const QPoint & ) ) );
@@ -104,6 +184,26 @@
     resize( 800, 400 );
 }
 
+
+AdBlockDlg::~AdBlockDlg()
+{
+}
+
+
+
+void AdBlockDlg::filterTextChanged(const QString &text)
+{
+    enableButton(KDialog::User2,!text.isEmpty());
+}
+
+
+void AdBlockDlg::setFilterText(const QString &text)
+{
+    m_filter->setText(text);
+    m_filter->setFocus(Qt::OtherFocusReason);
+}
+
+
 void AdBlockDlg::updateFilter(QTreeWidgetItem *selected)
 {
     ListViewItem *item = static_cast<ListViewItem *>(selected);
@@ -114,19 +214,35 @@
 	return;
     }
 
-    m_filter->setText( item->text(0) );
+    setFilterText( item->text(0) );
 }
 
-void AdBlockDlg::validateFilter()
+void AdBlockDlg::slotAddFilter()
 {
     const QString text = m_filter->text().trimmed();
+    if (text.isEmpty()) return;
 
-    if (!text.isEmpty())
-        emit notEmptyFilter(text);
+    kDebug() << "adding filter" << text;
+    emit notEmptyFilter(text);
 
+    for (QTreeWidgetItemIterator it(m_list); (*it)!=0; ++it)
+    {
+        ListViewItem *item = static_cast<ListViewItem *>(*it);
+        item->setBlocked(item->element()->isBlocked());
+        // TODO: setBlockedBy() appropriately
+    }
+
+    enableButton(KDialog::User2,false);			// now that filter has been added
+}							// until it is changed again
+
+
+void AdBlockDlg::slotConfigureFilters()
+{
+    emit configureFilters();
     delayedDestruct();
 }
 
+
 void AdBlockDlg::showContextMenu(const QPoint & pos)
 {
     QPoint newPos = m_list->viewport()->mapToGlobal( pos );
@@ -136,23 +252,57 @@
     m_menu->popup(newPos);
 }
 
+
+
+
+
 void AdBlockDlg::filterItem()
 {
     QTreeWidgetItem* item = m_list->currentItem();
-    m_filter->setText( item->text(0) );
+    setFilterText( item->text(0) );
 }
 
-void AdBlockDlg::filterPath()
+KUrl AdBlockDlg::getItem()
 {
     QTreeWidgetItem* item = m_list->currentItem();
-    QString value = item->text(0);
-    m_filter->setText( value.section( '/', 0, -2 ).append("/*") );
+    KUrl u(item->text(0));
+    u.setQuery(QString());
+    u.setRef(QString());
+    return (u);
 }
 
+void AdBlockDlg::filterPath()
+{
+    KUrl u(getItem());
+    u.setFileName("*");
+    setFilterText(u.url());
+}
+
+void AdBlockDlg::filterHost()
+{
+    KUrl u(getItem());
+    u.setPath("/*");
+    setFilterText(u.url());
+}
+
+void AdBlockDlg::filterDomain()
+{
+    KUrl u(getItem());
+
+    QString host = u.host();
+    if (host.isEmpty()) return;
+    int idx = host.indexOf('.');
+    if (idx<0) return;
+    u.setHost("*"+host.mid(idx));
+
+    u.setPath("/*");
+    setFilterText(u.url());
+}
+
 void AdBlockDlg::addWhiteList()
 {
     QTreeWidgetItem* item = m_list->currentItem();
-    m_filter->setText( "@@" + item->text(0) );
+    setFilterText( "@@" + item->text(0) );
 }
 
 void AdBlockDlg::copyLinkAddress()
@@ -178,37 +328,5 @@
     new KRun( m_list->currentItem()->text( 0 ), 0 );
 }
 
-AdBlockDlg::~AdBlockDlg()
-{
-}
 
-// ----------------------------------------------------------------------------
-bool ListViewItem::isBlocked() const
-{
-    return m_blocked;
-}
-
-void ListViewItem::setBlocked(bool blocked)
-{
-    if ( blocked )
-    {
-        setData ( 0,Qt::TextColorRole, Qt::red);
-        QFont itemFont =  font(0);
-        itemFont.setItalic( true );
-        itemFont.setBold( true );
-        setData( 0, Qt::FontRole, itemFont );
-
-    }
-}
-
-DOM::Node ListViewItem::node() const
-{
-    return m_node;
-}
-
-void ListViewItem::setNode( const DOM::Node& node )
-{
-    m_node = node;
-}
-
 #include "adblockdialog.moc"
Index: extragear/base/konq-plugins/adblock/adblock.h
===================================================================
--- extragear/base/konq-plugins/adblock/adblock.h	(revision 990526)
+++ extragear/base/konq-plugins/adblock/adblock.h	(working copy)
@@ -26,6 +26,8 @@
 #include <qlist.h>
 #include <kparts/plugin.h>
 #include <dom/dom_node.h>
+#include <kurl.h>
+
 class KHTMLPart;
 class KUrlLabel;
 class KHTMLSettings;
@@ -56,10 +58,9 @@
     KUrlLabel *m_label;
     KActionMenu *m_menu;
 
-    void fillBlockableElements(AdElementList &elements);
-    void fillWithImages(AdElementList &elements);
-    void fillWithHtmlTag(AdElementList &elements,
-			 const DOM::DOMString &tagName,
+    void fillBlockableElements();
+    void fillWithImages();
+    void fillWithHtmlTag(const DOM::DOMString &tagName,
 			 const DOM::DOMString &attrName,
 			 const QString &category);
 
@@ -71,6 +72,11 @@
     void showKCModule();
     void slotDisableForThisPage();
     void slotDisableForThisSite();
+
+private:
+    void disableForUrl(KUrl url);
+
+    AdElementList *m_elements;
 };
 
 // ----------------------------------------------------------------------------
Index: extragear/base/konq-plugins/adblock/adblockdialog.h
===================================================================
--- extragear/base/konq-plugins/adblock/adblockdialog.h	(revision 990526)
+++ extragear/base/konq-plugins/adblock/adblockdialog.h	(working copy)
@@ -33,48 +33,37 @@
 {
     Q_OBJECT
 public:
-    AdBlockDlg(QWidget *parent, AdElementList &elements, KHTMLPart*part);
+    AdBlockDlg(QWidget *parent, const AdElementList *elements, 
KHTMLPart*part);
     ~AdBlockDlg();
 
 private slots:
-    void validateFilter ();
+    void slotAddFilter();
+    void slotConfigureFilters();
+
     void updateFilter(QTreeWidgetItem *item);
     void showContextMenu(const QPoint&);
-    void filterPath();
     void filterItem();
+    void filterPath();
+    void filterHost();
+    void filterDomain();
     void addWhiteList();
     void copyLinkAddress();
     void highLightElement();
     void showElement();
+    void filterTextChanged(const QString &text);
+
 signals:
-    void notEmptyFilter (const QString &url);
+    void notEmptyFilter(const QString &url);
+    void configureFilters();
 
-
 private:
+    KUrl getItem();
+    void setFilterText(const QString &text);
+
     QLineEdit *m_filter;
     QTreeWidget *m_list;
-    QLabel *m_label1;
-    QLabel *m_label2;
     KMenu *m_menu;
     KHTMLPart *m_part;
 };
 
-// ----------------------------------------------------------------------------
-
-class ListViewItem : public QTreeWidgetItem
-{
-public:
-    ListViewItem(QTreeWidget *listView, const QStringList& lst)
-        : QTreeWidgetItem( listView, lst),
-          m_blocked(false){};
-
-    bool isBlocked() const;
-    void setBlocked(bool blocked);
-    void setNode( const DOM::Node& node );
-    DOM::Node node()const;
-private:
-    bool m_blocked;
-    DOM::Node m_node;
-};
-
 #endif
-------------------------------------------------------------------------

-- 
Jonathan Marten                         http://www.keelhaul.demon.co.uk
Twickenham, UK                          jjm2 at keelhaul.demon.co.uk




More information about the kfm-devel mailing list