/***************************************************************************
                               domtreeview.cpp
                             -------------------

    copyright            : (C) 2001 - The Kafka Team/Andreas Schlapbach
                           (C) 2005 - Leo Savernik
    email                : kde-kafka@master.kde.org
                           schlpbch@iam.unibe.ch
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "domtreeview.h"
#include "domlistviewitem.h"
#include "domtreewindow.h"
#include "domtreecommands.h"

#include "attributeeditdialog.h"
#include "elementeditdialog.h"
#include "texteditdialog.h"

#include "signalreceiver.h"

#include <assert.h>

#include <tqapplication.h>
#include <tqcheckbox.h>
#include <tqevent.h>
#include <tqfont.h>
#include <tqfile.h>
#include <tqlabel.h>
#include <tqlayout.h>
#include <tqpopupmenu.h>
#include <tqtextstream.h>
#include <tqtimer.h>
#include <tqwidgetstack.h>

#include <dom/dom_core.h>
#include <dom/html_base.h>

#include <tdeaction.h>
#include <kdebug.h>
#include <kcombobox.h>
#include <kdialog.h>
#include <keditcl.h>
#include <tdefiledialog.h>
#include <tdeglobalsettings.h>
#include <tdehtml_part.h>
#include <klineedit.h>
#include <tdelistview.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kpushbutton.h>
#include <tdeshortcut.h>
#include <kstdguiitem.h>
#include <ktextedit.h>

using namespace domtreeviewer;

DOMTreeView::DOMTreeView(TQWidget *parent, const char* name, bool /*allowSaving*/)
  : DOMTreeViewBase(parent, name), m_expansionDepth(5), m_maxDepth(0),
    m_bPure(true), m_bShowAttributes(true), m_bHighlightHTML(true),
    m_findDialog(0), focused_child(0)
{
  part = 0;

  const TQFont font(TDEGlobalSettings::generalFont());
  m_listView->setFont( font );
  m_listView->setSorting(-1);
  m_rootListView = m_listView;

  m_pureCheckBox->setChecked(m_bPure);
  connect(m_pureCheckBox, TQ_SIGNAL(toggled(bool)), this, TQ_SLOT(slotPureToggled(bool)));

  m_showAttributesCheckBox->setChecked(m_bShowAttributes);
  connect(m_showAttributesCheckBox, TQ_SIGNAL(toggled(bool)), this,
         TQ_SLOT(slotShowAttributesToggled(bool)));

  m_highlightHTMLCheckBox->setChecked(m_bHighlightHTML);
  connect(m_highlightHTMLCheckBox, TQ_SIGNAL(toggled(bool)), this,
         TQ_SLOT(slotHighlightHTMLToggled(bool)));

  connect(m_listView, TQ_SIGNAL(clicked(TQListViewItem *)), this,
	  TQ_SLOT(slotItemClicked(TQListViewItem *)));
  connect(m_listView, TQ_SIGNAL(contextMenuRequested(TQListViewItem *, const TQPoint &, int)),
  	TQ_SLOT(showDOMTreeContextMenu(TQListViewItem *, const TQPoint &, int)));
  connect(m_listView, TQ_SIGNAL(moved(TQPtrList<TQListViewItem> &, TQPtrList<TQListViewItem> &, TQPtrList<TQListViewItem> &)),
        TQ_SLOT(slotMovedItems(TQPtrList<TQListViewItem> &, TQPtrList<TQListViewItem> &, TQPtrList<TQListViewItem> &)));

  // set up message line
  messageLinePane->hide();
  connect(messageHideBtn, TQ_SIGNAL(clicked()), TQ_SLOT(hideMessageLine()));
  connect(messageListBtn, TQ_SIGNAL(clicked()), mainWindow(), TQ_SLOT(showMessageLog()));

  installEventFilter(m_listView);

  ManipulationCommand::connect(TQ_SIGNAL(nodeChanged(const DOM::Node &)), this, TQ_SLOT(slotRefreshNode(const DOM::Node &)));
  ManipulationCommand::connect(TQ_SIGNAL(structureChanged()), this, TQ_SLOT(refresh()));

  initDOMNodeInfo();

  installEventFilter(this);
}

DOMTreeView::~DOMTreeView()
{
  delete m_findDialog;
  disconnectFromActivePart();
}

void DOMTreeView::setHtmlPart(TDEHTMLPart *_part)
{
  TDEHTMLPart *oldPart = part;
  part = _part;

  if (oldPart) {
    // nothing here yet
  }

  parentWidget()->setCaption( part ? i18n( "DOM Tree for %1" ).arg(part->url().prettyURL()) : i18n("DOM Tree") );

  TQTimer::singleShot(0, this, TQ_SLOT(slotSetHtmlPartDelayed()));
}

DOMTreeWindow *DOMTreeView::mainWindow() const
{
  return static_cast<DOMTreeWindow *>(parentWidget());
}

bool DOMTreeView::eventFilter(TQObject *o, TQEvent *e)
{
  if (e->type() == TQEvent::AccelOverride) {
    TQKeyEvent *ke = static_cast<TQKeyEvent*>(e);
    kdDebug(90180) << " acceloverride " << ke->key() << " o " << o->name() << endl;

    if (o == m_listView) {	// DOM tree
      KKey ks = mainWindow()->deleteNodeAction()->shortcut().seq(0).key(0);
      if (ke->key() == ks.keyCodeQt())
        return true;

    } else if (o == nodeAttributes) {
      KKey ks = mainWindow()->deleteAttributeAction()->shortcut().seq(0).key(0);
      if (ke->key() == ks.keyCodeQt())
        return true;

    }

  } else if (e->type() == TQEvent::FocusIn) {

    kdDebug(90180) << " focusin o " << o->name() << endl;
    if (o != this) {
      focused_child = o;
    }

  } else if (e->type() == TQEvent::FocusOut) {

    kdDebug(90180) << " focusout o " << o->name() << endl;
    if (o != this) {
      focused_child = 0;
    }

  }

  return false;
}

void DOMTreeView::activateNode(const DOM::Node &node)
{
  slotShowNode(node);
  initializeOptionsFromNode(node);
}

void DOMTreeView::slotShowNode(const DOM::Node &pNode)
{

  if (TQListViewItem *item = m_itemdict[pNode.handle()]) {
    m_listView->setCurrentItem(item);
    m_listView->ensureItemVisible(item);
  }
}

void DOMTreeView::slotShowTree(const DOM::Node &pNode)
{
  DOM::Node child;

  m_listView->clear();
  m_itemdict.clear();

  try
  {
    child = pNode.firstChild();
  }
  catch (DOM::DOMException &)
  {
    return;
  }

  while(!child.isNull()) {
    showRecursive(0, child, 0);
    child = child.nextSibling();
  }

  m_maxDepth--;
  //kdDebug(90180) << " Max Depth: " << m_maxDepth << endl;
}

void DOMTreeView::showRecursive(const DOM::Node &pNode, const DOM::Node &node, uint depth)
{
  DOMListViewItem *cur_item;
  DOMListViewItem *parent_item = m_itemdict[pNode.handle()];

  if (depth > m_maxDepth) {
    m_maxDepth = depth;
  }

  if (depth == 0) {
    cur_item = new DOMListViewItem(node, m_listView);
    m_document = pNode.ownerDocument();
  } else {
    cur_item = new DOMListViewItem(node, parent_item);
  }

  //kdDebug(90180) << node.nodeName().string() << " [" << depth << "]" << endl;
  addElement (node, cur_item, false);
  cur_item->setOpen(depth < m_expansionDepth);

  if(node.handle()) {
    m_itemdict.insert(node.handle(), cur_item);
  }

  DOM::Node child = node.lastChild();
  if (child.isNull()) {
    DOM::HTMLFrameElement frame = node;
    if (!frame.isNull()) child = frame.contentDocument().documentElement();
  }
  while(!child.isNull()) {
    showRecursive(node, child, depth + 1);
    child = child.previousSibling();
  }

  const DOM::Element element = node;
  if (!m_bPure) {
    if (!element.isNull() && !element.firstChild().isNull()) {
      if(depth == 0) {
	cur_item = new DOMListViewItem(node, m_listView, cur_item);
	m_document = pNode.ownerDocument();
      } else {
	cur_item = new DOMListViewItem(node, parent_item, cur_item);
      }
      //kdDebug(90180) << "</" << node.nodeName().string() << ">" << endl;
      addElement(element, cur_item, true);
//       cur_item->setOpen(depth < m_expansionDepth);
    }
  }
}

void DOMTreeView::addElement ( const DOM::Node &node,  DOMListViewItem *cur_item, bool isLast)
{
  cur_item->setClosing(isLast);

  const TQString nodeName(node.nodeName().string());
  TQString text;
  const DOM::Element element = node;
  if (!element.isNull()) {
    if (!m_bPure) {
      if (isLast) {
	text ="</";
      } else {
	text = "<";
      }
      text += nodeName;
    } else {
      text = nodeName;
    }

    if (m_bShowAttributes && !isLast) {
      TQString attributes;
      DOM::Attr attr;
      DOM::NamedNodeMap attrs = element.attributes();
      unsigned long lmap = attrs.length();
      for( unsigned int j=0; j<lmap; j++ ) {
	attr = static_cast<DOM::Attr>(attrs.item(j));
	attributes += " " + attr.name().string() + "=\"" + attr.value().string() + "\"";
      }
      if (!(attributes.isEmpty())) {
	text += " ";
      }
      text += attributes.simplifyWhiteSpace();
    }

    if (!m_bPure) {
      if(element.firstChild().isNull()) {
	text += "/>";
      } else {
	text += ">";
      }
    }
    cur_item->setText(0, text);
  } else {
    text = "`" + node.nodeValue().string() + "'";

    // Hacks to deal with PRE
    TQTextStream ts( text, IO_ReadOnly );
    while (!ts.eof()) {
      const TQString txt(ts.readLine());
      const TQFont font(TDEGlobalSettings::fixedFont());
      cur_item->setFont( font );
      cur_item->setText(0, txt);

      if(node.handle()) {
	m_itemdict.insert(node.handle(), cur_item);
      }

      DOMListViewItem *parent;
      if (cur_item->parent()) {
	parent = static_cast<DOMListViewItem *>(cur_item->parent());
      } else {
	parent = cur_item;
      }
      cur_item = new DOMListViewItem(node, parent, cur_item);
    }
    // This is one is too much
    DOMListViewItem *notLastItem = static_cast<DOMListViewItem *>(cur_item->itemAbove());
    delete cur_item;
    cur_item = notLastItem;
  }

  if (m_bHighlightHTML && node.ownerDocument().isHTMLDocument()) {
    highlightHTML(cur_item, nodeName);
  }
}

void DOMTreeView::highlightHTML(DOMListViewItem *cur_item, const TQString &nodeName)
{
  /* This is slow. I could make it O(1) be using the tokenizer of tdehtml but I don't
   * think it's worth it.
   */

  TQColor namedColor(palette().active().text());
  TQString tagName = nodeName.upper();
  if ( tagName == "HTML" ) {
    namedColor = "#0000ff";
    cur_item->setBold(true);
  } else if ( tagName == "HEAD" ) {
    namedColor = "#0022ff";
    cur_item->setBold(true);

  } else if ( tagName == "TITLE" ) {
    namedColor = "#2200ff";
  } else if ( tagName == "SCRIPT" ) {
    namedColor = "#4400ff";
  } else if ( tagName == "NOSCRIPT" ) {
    namedColor = "#0044ff";
  } else if ( tagName == "STYLE" ) {
    namedColor = "#0066ff";
  } else if ( tagName == "LINK" ) {
    namedColor = "#6600ff";
  } else if ( tagName == "META" ) {
    namedColor = "#0088ff";

  } else if ( tagName == "BODY" ) {
    namedColor = "#ff0000";
    cur_item->setBold(true);
  } else if ( tagName == "A") {
    namedColor = "blue";
    cur_item->setUnderline(true);
  } else if ( tagName == "IMG") {
    namedColor = "magenta";
    cur_item->setUnderline(true);

  } else if ( tagName == "DIV" ) {
    namedColor = "#ff0044";
  } else if ( tagName == "SPAN" ) {
    namedColor = "#ff4400";
  } else if ( tagName == "P" ) {
    namedColor = "#ff0066";

  } else if ( tagName == "DL" || tagName == "OL"|| tagName == "UL" || tagName == "TABLE" ) {
    namedColor = "#880088";
  } else if ( tagName == "LI" ) {
    namedColor = "#884488";
  } else if ( tagName == "TBODY" ){
    namedColor = "#888888";
  } else if ( tagName == "TR" ) {
    namedColor = "#882288";
  } else if ( tagName == "TD" ) {
    namedColor = "#886688";

  } else if ((tagName == "H1")||(tagName == "H2")||(tagName == "H3") ||
	     (tagName == "H4")||(tagName == "H5")||(tagName == "H6")) {
    namedColor = "#008800";
  } else if (tagName == "HR" ) {
    namedColor = "#228822";

  } else if ( tagName == "FRAME" || tagName == "IFRAME" ) {
    namedColor = "#ff22ff";
  } else if ( tagName == "FRAMESET" ) {
    namedColor = "#dd22dd";

  } else if ( tagName == "APPLET" || tagName == "OBJECT" ) {
    namedColor = "#bb22bb";

  } else if ( tagName == "BASEFONT" || tagName == "FONT" ) {
    namedColor = "#097200";

  } else if ( tagName == "B" || tagName == "STRONG" ) {
    cur_item->setBold(true);
  } else if ( tagName == "I" || tagName == "EM" ) {
    cur_item->setItalic(true);
  } else if ( tagName == "U") {
    cur_item->setUnderline(true);
  }

  cur_item->setColor(namedColor);
}

void DOMTreeView::slotItemClicked(TQListViewItem *cur_item)
{
  DOMListViewItem *cur = static_cast<DOMListViewItem *>(cur_item);
  if (!cur) return;

  DOM::Node handle = cur->node();
  if (!handle.isNull()) {
    part->setActiveNode(handle);
  }
}

void DOMTreeView::slotFindClicked()
{
  if (m_findDialog == 0) {
    m_findDialog = new KEdFind(this);
    connect(m_findDialog, TQ_SIGNAL(search()), this, TQ_SLOT(slotSearch()));
  }
  m_findDialog->show();
}

void DOMTreeView::slotRefreshNode(const DOM::Node &pNode)
{
  DOMListViewItem *cur = static_cast<DOMListViewItem *>(m_itemdict[pNode.handle()]);
  if (!cur) return;

  addElement(pNode, cur, false);
}

void DOMTreeView::slotPrepareMove()
{
  DOMListViewItem *item = static_cast<DOMListViewItem *>(m_listView->currentItem());

  if (!item)
    current_node = DOM::Node();
  else
    current_node = item->node();
}

void DOMTreeView::slotMovedItems(TQPtrList<TQListViewItem> &items, TQPtrList<TQListViewItem> &/*afterFirst*/, TQPtrList<TQListViewItem> &afterNow)
{
  MultiCommand *cmd = new MultiCommand(i18n("Move Nodes"));
  _refreshed = false;

  TQPtrList<TQListViewItem>::Iterator it = items.begin();
  TQPtrList<TQListViewItem>::Iterator anit = afterNow.begin();
  for (; it != items.end(); ++it, ++anit) {
    DOMListViewItem *item = static_cast<DOMListViewItem *>(*it);
    DOMListViewItem *anitem = static_cast<DOMListViewItem *>(*anit);
    DOM::Node parent = static_cast<DOMListViewItem *>(item->parent())->node();
    Q_ASSERT(!parent.isNull());

// kdDebug(90180) << " afternow " << anitem << " node " << (anitem ? anitem->node().nodeName().string() : TQString()) << "=" << (anitem ? anitem->node().nodeValue().string() : TQString()) << endl;

    cmd->addCommand(new MoveNodeCommand(item->node(), parent,
      anitem ? anitem->node().nextSibling() : parent.firstChild())
    );
  }

  mainWindow()->executeAndAddCommand(cmd);

  // refresh *anyways*, otherwise consistency is disturbed
  if (!_refreshed) refresh();

  slotShowNode(current_node);
}

void DOMTreeView::slotSearch()
{
  assert(m_findDialog);
  const TQString& searchText = m_findDialog->getText();
  bool caseSensitive = m_findDialog->case_sensitive();

  searchRecursive(static_cast<DOMListViewItem*>(m_rootListView->firstChild()),
		  searchText, caseSensitive);

  m_findDialog->hide();
}

void DOMTreeView::searchRecursive(DOMListViewItem* cur_item, const TQString& searchText,
				  bool caseSensitive)
{
  const TQString text(cur_item->text(0));
  if (text.contains(searchText, caseSensitive) > 0) {
    cur_item->setUnderline(true);
    cur_item->setItalic(true);
    m_listView->setCurrentItem(cur_item);
    m_listView->ensureItemVisible(cur_item);
  } else {
    cur_item->setOpen(false);
  }

  DOMListViewItem* child = static_cast<DOMListViewItem *>(cur_item->firstChild());
  while( child ) {
    searchRecursive(child, searchText, caseSensitive);
    child = static_cast<DOMListViewItem *>(child->nextSibling());
  }
}

#if 0
void DOMTreeView::slotSaveClicked()
{
  //kdDebug(90180) << "void KfingerCSSWidget::slotSaveAs()" << endl;
  KURL url = KFileDialog::getSaveFileName( part->url().url(), "*.html",
					   this, i18n("Save DOM Tree as HTML") );
  if (!(url.isEmpty()) && url.isValid()) {
    TQFile file(url.path());

    if (file.exists()) {
      const TQString title = i18n( "File Exists" );
      const TQString text = i18n( "Do you really want to overwrite: \n%1?" ).arg(url.url());
      if (KMessageBox::Continue != KMessageBox::warningContinueCancel(this, text, title, i18n("Overwrite") ) ) {
	return;
      }
    }

    if (file.open(IO_WriteOnly) ) {
      kdDebug(90180) << "Opened File: " << url.url() << endl;
      m_textStream = new TQTextStream(&file); //(stdOut)
      saveTreeAsHTML(part->document());
      file.close();
      kdDebug(90180) << "File closed " << endl;
      delete m_textStream;
    } else {
      const TQString title = i18n( "Unable to Open File" );
      const TQString text = i18n( "Unable to open \n %1 \n for writing" ).arg(url.path());
      KMessageBox::sorry( this, text, title );
    }
  } else {
    const TQString title = i18n( "Invalid URL" );
    const TQString text = i18n( "This URL \n %1 \n is not valid." ).arg(url.url());
    KMessageBox::sorry( this, text, title );
  }
}

void DOMTreeView::saveTreeAsHTML(const DOM::Node &pNode)
{
  assert(m_textStream);

  // Add a doctype

  (*m_textStream) <<"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">" << endl;
  if(pNode.ownerDocument().isNull()) {
    saveRecursive(pNode, 0);
  } else {
    saveRecursive(pNode.ownerDocument(), 0);
  }
}

void DOMTreeView::saveRecursive(const DOM::Node &pNode, int indent)
{
  const TQString nodeName(pNode.nodeName().string());
  TQString text;
  TQString strIndent;
  strIndent.fill(' ', indent);
  const DOM::Element element = static_cast<const DOM::Element>(pNode);

  text = strIndent;

  if ( !element.isNull() ) {
    if (nodeName.at(0)=='-') {
      /* Don't save tdehtml internal tags '-konq..'
       * Approximating it with <DIV>
       */
      text += "<DIV> <!-- -KONG_BLOCK -->";
    } else {
      text += "<" + nodeName;

      TQString attributes;
      DOM::Attr attr;
      const DOM::NamedNodeMap attrs = element.attributes();
      unsigned long lmap = attrs.length();
      for( uint j=0; j<lmap; j++ ) {
	attr = static_cast<DOM::Attr>(attrs.item(j));
	attributes += " " + attr.name().string() + "=\"" + attr.value().string() + "\"";
      }
      if (!(attributes.isEmpty())){
	text += " ";
      }

      text += attributes.simplifyWhiteSpace();

      if(element.firstChild().isNull()) {
	text += "/>";
      } else {
	text += ">";
      }
    }
  } else {
    text = strIndent + pNode.nodeValue().string();
  }

  kdDebug(90180) << text << endl;
  if (!(text.isEmpty())) {
    (*m_textStream) << text << endl;
  }

  DOM::Node child = pNode.firstChild();
  while(!child.isNull()) {
    saveRecursive(child, indent+2);
    child = child.nextSibling();
  }

  if (!(element.isNull()) && (!(element.firstChild().isNull()))) {
    if (nodeName.at(0)=='-') {
      text = strIndent + "</DIV> <!-- -KONG_BLOCK -->";
    } else {
      text = strIndent + "</" + pNode.nodeName().string() + ">";
    }
    kdDebug(90180) << text << endl;
    (*m_textStream) << text << endl;
  }
}
#endif

void DOMTreeView::updateIncrDecreaseButton()
{
#if 0
    m_decreaseButton->setEnabled((m_expansionDepth > 0));
    m_increaseButton->setEnabled((m_expansionDepth < m_maxDepth));
#endif
}

void DOMTreeView::refresh()
{
  if (!part) return;
  scroll_ofs_x = m_listView->contentsX();
  scroll_ofs_y = m_listView->contentsY();

  m_listView->setUpdatesEnabled(false);
  slotShowTree(part->document());

  TQTimer::singleShot(0, this, TQ_SLOT(slotRestoreScrollOffset()));
  _refreshed = true;
}

void DOMTreeView::increaseExpansionDepth()
{
  if (!part) return;
  if (m_expansionDepth < m_maxDepth) {
    ++m_expansionDepth;
    adjustDepth();
    updateIncrDecreaseButton();
  } else {
    TQApplication::beep();
  }
}

void DOMTreeView::decreaseExpansionDepth()
{
  if (!part) return;
  if (m_expansionDepth > 0) {
    --m_expansionDepth;
    adjustDepth();
    updateIncrDecreaseButton();
  } else {
    TQApplication::beep();
  }
}

void DOMTreeView::adjustDepth()
{
  // get current item in a hypersmart way
  DOMListViewItem *cur_node_item = m_itemdict[infoNode.handle()];
  if (!cur_node_item)
    cur_node_item = static_cast<DOMListViewItem *>(m_listView->currentItem());

  adjustDepthRecursively(m_rootListView->firstChild(), 0);

  // make current item visible again if possible
  if (cur_node_item)
    m_listView->ensureVisible(0, cur_node_item->itemPos());

}

void DOMTreeView::adjustDepthRecursively(TQListViewItem *cur_item,  uint currDepth)
{
  if (!(cur_item == 0)) {
    while( cur_item ) {
      cur_item->setOpen( (m_expansionDepth > currDepth) );
      adjustDepthRecursively(cur_item->firstChild(), currDepth+1);
      cur_item = cur_item->nextSibling();
    }
  }
}

void DOMTreeView::setMessage(const TQString &msg)
{
  messageLine->setText(msg);
  messageLinePane->show();
}

void DOMTreeView::hideMessageLine()
{
  messageLinePane->hide();
}

void DOMTreeView::moveToParent()
{
  // This is a hypersmart algorithm.
  // If infoNode is defined, go to the parent of infoNode, otherwise, go
  // to the parent of the tree view's current item.
  // Hope this isn't too smart.

  DOM::Node cur = infoNode;
  if (cur.isNull()) cur = static_cast<DOMListViewItem *>(m_listView->currentItem())->node();

  if (cur.isNull()) return;

  cur = cur.parentNode();
  activateNode(cur);
}

void DOMTreeView::showDOMTreeContextMenu(TQListViewItem */*lvi*/, const TQPoint &pos, int /*col*/)
{
  TQPopupMenu *ctx = mainWindow()->domTreeViewContextMenu();
  Q_ASSERT(ctx);
  ctx->popup(pos);
}

void DOMTreeView::slotPureToggled(bool b)
{
  m_bPure = b;
  refresh();
}

void DOMTreeView::slotShowAttributesToggled(bool b)
{
  m_bShowAttributes = b;
  refresh();
}

void DOMTreeView::slotHighlightHTMLToggled(bool b)
{
  m_bHighlightHTML = b;
  refresh();
}

void DOMTreeView::deleteNodes()
{
// kdDebug(90180) << k_funcinfo << endl;

  DOM::Node last;
  MultiCommand *cmd = new MultiCommand(i18n("Delete Nodes"));
  TQListViewItemIterator it(m_listView, TQListViewItemIterator::Selected);
  for (; *it; ++it) {
    DOMListViewItem *item = static_cast<DOMListViewItem *>(*it);
//     kdDebug(90180) << " item->node " << item->node().nodeName().string() << " clos " << item->isClosing() << endl;
    if (item->isClosing()) continue;

    // don't regard node more than once
    if (item->node() == last) continue;

    // check for selected parent
    bool has_selected_parent = false;
    for (TQListViewItem *p = item->parent(); p; p = p->parent()) {
      if (p->isSelected()) { has_selected_parent = true; break; }
    }
    if (has_selected_parent) continue;

//     kdDebug(90180) << " item->node " << item->node().nodeName().string() << ": schedule for removal" << endl;
    // remove this node if it isn't already recursively removed by its parent
    cmd->addCommand(new RemoveNodeCommand(item->node(), item->node().parentNode(), item->node().nextSibling()));
    last = item->node();
  }
  mainWindow()->executeAndAddCommand(cmd);
}

void DOMTreeView::disconnectFromTornDownPart()
{
  if (!part) return;

  m_listView->clear();
  initializeOptionsFromNode(DOM::Node());

  // remove all references to nodes
  infoNode = DOM::Node();	// ### have this handled by dedicated info node panel method
  current_node = DOM::Node();
  active_node_rule = DOM::CSSRule();
  stylesheet = DOM::CSSStyleSheet();
}

void DOMTreeView::connectToPart()
{
  if (part) {
    connect(part, TQ_SIGNAL(nodeActivated(const DOM::Node &)), this,
	  TQ_SLOT(activateNode(const DOM::Node &)));
    connect(part, TQ_SIGNAL(completed()), this, TQ_SLOT(refresh()));

    // insert a style rule to indicate activated nodes
    try {
kdDebug(90180) << "(1) part.document: " << part->document().handle() << endl;
      stylesheet = part->document().implementation().createCSSStyleSheet("-domtreeviewer-style", "screen");
kdDebug(90180) << "(2)" << endl;
      stylesheet.insertRule(":focus { outline: medium #f00 solid }", 0);
      // ### for testing only
//       stylesheet.insertRule("body { background: #f0f !important }", 1);
kdDebug(90180) << "(3)" << endl;
      active_node_rule = stylesheet.cssRules().item(0);
kdDebug(90180) << "(4)" << endl;
      part->document().addStyleSheet(stylesheet);
kdDebug(90180) << "(5)" << endl;
    } catch (DOM::CSSException &ex) {
      kdDebug(90180) << "CSS Exception " << ex.code << endl;
    } catch (DOM::DOMException &ex) {
      kdDebug(90180) << "DOM Exception " << ex.code << endl;
    }
  }

  slotShowTree(part ? (DOM::Node)part->document() : DOM::Node());
  updateIncrDecreaseButton();
}

void DOMTreeView::disconnectFromActivePart()
{
  if (!part) return;

  // remove style sheet
  try {
    part->document().removeStyleSheet(stylesheet);
  } catch (DOM::CSSException &ex) {
    kdDebug(90180) << "CSS Exception " << ex.code << endl;
  } catch (DOM::DOMException &ex) {
    kdDebug(90180) << "DOM Exception " << ex.code << endl;
  }

}

void DOMTreeView::slotSetHtmlPartDelayed()
{
  connectToPart();
  emit htmlPartChanged(part);
}

void DOMTreeView::slotRestoreScrollOffset()
{
  m_listView->setUpdatesEnabled(true);
  m_listView->setContentsPos(scroll_ofs_x, scroll_ofs_y);
}

void DOMTreeView::slotAddElementDlg()
{
  DOMListViewItem *item = static_cast<DOMListViewItem *>(m_listView->currentItem());
  if (!item) return;

  TQString qname;
  TQString namespc;
  SignalReceiver addBefore;

  {
    ElementEditDialog dlg(this, "ElementEditDialog", true);
    connect(dlg.insBeforeBtn, TQ_SIGNAL(clicked()), &addBefore, TQ_SLOT(slot()));

    // ### activate when namespaces are supported
    dlg.elemNamespace->setEnabled(false);

    if (dlg.exec() != TQDialog::Accepted) return;

    qname = dlg.elemName->text();
    namespc = dlg.elemNamespace->currentText();
  }

  DOM::Node curNode = item->node();

  try {
    DOM::Node parent = addBefore() ? curNode.parentNode() : curNode;
    DOM::Node after = addBefore() ? curNode : 0;

    // ### take namespace into account
    DOM::Node newNode = curNode.ownerDocument().createElement(qname);

    ManipulationCommand *cmd = new InsertNodeCommand(newNode, parent, after);
    mainWindow()->executeAndAddCommand(cmd);

    if (cmd->isValid()) activateNode(newNode);

  } catch (DOM::DOMException &ex) {
    mainWindow()->addMessage(ex.code, domErrorMessage(ex.code));
  }
}

void DOMTreeView::slotAddTextDlg()
{
  DOMListViewItem *item = static_cast<DOMListViewItem *>(m_listView->currentItem());
  if (!item) return;

  TQString text;
  SignalReceiver addBefore;

  {
    TextEditDialog dlg(this, "TextEditDialog", true);
    connect(dlg.insBeforeBtn, TQ_SIGNAL(clicked()), &addBefore, TQ_SLOT(slot()));

    if (dlg.exec() != TQDialog::Accepted) return;

    text = dlg.textPane->text();
  }

  DOM::Node curNode = item->node();

  try {
    DOM::Node parent = addBefore() ? curNode.parentNode() : curNode;
    DOM::Node after = addBefore() ? curNode : 0;

    DOM::Node newNode = curNode.ownerDocument().createTextNode(text);

    ManipulationCommand *cmd = new InsertNodeCommand(newNode, parent, after);
    mainWindow()->executeAndAddCommand(cmd);

    if (cmd->isValid()) activateNode(newNode);

  } catch (DOM::DOMException &ex) {
    mainWindow()->addMessage(ex.code, domErrorMessage(ex.code));
  }
}

// == DOM Node info panel =============================================

static TQString *clickToAdd;

/**
 * List view item for attribute list.
 */
class AttributeListItem : public TQListViewItem
{
  typedef TQListViewItem super;

  bool _new;

public:
  AttributeListItem(TQListView *parent, TQListViewItem *prev)
  : super(parent, prev), _new(true)
  {
  }

  AttributeListItem(const TQString &attrName, const TQString &attrValue,
		TQListView *parent, TQListViewItem *prev)
  : super(parent, prev), _new(false)
  {
    setText(0, attrName);
    setText(1, attrValue);
  }

  bool isNew() const { return _new; }
  void setNew(bool s) { _new = s; }

  virtual int compare(TQListViewItem *item, int column, bool ascend) const
  {
    return _new ? 1 : super::compare(item, column, ascend);
  }

protected:
  virtual void paintCell( TQPainter *p, const TQColorGroup &cg,
			  int column, int width, int alignment )
  {
    bool updates_enabled = listView()->isUpdatesEnabled();
    listView()->setUpdatesEnabled(false);

    TQColor c = cg.text();
    bool text_changed = false;
    TQString oldText;

    if (_new) {
      c = TQApplication::palette().color( TQPalette::Disabled, TQColorGroup::Text );

      if (!clickToAdd) clickToAdd = new TQString(i18n("<Click to add>"));
      oldText = text(column);
      text_changed = true;
      if (column == 0) setText(0, *clickToAdd); else setText(1, TQString());
    }

    TQColorGroup _cg( cg );
    _cg.setColor( TQColorGroup::Text, c );
    super::paintCell( p, _cg, column, width, alignment );

    if (text_changed) setText(column, oldText);
    listView()->setUpdatesEnabled(updates_enabled);
  }

};

void DOMTreeView::initDOMNodeInfo()
{
  connect(m_listView, TQ_SIGNAL(clicked(TQListViewItem *)),
	  TQ_SLOT(initializeOptionsFromListItem(TQListViewItem *)));

  connect(nodeAttributes, TQ_SIGNAL(itemRenamed(TQListViewItem *, const TQString &, int)),
	TQ_SLOT(slotItemRenamed(TQListViewItem *, const TQString &, int)));
  connect(nodeAttributes, TQ_SIGNAL(executed(TQListViewItem *, const TQPoint &, int)),
	  TQ_SLOT(slotEditAttribute(TQListViewItem *, const TQPoint &, int)));
  connect(nodeAttributes, TQ_SIGNAL(contextMenuRequested(TQListViewItem *, const TQPoint &, int)),
  	TQ_SLOT(showInfoPanelContextMenu(TQListViewItem *, const TQPoint &, int)));

  connect(applyContent, TQ_SIGNAL(clicked()), TQ_SLOT(slotApplyContent()));

  ManipulationCommand::connect(TQ_SIGNAL(nodeChanged(const DOM::Node &)), this, TQ_SLOT(initializeOptionsFromNode(const DOM::Node &)));

  nodeAttributes->setRenameable(0, true);
  nodeAttributes->setRenameable(1, true);

  nodeInfoStack->raiseWidget(EmptyPanel);

  installEventFilter(nodeAttributes);
}

void DOMTreeView::initializeOptionsFromNode(const DOM::Node &node)
{
  infoNode = node;

  nodeName->clear();
  nodeType->clear();
  nodeNamespace->clear();
  nodeValue->clear();

  if (node.isNull()) {
    nodeInfoStack->raiseWidget(EmptyPanel);
    return;
  }

  nodeName->setText(node.nodeName().string());
  nodeType->setText(TQString::number(node.nodeType()));
  nodeNamespace->setText(node.namespaceURI().string());
//   nodeValue->setText(node.value().string());

  DOM::Element element = node;
  if (!element.isNull()) {
    initializeOptionsFromElement(element);
    return;
  }

  DOM::CharacterData cdata = node;
  if (!cdata.isNull()) {
    initializeOptionsFromCData(cdata);
    return;
  }

  // Fallback
  nodeInfoStack->raiseWidget(EmptyPanel);
}

void DOMTreeView::initializeOptionsFromListItem(TQListViewItem *item)
{
  const DOMListViewItem *cur_item = static_cast<const DOMListViewItem *>(item);

//   kdDebug(90180) << "cur_item: " << cur_item << endl;
  initializeOptionsFromNode(cur_item ? cur_item->node() : DOM::Node());
}

void DOMTreeView::initializeOptionsFromElement(const DOM::Element &element)
{
  TQListViewItem *last = 0;
  nodeAttributes->clear();

  DOM::NamedNodeMap attrs = element.attributes();
  unsigned long lmap = attrs.length();
  for (unsigned int j = 0; j < lmap; j++) {
    DOM::Attr attr = attrs.item(j);
//     kdDebug(90180) << attr.name().string() << "=" << attr.value().string() << endl;
    TQListViewItem *item = new AttributeListItem(attr.name().string(),
    			attr.value().string(), nodeAttributes, last);
    last = item;
  }

  // append new item
  last = new AttributeListItem(nodeAttributes, last);

  nodeInfoStack->raiseWidget(ElementPanel);
}

void DOMTreeView::initializeOptionsFromCData(const DOM::CharacterData &cdata)
{
  contentEditor->setText(cdata.data().string());

  DOM::Text text = cdata;
  contentEditor->setEnabled(!text.isNull());

  nodeInfoStack->raiseWidget(CDataPanel);
}

void DOMTreeView::slotItemRenamed(TQListViewItem *lvi, const TQString &str, int col)
{
  AttributeListItem *item = static_cast<AttributeListItem *>(lvi);

  DOM::Element element = infoNode;
  if (element.isNull()) return; // Should never happen

  switch (col) {
    case 0: {
      ManipulationCommand *cmd;
//       kdDebug(90180) << k_funcinfo << "col 0: " << element.nodeName() << " isNew: " << item->isNew() << endl;
      if (item->isNew()) {
        cmd = new AddAttributeCommand(element, str, item->text(1));
	item->setNew(false);
      } else
        cmd = new RenameAttributeCommand(element, item->text(0), str);

      mainWindow()->executeAndAddCommand(cmd);
      break;
    }
    case 1: {
      if (item->isNew()) { lvi->setText(1, TQString()); break; }

      ChangeAttributeValueCommand *cmd = new ChangeAttributeValueCommand(
      			  element, item->text(0), str);
      mainWindow()->executeAndAddCommand(cmd);
      break;
    }
  }
}

void DOMTreeView::slotEditAttribute(TQListViewItem *lvi, const TQPoint &, int col)
{
  if (!lvi) return;

  TQString attrName = lvi->text(0);
  TQString attrValue = lvi->text(1);
  int res = 0;

  {
    AttributeEditDialog dlg(this, "AttributeEditDialog", true);
    dlg.attrName->setText(attrName);
    dlg.attrValue->setText(attrValue);

    if (col == 0) {
      dlg.attrName->setFocus();
      dlg.attrName->selectAll();
    } else {
      dlg.attrValue->setFocus();
      dlg.attrValue->selectAll();
    }

    res = dlg.exec();

    attrName = dlg.attrName->text();
    attrValue = dlg.attrValue->text();
  }

//   kdDebug(90180) << "name=" << attrName << " value=" << attrValue << endl;

  if (res == TQDialog::Accepted) do {
    if (attrName.isEmpty()) break;

    if (lvi->text(0) != attrName) {
      // hack: set value to assign attribute/value pair in one go
      lvi->setText(1, attrValue);

      slotItemRenamed(lvi, attrName, 0);
      // Reget, item may have been changed
      lvi = nodeAttributes->findItem(attrName, 0);
    }

    if (lvi && lvi->text(1) != attrValue)
      slotItemRenamed(lvi, attrValue, 1);

  } while(false) /*end if*/;
}


void DOMTreeView::slotApplyContent()
{
  DOM::CharacterData cdata = infoNode;

  if (cdata.isNull()) return;

  ManipulationCommand *cmd = new ChangeCDataCommand(cdata, contentEditor->text());
  mainWindow()->executeAndAddCommand(cmd);
}

void DOMTreeView::showInfoPanelContextMenu(TQListViewItem */*lvi*/, const TQPoint &pos, int /*col*/)
{
  TQPopupMenu *ctx = mainWindow()->infoPanelAttrContextMenu();
  Q_ASSERT(ctx);
  ctx->popup(pos);
}

void DOMTreeView::copyAttributes()
{
  // TODO implement me
}

void DOMTreeView::cutAttributes()
{
  // TODO implement me
}

void DOMTreeView::pasteAttributes()
{
  // TODO implement me
}

void DOMTreeView::deleteAttributes()
{
  MultiCommand *cmd = new MultiCommand(i18n("Delete Attributes"));
  TQListViewItemIterator it(nodeAttributes, TQListViewItemIterator::Selected);
  for (; *it; ++it) {
    AttributeListItem *item = static_cast<AttributeListItem *>(*it);
    if (item->isNew()) continue;

    cmd->addCommand(new RemoveAttributeCommand(infoNode, item->text(0)));
  }
  mainWindow()->executeAndAddCommand(cmd);
}

#include "domtreeview.moc"
