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.
tdewebdev/quanta/src/document.cpp

3193 lines
96 KiB

/***************************************************************************
document.cpp - description
-------------------
begin : Tue Jun 6 2000
copyright : (C) 2000 by Dmitry Poplavsky & Alexander Yakovlev & Eric Laffoon <pdima@users.sourceforge.net,yshurik@penguinpowered.com,sequitur@easystreet.com>
(C) 2001-2004 Andras Mantia <amantia@kde.org>
***************************************************************************/
/***************************************************************************
* *
* 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 <list>
#include <cctype>
#include <cstdlib>
#include <stdlib.h>
//QT includes
#include <tqcheckbox.h>
#include <tqdir.h>
#include <tqeventloop.h>
#include <tqfile.h>
#include <tqfileinfo.h>
#include <tqlineedit.h>
#include <tqtextcodec.h>
#include <tqtextstream.h>
#include <tqregexp.h>
#include <tqradiobutton.h>
// KDE includes
#include <kapplication.h>
#include <kwin.h>
#include <klocale.h>
#include <kaction.h>
#include <kactionclasses.h>
#include <kdialogbase.h>
#include <kiconloader.h>
#include <kmdcodec.h>
#include <kmessagebox.h>
#include <ktempfile.h>
#include <kdirwatch.h>
#include <kdebug.h>
#include <kprogress.h>
#include <kio/netaccess.h>
#include <kstandarddirs.h>
#include <ktexteditor/document.h>
#include <ktexteditor/view.h>
#include <ktexteditor/cursorinterface.h>
#include <ktexteditor/clipboardinterface.h>
#include <ktexteditor/codecompletioninterface.h>
#include <ktexteditor/configinterface.h>
#include <ktexteditor/editinterface.h>
#include <ktexteditor/editinterfaceext.h>
#include <ktexteditor/encodinginterface.h>
#include <ktexteditor/selectioninterface.h>
#include <ktexteditor/selectioninterfaceext.h>
#include <ktexteditor/viewcursorinterface.h>
#include <ktexteditor/wordwrapinterface.h>
#include <ktexteditor/markinterfaceextension.h>
#include <kate/view.h>
#include "tag.h"
#include "quantacommon.h"
#include "document.h"
#include "resource.h"
#include "dirtydlg.h"
#include "dirtydialog.h"
#include "casewidget.h"
#include "project.h"
#include "dtdselectdialog.h"
#include "quanta.h"
#include "quantaview.h"
#include "structtreeview.h"
#include "qextfileinfo.h"
#include "viewmanager.h"
#include "messageoutput.h"
#include "undoredo.h"
#include "dtds.h"
#include "sagroupparser.h"
#define STEP 1
extern GroupElementMapList globalGroupMap;
Document::Document(KTextEditor::Document *doc,
TQWidget *parent, const char *name, WFlags f )
: TQWidget(parent, name, f)
{
m_dirty = false;
busy = false;
changed = false;
m_md5sum = "";
m_doc = doc;
m_view = 0L; //needed, because createView() calls processEvents() and the "this" may be deleted before m_view gets a value => crash on delete m_view;
m_view = m_doc->createView(parent, 0L);
completionInProgress = false;
argHintVisible = false;
completionRequested = false;
userTagList.setAutoDelete(true);
// remove the unwanted actions
KAction *a = m_view->actionCollection()->action( "file_export" );
if (a)
m_view->actionCollection()->take(a);
a = m_view->actionCollection()->action( "file_save" );
if (a)
m_view->actionCollection()->take(a);
a = m_view->actionCollection()->action( "file_save_as" );
if (a)
m_view->actionCollection()->take(a);
a = m_view->actionCollection()->action( "file_reload" );
if (a)
m_view->actionCollection()->take(a);
a = m_view->actionCollection()->action( "edit_undo" );
if (a)
m_view->actionCollection()->take(a);
a = m_view->actionCollection()->action( "edit_redo" );
if (a)
m_view->actionCollection()->take(a);
//because they are not implemented in VPL
a = m_view->actionCollection()->action( "edit_copy" );
if (a)
m_view->actionCollection()->take(a);
a = m_view->actionCollection()->action( "edit_cut" );
if (a)
m_view->actionCollection()->take(a);
a = m_view->actionCollection()->action( "edit_paste" );
if (a)
m_view->actionCollection()->take(a);
//conflicting shortcuts, so change them
a = m_view->actionCollection()->action("view_border");
if (a)
a->setShortcut(Qt::SHIFT + Qt::Key_F9);
a = m_view->actionCollection()->action("view_folding_markers");
if (a)
a->setShortcut(Qt::SHIFT + Qt::Key_F11);
KActionMenu *bookmarkAction = dynamic_cast<KActionMenu*>(m_view->actionCollection()->action( "bookmarks" ));
if (bookmarkAction)
{
m_view->actionCollection()->take(bookmarkAction);
//kdDebug(24000) << "Bookmarks found!" << endl;
//bookmarkAction->insert(quantaApp->actionCollection()->action( "file_quit" ));
}
editIf = dynamic_cast<KTextEditor::EditInterface *>(m_doc);
editIfExt = dynamic_cast<KTextEditor::EditInterfaceExt *>(m_doc);
encodingIf = dynamic_cast<KTextEditor::EncodingInterface*>(m_doc);
m_encoding = quantaApp->defaultEncoding();
if (encodingIf)
m_encoding = encodingIf->encoding();
if (m_encoding.isEmpty())
m_encoding = "utf8"; //final fallback
m_codec = TQTextCodec::codecForName(m_encoding.ascii());
selectionIf = dynamic_cast<KTextEditor::SelectionInterface *>(m_doc);
selectionIfExt = dynamic_cast<KTextEditor::SelectionInterfaceExt *>(m_doc);
configIf = dynamic_cast<KTextEditor::ConfigInterface*>(m_doc);
if (configIf)
configIf->readConfig();
viewCursorIf = dynamic_cast<KTextEditor::ViewCursorInterface *>(m_view);
codeCompletionIf = dynamic_cast<KTextEditor::CodeCompletionInterface *>(m_view);
markIf = dynamic_cast<KTextEditor::MarkInterface *>(m_doc);
KTextEditor::MarkInterfaceExtension* iface = dynamic_cast<KTextEditor::MarkInterfaceExtension*>(m_doc);
if (iface)
{
iface->setPixmap(KTextEditor::MarkInterface::markType07, SmallIcon("stop"));
iface->setPixmap(KTextEditor::MarkInterface::markType02, SmallIcon("debug_breakpoint"));
iface->setDescription(KTextEditor::MarkInterface::markType02, i18n("Breakpoint"));
iface->setPixmap(KTextEditor::MarkInterface::markType05, SmallIcon("debug_currentline"));
iface->setDescription(KTextEditor::MarkInterface::markType08, i18n("Annotation"));
iface->setPixmap(KTextEditor::MarkInterface::markType08, SmallIcon("stamp"));
// This is allows user to set breakpoints and bookmarks by clicking or rightclicking on the icon border.
iface->setMarksUserChangable(KTextEditor::MarkInterface::markType01 + KTextEditor::MarkInterface::markType02);
}
tempFile = 0;
m_tempFileName = TQString::null;
dtdName = Project::ref()->defaultDTD();
reparseEnabled = true;
repaintEnabled = true;
delayedTextChangedEnabled = true;
docUndoRedo = new undoRedo(this);
//path of the backup copy file of the document
m_backupPathValue = TQString::null;
connect( m_doc, TQT_SIGNAL(charactersInteractivelyInserted (int ,int ,const TQString&)),
this, TQT_SLOT(slotCharactersInserted(int ,int ,const TQString&)) );
connect( m_view, TQT_SIGNAL(completionAborted()),
this, TQT_SLOT( slotCompletionAborted()) );
connect( m_view, TQT_SIGNAL(completionDone(KTextEditor::CompletionEntry)),
this, TQT_SLOT( slotCompletionDone(KTextEditor::CompletionEntry)) );
connect( m_view, TQT_SIGNAL(filterInsertString(KTextEditor::CompletionEntry*,TQString *)),
this, TQT_SLOT( slotFilterCompletion(KTextEditor::CompletionEntry*,TQString *)) );
connect( m_doc, TQT_SIGNAL(textChanged()), TQT_SLOT(slotTextChanged()));
connect(m_view, TQT_SIGNAL(gotFocus(Kate::View*)), TQT_SIGNAL(editorGotFocus()));
connect(fileWatcher, TQT_SIGNAL(dirty(const TQString&)), TQT_SLOT(slotFileDirty(const TQString&)));
// connect(m_doc, TQT_SIGNAL(marksChanged()), this, TQT_SLOT(slotMarksChanged()));
connect(m_doc, TQT_SIGNAL(markChanged(KTextEditor::Mark, KTextEditor::MarkInterfaceExtension::MarkChangeAction)), this, TQT_SLOT(slotMarkChanged(KTextEditor::Mark, KTextEditor::MarkInterfaceExtension::MarkChangeAction)));
}
Document::~Document()
{
if (configIf)
configIf->writeConfig();
parser->clearGroups();
// kdDebug(24000) << "Document::~ Document: " << this << endl;
m_doc->closeURL(false); //TODO: Workaround for a Kate bug. Remove when KDE < 3.2.0 support is dropped.
delete m_doc;
}
void Document::setUntitledUrl(const TQString &url)
{
untitledUrl = url;
openURL(KURL());
}
bool Document::isUntitled()
{
return (m_doc->url().url().isEmpty()) ? true : false;
}
KURL Document::url()
{
return ( isUntitled() ) ? KURL("file:"+untitledUrl) : m_doc->url();
}
// kwrite addons
void Document::insertTag(const TQString &s1, const TQString &s2)
{
TQString selection;
if (selectionIf && selectionIf->hasSelection())
{
reparseEnabled = false;
selection = selectionIf->selection();
selectionIf->removeSelectedText();
reparseEnabled = true;
}
insertText(s1 + selection);
insertText(s2, false); // don't adjust cursor, thereby leaving it in the middle of tag
}
/** Change the current tag's attributes with those from dict */
void Document::changeTag(Tag *tag, TQDict<TQString> *dict )
{
tag->modifyAttributes(dict);
TQString tagStr = tag->toString();
reparseEnabled = false;
int bLine, bCol, eLine, eCol;
tag->beginPos(bLine,bCol);
tag->endPos(eLine,eCol);
editIf->removeText(bLine, bCol, eLine, eCol+1);
viewCursorIf->setCursorPositionReal((uint)bLine, (uint)bCol);
insertText(tagStr);
}
/**Change the namespace in a tag. Add if it's not present, or remove if the
namespace argument is empty*/
void Document::changeTagNamespace(Tag *tag, const TQString& nameSpace)
{
int bl, bc;
int nl, nc;
if (!tag->nameSpace.isEmpty())
{
tag->beginPos(bl, bc);
if (tag->type == Tag::XmlTagEnd)
bc++;
tag->namePos(nl, nc);
reparseEnabled = false;
editIf->removeText(bl, bc + 1, nl, nc);
reparseEnabled = true;
} else
{
tag->beginPos(bl, bc);
if (tag->type == Tag::XmlTagEnd)
bc++;
}
if (!nameSpace.isEmpty())
{
viewCursorIf->setCursorPositionReal((uint)bl, (uint)(bc + 1));
insertText(nameSpace + ":", true, false);
}
slotDelayedTextChanged(true);
quantaApp->slotNewLineColumn();
}
/**Change the attr value of the called attrName to attrValue*/
void Document::changeTagAttribute(Tag *tag, const TQString& attrName, const TQString& attrValue)
{
TQString value;
int line, col;
int index = tag->attributeIndex(attrName);
if (index != -1)
{
int endCol;
value = tag->attributeValue(index);
if (value == attrValue)
return;
int aLine, aCol;
tag->attributeNamePos(index, aLine, aCol);
tag->attributeValuePos(index, line, col);
if (line == aLine && col == aCol)
{
col += tag->attribute(index).length();
value = TQString("=") + qConfig.attrValueQuotation + attrValue + qConfig.attrValueQuotation;
} else
{
endCol = col + value.length();
if (attrValue.isEmpty())
{
tag->attributeNamePos(index, line, col);
endCol++;
}
reparseEnabled = false;
TQString textLine = editIf->textLine(line);
while (col > 1 && textLine[col-1].isSpace())
col--;
editIf->removeText(line, col, line, endCol);
reparseEnabled = true;
value = attrValue;
}
} else
{
index = tag->attrCount() - 1;
if (tag->attribute(index) == "/")
{
tag->attributeNamePos(index, line, col);
col--;
} else
{
tag->endPos(line, col);
}
if (attrValue.isEmpty())
{
value = "";
} else
{
value = " " + QuantaCommon::attrCase(attrName) + "=" + qConfig.attrValueQuotation + attrValue + qConfig.attrValueQuotation;
}
}
if (!value.isEmpty())
{
viewCursorIf->setCursorPositionReal((uint)line, (uint)col);
insertText(value);
}
quantaApp->slotNewLineColumn();
//else
// slotDelayedTextChanged();
}
void Document::selectText(int x1, int y1, int x2, int y2 )
{
if (selectionIf)
selectionIf->setSelection(x1, y1, x2, y2);
}
void Document::replaceSelected(const TQString &s)
{
if (selectionIf)
{
unsigned int line, col;
viewCursorIf->cursorPositionReal(&line, &col);
reparseEnabled = false;
selectionIf->removeSelectedText();
reparseEnabled = true;
editIf->insertText(line, col, s);
}
}
void Document::insertFile(const KURL& url)
{
TQString fileName;
if (url.isLocalFile())
{
fileName = url.path();
} else
{
if (!KIO::NetAccess::download(url, fileName, this))
{
KMessageBox::error(this, i18n("<qt>Cannot download <b>%1</b>.</qt>").arg( url.prettyURL(0, KURL::StripFileProtocol)));
return;
}
}
TQFile file(fileName);
if (file.open(IO_ReadOnly))
{
TQTextStream stream( &file );
stream.setEncoding(TQTextStream::UnicodeUTF8);
insertText(stream.read());
file.close();
} else
KMessageBox::error(this, i18n("<qt>Cannot open <b>%1</b> for reading.</qt>").arg(url.prettyURL(0, KURL::StripFileProtocol)));
}
/** Inserts text at the current cursor position */
void Document::insertText(const TQString &a_text, bool adjustCursor, bool reparse)
{
TQString text = a_text;
if(text.isEmpty())
return;
reparseEnabled = false;
unsigned int line, col;
viewCursorIf->cursorPositionReal(&line, &col);
Node *n = parser->nodeAt(line, col, true);
if (n && n->tag->dtd()->family != Xml)
{
int bLine, bCol;
n->tag->beginPos(bLine, bCol);
TQString s = this->text(bLine, bCol, line, col);
bool insideQuotes = false;
for (int i = 0 ; i < (int)s.length() - 1; i++)
{
if (s[i] == '"' && (i == 0 || s[i-1] != '\\'))
insideQuotes = !insideQuotes;
}
int eLine, eCol;
n->tag->endPos(eLine, eCol);
s = this->text(line + 1, col, eLine, eCol);
bool closeQuotationFound = false;
for (int i = 0 ; i < (int)s.length() - 1; i++)
{
if (s[i] == '"' && (i == 0 || s[i-1] != '\\'))
{
closeQuotationFound = true;
break;
}
}
if (insideQuotes && closeQuotationFound)
{
text.replace("\\\"", "\"");
text.replace("\"", "\\\"");
}
}
editIf->insertText(line, col, text);
// calculate new cursor position
// counts the words and whitespace of the text so we can place the
// cursor correctly and quickly with the viewCursorInterace, avoiding
// the KTexEditor::insertText method
if(adjustCursor)
{
unsigned textLength = text.length();
unsigned int wordWrapAt = 80;
bool noWordWrap = false;
KTextEditor::WordWrapInterface *wordWrapIf = dynamic_cast<KTextEditor::WordWrapInterface *>(m_doc);
if (wordWrapIf)
{
wordWrapAt = wordWrapIf->wordWrapAt();
noWordWrap = !(wordWrapIf->wordWrap());
}
uint i=0, j=0;
int wordLength;
const char *ascii = text.latin1(); // use ascii for maximum speed
bool lineLock =false;
while(i < textLength)
{
if(ascii[i] == '\n') // add a line, first column
{
++line; col=0; ++i; lineLock = false;
}
else if(ascii[i] == '\r')
{
col = 0; ++i;
}
else if(!noWordWrap && !(isspace(ascii[i]))) // new word, see if it wraps
{
// TOO SLOW int wordLength = (text.mid(i).section(TQRegExp("[ \t\r\n]"), 0, 0).length());
wordLength = -1;
for(j = i+1;ascii[j];++j) // get word size, ascii is MUCH faster
{
if(isspace(ascii[j]))
{
wordLength = j-i;
break;
}
}
if(wordLength == -1)
wordLength = (textLength)-i;
if((wordLength+col) > wordWrapAt)
{
if(col && !lineLock) // wraps onto new line unless locked by kate
{
col=0;
++line;
}
}
col += wordLength;
i += wordLength;
if(wordLength > (int) wordWrapAt)
lineLock = true; // words > wordWrapAt lock the rest of the line
}
else // whitespace
{
++col; ++i;
if(!noWordWrap)
if(col > wordWrapAt && !lineLock) // wrap like words
{
col -= wordWrapAt;
++line;
}
}
}
}
viewCursorIf->setCursorPositionReal(line, col);
reparseEnabled = true;
if (reparse)
{
baseNode = parser->rebuild(this);
if (qConfig.instantUpdate && quantaApp->structTreeVisible())
{
typingInProgress = false;
StructTreeView::ref()->slotReparse(this, baseNode , qConfig.expandLevel);
}
quantaApp->updateTreeViews();
}
}
bool Document::insertChildTags(QTag *tag, QTag *lastTag)
{
bool childInserted = false;
if (!tag || tag == lastTag) //avoid infinite recursion
{
return false;
}
TQMap<TQString, bool>::Iterator it;
for (it = tag->childTags.begin(); it != tag->childTags.end(); ++it)
{
if (it.data())
{
childInserted = true;
QTag *childTag = QuantaCommon::tagFromDTD(tag->parentDTD, it.key());
TQString tagStr =QuantaCommon::tagCase(it.key());
if ( tag->parentDTD->singleTagStyle == "xml" &&
( childTag->isSingle() ||
(childTag->isOptional() && !qConfig.closeOptionalTags)) )
{
insertText("<" +tagStr + "/>", true, false);
} else
{
insertText("<" +tagStr + ">", true, false);
}
TQString closingStr;
if (insertChildTags(childTag, tag))
{
closingStr = "";
}
if ( (!childTag->isSingle() && !childTag->isOptional() && qConfig.closeTags) ||
(childTag->isOptional() && qConfig.closeOptionalTags) )
{
insertText(closingStr + "</" + tagStr + ">", true, false);
}
}
}
return childInserted;
}
/** Get the view of the document */
KTextEditor::View* Document::view()
{
return m_view;
}
/** Get the KTextEditor::Document of the document */
KTextEditor::Document* Document::doc()
{
return m_doc;
}
/** Returns true if the document was modified. */
bool Document::isModified()
{
bool modified = false;
if (m_doc)
modified = m_doc->isModified();
return modified;
}
void Document::setModified(bool flag)
{
if (m_doc)
m_doc->setModified(flag);
}
void Document::createTempFile()
{
closeTempFile();
tempFile = new KTempFile(tmpDir);
tempFile->setAutoDelete(true);
m_tempFileName = TQFileInfo(*(tempFile->file())).filePath();
TQString encoding = quantaApp->defaultEncoding();
if (encodingIf)
encoding = encodingIf->encoding();
if (encoding.isEmpty())
encoding = "utf8"; //final fallback
tempFile->textStream()->setCodec(TQTextCodec::codecForName(encoding.ascii()));
* (tempFile->textStream()) << editIf->text();
m_tempFileName = TQFileInfo(*(tempFile->file())).filePath();
tempFile->close();
// kdDebug(24000) << "Creating tempfile " << m_tempFileName << " for " << url() << endl;
}
void Document::closeTempFile()
{
if (tempFile != 0)
{
delete tempFile;
tempFile = 0L;
}
if (TQFileInfo(m_tempFileName).exists())
TQFile::remove(m_tempFileName);
m_tempFileName = TQString::null;
}
TQString Document::tempFileName()
{
return m_tempFileName;
}
/** This will return the current tag name at the given position.
It will work even if the tag has not been completed yet. An
empty string will be returned if no tag is found.
*/
TQString Document::getTagNameAt(int line, int col )
{
TQString name = "";
TQString textLine = editIf->textLine(line);
textLine = textLine.left(col);
while (line >= 0)
{
QuantaCommon::removeCommentsAndQuotes(textLine, completionDTD);
int pos = textLine.findRev("<");
int pos2 = textLine.findRev(">");
if (pos != -1 && pos2 < pos)
{
textLine.remove(0, pos + 1);
pos = 0;
while (pos < (int)textLine.length() &&
!textLine[pos].isSpace() &&
textLine[pos] != '>')
pos++;
name = textLine.left(pos).stripWhiteSpace();
pos = name.find(":");
if (pos != -1)
name = name.mid(pos + 1);
break;
} else
{
if (pos2 == -1)
{
line--;
if (line >= 0)
textLine = editIf->textLine(line);
} else
{
name = "";
break;
}
}
}
return name;
}
/** Show the code completions passed in as an argument */
void Document::showCodeCompletions( TQValueList<KTextEditor::CompletionEntry> *completions ) {
bool reparse = reparseEnabled;
reparseEnabled = false;
codeCompletionIf->showCompletionBox( *completions, false );
reparseEnabled = reparse;
argHintVisible = false;
delete completions;
}
/** Once the completed text has been inserted into the document we
want to update the cursor position.
*/
void Document::slotCompletionDone( KTextEditor::CompletionEntry completion )
{
unsigned int line,col;
completionInProgress = false;
argHintVisible = false;
viewCursorIf->cursorPositionReal(&line,&col);
const DTDStruct* dtd = currentDTD();
/* if (completion.type == "charCompletion")
{
m_lastCompletionList = getCharacterCompletions(completion.userdata);
TQTimer::singleShot(0, this, TQT_SLOT(slotDelayedShowCodeCompletion()));
} else*/
if (completion.type == "attribute")
{
viewCursorIf->setCursorPositionReal(line,col-1);
if (dtd)
{
QTag *tag = QuantaCommon::tagFromDTD(dtd,completion.userdata);
if (tag)
{
m_lastCompletionList = getAttributeValueCompletions(tag->name(), completion.text);
TQTimer::singleShot(0, this, TQT_SLOT(slotDelayedShowCodeCompletion()));
}
}
} else
if (completion.type == "attributeValue")
{
viewCursorIf->setCursorPositionReal(line, col);
} else
if (completion.type == "doctypeList")
{
viewCursorIf->setCursorPositionReal(line,col+1);
} else
if (completion.type == "script")
{
viewCursorIf->setCursorPositionReal(line,col);
if (dtd)
{
m_lastLine = line;
m_lastCol = col - 1;
TQTimer::singleShot(0, this, TQT_SLOT(slotDelayedScriptAutoCompletion()));
}
}
}
void Document::slotDelayedScriptAutoCompletion()
{
scriptAutoCompletion(m_lastLine, m_lastCol, "");
}
void Document::slotDelayedShowCodeCompletion()
{
showCodeCompletions(m_lastCompletionList);
}
/** This is called when the user selects a completion. We
can filter this completion to allow more intelligent
code compeltions
*/
void Document::slotFilterCompletion( KTextEditor::CompletionEntry *completion ,TQString *string )
{
kdDebug(24000) << *string << endl;
kdDebug(24000) << completion->userdata << endl;
int pos = completion->userdata.find("|");
TQString s = completion->userdata.left(pos);
completion->userdata.remove(0,pos+1);
string->remove(0, s.length());
kdDebug(24000) << *string << endl;
kdDebug(24000) << completion->userdata << endl;
if (completion->type == "charCompletion")
{
*string = completion->userdata;
uint line, col;
viewCursorIf->cursorPositionReal(&line, &col);
TQString s2 = editIf->textLine(line).left(col);
kdDebug(24000) << s2 << endl;
int pos = s2.findRev('&');
if (pos != -1)
{
s2 = s2.mid(pos + 1);
string->remove(s2);
}
string->append(";");
kdDebug(24000) << *string << endl;
} else
if ( completion->type == "attributeValue")
{
uint line, col;
viewCursorIf->cursorPositionReal(&line, &col);
TQString textLine = editIf->textLine(line);
TQChar tagSeparator = completionDTD->tagSeparator;
if (tagSeparator == '\'' || tagSeparator =='"')
tagSeparator = qConfig.attrValueQuotation;
if (textLine[col] != tagSeparator)
string->append(tagSeparator);
} else
if ( completion->type == "attribute" )
{
string->append("="+TQString(qConfig.attrValueQuotation)+TQString(qConfig.attrValueQuotation));
} else
if (completion->type == "doctypeList")
{
s = *string;
string->remove(0, string->length());
TQString s2 = TQString("public \""+DTDs::ref()->getDTDNameFromNickName(s)+"\"");
const DTDStruct *dtd = DTDs::ref()->find(DTDs::ref()->getDTDNameFromNickName(s));
if (dtd && !dtd->url.isEmpty())
{
s2 += " \""+dtd->url+"\"";
}
string->append(QuantaCommon::attrCase(s2));
} else
if (completion->type == "script")
{
string->append(completionDTD->attrAutoCompleteAfter);
}
}
void Document::slotReplaceChar()
{
reparseEnabled = false;
editIf->removeText(m_replaceLine, m_replaceCol, m_replaceLine, m_replaceCol+1);
insertText(m_replaceStr, true, false);
}
/** Called when a user types in a character. From this we can show possibile
completions based on what they are trying to input.
*/
void Document::slotCharactersInserted(int line, int column, const TQString& string)
{
if (qConfig.replaceNotInEncoding)
{
if (encodingIf)
{
TQString encoding = encodingIf->encoding();
if (encoding != m_encoding)
{
m_encoding = encoding;
m_codec = TQTextCodec::codecForName(encoding.ascii());
}
if (!m_codec->canEncode(string[0]))
{
m_replaceLine = line;
m_replaceCol = column;
m_replaceStr = QuantaCommon::encodedChar(string[0].unicode());
TQTimer::singleShot(0, this, TQT_SLOT(slotReplaceChar()));
return;
}
}
}
if (qConfig.replaceAccented)
{
uint c = string[0].unicode();
if (c > 191)
{
m_replaceLine = line;
m_replaceCol = column;
m_replaceStr = QuantaCommon::encodedChar(c);
TQTimer::singleShot(0, this, TQT_SLOT(slotReplaceChar()));
return;
}
}
if ( (string == ">") ||
(string == "<") )
{
slotDelayedTextChanged(true);
}
bool handled = false;
if (qConfig.useAutoCompletion)
{
if (completionInProgress)
{
handleCodeCompletion();
} else
{
completionDTD = currentDTD();
if (completionDTD->family == Xml)
{
handled = xmlAutoCompletion(line, column, string);
}
if (completionDTD->family == Script)
{
handled = scriptAutoCompletion(line, column, string);
if (!handled && string == ">")
{
Node *node = parser->nodeAt(line, column, false);
if (node && node->tag->validXMLTag && node->tag->type == Tag::ScriptTag)
{
column++;
editIf->insertText(line, column, "</" + node->tag->name + ">");
viewCursorIf->setCursorPositionReal( line, column );
}
}
handled = true;
}
if (!handled)
{
const DTDStruct *lastDTD = completionDTD;
completionDTD = defaultDTD();
if (lastDTD != completionDTD && completionDTD->family == Xml)
{
handled = xmlAutoCompletion(line, column, string);
}
/*TODO: Can the default DTD be a script?
if (dtd->family == Script)
{
scriptAutoCompletion(dtd, line, column, string);
}
*/
}
}
}
}
/** Called whenever a user inputs text in an XML type document.
Returns true if the code completionw as handled.
*/
bool Document::xmlAutoCompletion(int line, int column, const TQString & string)
{
QTag *tag;
TQString tagName;
bool handled = false;
tagName = getTagNameAt(line, column);
tag = QuantaCommon::tagFromDTD(completionDTD, tagName);
if (!tag && !tagName.isEmpty())
tag = userTagList.find(tagName.lower());
TQString s = editIf->textLine(line).left(column + 1);
bool namespacecompletion = false;
if (!tagName.isEmpty() && string ==":" && s.endsWith("<" + tagName + ":"))
namespacecompletion = true;
int i = column;
while (i > 0 && s[i].isSpace())
i--;
s = s.left(i + 1);
if ( !tag || tagName.isEmpty() || namespacecompletion) //we are outside of any tag
{
if (s.endsWith(completionDTD->tagAutoCompleteAfter) ||
namespacecompletion) // a tag is started, either with < or <namespace:
{
//we need to complete a tag name
showCodeCompletions( getTagCompletions(line, column + 1) );
handled = true;
} else
if (string == ">" && !tagName.isEmpty() && tagName[0] != '!' && tagName[0] != '?' &&
tagName[0] != '/' && !tagName.endsWith("/") && !s.endsWith("/>") &&
qConfig.closeTags &&
currentDTD(true)->family == Xml) //close unknown tags
{
//add closing tag if wanted
column++;
editIf->insertText(line, column, "</" + tagName + ">");
docUndoRedo->dontAddModifsSet(2);
viewCursorIf->setCursorPositionReal( line, column );
handled = true;
} else
if (string == "/" && s.endsWith("</") && tagName.isEmpty())
{
Node *node = parser->nodeAt(line, column, false);
if (node && node->parent )
{
node = node->parent;
if (node->tag->type == Tag::XmlTag && (!node->next || !QuantaCommon::closesTag(node->tag, node->next->tag)))
{
TQString name = node->tag->name;
name = name.left(name.find(" | "));
if (!node->tag->nameSpace.isEmpty())
name.prepend(node->tag->nameSpace + ":");
editIf->insertText(line, column + 1, name + ">");
docUndoRedo->dontAddModifsSet(2);
viewCursorIf->setCursorPositionReal( line, column + name.length() + 2);
handled = true;
}
}
}
}
else // we are inside of a tag
{
if ( string == ">" && tagName[0] != '/' && !tagName.endsWith("/") &&
!s.endsWith("/>") && tag)
{
if ( tag->parentDTD->singleTagStyle == "xml" &&
(tag->isSingle() || (!qConfig.closeOptionalTags && tag->isOptional()))
)
{
editIf->insertText(line, column, " /");
docUndoRedo->dontAddModifsSet(2);
viewCursorIf->setCursorPositionReal( line, column+3 );
handled = true;
}
if ( ( !tag->isSingle() && !tag->isOptional() && qConfig.closeTags) ||
( tag->isOptional() && qConfig.closeOptionalTags ) )
{
//add closing tag if wanted
Node *node = parser->nodeAt(line, column, false);
if (node && (!node->next || !QuantaCommon::closesTag(node->tag, node->next->tag)))
{
if (node && !node->tag->nameSpace.isEmpty())
tagName.prepend(node->tag->nameSpace + ":");
column++;
editIf->insertText(line, column, "</" + tagName + ">");
docUndoRedo->dontAddModifsSet(2);
viewCursorIf->setCursorPositionReal( line, column );
handled = true;
}
}
if (!tag->childTags.isEmpty())
{
reparseEnabled = false;
// insertText("\n", false, false);
insertChildTags(tag);
reparseEnabled = true;
baseNode = parser->rebuild(this);
if (qConfig.instantUpdate && quantaApp->structTreeVisible())
{
typingInProgress = false;
StructTreeView::ref()->slotReparse(this, baseNode , qConfig.expandLevel);
}
}
}
else if ( string == " " )
{
TQString textLine = editIf->textLine(line);
if (!QuantaCommon::insideCommentsOrQuotes(column, textLine, completionDTD))
{
showCodeCompletions(getAttributeCompletions(tagName, ""));
handled = true;
}
}
else if ( string[0] == qConfig.attrValueQuotation )
{
//we need to find the attribute name
TQString textLine = editIf->textLine(line).left(column-1);
TQString attribute = textLine.mid(textLine.findRev(' ')+1);
if (attribute == "style" && completionDTD->insideDTDs.contains("css"))
{
completionDTD = DTDs::ref()->find("text/css");
completionRequested = true;
return scriptAutoCompletion(line, column + 1, string);
}
showCodeCompletions( getAttributeValueCompletions(tagName, attribute) );
handled = true;
}
} // else - we are inside of a tag
if (!handled)
{
//check if we are inside a style attribute, and use css autocompletion if we are
TQString textLine = editIf->textLine(line);
textLine = textLine.left(column);
int pos = textLine.findRev('"');
if (pos != -1)
{
pos = textLine.findRev(' ', pos);
if (pos != -1)
{
textLine = textLine.mid(pos + 1);
pos = textLine.find('=');
if (pos != -1)
{
TQString attribute = textLine.left(pos);
if (attribute == "style" && completionDTD->insideDTDs.contains("css"))
{
completionDTD = DTDs::ref()->find("text/css");
completionRequested = true;
return scriptAutoCompletion(line, column + 1, string);
}
}
}
}
TQString s = editIf->textLine(line).left(column + 1);
pos = s.findRev('&');
if (pos != -1)
{
//complete character codes
s = s.mid(pos + 1);
showCodeCompletions(getCharacterCompletions(s));
handled = true;
}
}
return handled;
}
/** Return a list of possible variable name completions */
TQValueList<KTextEditor::CompletionEntry>* Document::getGroupCompletions(Node *node, const StructTreeGroup& group, int line, int col)
{
TQValueList<KTextEditor::CompletionEntry> *completions = new TQValueList<KTextEditor::CompletionEntry>();
KTextEditor::CompletionEntry completion;
completion.type = "variable";
TQString textLine = editIf->textLine(line).left(col);
TQString word = findWordRev(textLine);
if (!group.removeFromAutoCompleteWordRx.pattern().isEmpty())
word.remove(group.removeFromAutoCompleteWordRx);
completion.userdata = word + "|";
GroupElementMapList::Iterator it;
TQString str = group.name;
str.append("|");
str.append(word);
for ( it = globalGroupMap.begin(); it != globalGroupMap.end(); ++it )
{
if (it.key().startsWith(str) && it.key() != str )
{
GroupElementList elementList = it.data();
for (uint i = 0; i < elementList.count(); i++)
{
if (elementList[i]->parentNode == 0L || elementList[i]->global)
{
completion.text = it.key().section('|', -1).stripWhiteSpace();
completions->append(completion);
break;
} else
{
Node *n = node;
while (n && n != elementList[i]->parentNode)
{
n = n->parent;
}
if (n == elementList[i]->parentNode)
{
completion.text = it.key().section('|', -1).stripWhiteSpace();
completions->append(completion);
break;
}
}
}
}
}
IncludedGroupElementsMap elements = parser->includedMap;
IncludedGroupElementsMap::Iterator it2;
for ( it2 = elements.begin(); it2 != elements.end(); ++it2 )
{
TQStringList list = it2.data()[group.name].keys();
list.sort();
for (uint i = 0; i < list.count(); i++)
{
if (list[i].startsWith(word) && list[i] != word)
{
completion.text = list[i].stripWhiteSpace();
completions->append(completion);
}
}
}
return completions;
}
bool Document::isDerivatedFrom(const TQString& className, const TQString &baseClass)
{
if (className.isEmpty() || !completionDTD->classInheritance.contains(className))
return false;
TQString parentClass = completionDTD->classInheritance[className];
int result = 0;
do {
if (parentClass == baseClass)
result = 1; //className extends baseClass
else
{
if (completionDTD->classInheritance.contains(parentClass))
parentClass = completionDTD->classInheritance[parentClass];
else
result = -1;//nothing was found in the inheritance list
}
} while (result == 0);
return (result == 1);
}
/** Return a list of possible tag name completions */
TQValueList<KTextEditor::CompletionEntry>* Document::getTagCompletions(int line, int col)
{
TQValueList<KTextEditor::CompletionEntry> *completions = new TQValueList<KTextEditor::CompletionEntry>();
KTextEditor::CompletionEntry completion;
switch (completionDTD->family)
{
case Xml: completion.type = "tag";
break;
case Script:
completion.type = "script";
break;
}
Node *node = parser->nodeAt(line, col);
if (node && node->tag->type != Tag::XmlTag)
node = node->parent;
if (node && node->tag->type != Tag::XmlTag)
node = 0L;
QTag *parentQTag= 0L;
if (node && node->parent)
parentQTag = QuantaCommon::tagFromDTD(node->parent);
TQString textLine = editIf->textLine(line).left(col);
TQString word = findWordRev(textLine, completionDTD).upper();
TQString classStr = "";
TQString objStr;
if (completionDTD->classGroupIndex != -1 && completionDTD->objectGroupIndex != -1)
{
textLine = textLine.left(textLine.length() - word.length());
int pos = completionDTD->memberAutoCompleteAfter.searchRev(textLine);
if (pos != -1)
{
textLine = textLine.left(pos);
TQRegExp *r = &(completionDTD->structTreeGroups[completionDTD->classGroupIndex].usageRx);
pos = r->searchRev(textLine);
if (pos != -1)
{
objStr = r->cap(1);
if (objStr == "this")
{
TQString parentGroupStr = "";
bool classFound = false;
parser->synchParseInDetail();
Node *n = parser->nodeAt(line, col);
while (n && !classFound)
{
//Need to parser for groups, as the node tree is rebuilt before
//autocompletion and none of the node has links to group elements
//at this position.
SAGroupParser *gParser = new SAGroupParser(0L, this, n, n->nextSibling(), true, false, false);
gParser->slotParseForScriptGroup();
GroupElementList::Iterator it = n->m_groupElements.begin();
while (it != n->m_groupElements.end())
{
GroupElement *e = *it;
if (parentGroupStr.isEmpty() && e->group->appendToTags)
{
parentGroupStr = e->group->parentGroup;
}
if (!parentGroupStr.isEmpty() && e->group->name == parentGroupStr)
{
classStr = e->tag->name;
classFound = true;
}
//detach the groupelement from the node
e->node = 0L;
e->group = 0L;
e->deleted = true;
it = n->m_groupElements.erase(it);
}
delete gParser;
n = n->parent;
}
} else
{
GroupElementList groupElementList = globalGroupMap[completionDTD->structTreeGroups[completionDTD->objectGroupIndex].name + "|" + objStr];
for (GroupElementList::Iterator it = groupElementList.begin(); it != groupElementList.end(); ++it)
{
if (!(*it)->tag)
continue;
#ifdef DEBUG_PARSER
kdDebug(24000) << "GroupElement: " << (*it) << " " << (*it)->tag->area().bLine << " " << (*it)->tag->area().bCol << " "<< (*it)->tag->area().eLine << " "<< (*it)->tag->area().eCol << " " << (*it)->tag->tagStr() << " " << (*it)->type << endl;
#endif
if (!(*it)->type.isEmpty())
{
classStr = (*it)->type;
break;
}
}
}
}
}
if ((!objStr.isEmpty() || !completionRequested) && classStr.isEmpty()) //the class cannot be identified for the object or there is no object.
return completions;
}
completion.userdata = word + "|";
TQStringList tagNameList;
TQMap<TQString, TQString> comments;
//A TQMap to hold the completion type (function/string/class/etc)
TQMap<TQString, TQString> type;
TQString tagName;
TQDictIterator<QTag> it(*(completionDTD->tagsList));
int i = 0;
for( ; it.current(); ++it )
{
QTag *tag = it.current();
if ((tag->type != "entity") && (tag->className == classStr ||
isDerivatedFrom(classStr, tag->className)))
{
tagName = tag->name();
if (!tagName.isEmpty() && tagName.upper().startsWith(word))
{
if (!parentQTag || (parentQTag && parentQTag->isChild(tagName)))
{
tagName = tag->name() + TQString("%1").arg(i, 10);
tagNameList += tagName;
comments.insert(tagName, tag->comment);
i++;
}
}
}
}
TQDictIterator<QTag> it2(userTagList);
for( ; it2.current(); ++it2 )
{
QTag *tag = it2.current();
if ((tag->className == classStr ||
isDerivatedFrom(classStr, tag->className)) && tag->name().upper().startsWith(word))
{
tagName = tag->name() + TQString("%1").arg(i, 10);
tagNameList += tagName;
comments.insert(tagName, tag->comment);
// If the completion family is script, then we want to update the tag type
// it appears we use "script" for adding the completionDTD->attrAutoCompleteAfter when we run the slotFilterCompletion
// so we will continue to use that for functions (they need the attribute added), but variables get a new type - and we do not
// have to auto-complete them
if(completionDTD->family==Script)
{
if(tag->type=="variable")
type.insert(tagName, tag->type);
else if(tag->type=="function")
type.insert(tagName, "script");
// We add the type to the comment variable, so it displays on the screen, giving the user some feedback
if(comments[tagName].length())
comments[tagName] = tag->type + "\n" + comments[tagName];
else
comments[tagName] = tag->type + comments[tagName];
}
i++;
}
}
tagNameList.sort();
// tagNameList is sorted above to sort the completions by name alphabetically
// Now we want to sort the completions by their types.
// We only want to do this if we are completing Script DTDs
// We are going to use a couple of iterators to sort the list by Type
// Type Sorting is as follows: 0:Other, 1:Variables, 2: Functions (script)
TQValueList<KTextEditor::CompletionEntry>::Iterator otherIt=completions->begin();
TQValueList<KTextEditor::CompletionEntry>::Iterator variableIt=completions->begin();
for (uint i = 0; i < tagNameList.count(); i++)
{
if (completionDTD->family == Xml)
completion.text = QuantaCommon::tagCase(tagNameList[i]);
else
completion.text = tagNameList[i];
completion.text = completion.text.left(completion.text.length() - 10).stripWhiteSpace();
completion.comment = comments[tagNameList[i]];
if(completionDTD->family==Script)
{
// Here we actually append the completion type
completion.type = type[tagNameList[i]];
// And here is out sorting...
if(completion.type.contains("variable"))
{
// Insert after the last variable
variableIt++;
variableIt = completions->insert(variableIt, completion);
}
else
{
if(completion.type.contains("script"))
{
//Scripts can go at the end of the list
completions->append(completion);
}
else
{
// Other types go first, after the last other type
otherIt++;
otherIt = completions->insert(otherIt, completion);
// If we have no variables in the list, we need to point variableIt to otherIt, so they will go after the 'others'
if((*variableIt).text.length()==0)
variableIt=otherIt;
}
}
}
else
completions->append( completion );
}
// completionInProgress = true;
return completions;
}
/** Return a list of valid attributes for the given tag */
TQValueList<KTextEditor::CompletionEntry>* Document::getAttributeCompletions(const TQString& tagName, const TQString& a_startsWith )
{
TQValueList<KTextEditor::CompletionEntry> *completions = new TQValueList<KTextEditor::CompletionEntry>();
KTextEditor::CompletionEntry completion;
QTag *tag = QuantaCommon::tagFromDTD(completionDTD, tagName);
if (!tag)
{
tag = userTagList.find(tagName.lower());
}
TQString startsWith = a_startsWith.upper();
if (tag)
{
switch (completionDTD->family)
{
case Xml:
{
completion.type = "attribute";
completion.userdata = startsWith+"|"+tag->name();
//list specified attributes for this tag
AttributeList *list = tag->attributes();
TQValueList<KTextEditor::CompletionEntry> tempCompletions;
TQStringList nameList;
for (uint i = 0; i < list->count(); i++)
{
TQString item = list->at(i)->name;
if (item.upper().startsWith(startsWith))
{
completion.text = QuantaCommon::attrCase(item);
completion.comment = list->at(i)->type;
tempCompletions.append( completion );
nameList.append(completion.text);
}
}
//list common attributes for this tag
for (TQStringList::Iterator it = tag->commonGroups.begin(); it != tag->commonGroups.end(); ++it)
{
AttributeList *attrs = tag->parentDTD->commonAttrs->find(*it);
for (uint j = 0; j < attrs->count(); j++)
{
TQString name = attrs->at(j)->name;
if (name.upper().startsWith(startsWith))
{
completion.text = QuantaCommon::attrCase(name);
completion.comment = attrs->at(j)->type;
tempCompletions.append( completion );
nameList.append(completion.text);
}
}
}
if (tag->name().contains("!doctype",false)) //special case, list all the known document types
{
TQStringList nickNames = DTDs::ref()->nickNameList(true);
for ( TQStringList::Iterator it = nickNames.begin(); it != nickNames.end(); ++it )
{
completion.type = "doctypeList";
completion.text = *it;
tempCompletions.append(completion);
nameList.append(completion.text);
}
}
//below isn't fast, but enough here. May be better with TQMap<TQString, KTextEditor::CompletionEntry>
nameList.sort();
for ( TQStringList::Iterator it = nameList.begin(); it != nameList.end(); ++it )
{
for (TQValueList<KTextEditor::CompletionEntry>::Iterator compIt = tempCompletions.begin(); compIt != tempCompletions.end(); ++compIt)
{
if ( (*compIt).text == *it)
{
completions->append(*compIt);
break;
}
}
}
break;
}
case Script:
{
completion.userdata = startsWith+"|"+tag->name();
completion.type = "script";
AttributeList *list = tag->attributes();
for (uint i = 0; i < list->count(); i++)
{
TQString item = list->at(i)->name;
completion.text = item;
completion.comment = list->at(i)->type;
completions->append( completion );
}
}
}
} // if (tag)
// completionInProgress = true;
return completions;
}
/** Return a list of valid attribute values for the given tag and attribute */
TQValueList<KTextEditor::CompletionEntry>* Document::getAttributeValueCompletions(const TQString& tagName, const TQString& attribute, const TQString& startsWith )
{
TQValueList<KTextEditor::CompletionEntry> *completions = new TQValueList<KTextEditor::CompletionEntry>();
KTextEditor::CompletionEntry completion;
completion.type = "attributeValue";
completion.userdata = startsWith+"|"+tagName + "," + attribute;
bool deleteValues;
TQStringList *values = tagAttributeValues(completionDTD->name,tagName, attribute, deleteValues);
if (attribute.lower() == "class")
{
if (!values)
{
values = new TQStringList(quantaApp->selectors(tagName));
deleteValues = true;
}
} else
if (attribute.lower() == "id")
{
if (!values)
{
values = new TQStringList(quantaApp->idSelectors());
deleteValues = true;
}
}
if (values)
{
for ( TQStringList::Iterator it = values->begin(); it != values->end(); ++it )
{
completion.text = *it;
if (completion.text.startsWith(startsWith))
{
completions->append( completion );
}
}
}
if (deleteValues)
delete values;
int andSignPos = startsWith.find('&');
if (andSignPos != -1)
{
TQValueList<KTextEditor::CompletionEntry> *charCompletions = getCharacterCompletions(startsWith.mid(andSignPos + 1));
*completions += *charCompletions;
delete charCompletions;
}
// completionInProgress = true;
return completions;
}
/** Return a list of character completions (like &nbsp; ...) */
TQValueList<KTextEditor::CompletionEntry>* Document::getCharacterCompletions(const TQString& startsWith)
{
TQValueList<KTextEditor::CompletionEntry> *completions = 0L;
TQMap<TQString, KTextEditor::CompletionEntry> completionMap;
//first search for entities defined in the document
const DTDStruct *dtdDTD = DTDs::ref()->find("dtd");
if (dtdDTD)
{
StructTreeGroup group;
for (uint j = 0; j < dtdDTD->structTreeGroups.count(); j++)
{
group = dtdDTD->structTreeGroups[j];
if (!group.autoCompleteAfterRx.pattern().isEmpty() &&
group.autoCompleteAfterRx.search("&") != -1)
{
uint line, col;
viewCursorIf->cursorPositionReal(&line, &col);
Node *node = parser->nodeAt(line, col, false);
completions = getGroupCompletions(node, group, line, col);
for (uint i = 0; i < completions->count(); i++)
{
(*completions)[i].type = "charCompletion";
(*completions)[i].userdata = (*completions)[i].text;
completionMap[(*completions)[i].text] = (*completions)[i];
}
break;
}
}
}
if (!completions)
completions = new TQValueList<KTextEditor::CompletionEntry>();
KTextEditor::CompletionEntry completion;
completion.type = "charCompletion";
//add the entities from the tag files
TQDictIterator<QTag> it(*(completionDTD->tagsList));
for( ; it.current(); ++it )
{
QTag *tag = it.current();
if (tag->type == "entity")
{
TQString tagName = tag->name(true);
if (tagName.upper().startsWith(startsWith.upper()) || startsWith.isEmpty())
{
completion.text = tagName;
completion.userdata = tagName;
completions->append( completion );
completionMap[tagName] = completion;
}
}
}
TQValueList<KTextEditor::CompletionEntry> *completions2 = new TQValueList<KTextEditor::CompletionEntry>();
for (TQMap<TQString, KTextEditor::CompletionEntry>::ConstIterator it = completionMap.constBegin(); it != completionMap.constEnd(); ++it)
{
completions2->append(it.data());
}
delete completions;
completions = completions2;
for ( TQStringList::Iterator it = charList.begin(); it != charList.end(); ++it )
{
completion.text = *it;
int begin = completion.text.find("(&") + 2;
if (begin == 1)
continue;
int length = completion.text.find(";)") - begin + 1;
TQString s = completion.text.mid(begin, length - 1);
completion.text = s + " : " + completion.text.left(begin -2) + " - " + completion.text.mid(begin + length + 1);
if (s.startsWith(startsWith))
{
completion.userdata = s.mid(startsWith.length());
completions->append( completion );
}
}
return completions;
}
/** Returns the DTD identifier for the document */
TQString Document::getDTDIdentifier()
{
return dtdName;
}
/** Sets the DTD identifier */
void Document::setDTDIdentifier(const TQString &id)
{
dtdName = id.lower();
m_groupsForDTEPs.clear();
}
/** Get a pointer to the current active DTD. If fallback is true, this always gives back a valid and known DTD pointer: the active, the document specified and in last case the application default document type. */
const DTDStruct* Document::currentDTD(bool fallback)
{
uint line, col;
viewCursorIf->cursorPositionReal(&line, &col);
const DTDStruct *dtd = parser->currentDTD(line, col);
if (fallback && !dtd) return defaultDTD();
return dtd;
}
/** Get a pointer to the default DTD (document, or app). */
const DTDStruct* Document::defaultDTD() const
{
const DTDStruct* dtd = DTDs::ref()->find(dtdName);
if (!dtd) dtd = DTDs::ref()->find(Project::ref()->defaultDTD());
if (!dtd) dtd = DTDs::ref()->find(qConfig.defaultDocType); //this will always exists
return dtd;
}
/** Find the DTD name for a part of the document. */
TQString Document::findDTDName(Tag **tag)
{
//Do some magic to find the document type
int endLine = editIf->numLines();
TQString foundText = "";
int pos = 0;
int i = 0;
int line, startPos;
TQString text;
do
{
text = editIf->textLine(i);
//search for !DOCTYPE tags
pos = text.find("!doctype",0,false);
if (pos != -1) //parse the found !DOCTYPE tag
{
int bl, bc, el, ec;
line = i;
bl = line;
startPos = text.findRev('<',pos);
while (startPos == -1 && line >=0)
{
text = editIf->textLine(line);
startPos = text.findRev('<');
bl = line;
line--;
}
if (startPos == -1)
{
i++;
continue;
}
bc = startPos;
line = i;
text = editIf->textLine(i);
startPos = text.find('>',pos);
el = line;
while (startPos == -1 && line < endLine)
{
text = editIf->textLine(line);
startPos = text.find('>');
el = line;
line++;
}
if (startPos == -1)
{
i++;
continue;
}
ec = startPos + 1;
*tag = new Tag();
(*tag)->setTagPosition(bl, bc, el, ec);
text = this->text(bl, bc, el, ec);
(*tag)->parse(text, this);
(*tag)->type = Tag::XmlTag;
text.replace("\\\"", "\"");
pos = text.find("public",0,false);
if (pos == -1) //if no PUBLIC info, use the word after !DOCTYPE as the doc.type
{
foundText = (*tag)->attribute(0);
} else
{ //use the quoted string after PUBLIC as doc. type
pos = text.find("\"", pos+1);
if (pos !=-1)
{
int endPos = text.find("\"",pos+1);
foundText = text.mid(pos+1, endPos-pos-1);
}
}
break;
}
i++;
} while (i < endLine);
return foundText.lower();
}
/** Called whenever a user inputs text in a script type document. */
bool Document::scriptAutoCompletion(int line, int column, const TQString& insertedString)
{
bool handled = false;
Node *node = parser->nodeAt(line, column);
if (!node) //happens in some cases in CSS
return false;
if (node->tag->type == Tag::Comment)
return true; //nothing to do
const DTDStruct *dtd = node->tag->dtd();
if (node->prev)
node = node->prev;
else
if (node->parent)
node = node->parent;
int bl, bc;
node->tag->beginPos(bl, bc);
TQString s = text(bl, bc, line, column);
if (QuantaCommon::insideCommentsOrQuotes(s.length() -1, s, dtd))
return true; //again, nothing to do
TQString s2 = s;
int i = s.length() - 1;
while (i > 0 && s[i].isSpace())
i--;
while (i > 0 && (s[i].isLetterOrNumber() || s[i] == '_' ||
(completionDTD->minusAllowedInWord && s[i] == '-') ) )
i--;
TQString startStr = s.mid(i + 1).stripWhiteSpace();
s = s.left(i + 1);
if (s[i] == completionDTD->attributeSeparator)
{
while (i > 0 && s[i] != completionDTD->attrAutoCompleteAfter)
i--;
s = s.left(i + 1);
} else
if (s[i] == completionDTD->tagSeparator)
{
while (i > 0 && s[i] != completionDTD->tagAutoCompleteAfter)
i--;
s = s.left(i + 1);
}
if ( s[i] == completionDTD->attrAutoCompleteAfter ||
s[i] == completionDTD->attributeSeparator ) //if we need to list the arguments of a function
{
TQString textLine = s.left(i);
TQString word = findWordRev(textLine, completionDTD);
TQValueList<QTag *> tags;
if (!word.isEmpty())
{
tags.append(userTagList.find(word.lower()));
TQDictIterator<QTag> it(*(completionDTD->tagsList));
for( ; it.current(); ++it )
{
if (it.currentKey() == word)
tags.append(it.current());
}
}
TQStringList argList;
for (TQValueList<QTag*>::ConstIterator it = tags.constBegin(); it != tags.constEnd(); ++it)
{
QTag *tag = *it;
if (!tag)
continue;
TQString arguments;
if (tag->type != "property")
{
for (int i =0; i < tag->attributeCount(); i++)
{
Attribute* attr = tag->attributeAt(i);
if (attr->status == "optional")
{
arguments = arguments + "["+attr->type +" "+attr->name +"], ";
} else
{
arguments = arguments + attr->type +" "+attr->name +", ";
}
}
arguments = tag->returnType +" "+tag->name() + "("+arguments.left(arguments.length()-2)+")";
argList.append(arguments);
codeCompletionIf->showArgHint(argList, "()" , completionDTD->attributeSeparator);
argHintVisible = true;
} else
{
if (hintRequested)
{
arguments = tag->name() + ": " + tag->attributeAt(0)->name + ";";
argList.append(arguments);
codeCompletionIf->showArgHint(argList, ":;" , completionDTD->attributeSeparator);
} else
showCodeCompletions( getAttributeValueCompletions(tag->name(), tag->attributeAt(0)->name, startStr));
}
handled = true;
}
} else
{
StructTreeGroup group;
for (uint j = 0; j < completionDTD->structTreeGroups.count(); j++)
{
group = completionDTD->structTreeGroups[j];
if (!group.autoCompleteAfterRx.pattern().isEmpty() &&
( group.autoCompleteAfterRx.search(s2) != -1||
group.autoCompleteAfterRx.search(s) != -1) )
{
Node *node = parser->nodeAt(line, column, false);
showCodeCompletions(getGroupCompletions(node, group, line, column + 1));
handled = true;
break;
}
}
}
if ( !handled && !argHintVisible &&
(completionRequested ||
(s[i] == completionDTD->tagAutoCompleteAfter && (insertedString == " " || (insertedString[0] == completionDTD->tagAutoCompleteAfter && !completionDTD->requestSpaceBeforeTagAutoCompletion))) ||
completionDTD->tagAutoCompleteAfter == '\1' || (!completionDTD->memberAutoCompleteAfter.pattern().isEmpty() && completionDTD->memberAutoCompleteAfter.searchRev(s) != -1))
)
{
showCodeCompletions(getTagCompletions(line, column + 1));
handled = true;
}
return handled;
}
/** Retrives the text from the specified rectangle. The KTextEditor::EditInterface::text seems to not
work correctly. */
TQString Document::text(int bLine, int bCol, int eLine, int eCol) const
{
if (bLine > eLine)
{
int tmp = bLine;
bLine = eLine;
eLine = tmp;
tmp = bCol;
bCol = eCol;
eCol = tmp;
}
TQString t = editIf->textLine(bLine);
if (bLine == eLine)
{
return t.mid(bCol, eCol-bCol +1);
}
t.remove(0, bCol);
t.append("\n");
//TODO: This is slow if the area is big. We need to speed it up!!
for (int i = bLine+1; i < eLine ; i++)
{
t.append(editIf->textLine(i)+"\n");
}
t = t+editIf->textLine(eLine).left(eCol+1);
return t;
}
//TODO: profile which one is used more often and time critical places and use
//that one as the default and call from that one the other version
TQString Document::text(const AreaStruct &area) const
{
return text(area.bLine, area.bCol, area.eLine, area.eCol);
}
TQString Document::find(const TQRegExp& regExp, int sLine, int sCol, int& fbLine, int&fbCol, int &feLine, int&feCol)
{
TQRegExp rx = regExp;
TQString foundText = "";
int maxLine = editIf->numLines();
TQString textToSearch = text(sLine, sCol, sLine, editIf->lineLength(sLine));
int pos;
int line = sLine;
do
{
pos = rx.search(textToSearch);
if (pos == -1)
{
/* if (line + STEP < maxLine)
{
line += STEP;
textToSearch.append("\n"+text(line - STEP + 1, 0, line, editIf->lineLength(line)));
} else*/
{
line ++;
if (line < maxLine) textToSearch.append("\n"+editIf->textLine(line));
}
}
} while (line < maxLine && pos == -1);
// pos = rx.search(text(sLine, sCol, maxLine -1, 100));
if (pos != -1)
{
foundText = rx.cap();
TQString s = textToSearch.left(pos);
int linesUntilFound = s.contains("\n");
fbLine = sLine + linesUntilFound;
fbCol = s.length()-s.findRev("\n")-1;
int linesInFound = foundText.contains("\n");
feCol = foundText.length()-foundText.findRev("\n")-2;
feLine = fbLine + linesInFound;
if (linesUntilFound == 0)
{
fbCol = fbCol + sCol;
}
if (linesInFound == 0)
{
feCol = feCol + fbCol;
}
if (fbCol < 0) fbCol = 0;
if (feCol < 0) feCol = 0;
/*
s = text(fbLine, fbCol, feLine, feCol);
if (s != foundText) //debug, error
{
KMessageBox::error(this,"Found: "+foundText+"\nRead: "+s);
}
*/
}
return foundText;
}
TQString Document::findRev(const TQRegExp& regExp, int sLine, int sCol, int& fbLine, int&fbCol, int &feLine, int&feCol)
{
TQRegExp rx = regExp;
TQString foundText = "";
int pos = -1;
int line = sLine;
TQString textToSearch = text(sLine, 0, sLine, sCol);
do
{
pos = rx.searchRev(textToSearch);
if (pos == -1)
{
/* if (line - STEP >= 0)
{
textToSearch.prepend(text(line - STEP, 0, line - 1, editIf->lineLength(line-1)) + "\n");
line -= STEP;
} else */
{
line--;
if (line >=0) textToSearch.prepend(editIf->textLine(line) + "\n");
}
}
} while (line >=0 && pos == -1);
if (pos != -1)
{
foundText = rx.cap();
fbLine = line;
fbCol = pos;
int linesInFound = foundText.contains("\n");
feCol = foundText.length()-foundText.findRev("\n")-2;
feLine = fbLine + linesInFound;
if (linesInFound == 0)
{
feCol = feCol + fbCol;
}
if (fbCol < 0) fbCol = 0;
if (feCol < 0) feCol = 0;
/*
TQString s = text(fbLine, fbCol, feLine, feCol);
if (s != foundText) //debug, error
{
KMessageBox::error(this,"FindRev\nFound: "+foundText+"\nRead: "+s);
}
*/
}
return foundText;
}
/** Code completion was requested by the user. */
void Document::codeCompletionRequested()
{
completionRequested = true;
completionInProgress = false;
argHintVisible = false;
hintRequested = false;
handleCodeCompletion();
completionRequested = false;
}
void Document::handleCodeCompletion()
{
slotDelayedTextChanged(true);
bool handled = false;
uint line, col;
viewCursorIf->cursorPositionReal(&line, &col);
completionDTD = currentDTD();
if (completionDTD->family == Xml)
{
handled = xmlCodeCompletion(line, col);
}
if (completionDTD->family == Script)
{
if (completionDTD->tagAutoCompleteAfter == '\0')
completionDTD->tagAutoCompleteAfter = '\1';
handled = scriptAutoCompletion(line, col - 1, "");
if (completionDTD->tagAutoCompleteAfter == '\1')
completionDTD->tagAutoCompleteAfter = '\0';
/* if (!handled)
{
completionDTD = defaultDTD();
TQString s = text(line, 0, line, col).stripWhiteSpace();
if (s.findRev("<") != -1)
{
//showCodeCompletions(getTagCompletions(line, col + 1));
handled = true;
}
}*/
}
if (!handled)
{
completionDTD = defaultDTD();
if (completionDTD->family == Xml)
{
// xmlCodeCompletion(line, col);
xmlAutoCompletion(line, col, " ");
}
}
completionInProgress = true;
}
/** Bring up the code completion tooltip. */
void Document::codeCompletionHintRequested()
{
completionRequested = true;
slotDelayedTextChanged(true);
uint line, col;
viewCursorIf->cursorPositionReal(&line, &col);
completionDTD = currentDTD();
if (completionDTD->family == Script)
{
// TQString textLine = editIf->textLine(line).left(col);
// int pos = textLine.findRev("(");
// int pos2 = textLine.findRev(")");
//if (pos > pos2 )
hintRequested = true;
scriptAutoCompletion(line, col - 1, "");
}
completionRequested = false;
}
TQString Document::currentWord()
{
uint line, col;
viewCursorIf->cursorPositionReal(&line, &col);
TQString textLine = editIf->textLine(line);
int startPos = textLine.findRev(TQRegExp("\\W"), col);
int endPos = textLine.find(TQRegExp("\\W"), col);
if (startPos == -1)
startPos = 0;
else
startPos++;
if (endPos == -1)
endPos = textLine.length();
return textLine.mid(startPos, endPos - startPos);
}
/** Find the word until the first word boundary backwards */
TQString Document::findWordRev(const TQString& textToSearch, const DTDStruct *dtd)
{
TQString t = textToSearch;
while (t.endsWith(" "))
t = t.left(t.length()-1);
int startPos = -1;
int pos;
bool end = false;
do{
pos = t.findRev(TQRegExp("\\W"), startPos);
if (t[pos] == '_' ||
(dtd && dtd->minusAllowedInWord && t[pos] == '-'))
{
startPos = pos - t.length()-1;
end = false;
} else
{
end = true;
}
} while (!end);
return t.remove(0,pos+1);
}
/** Invoke code completion dialog for XML like tags according to the position (line, col), using DTD dtd. */
bool Document::xmlCodeCompletion(int line, int col)
{
bool handled = false;
Node *node = parser->nodeAt(line, col);
if (node && node->tag && node->tag->type == Tag::XmlTag )
{
Tag *tag = node->tag;
int bLine, bCol;
tag->beginPos(bLine, bCol);
TQString s;
int index;
TQString tagName = tag->name.section('|', 0, 0).stripWhiteSpace();
int nameCol = bCol + tagName.length() + 1;
if (!tag->nameSpace.isEmpty())
nameCol += 1 + tag->nameSpace.length();
if (col > bCol && col <= nameCol) //we are inside a tag name, so show the possible tags
{
showCodeCompletions( getTagCompletions(line, col) );
handled = true;
} else
{
index = tag->valueIndexAtPos(line,col);
if (index != -1) //inside a value
{
s = tag->attribute(index);
if (s == "style" && completionDTD->insideDTDs.contains("css"))
{
completionDTD = DTDs::ref()->find("text/css");
return scriptAutoCompletion(line, col, "");
} else
{
tag->attributeValuePos(index, bLine, bCol);
s = tag->attributeValue(index).left(col - bCol);
showCodeCompletions( getAttributeValueCompletions(tagName, tag->attribute(index), s) );
handled = true;
}
} else
{
index = tag->attributeIndexAtPos(line,col);
s = text(line,col,line,col);
if (index != -1 || s ==" " || s==">" || s == "/") //inside an attribute or between attributes
{
if (index !=-1)
{
tag->attributeNamePos(index, bLine, bCol);
s = tag->attribute(index).left(col - bCol);
} else
{
s = text(line, 0, line, col -1);
s = s.section(' ', -1);
}
showCodeCompletions( getAttributeCompletions(tagName, s) );
handled = true;
}
}
}
}
if (!handled)
{
TQString s = editIf->textLine(line).left(col);
int pos = s.findRev('&');
if (pos != -1)
{
s = s.mid(pos + 1);
if (!s.stripWhiteSpace().isEmpty())
{
//complete character codes
showCodeCompletions(getCharacterCompletions(s));
handled = true;
}
}
}
return handled;
}
void Document::slotCompletionAborted()
{
completionInProgress = false;
argHintVisible = false;
}
/** Ask for user confirmation if the file was changed outside. */
void Document::checkDirtyStatus()
{
TQString fileName;
if (url().isLocalFile())
fileName = url().path();
if (m_dirty)
{
createTempFile();
if (!fileName.isEmpty())
{
TQDateTime modifTime = TQFileInfo(fileName).lastModified();
if (modifTime == m_modifTime)
m_dirty = false;
}
if (m_dirty)
{
if (m_md5sum.isEmpty())
{
TQFile f(fileName);
if (f.open(IO_ReadOnly))
{
const char* c = "";
KMD5 context(c);
context.reset();
context.update(f);
m_md5sum = context.hexDigest();
f.close();
}
m_dirty = false;
} else
{
//check if the file is changed, also by file content. Might help to reduce
//unwanted warning on NFS
TQFile f(fileName);
if (f.open(IO_ReadOnly))
{
TQString md5sum;
const char* c = "";
KMD5 context(c);
context.reset();
context.update(f);
md5sum = context.hexDigest();
kdDebug(24000) << "MD5 sum of current doc: " << m_md5sum << endl;
kdDebug(24000) << "MD5 sum of doc on disc : " << md5sum << endl;
if (md5sum == m_md5sum)
{
m_dirty = false;
}
f.close();
}
}
}
if (m_dirty)
{
DirtyDlg *dlg = new DirtyDlg(url().path(), m_tempFileName, false, this);
DirtyDialog *w = static_cast<DirtyDialog*>(dlg->mainWidget());
TQString kompareStr = KStandardDirs::findExe("kompare");
if (kompareStr.isEmpty())
{
w->buttonCompare->setEnabled(false);
w->buttonLoad->setChecked(true);
}
if (dlg->exec())
{
m_doc->setModified(false);
openURL(url());
}
m_modifTime = TQFileInfo(fileName).lastModified();
delete dlg;
}
closeTempFile();
m_dirty = false;
}
}
/** Save the document and reset the dirty status. */
void Document::save()
{
if (url().isLocalFile())
{
TQString fileName;
fileName = url().path();
fileWatcher->removeFile(fileName);
// kdDebug(24000) << "removeFile[save]: " << fileName << endl;
m_doc->save();
m_dirty = false;
m_modifTime = TQFileInfo(fileName).lastModified();
fileWatcher->addFile(fileName);
// kdDebug(24000) << "addFile[save]: " << fileName << endl;
} else
{
m_doc->save();
m_dirty = false;
}
// kdDebug(24000) << "Document " << url() << " saved." << endl;
}
bool Document::saveAs(const KURL& url)
{
bool result = m_doc->saveAs(url);
if (result)
{
m_md5sum = "";
if (url.isLocalFile())
{
TQFile f(url.path());
if (f.open(IO_ReadOnly))
{
const char* c = "";
KMD5 context(c);
context.reset();
context.update(f);
m_md5sum = context.hexDigest();
f.close();
}
}
}
return result;
}
void Document::enableGroupsForDTEP(const TQString& dtepName, bool enable)
{
if (m_groupsForDTEPs.isEmpty())
m_groupsForDTEPs = m_DTEPList;
if (enable)
{
if (m_groupsForDTEPs.contains(dtepName) == 0)
m_groupsForDTEPs.append(dtepName);
} else
{
m_groupsForDTEPs.remove(dtepName);
}
}
void Document::resetGroupsForDTEPList()
{
m_groupsForDTEPs.clear();
}
/** Returns true if the number of " (excluding \") inside text is even. */
bool Document::evenQuotes(const TQString &text)
{
int num = text.contains(TQRegExp("[^\\\\]\""));
return (num /2 *2 == num);
}
void Document::slotTextChanged()
{
changed = true;
parser->setSAParserEnabled(false); //disable special area parsing if the text was changed.
if (reparseEnabled && delayedTextChangedEnabled)
{
kdDebug(24000) << "Delayed text changed called." << endl;
//delay the handling, otherwise we may get wrong values for (line,column)
TQTimer::singleShot(0, this, TQT_SLOT(slotDelayedTextChanged()));
delayedTextChangedEnabled = false;
}
}
void Document::slotDelayedTextChanged(bool forced)
{
if (!forced && typingInProgress)
{
kdDebug(24000) << "Reparsing delayed!" << endl;
parser->setParsingNeeded(true);
TQTimer::singleShot(1000, this, TQT_SLOT(slotDelayedTextChanged()));
reparseEnabled = false;
delayedTextChangedEnabled = false;
return;
}
uint line, column;
TQString oldNodeName = "";
Node *node;
Node *currentNode = 0L; //holds a copy of the node which is at (line,column)
Node *previousNode = 0L;//holds a copy of the node before currentNode
if (qConfig.updateClosingTags)
{
viewCursorIf->cursorPositionReal(&line, &column);
node = parser->nodeAt(line, column, false);
if (node &&
((node->tag->type == Tag::XmlTag && !node->tag->single) ||
node->tag->type == Tag::XmlTagEnd)
)
{
Tag *tag;
tag = new Tag(*node->tag);
currentNode = new Node(0L);
currentNode->removeAll = false;
currentNode->tag = tag;
node = node->previousSibling();
if (node)
{
tag = new Tag(*node->tag);
previousNode = new Node(0L);
previousNode->removeAll = false;
previousNode->tag = tag;
}
}
}
parser->setSAParserEnabled(true); //enable special area parsing, it was disabled in slotTextChanged()
baseNode = parser->rebuild(this);
if (qConfig.updateClosingTags && currentNode)
{
viewCursorIf->cursorPositionReal(&line, &column);
node = parser->nodeAt(line, column, false);
if (node &&
node->tag->nameSpace + node->tag->name != currentNode->tag->nameSpace + currentNode->tag->name &&
((node->tag->type == Tag::XmlTag && !node->tag->single) || node->tag->type == Tag::XmlTagEnd) && node->tag->validXMLTag)
{
int bl, bc, bl2, bc2;
node->tag->beginPos(bl, bc);
currentNode->tag->beginPos(bl2,bc2);
if ( (bl != bl2 || bc !=bc2) && previousNode)
{
previousNode->tag->beginPos(bl2, bc2);
Node::deleteNode(currentNode);
currentNode = previousNode;
previousNode = 0L;
} else
{
Node::deleteNode(previousNode);
previousNode = 0L;
}
if (bl == bl2 && bc == bc2 &&
((node->tag->type == Tag::XmlTag && !node->tag->single) || currentNode->tag->type == Tag::XmlTagEnd))
{
TQString newName = node->tag->name;
bool updateClosing = (currentNode->tag->type == Tag::XmlTag) && !newName.startsWith("!");
int num = 1;
if (!node->tag->nameSpace.isEmpty())
newName.prepend(node->tag->nameSpace + ":");
if (updateClosing)
node = node->nextSibling();
else
node = node->previousSibling();
while (node)
{
if (node->tag->validXMLTag && ((node->tag->type == Tag::XmlTag && !node->tag->single) || node->tag->type == Tag::XmlTagEnd))
{
if (node->tag->nameSpace + node->tag->name == currentNode->tag->nameSpace + currentNode->tag->name )
{
num++;
}
if ( (updateClosing && QuantaCommon::closesTag(currentNode->tag, node->tag)) ||
(!updateClosing && QuantaCommon::closesTag(node->tag, currentNode->tag)) )
{
num--;
}
if (num == 0)
{
reparseEnabled = false;
node->tag->beginPos(bl, bc);
bc++;
if(editIfExt)
editIfExt->editBegin();
int len = node->tag->name.length();
if (!node->tag->nameSpace.isEmpty())
len += 1 + node->tag->nameSpace.length();
editIf->removeText(bl, bc, bl, bc + len);
if (updateClosing)
{
editIf->insertText(bl, bc, "/"+newName);
} else
{
editIf->insertText(bl, bc, newName.mid(1));
if (bl == (int)line)
{
column += (newName.length() - currentNode->tag->name.length());
}
}
if(editIfExt)
editIfExt->editEnd();
viewCursorIf->setCursorPositionReal(bl, bc);
docUndoRedo->mergeNextModifsSet();
baseNode = parser->parse(this, true);
viewCursorIf->setCursorPositionReal(line, column);
reparseEnabled = true;
break;
}
}
if (updateClosing)
node = node->nextSibling();
else
node = node->previousSibling();
}
}
}
Node::deleteNode(currentNode);
Node::deleteNode(previousNode);
}
quantaApp->slotNewLineColumn();
if (qConfig.instantUpdate && quantaApp->structTreeVisible())
{
typingInProgress = false;
StructTreeView::ref()->slotReparse(this, baseNode , qConfig.expandLevel);
}
reparseEnabled = true;
delayedTextChangedEnabled = true;
}
/** Returns list of values for attribute */
TQStringList* Document::tagAttributeValues(const TQString& dtdName, const TQString& tag, const TQString &attribute, bool &deleteResult)
{
TQStringList *values = 0L;
deleteResult = true;
const DTDStruct* dtd = DTDs::ref()->find(dtdName);
if (dtd)
{
TQString searchForAttr = (dtd->caseSensitive) ? attribute : attribute.upper();
AttributeList* attrs = QuantaCommon::tagAttributes(dtdName, tag);
if (attrs)
{
Attribute *attr;
KURL u;
KURL base = url();
base.setPath(base.directory(false,false));
TQString s;
for ( attr = attrs->first(); attr; attr = attrs->next() )
{
TQString attrName = (dtd->caseSensitive) ? attr->name : attr->name.upper();
if (attrName == searchForAttr)
{
if (attr->type == "url") {
Project *project = Project::ref();
if (project->hasProject())
{
values = new TQStringList(project->fileNameList());
for (uint i = 0; i < values->count(); i++)
{
u = (*values)[i];
u = QExtFileInfo::toRelative(u, base);
(*values)[i] = u.path();
}
values->remove(values->at(0));
values->append("mailto:" + project->email());
} else
{
TQDir dir = TQDir(url().directory());
values = new TQStringList(dir.entryList());
}
break;
} else {
values = &attr->values;
deleteResult = false;
break;
}
}
}
}
}
return values;
}
bool Document::hasChanged()
{
bool b = changed;
changed = false;
return b;
}
void Document::setChanged(bool newStatus)
{
changed = newStatus;
}
void Document::paste()
{
reparseEnabled = false;
dynamic_cast<KTextEditor::ClipboardInterface*>(view())->paste();
reparseEnabled = true;
baseNode = parser->rebuild(this);
}
/** returns all the areas that are between tag and it's closing pair */
TQStringList Document::tagAreas(const TQString& tag, bool includeCoordinates, bool skipFoundContent)
{
Node *node = baseNode;
int bl, bc, el, ec;
TQStringList result;
while (node)
{
if (node->tag->type == Tag::XmlTag)
{
if ( (node->tag->dtd()->caseSensitive && node->tag->name == tag) ||
(!node->tag->dtd()->caseSensitive && node->tag->name.lower() == tag.lower()) )
{
node->tag->beginPos(bl, bc);
if (node->next)
node->next->tag->endPos(el, ec);
else
{
el = editIf->numLines()-1;
ec = editIf->lineLength(el);
}
TQString s = text(bl, bc, el, ec);
if (includeCoordinates)
{
s.prepend(TQString("%1,%2,%3,%4\n").arg(bl).arg(bc).arg(el).arg(ec));
}
result += s;
if (skipFoundContent)
node = node->next;
else
node = node->nextSibling();
} else
node = node->nextSibling();
} else
node = node->nextSibling();
}
return result;
}
void Document::activateRepaintView(bool activation)
{
repaintEnabled = activation;
m_view->setUpdatesEnabled(activation);
}
void Document::setErrorMark(int line)
{
if (!markIf)
return;
markIf->addMark(line, KTextEditor::MarkInterface::markType07);
}
void Document::clearErrorMarks()
{
if (!markIf)
return;
TQPtrList<KTextEditor::Mark> marks = markIf->marks();
KTextEditor::Mark* mark;
for (mark = marks.first(); mark; mark = marks.next())
{
if (mark->type & KTextEditor::MarkInterface::markType07)
markIf->removeMark(mark->line, KTextEditor::MarkInterface::markType07);
}
}
TQString Document::backupPathEntryValue()
{
return m_backupPathValue;
}
void Document::setBackupPathEntryValue(const TQString& ev)
{
m_backupPathValue = ev;
}
/** if the document is modified then backup it and insert an entry in quantarc */
void Document::createBackup(KConfig* config)
{
if (isModified())
{
if (isUntitled())
{
m_backupPathValue = qConfig.backupDirPath + untitledUrl + "." + hashFilePath("file:///" + untitledUrl) + "U";
} else
{
m_backupPathValue = qConfig.backupDirPath + url().fileName() + "." + hashFilePath(url().url());
}
TQString backupPathValueURL = KURL::fromPathOrURL(m_backupPathValue).url();
//the encoding used for the current document
TQString encoding = quantaApp->defaultEncoding();
if (encodingIf)
encoding = encodingIf->encoding();
if (encoding.isEmpty())
encoding = "utf8"; //final fallback
//creates an entry string in quantarc if it does not exist yet
config->setGroup("General Options");
TQStringList backedupFilesEntryList = QuantaCommon::readPathListEntry(config, "List of backedup files"); //the files that were backedup
TQStringList autosavedFilesEntryList = QuantaCommon::readPathListEntry(config, "List of autosaved files"); //the list of actual backup files inside $KDEHOME/share/apps/quanta/backups
if (!autosavedFilesEntryList.contains(backupPathValueURL)) //not yet backed up, add an entry for this file
{
autosavedFilesEntryList.append(backupPathValueURL);
config->writePathEntry("List of autosaved files", autosavedFilesEntryList);
if (!isUntitled())
backedupFilesEntryList.append(KURL::fromPathOrURL(url().path() + "." + qConfig.quantaPID).url());
else
backedupFilesEntryList.append(url().url() + "." + qConfig.quantaPID);
config->writePathEntry("List of backedup files", backedupFilesEntryList);
config->sync();
}
//creates a copy of this specific document
TQFile file(m_backupPathValue);
if (file.open(IO_WriteOnly))
{
TQTextStream stream(&file);
stream.setCodec(TQTextCodec::codecForName(encoding.ascii()));
stream << editIf->text();
file.close();
}
}
}
/** if there is no more need for a backup copy then remove it */
void Document::removeBackup(KConfig *config)
{
TQString backupPathValueURL = KURL::fromPathOrURL(m_backupPathValue).url();
config->reparseConfiguration();
config->setGroup("General Options");
TQStringList backedupFilesEntryList = QuantaCommon::readPathListEntry(config, "List of backedup files");
TQStringList autosavedFilesEntryList = QuantaCommon::readPathListEntry(config, "List of autosaved files");
autosavedFilesEntryList.remove(backupPathValueURL);
config->writePathEntry("List of autosaved files", autosavedFilesEntryList);
backedupFilesEntryList.remove(KURL::fromPathOrURL(url().path() + "." + qConfig.quantaPID).url());
config->writePathEntry("List of backedup files", backedupFilesEntryList);
config->sync();
if(TQFile::exists(m_backupPathValue))
TQFile::remove(m_backupPathValue);
}
/** creates a string by hashing a bit the path string of this document */
TQString Document::hashFilePath(const TQString& p)
{
switch(p.length())
{
case 1: {
int c = int(p[0]);
return TQString::number(c, 10) + "P" + qConfig.quantaPID;
}
case 2: {
int c = int(p[1]) * 2;
return TQString::number(c, 10) + "P" + qConfig.quantaPID;
}
default: {
int sign = 1,
sum = 0;
uint plen = p.length();
for (uint i = 0; i+1 < plen; i++)
{
sum += int(p[i]) + int(p[i + 1]) * sign;
sign *= -1;
}
if( sum >= 0 )
return TQString::number(sum, 10) + "P" + qConfig.quantaPID;
else
return TQString::number(sum*(-1), 10) + "N" + qConfig.quantaPID;
}
}
}
void Document::convertCase()
{
int tagCase = 0;
int attrCase = 0;
KDialogBase dlg(this, 0L, false, i18n("Change Tag & Attribute Case"), KDialogBase::Ok | KDialogBase::Cancel);
CaseWidget w(&dlg);
dlg.setMainWidget(&w);
const DTDStruct *dtd = defaultDTD();
switch (qConfig.attrCase)
{
case 1: {w.lowerAttr->setChecked(true); break;}
case 2: {w.upperAttr->setChecked(true); break;}
default:{w.unchangedAttr->setChecked(true); break;}
}
switch (qConfig.tagCase)
{
case 1: {w.lowerTag->setChecked(true); break;}
case 2: {w.upperTag->setChecked(true); break;}
default:{w.unchangedTag->setChecked(true); break;}
}
if (dlg.exec())
{
KProgressDialog progressDlg(this, 0, i18n("Working..."));
progressDlg.setLabel(i18n("Changing tag and attribute case. This may take some time, depending on the document complexity."));
progressDlg.setAllowCancel(false);
progressDlg.show();
kapp->eventLoop()->processEvents( TQEventLoop::ExcludeUserInput | TQEventLoop::ExcludeSocketNotifiers);
KProgress *pBar = progressDlg.progressBar();
pBar->setValue(0);
pBar->setTotalSteps(nodeNum);
pBar->setTextEnabled(true);
if (w.lowerTag->isChecked())
tagCase = 1;
if (w.upperTag->isChecked())
tagCase = 2;
if (w.lowerAttr->isChecked())
attrCase = 1;
if (w.upperAttr->isChecked())
attrCase = 2;
if (tagCase == 0 && attrCase == 0)
return;
reparseEnabled = false;
int bl, bc, ec;
uint line, col;
viewCursorIf->cursorPositionReal(&line, &col);
Node *node = baseNode;
while (node)
{
pBar->advance(1);
if (node->tag->dtd() == dtd)
{
if (tagCase !=0)
{
if(editIfExt)
editIfExt->editBegin();
node->tag->namePos(bl, bc);
ec = bc + node->tag->name.length();
editIf->removeText(bl, bc, bl, ec);
viewCursorIf->setCursorPositionReal(bl, bc);
TQString newName = node->tag->name;
if (tagCase == 1)
newName = newName.lower();
else if (tagCase == 2)
newName = newName.upper();
editIf->insertText(bl, bc, newName);
if(editIfExt)
editIfExt->editEnd();
}
if (attrCase != 0)
{
TQString newName;
for (int i = 0; i < node->tag->attrCount(); i++)
{
if(editIfExt)
editIfExt->editBegin();
node->tag->attributeNamePos(i, bl, bc);
newName = node->tag->attribute(i);
ec = bc + newName.length();
editIf->removeText(bl, bc, bl, ec);
if (attrCase == 1)
newName = newName.lower();
else if (attrCase == 2)
newName = newName.upper();
editIf->insertText(bl, bc, newName);
if(editIfExt)
editIfExt->editEnd();
}
}
}
node = node->nextSibling();
}
reparseEnabled = true;
viewCursorIf->setCursorPositionReal(line, col);
quantaApp->reparse(true);
}
}
void Document::open(const KURL &url, const TQString &encoding)
{
if (encodingIf)
{
encodingIf->setEncoding(encoding);
m_encoding = encoding;
m_codec = TQTextCodec::codecForName(m_encoding.ascii());
}
connect(m_doc, TQT_SIGNAL(completed()), this, TQT_SLOT(slotOpeningCompleted()));
connect(m_doc, TQT_SIGNAL(canceled(const TQString&)), this, TQT_SLOT(slotOpeningFailed(const TQString&)));
if (!openURL(url))
slotOpeningFailed(TQString::null);
if (!url.isLocalFile())
{
QExtFileInfo internalFileInfo;
internalFileInfo.enter_loop();
}
}
void Document::slotOpeningCompleted()
{
KURL u = url();
if (!u.isLocalFile())
{
m_modifTime = TQDateTime();
qApp->exit_loop();
}
else
{
fileWatcher->addFile(u.path());
m_modifTime = TQFileInfo(u.path()).lastModified();
// kdDebug(24000) << "addFile[Document::open]: " << u.path() << endl;
}
disconnect(m_doc, TQT_SIGNAL(completed()), this, TQT_SLOT(slotOpeningCompleted()));
disconnect(m_doc, TQT_SIGNAL(canceled(const TQString&)), this, TQT_SLOT(slotOpeningFailed(const TQString&)));
m_dirty = false;
m_view->setFocus();
processDTD();
emit openingCompleted(u);
}
void Document::slotOpeningFailed(const TQString &errorMessage)
{
m_md5sum = "";
Q_UNUSED(errorMessage); //TODO: append the error message to our own error message
if (!url().isLocalFile())
qApp->exit_loop();
disconnect(m_doc, TQT_SIGNAL(completed()), this, TQT_SLOT(slotOpeningCompleted()));
disconnect(m_doc, TQT_SIGNAL(canceled(const TQString&)), this, TQT_SLOT(slotOpeningFailed(const TQString&)));
emit openingFailed(url());
}
void Document::processDTD(const TQString& documentType)
{
TQString foundName;
TQString projectDTD = Project::ref()->defaultDTD();
setDTDIdentifier(projectDTD);
Tag *tag = 0L;
if (documentType.isEmpty())
{
foundName = findDTDName(&tag); //look up the whole file for DTD definition
bool found = false;
if (!foundName.isEmpty()) //!DOCTYPE found in file
{
KDialogBase dlg(this, 0L, true, i18n("DTD Selector"), KDialogBase::Ok | KDialogBase::Cancel);
DTDSelectDialog *dtdWidget = new DTDSelectDialog(&dlg);
dlg.setMainWidget(dtdWidget);
TQStringList lst = DTDs::ref()->nickNameList(true);
TQString foundNickName = DTDs::ref()->getDTDNickNameFromName(foundName);
for (uint i = 0; i < lst.count(); i++)
{
dtdWidget->dtdCombo->insertItem(lst[i]);
if (lst[i] == foundNickName)
{
setDTDIdentifier(foundName);
found =true;
}
}
if (!DTDs::ref()->find(foundName))
{
//try to find the closest matching DTD
TQString s = foundName.lower();
uint spaceNum = s.contains(' ');
TQStringList dtdList = DTDs::ref()->nameList();
TQStringList lastDtdList;
for (uint i = 0; i <= spaceNum && !dtdList.empty(); i++)
{
lastDtdList = dtdList;
TQStringList::Iterator strIt = dtdList.begin();
while (strIt != dtdList.end())
{
if (!(*strIt).startsWith(s.section(' ', 0, i)))
{
strIt = dtdList.remove(strIt);
} else
{
++strIt;
}
}
}
dtdList = lastDtdList;
for (uint i = 0; i <= spaceNum && !dtdList.empty(); i++)
{
lastDtdList = dtdList;
TQStringList::Iterator strIt = dtdList.begin();
while (strIt != dtdList.end())
{
if (!(*strIt).endsWith(s.section(' ', -(i+1), -1)))
{
strIt = dtdList.remove(strIt);
} else
{
++strIt;
}
}
}
if (lastDtdList.count() == 1 || lastDtdList[0].startsWith(s.section(' ', 0, 0)))
{
projectDTD = lastDtdList[0];
}
}
// dlg->dtdCombo->insertItem(i18n("Create New DTD Info"));
dtdWidget->messageLabel->setText(i18n("This DTD is not known for Quanta. Choose a DTD or create a new one."));
dtdWidget->currentDTD->setText(DTDs::ref()->getDTDNickNameFromName(foundName));
TQString projectDTDNickName = DTDs::ref()->getDTDNickNameFromName(projectDTD);
for (int i = 0; i < dtdWidget->dtdCombo->count(); i++)
{
if (dtdWidget->dtdCombo->text(i) == projectDTDNickName)
{
dtdWidget->dtdCombo->setCurrentItem(i);
break;
}
}
if (!found && qConfig.showDTDSelectDialog)
{
quantaApp->slotHideSplash();
if (dlg.exec())
{
qConfig.showDTDSelectDialog = !dtdWidget->useClosestMatching->isChecked();
setDTDIdentifier(DTDs::ref()->getDTDNameFromNickName(dtdWidget->dtdCombo->currentText()));
const DTDStruct *dtd = DTDs::ref()->find(dtdName);
if (dtdWidget->convertDTD->isChecked() && dtd->family == Xml)
{
int bLine, bCol, eLine, eCol;
tag->beginPos(bLine,bCol);
tag->endPos(eLine,eCol);
editIf->removeText(bLine, bCol, eLine, eCol+1);
viewCursorIf->setCursorPositionReal((uint)bLine, (uint)bCol);
insertText("<!DOCTYPE" + dtd->doctypeStr +">");
}
}
}
} else //DOCTYPE not found in file
{
KURL u = url();
TQString dtdId = DTDs::ref()->DTDforURL(u)->name;
// if (dtdId == "empty")
{
const DTDStruct * dtd = DTDs::ref()->find(projectDTD);
if (DTDs::canHandle(dtd, u))
dtdId = projectDTD;
else
{
dtd = DTDs::ref()->find(qConfig.defaultDocType);
if (DTDs::canHandle(dtd, u))
dtdId = qConfig.defaultDocType;
}
}
setDTDIdentifier(dtdId);
}
} else //dtdName is read from the method's parameter
{
setDTDIdentifier(documentType);
}
if (!isUntitled())
{
quantaApp->messageOutput()->showMessage(i18n("\"%1\" is used for \"%2\".\n").arg(DTDs::ref()->getDTDNickNameFromName(dtdName)).arg(url().prettyURL(0, KURL::StripFileProtocol)));
}
quantaApp->slotLoadToolbarForDTD(dtdName);
StructTreeView::ref()->useOpenLevelSetting = true;
delete tag;
}
/** Called when a file on the disk has changed. */
void Document::slotFileDirty(const TQString& fileName)
{
if ( url().path() == fileName && !dirty() )
{
setDirtyStatus(true);
if (this == ViewManager::ref()->activeDocument())
{
checkDirtyStatus();
}
}
}
void Document::slotMarkChanged(KTextEditor::Mark mark, KTextEditor::MarkInterfaceExtension::MarkChangeAction action)
{
if(mark.type & KTextEditor::MarkInterface::markType02)
{
if(action == KTextEditor::MarkInterfaceExtension::MarkRemoved)
emit breakpointUnmarked(this, mark.line);
else
emit breakpointMarked(this, mark.line);
}
}
void Document::resetDTEPs()
{
m_DTEPList.clear();
m_DTEPList.append(defaultDTD()->name);
}
void Document::addDTEP(const TQString &dtepName)
{
if (m_DTEPList.contains(dtepName) == 0)
{
m_DTEPList.append(dtepName);
}
}
TQStringList Document::groupsForDTEPs()
{
if (m_groupsForDTEPs.isEmpty())
return m_DTEPList;
else
return m_groupsForDTEPs;
}
TQString Document::annotationText(uint line)
{
TQMap<uint, QPair<TQString, TQString> >::Iterator it = m_annotations.find(line);
if (it != m_annotations.end())
return it.data().first;
else
return TQString::null;
}
void Document::setAnnotationText(uint line, const TQString& text)
{
if (text.isEmpty())
{
m_annotations.remove(line);
if (markIf)
markIf->removeMark(line, KTextEditor::MarkInterface::markType08);
} else
{
m_annotations.insert(line, qMakePair(text, TQString("")));
if (markIf)
markIf->setMark(line, KTextEditor::MarkInterface::markType08);
uint line, column;
viewCursorIf->cursorPositionReal(&line, &column);
viewCursorIf->setCursorPositionReal(line, 0);
const DTDStruct *dtd = currentDTD(true);
TQString commentBegin = "";
TQString commentEnd = "";
for (TQMap<TQString, TQString>::ConstIterator it = dtd->comments.constBegin(); it != dtd->comments.constEnd(); ++it)
{
commentBegin = it.key();
commentEnd = it.data();
if (commentEnd != "\n")
break;
}
if (commentBegin.isEmpty())
{
if (dtd->family == Xml)
{
commentBegin = "<!--";
commentEnd = "-->";
} else
{
commentBegin = "/*";
commentEnd = "*/";
}
}
TQString s = "@annotation: " + text;
s.prepend(commentBegin + " ");
s.append(" " + commentEnd + "\n");
insertText(s, true, true);
emit showAnnotation(line, "", qMakePair(text, TQString("")));
}
}
void Document::addAnnotation(uint line, const QPair<TQString, TQString>& annotation)
{
m_annotations.insert(line, annotation);
if (markIf)
markIf->setMark(line, KTextEditor::MarkInterface::markType08);
emit showAnnotation(line, "", annotation);
}
void Document::clearAnnotations()
{
if (markIf)
{
TQPtrList<KTextEditor::Mark> m = markIf->marks();
for (uint i=0; i < m.count(); i++)
markIf->removeMark( m.at(i)->line, KTextEditor::MarkInterface::markType08 );
}
m_annotations.clear();
}
bool Document::openURL(const KURL& url)
{
m_md5sum = "";
if (url.isLocalFile())
{
TQFile f(url.path());
if (f.open(IO_ReadOnly))
{
const char* c = "";
KMD5 context(c);
context.reset();
context.update(f);
m_md5sum = context.hexDigest();
f.close();
}
}
return m_doc->openURL(url);
}
#include "document.moc"