You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1227 lines
34 KiB
1227 lines
34 KiB
/***************************************************************************
|
|
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 <dom/html_base.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();
|
|
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 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::Continue != KMessageBox::warningContinueCancel(this, text, title, i18n("Overwrite") ) ) {
|
|
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"
|