/***************************************************************************
                               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 <qapplication.h>
#include <qcheckbox.h>
#include <qevent.h>
#include <qfont.h>
#include <qfile.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qpopupmenu.h>
#include <qtextstream.h>
#include <qtimer.h>
#include <qwidgetstack.h>

#include <dom/dom_core.h>

#include <kaction.h>
#include <kdebug.h>
#include <kcombobox.h>
#include <kdialog.h>
#include <keditcl.h>
#include <kfiledialog.h>
#include <kglobalsettings.h>
#include <khtml_part.h>
#include <klineedit.h>
#include <klistview.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kpushbutton.h>
#include <kshortcut.h>
#include <kstdguiitem.h>
#include <ktextedit.h>

using namespace domtreeviewer;

DOMTreeView::DOMTreeView(QWidget *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 QFont font(KGlobalSettings::generalFont());
  m_listView->setFont( font );
  m_listView->setSorting(-1);
  m_rootListView = m_listView;
  
  m_pureCheckBox->setChecked(m_bPure);
  connect(m_pureCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotPureToggled(bool)));

  m_showAttributesCheckBox->setChecked(m_bShowAttributes);
  connect(m_showAttributesCheckBox, SIGNAL(toggled(bool)), this,
         SLOT(slotShowAttributesToggled(bool)));

  m_highlightHTMLCheckBox->setChecked(m_bHighlightHTML);
  connect(m_highlightHTMLCheckBox, SIGNAL(toggled(bool)), this,
         SLOT(slotHighlightHTMLToggled(bool)));
  
  connect(m_listView, SIGNAL(clicked(QListViewItem *)), this,
	  SLOT(slotItemClicked(QListViewItem *)));
  connect(m_listView, SIGNAL(contextMenuRequested(QListViewItem *, const QPoint &, int)),
  	SLOT(showDOMTreeContextMenu(QListViewItem *, const QPoint &, int)));
  connect(m_listView, SIGNAL(moved(QPtrList<QListViewItem> &, QPtrList<QListViewItem> &, QPtrList<QListViewItem> &)),
        SLOT(slotMovedItems(QPtrList<QListViewItem> &, QPtrList<QListViewItem> &, QPtrList<QListViewItem> &)));
	
  // set up message line
  messageLinePane->hide();
  connect(messageHideBtn, SIGNAL(clicked()), SLOT(hideMessageLine()));
  connect(messageListBtn, SIGNAL(clicked()), mainWindow(), SLOT(showMessageLog()));

  installEventFilter(m_listView);
  
  ManipulationCommand::connect(SIGNAL(nodeChanged(const DOM::Node &)), this, SLOT(slotRefreshNode(const DOM::Node &)));
  ManipulationCommand::connect(SIGNAL(structureChanged()), this, SLOT(refresh()));
  
  initDOMNodeInfo();
  
  installEventFilter(this);
}

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

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

  if (oldPart) {
    // nothing here yet
  }
  
  parentWidget()->setCaption( part ? i18n( "DOM Tree for %1" ).arg(part->url().prettyURL()) : i18n("DOM Tree") );

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

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

bool DOMTreeView::eventFilter(QObject *o, QEvent *e)
{
  if (e->type() == QEvent::AccelOverride) {
    QKeyEvent *ke = static_cast<QKeyEvent *>(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() == QEvent::FocusIn) {
    
    kdDebug(90180) << " focusin o " << o->name() << endl;
    if (o != this) {
      focused_child = o;
    }
  
  } else if (e->type() == QEvent::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 (QListViewItem *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();
  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 QString nodeName(node.nodeName().string());
  QString 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) {
      QString 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
    QTextStream ts( text, IO_ReadOnly );
    while (!ts.eof()) {
      const QString txt(ts.readLine());
      const QFont font(KGlobalSettings::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 QString &nodeName)
{
  /* This is slow. I could make it O(1) be using the tokenizer of khtml but I don't
   * think it's worth it.
   */

  QColor namedColor(palette().active().text());
  QString 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(QListViewItem *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, SIGNAL(search()), this, 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(QPtrList<QListViewItem> &items, QPtrList<QListViewItem> &/*afterFirst*/, QPtrList<QListViewItem> &afterNow)
{
  MultiCommand *cmd = new MultiCommand(i18n("Move Nodes"));
  _refreshed = false;
    
  QPtrList<QListViewItem>::Iterator it = items.begin();
  QPtrList<QListViewItem>::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() : QString()) << "=" << (anitem ? anitem->node().nodeValue().string() : QString()) << 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 QString& 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 QString& searchText,
				  bool caseSensitive)
{
  const QString 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()) {
    QFile file(url.path());

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

    if (file.open(IO_WriteOnly) ) {
      kdDebug(90180) << "Opened File: " << url.url() << endl;
      m_textStream = new QTextStream(&file); //(stdOut)
      saveTreeAsHTML(part->document());
      file.close();
      kdDebug(90180) << "File closed " << endl;
      delete m_textStream;
    } else {
      const QString title = i18n( "Unable to Open File" );
      const QString text = i18n( "Unable to open \n %1 \n for writing" ).arg(url.path());
      KMessageBox::sorry( this, text, title );
    }
  } else {
    const QString title = i18n( "Invalid URL" );
    const QString 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 QString nodeName(pNode.nodeName().string());
  QString text;
  QString 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 khtml internal tags '-konq..'
       * Approximating it with <DIV>
       */
      text += "<DIV> <!-- -KONG_BLOCK -->";
    } else {
      text += "<" + nodeName;

      QString 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());
  
  QTimer::singleShot(0, this, SLOT(slotRestoreScrollOffset()));
  _refreshed = true;
}

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

void DOMTreeView::decreaseExpansionDepth()
{
  if (!part) return;
  if (m_expansionDepth > 0) {
    --m_expansionDepth;
    adjustDepth();
    updateIncrDecreaseButton();
  } else {
    QApplication::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(QListViewItem *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 QString &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(QListViewItem */*lvi*/, const QPoint &pos, int /*col*/)
{
  QPopupMenu *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"));
  QListViewItemIterator it(m_listView, QListViewItemIterator::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 (QListViewItem *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, SIGNAL(nodeActivated(const DOM::Node &)), this,
	  SLOT(activateNode(const DOM::Node &)));
    connect(part, SIGNAL(completed()), this, 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;

  QString qname;
  QString namespc;
  SignalReceiver addBefore;
    
  {
    ElementEditDialog dlg(this, "ElementEditDialog", true);
    connect(dlg.insBeforeBtn, SIGNAL(clicked()), &addBefore, SLOT(slot()));
    
    // ### activate when namespaces are supported
    dlg.elemNamespace->setEnabled(false);
  
    if (dlg.exec() != QDialog::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;

  QString text;
  SignalReceiver addBefore;
    
  {
    TextEditDialog dlg(this, "TextEditDialog", true);
    connect(dlg.insBeforeBtn, SIGNAL(clicked()), &addBefore, SLOT(slot()));
    
    if (dlg.exec() != QDialog::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 QString *clickToAdd;

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

  bool _new;

public:
  AttributeListItem(QListView *parent, QListViewItem *prev)
  : super(parent, prev), _new(true)
  {
  }

  AttributeListItem(const QString &attrName, const QString &attrValue,
		QListView *parent, QListViewItem *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(QListViewItem *item, int column, bool ascend) const
  {
    return _new ? 1 : super::compare(item, column, ascend);
  } 

protected:
  virtual void paintCell( QPainter *p, const QColorGroup &cg,
			  int column, int width, int alignment )
  {
    bool updates_enabled = listView()->isUpdatesEnabled();
    listView()->setUpdatesEnabled(false);
    
    QColor c = cg.text();
    bool text_changed = false;
    QString oldText;
    
    if (_new) {
      c = QApplication::palette().color( QPalette::Disabled, QColorGroup::Text );
      
      if (!clickToAdd) clickToAdd = new QString(i18n("<Click to add>"));
      oldText = text(column);
      text_changed = true;
      if (column == 0) setText(0, *clickToAdd); else setText(1, QString());
    }

    QColorGroup _cg( cg );
    _cg.setColor( QColorGroup::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, SIGNAL(clicked(QListViewItem *)),
	  SLOT(initializeOptionsFromListItem(QListViewItem *)));

  connect(nodeAttributes, SIGNAL(itemRenamed(QListViewItem *, const QString &, int)),
	SLOT(slotItemRenamed(QListViewItem *, const QString &, int)));
  connect(nodeAttributes, SIGNAL(executed(QListViewItem *, const QPoint &, int)),
	  SLOT(slotEditAttribute(QListViewItem *, const QPoint &, int)));
  connect(nodeAttributes, SIGNAL(contextMenuRequested(QListViewItem *, const QPoint &, int)),
  	SLOT(showInfoPanelContextMenu(QListViewItem *, const QPoint &, int)));

  connect(applyContent, SIGNAL(clicked()), SLOT(slotApplyContent()));

  ManipulationCommand::connect(SIGNAL(nodeChanged(const DOM::Node &)), this, 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(QString::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(QListViewItem *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)
{
  QListViewItem *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;
    QListViewItem *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(QListViewItem *lvi, const QString &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, QString()); break; }
      
      ChangeAttributeValueCommand *cmd = new ChangeAttributeValueCommand(
      			  element, item->text(0), str);
      mainWindow()->executeAndAddCommand(cmd);
      break;
    }
  }
}

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

  QString attrName = lvi->text(0);
  QString 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 == QDialog::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(QListViewItem */*lvi*/, const QPoint &pos, int /*col*/)
{
  QPopupMenu *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"));
  QListViewItemIterator it(nodeAttributes, QListViewItemIterator::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"
