diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | ce4a32fe52ef09d8f5ff1dd22c001110902b60a2 (patch) | |
tree | 5ac38a06f3dde268dc7927dc155896926aaf7012 /kate/part | |
download | tdelibs-ce4a32fe52ef09d8f5ff1dd22c001110902b60a2.tar.gz tdelibs-ce4a32fe52ef09d8f5ff1dd22c001110902b60a2.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdelibs@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kate/part')
74 files changed, 47804 insertions, 0 deletions
diff --git a/kate/part/.kateconfig b/kate/part/.kateconfig new file mode 100644 index 000000000..5b0885abe --- /dev/null +++ b/kate/part/.kateconfig @@ -0,0 +1 @@ +kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/Makefile.am b/kate/part/Makefile.am new file mode 100644 index 000000000..4a182452e --- /dev/null +++ b/kate/part/Makefile.am @@ -0,0 +1,44 @@ +kde_module_LTLIBRARIES = libkatepart.la + +noinst_LTLIBRARIES = libkate.la + +libkate_la_SOURCES = katesearch.cpp katebuffer.cpp katecmds.cpp \ + kateundo.cpp katecursor.cpp katedialogs.cpp katedocument.cpp \ + katefactory.cpp katehighlight.cpp katesyntaxdocument.cpp \ + katetextline.cpp kateview.cpp kateconfig.cpp kateviewhelpers.cpp \ + katecodecompletion.cpp katedocumenthelpers.cpp \ + katecodefoldinghelpers.cpp kateviewinternal.cpp katebookmarks.cpp \ + kateprinter.cpp katefont.cpp katelinerange.cpp katesupercursor.cpp \ + katearbitraryhighlight.cpp katerenderer.cpp kateattribute.cpp \ + kateautoindent.cpp katefiletype.cpp kateschema.cpp katedocument.skel \ + katetemplatehandler.cpp katejscript.cpp katespell.cpp kateindentscriptabstracts.cpp \ + kateluaindentscript.cpp + +libkatepart_la_SOURCES = dummy.cpp + +libkatepart_la_LIBADD = libkate.la ../interfaces/libkatepartinterfaces.la $(top_builddir)/kdeprint/libkdeprint.la $(top_builddir)/kutils/libkutils.la $(top_builddir)/kjs/libkjs.la $(LUA_LIBS) + +libkatepart_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) + +INCLUDES= -I../interfaces -I$(top_srcdir) -I$(top_srcdir)/kdeprint -I$(top_srcdir)/interfaces -I$(top_srcdir)/interfaces/kregexpeditor -I$(top_srcdir)/kdefx -I$(top_srcdir)/kutils -I$(top_builddir)/kjs $(LUA_INCLUDES) $(all_includes) + +METASOURCES = AUTO + +LUT_FILES = katejscript.lut.h + +CREATE_HASH_TABLE = $(top_srcdir)/kjs/create_hash_table + +dummy.cpp: $(srcdir)/Makefile.am + touch $@ + +katejscript.lut.h : $(srcdir)/katejscript.cpp $(CREATE_HASH_TABLE) + $(PERL) $(CREATE_HASH_TABLE) $(srcdir)/katejscript.cpp > $@ +katejscript.lo: katejscript.lut.h + +CLEANFILES = $(LUT_FILES) + +## test program +TESTS = testkateregression +check_PROGRAMS = testkateregression +testkateregression_SOURCES = test_regression.cpp +testkateregression_LDADD = $(libkatepart_la_LIBADD) diff --git a/kate/part/configure.in.in b/kate/part/configure.in.in new file mode 100644 index 000000000..faf0717e9 --- /dev/null +++ b/kate/part/configure.in.in @@ -0,0 +1,89 @@ +AC_DEFUN([AC_PATH_LUA], [ + +dnl Based on the lua check used by yzis-M3 + + HAVE_LUA="" + + AC_ARG_WITH([lua], + AC_HELP_STRING([--without-lua], [Build without Lua libraries (default: check)])) + + AC_ARG_WITH(lua-dir, + AC_HELP_STRING([--with-lua-dir=DIR],[where the root of Lua 5.x is installed]), + [ + LUA="$withval" + LUA_INCLUDES=-I"$withval"/include + LUA_LIBS="-L$withval/lib" ]) + + AC_ARG_WITH(lua-includes, + AC_HELP_STRING([--with-lua-includes=DIR],[where the Lua includes are]), + [ LUA_INCLUDES="-I$withval" ]) + + AC_ARG_WITH(lua-libraries, + AC_HELP_STRING([--with-lua-libraries=DIR],[where the Lua library is installed]), + [ + LUA_LIBS="-L$withval" ]) + + + if test "x$with_lua" = "xno"; then + AC_MSG_RESULT([Not using Lua]) + else + if ! test "x$LUA" = "x"; then + AC_MSG_RESULT(using Lua from $LUA) + fi + if ! test "x$LUA_LIBS" = "x"; then + AC_MSG_RESULT(using Lua libraries in $LUA_LIBS) + fi + if ! test "x$LUA_INCLUDES" = "x"; then + AC_MSG_RESULT(using Lua includes in $LUA_INCLUDES) + fi + + dnl checking some headers first + ac_save_CFLAGS="$CFLAGS" + ac_save_CPPFLAGS="$CPPFLAGS" + ac_save_LDFLAGS="$LDFLAGS" + CFLAGS="$LUA_INCLUDES $CFLAGS" + CPPFLAGS="$LUA_INCLUDES $CPPFLAGS" + LDFLAGS="$LUA_LIBS $LDFLAGS" + + LUAH_FOUND="" + AC_CHECK_HEADER(lua.h,LUAH_FOUND="true", + [ AC_MSG_RESULT([lua.h was not found or was not usable, Lua 5.0 headers are required !]) ] + ) + LUALIBH_FOUND="" + AC_CHECK_HEADER(lualib.h,LUALIBH_FOUND="true", + [ AC_MSG_RESULT([lualib.h was not found or was not usable, Lua 5.0 headers are required !]) ] + ) + + + dnl find the libs name + if test -z "$LUALIBH_FOUND" -o -z "$LUAH_FOUND"; then + LUA_LIBS="" + else + AC_CHECK_LIB(lua50,lua_version, LUA_LIBS="$LUA_LIBS -llua50 -llualib50", + AC_CHECK_LIB(lua,lua_version, LUA_LIBS="$LUA_LIBS -llua -llualib", + [LUA_LIBS="" + AC_MSG_RESULT([Lua 5.0 libraries were not found !]) ] + ) + ) + fi + CFLAGS="$ac_save_CFLAGS" + CPPFLAGS="$ac_save_CPPFLAGS" + LDFLAGS="$ac_save_LDFLAGS" + + + if test -z "$LUA_LIBS"; then + LUA="" + LUA_INCLUDES="" + LUA_LIBS="" + else + AC_DEFINE_UNQUOTED(HAVE_LUA, 1, [Define if you have LUA > 5.0]) + HAVE_LUA="yes" + fi + AC_SUBST(LUA) + AC_SUBST(LUA_INCLUDES) + AC_SUBST(LUA_LIBS) + fi + +]) + +AC_PATH_LUA diff --git a/kate/part/katearbitraryhighlight.cpp b/kate/part/katearbitraryhighlight.cpp new file mode 100644 index 000000000..2ecbececc --- /dev/null +++ b/kate/part/katearbitraryhighlight.cpp @@ -0,0 +1,162 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Hamish Rodda <rodda@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "katearbitraryhighlight.h" +#include "katearbitraryhighlight.moc" + +#include "katesupercursor.h" +#include "katedocument.h" + +#include <kdebug.h> + +#include <qfont.h> + +KateArbitraryHighlightRange::KateArbitraryHighlightRange(KateSuperCursor* start, +KateSuperCursor* end, QObject* parent, const char* name) : +KateSuperRange(start, end, parent, name) { +} + +KateArbitraryHighlightRange::KateArbitraryHighlightRange(KateDocument* doc, const KateRange& range, QObject* parent, const char* name) + : KateSuperRange(doc, range, parent, name) +{ +} + +KateArbitraryHighlightRange::KateArbitraryHighlightRange(KateDocument* doc, const KateTextCursor& start, const KateTextCursor& end, QObject* parent, const char* name) + : KateSuperRange(doc, start, end, parent, name) +{ +} + +KateArbitraryHighlightRange::~KateArbitraryHighlightRange() +{ +} + +KateArbitraryHighlight::KateArbitraryHighlight(KateDocument* parent, const char* name) + : QObject(parent, name) +{ +} + +KateAttribute KateArbitraryHighlightRange::merge(QPtrList<KateSuperRange> ranges) +{ + ranges.sort(); + + KateAttribute ret; + + if (ranges.first() && ranges.current()->inherits("KateArbitraryHighlightRange")) + ret = *(static_cast<KateArbitraryHighlightRange*>(ranges.current())); + + KateSuperRange* r; + while ((r = ranges.next())) { + if (r->inherits("KateArbitraryHighlightRange")) { + KateArbitraryHighlightRange* hl = static_cast<KateArbitraryHighlightRange*>(r); + ret += *hl; + } + } + + return ret; +} + +void KateArbitraryHighlight::addHighlightToDocument(KateSuperRangeList* list) +{ + m_docHLs.append(list); + connect(list, SIGNAL(rangeEliminated(KateSuperRange*)), SLOT(slotRangeEliminated(KateSuperRange*))); + connect(list, SIGNAL(destroyed(QObject*)),SLOT(slotRangeListDeleted(QObject*))); +} + +void KateArbitraryHighlight::addHighlightToView(KateSuperRangeList* list, KateView* view) +{ + if (!m_viewHLs[view]) + m_viewHLs.insert(view, new QPtrList<KateSuperRangeList>()); + + m_viewHLs[view]->append(list); + + connect(list, SIGNAL(rangeEliminated(KateSuperRange*)), SLOT(slotTagRange(KateSuperRange*))); + connect(list, SIGNAL(tagRange(KateSuperRange*)), SLOT(slotTagRange(KateSuperRange*))); + connect(list, SIGNAL(destroyed(QObject*)),SLOT(slotRangeListDeleted(QObject*))); +} + +void KateArbitraryHighlight::slotRangeListDeleted(QObject* obj) { + int id=m_docHLs.findRef(static_cast<KateSuperRangeList*>(obj)); + if (id>=0) m_docHLs.take(id); + + for (QMap<KateView*, QPtrList<KateSuperRangeList>* >::Iterator it = m_viewHLs.begin(); it != m_viewHLs.end(); ++it) + for (KateSuperRangeList* l = (*it)->first(); l; l = (*it)->next()) + if (l==obj) { + l->take(); + break; //should we check if one list is stored more than once for a view ?? I don't think adding the same list 2 or more times is sane, but who knows (jowenn) + } +} + +KateSuperRangeList& KateArbitraryHighlight::rangesIncluding(uint line, KateView* view) +{ + // OPTIMISE make return value persistent + + static KateSuperRangeList s_return(false); + + Q_ASSERT(!s_return.autoDelete()); + s_return.clear(); + + //--- TEMPORARY OPTIMISATION: return the actual range when there are none or one. --- + if (m_docHLs.count() + m_viewHLs.count() == 0) + return s_return; + else if (m_docHLs.count() + m_viewHLs.count() == 1) + if (m_docHLs.count()) + return *(m_docHLs.first()); + else + if (m_viewHLs.values().first() && m_viewHLs.values().first()->count() == 1) + if (m_viewHLs.keys().first() == view && m_viewHLs.values().first()) + return *(m_viewHLs.values().first()->first()); + //--- END Temporary optimisation --- + + if (view) { + QPtrList<KateSuperRangeList>* list = m_viewHLs[view]; + if (list) + for (KateSuperRangeList* l = list->first(); l; l = list->next()) + if (l->count()) + s_return.appendList(l->rangesIncluding(line)); + + } else { + for (QMap<KateView*, QPtrList<KateSuperRangeList>* >::Iterator it = m_viewHLs.begin(); it != m_viewHLs.end(); ++it) + for (KateSuperRangeList* l = (*it)->first(); l; l = (*it)->next()) + if (l->count()) + s_return.appendList(l->rangesIncluding(line)); + } + + for (KateSuperRangeList* l = m_docHLs.first(); l; l = m_docHLs.next()) + if (l->count()) + s_return.appendList(l->rangesIncluding(line)); + + return s_return; +} + +void KateArbitraryHighlight::slotTagRange(KateSuperRange* range) +{ + emit tagLines(viewForRange(range), range); +} + +KateView* KateArbitraryHighlight::viewForRange(KateSuperRange* range) +{ + for (QMap<KateView*, QPtrList<KateSuperRangeList>* >::Iterator it = m_viewHLs.begin(); it != m_viewHLs.end(); ++it) + for (KateSuperRangeList* l = (*it)->first(); l; l = (*it)->next()) + if (l->contains(range)) + return it.key(); + + // This must belong to a document-global highlight + return 0L; +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katearbitraryhighlight.h b/kate/part/katearbitraryhighlight.h new file mode 100644 index 000000000..7955e7889 --- /dev/null +++ b/kate/part/katearbitraryhighlight.h @@ -0,0 +1,87 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Hamish Rodda <rodda@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KATEARBITRARYHIGHLIGHT_H +#define KATEARBITRARYHIGHLIGHT_H + +#include "kateattribute.h" +#include "katesupercursor.h" + +#include <qobject.h> +#include <qptrlist.h> +#include <qmap.h> + +class KateDocument; +class KateView; + +class KateArbitraryHighlightRange : public KateSuperRange, public KateAttribute +{ + Q_OBJECT + +public: + KateArbitraryHighlightRange(KateSuperCursor* start, KateSuperCursor* end, QObject* parent = 0L, const char* name = 0L); + KateArbitraryHighlightRange(KateDocument* doc, const KateRange& range, QObject* parent = 0L, const char* name = 0L); + KateArbitraryHighlightRange(KateDocument* doc, const KateTextCursor& start, const KateTextCursor& end, QObject* parent = 0L, const char* name = 0L); + + virtual ~KateArbitraryHighlightRange(); + + virtual void changed() { slotTagRange(); }; + + static KateAttribute merge(QPtrList<KateSuperRange> ranges); +}; + +/** + * An arbitrary highlighting interface for Kate. + * + * Ideas for more features: + * - integration with syntax highlighting: + * - eg. a signal for when a new context is created, destroyed, changed + * - hopefully make this extension more complimentary to the current syntax highlighting + * - signal for cursor movement + * - signal for mouse movement + * - identical highlight for whole list + * - signals for view movement + */ +class KateArbitraryHighlight : public QObject +{ + Q_OBJECT + +public: + KateArbitraryHighlight(KateDocument* parent = 0L, const char* name = 0L); + + void addHighlightToDocument(KateSuperRangeList* list); + void addHighlightToView(KateSuperRangeList* list, KateView* view); + + KateSuperRangeList& rangesIncluding(uint line, KateView* view = 0L); + +signals: + void tagLines(KateView* view, KateSuperRange* range); + +private slots: + void slotTagRange(KateSuperRange* range); + void slotRangeListDeleted(QObject* obj); +private: + KateView* viewForRange(KateSuperRange* range); + + QMap<KateView*, QPtrList<KateSuperRangeList>* > m_viewHLs; + QPtrList<KateSuperRangeList> m_docHLs; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateattribute.cpp b/kate/part/kateattribute.cpp new file mode 100644 index 000000000..5de93a406 --- /dev/null +++ b/kate/part/kateattribute.cpp @@ -0,0 +1,268 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Hamish Rodda <rodda@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kateattribute.h" + +KateAttribute::KateAttribute() + : m_weight(QFont::Normal) + , m_italic(false) + , m_underline(false) + , m_overline(false) + , m_strikeout(false) + , m_itemsSet(0) + +{ +} + +KateAttribute::~KateAttribute() +{ +} + +void KateAttribute::clear() +{ + m_itemsSet=0; +} + +KateAttribute& KateAttribute::operator+=(const KateAttribute& a) +{ + if (a.itemSet(Weight)) + setWeight(a.weight()); + + if (a.itemSet(Italic)) + setItalic(a.italic()); + + if (a.itemSet(Underline)) + setUnderline(a.underline()); + + if (a.itemSet(Overline)) + setOverline(a.overline()); + + if (a.itemSet(StrikeOut)) + setStrikeOut(a.strikeOut()); + + if (a.itemSet(Outline)) + setOutline(a.outline()); + + if (a.itemSet(TextColor)) + setTextColor(a.textColor()); + + if (a.itemSet(SelectedTextColor)) + setSelectedTextColor(a.selectedTextColor()); + + if (a.itemSet(BGColor)) + setBGColor(a.bgColor()); + + if (a.itemSet(SelectedBGColor)) + setSelectedBGColor(a.selectedBGColor()); + + return *this; +} + +QFont KateAttribute::font(const QFont& ref) +{ + QFont ret = ref; + + if (itemSet(Weight)) + ret.setWeight(weight()); + if (itemSet(Italic)) + ret.setItalic(italic()); + if (itemSet(Underline)) + ret.setUnderline(underline()); + if (itemSet(Overline)) + ret.setOverline(overline()); + if (itemSet(StrikeOut)) + ret.setStrikeOut(strikeOut()); + + return ret; +} + +void KateAttribute::setWeight(int weight) +{ + if (!(m_itemsSet & Weight) || m_weight != weight) + { + m_itemsSet |= Weight; + + m_weight = weight; + + changed(); + } +} + +void KateAttribute::setBold(bool enable) +{ + setWeight(enable ? QFont::Bold : QFont::Normal); +} + +void KateAttribute::setItalic(bool enable) +{ + if (!(m_itemsSet & Italic) || m_italic != enable) + { + m_itemsSet |= Italic; + + m_italic = enable; + + changed(); + } +} + +void KateAttribute::setUnderline(bool enable) +{ + if (!(m_itemsSet & Underline) || m_underline != enable) + { + m_itemsSet |= Underline; + + m_underline = enable; + + changed(); + } +} + +void KateAttribute::setOverline(bool enable) +{ + if (!(m_itemsSet & Overline) || m_overline != enable) + { + m_itemsSet |= Overline; + + m_overline = enable; + + changed(); + } +} + +void KateAttribute::setStrikeOut(bool enable) +{ + if (!(m_itemsSet & StrikeOut) || m_strikeout != enable) + { + m_itemsSet |= StrikeOut; + + m_strikeout = enable; + + changed(); + } +} + +void KateAttribute::setOutline(const QColor& color) +{ + if (!(m_itemsSet & Outline) || m_outline != color) + { + m_itemsSet |= Outline; + + m_outline = color; + + changed(); + } +} + +void KateAttribute::setTextColor(const QColor& color) +{ + if (!(m_itemsSet & TextColor) || m_textColor != color) + { + m_itemsSet |= TextColor; + + m_textColor = color; + + changed(); + } +} + +void KateAttribute::setSelectedTextColor(const QColor& color) +{ + if (!(m_itemsSet & SelectedTextColor) || m_selectedTextColor != color) + { + m_itemsSet |= SelectedTextColor; + + m_selectedTextColor = color; + + changed(); + } +} + +void KateAttribute::setBGColor(const QColor& color) +{ + if (!(m_itemsSet & BGColor) || m_bgColor != color) + { + m_itemsSet |= BGColor; + + m_bgColor = color; + + changed(); + } +} + +void KateAttribute::setSelectedBGColor(const QColor& color) +{ + if (!(m_itemsSet & SelectedBGColor) || m_selectedBGColor != color) + { + m_itemsSet |= SelectedBGColor; + + m_selectedBGColor = color; + + changed(); + } +} + +bool operator ==(const KateAttribute& h1, const KateAttribute& h2) +{ + if (h1.m_itemsSet != h2.m_itemsSet) + return false; + + if (h1.itemSet(KateAttribute::Weight)) + if (h1.m_weight != h2.m_weight) + return false; + + if (h1.itemSet(KateAttribute::Italic)) + if (h1.m_italic != h2.m_italic) + return false; + + if (h1.itemSet(KateAttribute::Underline)) + if (h1.m_underline != h2.m_underline) + return false; + + if (h1.itemSet(KateAttribute::StrikeOut)) + if (h1.m_strikeout != h2.m_strikeout) + return false; + + if (h1.itemSet(KateAttribute::Outline)) + if (h1.m_outline != h2.m_outline) + return false; + + if (h1.itemSet(KateAttribute::TextColor)) + if (h1.m_textColor != h2.m_textColor) + return false; + + if (h1.itemSet(KateAttribute::SelectedTextColor)) + if (h1.m_selectedTextColor != h2.m_selectedTextColor) + return false; + + if (h1.itemSet(KateAttribute::BGColor)) + if (h1.m_bgColor != h2.m_bgColor) + return false; + + if (h1.itemSet(KateAttribute::SelectedBGColor)) + if (h1.m_selectedBGColor != h2.m_selectedBGColor) + return false; + + return true; +} + +bool operator !=(const KateAttribute& h1, const KateAttribute& h2) +{ + return !(h1 == h2); +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateattribute.h b/kate/part/kateattribute.h new file mode 100644 index 000000000..c4933e690 --- /dev/null +++ b/kate/part/kateattribute.h @@ -0,0 +1,147 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Hamish Rodda <rodda@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __KATE_ATTRIBUTE_H__ +#define __KATE_ATTRIBUTE_H__ + +#include "katefont.h" + +#include <qcolor.h> + +/** + * The Attribute class incorporates all text decorations supported by Kate. + * + * TODO: store the actual font as well. + * TODO: update changed mechanism - use separate bitfield + */ +class KateAttribute +{ +public: + enum items { + Weight = 0x1, + Bold = 0x2, + Italic = 0x4, + Underline = 0x8, + StrikeOut = 0x10, + Outline = 0x20, + TextColor = 0x40, + SelectedTextColor = 0x80, + BGColor = 0x100, + SelectedBGColor = 0x200, + Overline = 0x400 + }; + + KateAttribute(); + virtual ~KateAttribute(); + + QFont font(const QFont& ref); + + inline int width(KateFontStruct& fs, const QString& text, int col, int tabWidth) const + { return fs.width(text, col, bold(), italic(), tabWidth); }; + + // Non-preferred function when you have a string and you want one char's width!! + inline int width(KateFontStruct& fs, const QChar& c, int tabWidth) const + { return fs.width(c, bold(), italic(), tabWidth); }; + + inline bool itemSet(int item) const + { return item & m_itemsSet; }; + + inline bool isSomethingSet() const + { return m_itemsSet; }; + + inline int itemsSet() const + { return m_itemsSet; }; + + inline void clearAttribute(int item) + { m_itemsSet &= (~item); } + + inline int weight() const + { return m_weight; }; + + void setWeight(int weight); + + inline bool bold() const + { return weight() >= QFont::Bold; }; + + void setBold(bool enable = true); + + inline bool italic() const + { return m_italic; }; + + void setItalic(bool enable = true); + + inline bool overline() const + { return m_overline; }; + + void setOverline(bool enable = true); + + inline bool underline() const + { return m_underline; }; + + void setUnderline(bool enable = true); + + inline bool strikeOut() const + { return m_strikeout; }; + + void setStrikeOut(bool enable = true); + + inline const QColor& outline() const + { return m_outline; }; + + void setOutline(const QColor& color); + + inline const QColor& textColor() const + { return m_textColor; }; + + void setTextColor(const QColor& color); + + inline const QColor& selectedTextColor() const + { return m_selectedTextColor; }; + + void setSelectedTextColor(const QColor& color); + + inline const QColor& bgColor() const + { return m_bgColor; }; + + void setBGColor(const QColor& color); + + inline const QColor& selectedBGColor() const + { return m_selectedBGColor; }; + + void setSelectedBGColor(const QColor& color); + + KateAttribute& operator+=(const KateAttribute& a); + + friend bool operator ==(const KateAttribute& h1, const KateAttribute& h2); + friend bool operator !=(const KateAttribute& h1, const KateAttribute& h2); + + virtual void changed() { m_changed = true; }; + bool isChanged() { bool ret = m_changed; m_changed = false; return ret; }; + + void clear(); + +private: + int m_weight; + bool m_italic, m_underline, m_overline,m_strikeout, m_changed; + QColor m_outline, m_textColor, m_selectedTextColor, m_bgColor, m_selectedBGColor; + int m_itemsSet; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateautoindent.cpp b/kate/part/kateautoindent.cpp new file mode 100644 index 000000000..7c58b6051 --- /dev/null +++ b/kate/part/kateautoindent.cpp @@ -0,0 +1,2543 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Jesse Yurkovich <yurkjes@iit.edu> + Copyright (C) 2004 >Anders Lund <anders@alweb.dk> (KateVarIndent class) + Copyright (C) 2005 Dominik Haumann <dhdev@gmx.de> (basic support for config page) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kateautoindent.h" +#include "kateautoindent.moc" + +#include "kateconfig.h" +#include "katehighlight.h" +#include "katefactory.h" +#include "katejscript.h" +#include "kateview.h" + +#include <klocale.h> +#include <kdebug.h> +#include <kpopupmenu.h> + +#include <cctype> + +//BEGIN KateAutoIndent + +KateAutoIndent *KateAutoIndent::createIndenter (KateDocument *doc, uint mode) +{ + if (mode == KateDocumentConfig::imNormal) + return new KateNormalIndent (doc); + else if (mode == KateDocumentConfig::imCStyle) + return new KateCSmartIndent (doc); + else if (mode == KateDocumentConfig::imPythonStyle) + return new KatePythonIndent (doc); + else if (mode == KateDocumentConfig::imXmlStyle) + return new KateXmlIndent (doc); + else if (mode == KateDocumentConfig::imCSAndS) + return new KateCSAndSIndent (doc); + else if ( mode == KateDocumentConfig::imVarIndent ) + return new KateVarIndent ( doc ); +// else if ( mode == KateDocumentConfig::imScriptIndent) +// return new KateScriptIndent ( doc ); + + return new KateAutoIndent (doc); +} + +QStringList KateAutoIndent::listModes () +{ + QStringList l; + + l << modeDescription(KateDocumentConfig::imNone); + l << modeDescription(KateDocumentConfig::imNormal); + l << modeDescription(KateDocumentConfig::imCStyle); + l << modeDescription(KateDocumentConfig::imPythonStyle); + l << modeDescription(KateDocumentConfig::imXmlStyle); + l << modeDescription(KateDocumentConfig::imCSAndS); + l << modeDescription(KateDocumentConfig::imVarIndent); +// l << modeDescription(KateDocumentConfig::imScriptIndent); + + return l; +} + +QString KateAutoIndent::modeName (uint mode) +{ + if (mode == KateDocumentConfig::imNormal) + return QString ("normal"); + else if (mode == KateDocumentConfig::imCStyle) + return QString ("cstyle"); + else if (mode == KateDocumentConfig::imPythonStyle) + return QString ("python"); + else if (mode == KateDocumentConfig::imXmlStyle) + return QString ("xml"); + else if (mode == KateDocumentConfig::imCSAndS) + return QString ("csands"); + else if ( mode == KateDocumentConfig::imVarIndent ) + return QString( "varindent" ); +// else if ( mode == KateDocumentConfig::imScriptIndent ) +// return QString( "scriptindent" ); + + return QString ("none"); +} + +QString KateAutoIndent::modeDescription (uint mode) +{ + if (mode == KateDocumentConfig::imNormal) + return i18n ("Normal"); + else if (mode == KateDocumentConfig::imCStyle) + return i18n ("C Style"); + else if (mode == KateDocumentConfig::imPythonStyle) + return i18n ("Python Style"); + else if (mode == KateDocumentConfig::imXmlStyle) + return i18n ("XML Style"); + else if (mode == KateDocumentConfig::imCSAndS) + return i18n ("S&S C Style"); + else if ( mode == KateDocumentConfig::imVarIndent ) + return i18n("Variable Based Indenter"); +// else if ( mode == KateDocumentConfig::imScriptIndent ) +// return i18n("JavaScript Indenter"); + + return i18n ("None"); +} + +uint KateAutoIndent::modeNumber (const QString &name) +{ + if (modeName(KateDocumentConfig::imNormal) == name) + return KateDocumentConfig::imNormal; + else if (modeName(KateDocumentConfig::imCStyle) == name) + return KateDocumentConfig::imCStyle; + else if (modeName(KateDocumentConfig::imPythonStyle) == name) + return KateDocumentConfig::imPythonStyle; + else if (modeName(KateDocumentConfig::imXmlStyle) == name) + return KateDocumentConfig::imXmlStyle; + else if (modeName(KateDocumentConfig::imCSAndS) == name) + return KateDocumentConfig::imCSAndS; + else if ( modeName( KateDocumentConfig::imVarIndent ) == name ) + return KateDocumentConfig::imVarIndent; +// else if ( modeName( KateDocumentConfig::imScriptIndent ) == name ) +// return KateDocumentConfig::imScriptIndent; + + return KateDocumentConfig::imNone; +} + +bool KateAutoIndent::hasConfigPage (uint mode) +{ +// if ( mode == KateDocumentConfig::imScriptIndent ) +// return true; + + return false; +} + +IndenterConfigPage* KateAutoIndent::configPage(QWidget *parent, uint mode) +{ +// if ( mode == KateDocumentConfig::imScriptIndent ) +// return new ScriptIndentConfigPage(parent, "script_indent_config_page"); + + return 0; +} + +KateAutoIndent::KateAutoIndent (KateDocument *_doc) +: QObject(), doc(_doc) +{ +} +KateAutoIndent::~KateAutoIndent () +{ +} + +//END KateAutoIndent + +//BEGIN KateViewIndentAction +KateViewIndentationAction::KateViewIndentationAction(KateDocument *_doc, const QString& text, QObject* parent, const char* name) + : KActionMenu (text, parent, name), doc(_doc) +{ + connect(popupMenu(),SIGNAL(aboutToShow()),this,SLOT(slotAboutToShow())); +} + +void KateViewIndentationAction::slotAboutToShow() +{ + QStringList modes = KateAutoIndent::listModes (); + + popupMenu()->clear (); + for (uint z=0; z<modes.size(); ++z) + popupMenu()->insertItem ( '&' + KateAutoIndent::modeDescription(z).replace('&', "&&"), this, SLOT(setMode(int)), 0, z); + + popupMenu()->setItemChecked (doc->config()->indentationMode(), true); +} + +void KateViewIndentationAction::setMode (int mode) +{ + doc->config()->setIndentationMode((uint)mode); +} +//END KateViewIndentationAction + +//BEGIN KateNormalIndent + +KateNormalIndent::KateNormalIndent (KateDocument *_doc) + : KateAutoIndent (_doc) +{ + // if highlighting changes, update attributes + connect(_doc, SIGNAL(hlChanged()), this, SLOT(updateConfig())); +} + +KateNormalIndent::~KateNormalIndent () +{ +} + +void KateNormalIndent::updateConfig () +{ + KateDocumentConfig *config = doc->config(); + + useSpaces = config->configFlags() & KateDocument::cfSpaceIndent || config->configFlags() & KateDocumentConfig::cfReplaceTabsDyn; + mixedIndent = useSpaces && config->configFlags() & KateDocumentConfig::cfMixedIndent; + keepProfile = config->configFlags() & KateDocument::cfKeepIndentProfile; + tabWidth = config->tabWidth(); + indentWidth = useSpaces? config->indentationWidth() : tabWidth; + + commentAttrib = 255; + doxyCommentAttrib = 255; + regionAttrib = 255; + symbolAttrib = 255; + alertAttrib = 255; + tagAttrib = 255; + wordAttrib = 255; + keywordAttrib = 255; + normalAttrib = 255; + extensionAttrib = 255; + preprocessorAttrib = 255; + stringAttrib = 255; + charAttrib = 255; + + KateHlItemDataList items; + doc->highlight()->getKateHlItemDataListCopy (0, items); + + for (uint i=0; i<items.count(); i++) + { + QString name = items.at(i)->name; + if (name.find("Comment") != -1 && commentAttrib == 255) + { + commentAttrib = i; + } + else if (name.find("Region Marker") != -1 && regionAttrib == 255) + { + regionAttrib = i; + } + else if (name.find("Symbol") != -1 && symbolAttrib == 255) + { + symbolAttrib = i; + } + else if (name.find("Alert") != -1) + { + alertAttrib = i; + } + else if (name.find("Comment") != -1 && commentAttrib != 255 && doxyCommentAttrib == 255) + { + doxyCommentAttrib = i; + } + else if (name.find("Tags") != -1 && tagAttrib == 255) + { + tagAttrib = i; + } + else if (name.find("Word") != -1 && wordAttrib == 255) + { + wordAttrib = i; + } + else if (name.find("Keyword") != -1 && keywordAttrib == 255) + { + keywordAttrib = i; + } + else if (name.find("Normal") != -1 && normalAttrib == 255) + { + normalAttrib = i; + } + else if (name.find("Extensions") != -1 && extensionAttrib == 255) + { + extensionAttrib = i; + } + else if (name.find("Preprocessor") != -1 && preprocessorAttrib == 255) + { + preprocessorAttrib = i; + } + else if (name.find("String") != -1 && stringAttrib == 255) + { + stringAttrib = i; + } + else if (name.find("Char") != -1 && charAttrib == 255) + { + charAttrib = i; + } + } +} + +bool KateNormalIndent::isBalanced (KateDocCursor &begin, const KateDocCursor &end, QChar open, QChar close, uint &pos) const +{ + int parenOpen = 0; + bool atLeastOne = false; + bool getNext = false; + + pos = doc->plainKateTextLine(begin.line())->firstChar(); + + // Iterate one-by-one finding opening and closing chars + // Assume that open and close are 'Symbol' characters + while (begin < end) + { + QChar c = begin.currentChar(); + if (begin.currentAttrib() == symbolAttrib) + { + if (c == open) + { + if (!atLeastOne) + { + atLeastOne = true; + getNext = true; + pos = measureIndent(begin) + 1; + } + parenOpen++; + } + else if (c == close) + { + parenOpen--; + } + } + else if (getNext && !c.isSpace()) + { + getNext = false; + pos = measureIndent(begin); + } + + if (atLeastOne && parenOpen <= 0) + return true; + + if (!begin.moveForward(1)) + break; + } + + return (atLeastOne) ? false : true; +} + +bool KateNormalIndent::skipBlanks (KateDocCursor &cur, KateDocCursor &max, bool newline) const +{ + int curLine = cur.line(); + if (newline) + cur.moveForward(1); + + if (cur >= max) + return false; + + do + { + uchar attrib = cur.currentAttrib(); + const QString hlFile = doc->highlight()->hlKeyForAttrib( attrib ); + + if (attrib != commentAttrib && attrib != regionAttrib && attrib != alertAttrib && attrib != preprocessorAttrib && !hlFile.endsWith("doxygen.xml")) + { + QChar c = cur.currentChar(); + if (!c.isNull() && !c.isSpace()) + break; + } + + if (!cur.moveForward(1)) + { + // not able to move forward, so set cur to max + cur = max; + break; + } + // Make sure col is 0 if we spill into next line i.e. count the '\n' as a character + if (curLine != cur.line()) + { + if (!newline) + break; + curLine = cur.line(); + cur.setCol(0); + } + } while (cur < max); + + if (cur > max) + cur = max; + return true; +} + +uint KateNormalIndent::measureIndent (KateDocCursor &cur) const +{ + // We cannot short-cut by checking for useSpaces because there may be + // tabs in the line despite this setting. + + return doc->plainKateTextLine(cur.line())->cursorX(cur.col(), tabWidth); +} + +QString KateNormalIndent::tabString(uint pos) const +{ + QString s; + pos = kMin (pos, 80U); // sanity check for large values of pos + + if (!useSpaces || mixedIndent) + { + while (pos >= tabWidth) + { + s += '\t'; + pos -= tabWidth; + } + } + while (pos > 0) + { + s += ' '; + pos--; + } + return s; +} + +void KateNormalIndent::processNewline (KateDocCursor &begin, bool /*needContinue*/) +{ + int line = begin.line() - 1; + int pos = begin.col(); + + while ((line > 0) && (pos < 0)) // search a not empty text line + pos = doc->plainKateTextLine(--line)->firstChar(); + + if (pos > 0) + { + QString filler = doc->text(line, 0, line, pos); + doc->insertText(begin.line(), 0, filler); + begin.setCol(filler.length()); + } + else + begin.setCol(0); +} + +//END + +//BEGIN KateCSmartIndent + +KateCSmartIndent::KateCSmartIndent (KateDocument *doc) +: KateNormalIndent (doc), + allowSemi (false), + processingBlock (false) +{ + kdDebug(13030)<<"CREATING KATECSMART INTDETER"<<endl; +} + +KateCSmartIndent::~KateCSmartIndent () +{ + +} + +void KateCSmartIndent::processLine (KateDocCursor &line) +{ + kdDebug(13030)<<"PROCESSING LINE "<<line.line()<<endl; + KateTextLine::Ptr textLine = doc->plainKateTextLine(line.line()); + + int firstChar = textLine->firstChar(); + // Empty line is worthless ... but only when doing more than 1 line + if (firstChar == -1 && processingBlock) + return; + + uint indent = 0; + + // TODO Here we do not check for beginning and ending comments ... + QChar first = textLine->getChar(firstChar); + QChar last = textLine->getChar(textLine->lastChar()); + + if (first == '}') + { + indent = findOpeningBrace(line); + } + else if (first == ')') + { + indent = findOpeningParen(line); + } + else if (first == '{') + { + // If this is the first brace, we keep the indent at 0 + KateDocCursor temp(line.line(), firstChar, doc); + if (!firstOpeningBrace(temp)) + indent = calcIndent(temp, false); + } + else if (first == ':') + { + // Initialization lists (handle c++ and c#) + int pos = findOpeningBrace(line); + if (pos == 0) + indent = indentWidth; + else + indent = pos + (indentWidth * 2); + } + else if (last == ':') + { + if (textLine->stringAtPos (firstChar, "case") || + textLine->stringAtPos (firstChar, "default") || + textLine->stringAtPos (firstChar, "public") || + textLine->stringAtPos (firstChar, "private") || + textLine->stringAtPos (firstChar, "protected") || + textLine->stringAtPos (firstChar, "signals") || + textLine->stringAtPos (firstChar, "Q_SIGNALS") || + textLine->stringAtPos (firstChar, "Q_SLOTS") || + textLine->stringAtPos (firstChar, "slots")) + { + indent = findOpeningBrace(line) + indentWidth; + } + } + else if (first == '*') + { + if (last == '/') + { + int lineEnd = textLine->lastChar(); + if (lineEnd > 0 && textLine->getChar(lineEnd - 1) == '*') + { + indent = findOpeningComment(line); + if (textLine->attribute(firstChar) == doxyCommentAttrib) + indent++; + } + else + return; + } + else + { + KateDocCursor temp = line; + if (textLine->attribute(firstChar) == doxyCommentAttrib) + indent = calcIndent(temp, false) + 1; + else + indent = calcIndent(temp, true); + } + } + else if (first == '#') + { + // c# regions + if (textLine->stringAtPos (firstChar, "#region") || + textLine->stringAtPos (firstChar, "#endregion")) + { + KateDocCursor temp = line; + indent = calcIndent(temp, true); + } + } + else + { + // Everything else ... + if (first == '/' && last != '/') + return; + + KateDocCursor temp = line; + indent = calcIndent(temp, true); + if (indent == 0) + { + KateNormalIndent::processNewline(line, true); + return; + } + } + + // Slightly faster if we don't indent what we don't have to + if (indent != measureIndent(line) || first == '}' || first == '{' || first == '#') + { + doc->removeText(line.line(), 0, line.line(), firstChar); + QString filler = tabString(indent); + if (indent > 0) doc->insertText(line.line(), 0, filler); + if (!processingBlock) line.setCol(filler.length()); + } +} + +void KateCSmartIndent::processSection (const KateDocCursor &begin, const KateDocCursor &end) +{ + kdDebug(13030)<<"PROCESS SECTION"<<endl; + KateDocCursor cur = begin; + QTime t; + t.start(); + + processingBlock = (end.line() - cur.line() > 0) ? true : false; + + while (cur.line() <= end.line()) + { + processLine (cur); + if (!cur.gotoNextLine()) + break; + } + + processingBlock = false; + kdDebug(13030) << "+++ total: " << t.elapsed() << endl; +} + +bool KateCSmartIndent::handleDoxygen (KateDocCursor &begin) +{ + // Factor out the rather involved Doxygen stuff here ... + int line = begin.line(); + int first = -1; + while ((line > 0) && (first < 0)) + first = doc->plainKateTextLine(--line)->firstChar(); + + if (first >= 0) + { + KateTextLine::Ptr textLine = doc->plainKateTextLine(line); + bool insideDoxygen = false; + bool justAfterDoxygen = false; + if (textLine->attribute(first) == doxyCommentAttrib || textLine->attribute(textLine->lastChar()) == doxyCommentAttrib) + { + const int last = textLine->lastChar(); + if (last <= 0 || !(justAfterDoxygen = textLine->stringAtPos(last-1, "*/"))) + insideDoxygen = true; + if (justAfterDoxygen) + justAfterDoxygen &= textLine->string().find("/**") < 0; + while (textLine->attribute(first) != doxyCommentAttrib && first <= textLine->lastChar()) + first++; + if (textLine->stringAtPos(first, "//")) + return false; + } + + // Align the *'s and then go ahead and insert one too ... + if (insideDoxygen) + { + textLine = doc->plainKateTextLine(begin.line()); + first = textLine->firstChar(); + int indent = findOpeningComment(begin); + QString filler = tabString (indent); + + bool doxygenAutoInsert = doc->config()->configFlags() & KateDocumentConfig::cfDoxygenAutoTyping; + + if ( doxygenAutoInsert && + ((first < 0) || (!textLine->stringAtPos(first, "*/") && !textLine->stringAtPos(first, "*")))) + { + filler = filler + " * "; + } + + doc->removeText (begin.line(), 0, begin.line(), first); + doc->insertText (begin.line(), 0, filler); + begin.setCol(filler.length()); + + return true; + } + // Align position with beginning of doxygen comment. Otherwise the + // indentation is one too much. + else if (justAfterDoxygen) + { + textLine = doc->plainKateTextLine(begin.line()); + first = textLine->firstChar(); + int indent = findOpeningComment(begin); + QString filler = tabString (indent); + + doc->removeText (begin.line(), 0, begin.line(), first); + doc->insertText (begin.line(), 0, filler); + begin.setCol(filler.length()); + + return true; + } + } + + return false; +} + +void KateCSmartIndent::processNewline (KateDocCursor &begin, bool needContinue) +{ + if (!handleDoxygen (begin)) + { + KateTextLine::Ptr textLine = doc->plainKateTextLine(begin.line()); + bool inMiddle = textLine->firstChar() > -1; + + int indent = calcIndent (begin, needContinue); + + if (indent > 0 || inMiddle) + { + QString filler = tabString (indent); + doc->insertText (begin.line(), 0, filler); + begin.setCol(filler.length()); + + // Handles cases where user hits enter at the beginning or middle of text + if (inMiddle) + { + processLine(begin); + begin.setCol(textLine->firstChar()); + } + } + else + { + KateNormalIndent::processNewline (begin, needContinue); + } + + if (begin.col() < 0) + begin.setCol(0); + } +} + +/** + * Returns true when the given attribute matches any "colon influence immune" + * attribute + * @param indenter indenter + * @param attr1 attribute of previous char + * @param attr2 attribute of char preceding previous char + * @param prev1 previous character (0 if none) + * @param prev2 character preceding previous character (0 if none) + */ +static inline bool isColonImmune(const KateNormalIndent &indenter, + uchar attr1, uchar attr2, + QChar prev1, QChar prev2) +{ + return attr1 == indenter.preprocessorAttrib + // FIXME: no way to discriminate against multiline comment and single + // line comment. Therefore, using prev? is futile. + || attr1 == indenter.commentAttrib /*&& prev2 != '*' && prev1 != '/'*/ + || attr1 == indenter.doxyCommentAttrib + || attr1 == indenter.stringAttrib && (attr2 != indenter.stringAttrib + || (prev1 != '"' || prev2 == '\\' && attr2 == indenter.charAttrib)) + || prev1 == '\'' && attr1 != indenter.charAttrib; +} + +/** + * Returns true when the colon is allowed to reindent the current line + * @param indenter current indenter + * @param line current line + * @param curCol column of most recently input character + */ +static inline bool colonPermitsReindent(const KateNormalIndent &indenter, + const KateTextLine::Ptr &line, + int curCol + ) +{ + const QString txt = line->string(0,curCol); + // do we have any significant preceding colon? + for (int pos = 0; (pos = txt.find(':', pos)) >= 0; pos++) { + if (line->attribute(pos) == indenter.symbolAttrib) + // yes, it has already contributed to this line's indentation, don't + // indent again + return false; + } + + // otherwise, check whether this colon is not within an influence + // immune attribute range + return !isColonImmune(indenter, line->attribute(curCol - 1), + line->attribute(curCol - 2), + txt[curCol - 1], txt[curCol - 2]); +} + +void KateCSmartIndent::processChar(QChar c) +{ + // You may be curious about 'n' among the triggers: + // It is used to discriminate C#'s #region/#endregion which are indented + // against normal preprocessing statements which aren't indented. + static const QString triggers("}{)/:#n"); + static const QString firstTriggers("}{)/:#"); + static const QString lastTriggers(":n"); + if (triggers.find(c) < 0) + return; + + KateView *view = doc->activeView(); + int curCol = view->cursorColumnReal() - 1; + KateDocCursor begin(view->cursorLine(), 0, doc); + + KateTextLine::Ptr textLine = doc->plainKateTextLine(begin.line()); + const QChar curChar = textLine->getChar(curCol); + const int first = textLine->firstChar(); + const QChar firstChar = textLine->getChar(first); + +#if 0 // nice try + // Only indent on symbols or preprocessing directives -- never on + // anything else + kdDebug() << "curChar " << curChar << " curCol " << curCol << " textlen " << textLine->length() << " a " << textLine->attribute( curCol ) << " sym " << symbolAttrib << " pp " << preprocessorAttrib << endl; + if (!(((curChar == '#' || curChar == 'n') + && textLine->attribute( curCol ) == preprocessorAttrib) + || textLine->attribute( curCol ) == symbolAttrib) + ) + return; + kdDebug() << "curChar " << curChar << endl; +#endif + + if (c == 'n') + { + if (firstChar != '#' || textLine->string(curCol-5, 5) != QString::fromLatin1("regio")) + return; + } + + if ( c == '/' ) + { + // dominik: if line is "* /", change it to "*/" + if ( textLine->attribute( begin.col() ) == doxyCommentAttrib ) + { + // if the first char exists and is a '*', and the next non-space-char + // is already the just typed '/', concatenate it to "*/". + if ( first != -1 + && firstChar == '*' + && textLine->nextNonSpaceChar( first+1 ) == view->cursorColumnReal()-1 ) + doc->removeText( view->cursorLine(), first+1, view->cursorLine(), view->cursorColumnReal()-1); + } + + // ls: never have comments change the indentation. + return; + } + + // ls: only reindent line if the user actually expects it + // I. e. take action on single braces on line or last colon, but inhibit + // any reindentation if any of those characters appear amidst some section + // of the line + const QChar lastChar = textLine->getChar(textLine->lastChar()); + int pos; + if (((c == firstChar && firstTriggers.find(firstChar) >= 0) + || (c == lastChar && lastTriggers.find(lastChar) >= 0)) + && (c != ':' || colonPermitsReindent(*this, textLine, curCol))) + processLine(begin); +} + + +uint KateCSmartIndent::calcIndent(KateDocCursor &begin, bool needContinue) +{ + KateTextLine::Ptr textLine; + KateDocCursor cur = begin; + + uint anchorIndent = 0; + int anchorPos = 0; + int parenCount = 0; // Possibly in a multiline for stmt. Used to skip ';' ... + bool found = false; + bool isSpecial = false; + bool potentialAnchorSeen = false; + bool isArg = false; // ...arg,<newline> + bool parenthesizedArg = false; // ...(arg,<newline> + + //kdDebug(13030) << "calcIndent begin line:" << begin.line() << " col:" << begin.col() << endl; + + // Find Indent Anchor Point + while (cur.gotoPreviousLine()) + { + isSpecial = found = false; + textLine = doc->plainKateTextLine(cur.line()); + + // Skip comments and handle cases like if (...) { stmt; + int pos = textLine->lastChar(); + int openCount = 0; + int otherAnchor = -1; + do + { + if (textLine->attribute(pos) == symbolAttrib) + { + QChar tc = textLine->getChar (pos); + if ((tc == ';' || tc == ':' || tc == ',') && otherAnchor == -1 && parenCount <= 0) { + otherAnchor = pos, potentialAnchorSeen = true; + isArg = tc == ','; + } else if (tc == ')') + parenCount++; + else if (tc == '(') + parenCount--, parenthesizedArg = isArg, potentialAnchorSeen = true; + else if (tc == '}') + openCount--; + else if (tc == '{') + { + openCount++, potentialAnchorSeen = true; + if (openCount == 1) + break; + } + } + } while (--pos >= textLine->firstChar()); + + if (openCount != 0 || otherAnchor != -1) + { + found = true; + QChar c; + if (openCount > 0) + c = '{'; + else if (openCount < 0) + c = '}'; + else if (otherAnchor >= 0) + c = textLine->getChar (otherAnchor); + + int specialIndent = 0; + if (c == ':' && needContinue) + { + QChar ch; + specialIndent = textLine->firstChar(); + if (textLine->stringAtPos(specialIndent, "case")) + ch = textLine->getChar(specialIndent + 4); + else if (textLine->stringAtPos(specialIndent, "default")) + ch = textLine->getChar(specialIndent + 7); + else if (textLine->stringAtPos(specialIndent, "public")) + ch = textLine->getChar(specialIndent + 6); + else if (textLine->stringAtPos(specialIndent, "private")) + ch = textLine->getChar(specialIndent + 7); + else if (textLine->stringAtPos(specialIndent, "protected")) + ch = textLine->getChar(specialIndent + 9); + else if (textLine->stringAtPos(specialIndent, "signals")) + ch = textLine->getChar(specialIndent + 7); + else if (textLine->stringAtPos(specialIndent, "Q_SIGNALS")) + ch = textLine->getChar(specialIndent + 9); + else if (textLine->stringAtPos(specialIndent, "slots")) + ch = textLine->getChar(specialIndent + 5); + else if (textLine->stringAtPos(specialIndent, "Q_SLOTS")) + ch = textLine->getChar(specialIndent + 7); + + if (ch.isNull() || (!ch.isSpace() && ch != '(' && ch != ':')) + continue; + + KateDocCursor lineBegin = cur; + lineBegin.setCol(specialIndent); + specialIndent = measureIndent(lineBegin); + isSpecial = true; + } + + // Move forward past blank lines + KateDocCursor skip = cur; + skip.setCol(textLine->lastChar()); + bool result = skipBlanks(skip, begin, true); + + anchorPos = skip.col(); + anchorIndent = measureIndent(skip); + + //kdDebug(13030) << "calcIndent anchorPos:" << anchorPos << " anchorIndent:" << anchorIndent << " at line:" << skip.line() << endl; + + // Accept if it's before requested position or if it was special + if (result && skip < begin) + { + cur = skip; + break; + } + else if (isSpecial) + { + anchorIndent = specialIndent; + break; + } + + // Are these on a line by themselves? (i.e. both last and first char) + if ((c == '{' || c == '}') && textLine->getChar(textLine->firstChar()) == c) + { + cur.setCol(anchorPos = textLine->firstChar()); + anchorIndent = measureIndent (cur); + break; + } + } + } + + // treat beginning of document as anchor position + if (cur.line() == 0 && cur.col() == 0 && potentialAnchorSeen) + found = true; + + if (!found) + return 0; + + uint continueIndent = (needContinue) ? calcContinue (cur, begin) : 0; + //kdDebug(13030) << "calcIndent continueIndent:" << continueIndent << endl; + + // Move forward from anchor and determine last known reference character + // Braces take precedance over others ... + textLine = doc->plainKateTextLine(cur.line()); + QChar lastChar = textLine->getChar (anchorPos); + int lastLine = cur.line(); + if (lastChar == '#' || lastChar == '[') + { + // Never continue if # or [ is encountered at this point here + // A fail-safe really... most likely an #include, #region, or a c# attribute + continueIndent = 0; + } + + int openCount = 0; + while (cur.validPosition() && cur < begin) + { + if (!skipBlanks(cur, begin, true)) + return isArg && !parenthesizedArg ? begin.col() : 0; + + QChar tc = cur.currentChar(); + //kdDebug(13030) << " cur.line:" << cur.line() << " cur.col:" << cur.col() << " currentChar '" << tc << "' " << textLine->attribute(cur.col()) << endl; + if (cur == begin || tc.isNull()) + break; + + if (!tc.isSpace() && cur < begin) + { + uchar attrib = cur.currentAttrib(); + if (tc == '{' && attrib == symbolAttrib) + openCount++; + else if (tc == '}' && attrib == symbolAttrib) + openCount--; + + lastChar = tc; + lastLine = cur.line(); + } + } + if (openCount > 0) // Open braces override + lastChar = '{'; + + uint indent = 0; + //kdDebug(13030) << "calcIndent lastChar '" << lastChar << "'" << endl; + + if (lastChar == '{' || (lastChar == ':' && isSpecial && needContinue)) + { + indent = anchorIndent + indentWidth; + } + else if (lastChar == '}') + { + indent = anchorIndent; + } + else if (lastChar == ';') + { + indent = anchorIndent + ((allowSemi && needContinue) ? continueIndent : 0); + } + else if (lastChar == ',' || lastChar == '(') + { + textLine = doc->plainKateTextLine(lastLine); + KateDocCursor start(lastLine, textLine->firstChar(), doc); + KateDocCursor finish(lastLine, textLine->lastChar() + 1, doc); + uint pos = 0; + + if (isBalanced(start, finish, QChar('('), QChar(')'), pos) && false) + indent = anchorIndent; + else + { + // TODO: Config option. If we're below 48, go ahead and line them up + indent = ((pos < 48) ? pos : anchorIndent + (indentWidth * 2)); + } + } + else if (!lastChar.isNull()) + { + if (anchorIndent != 0) + indent = anchorIndent + continueIndent; + else + indent = continueIndent; + } + + return indent; +} + +uint KateCSmartIndent::calcContinue(KateDocCursor &start, KateDocCursor &end) +{ + KateDocCursor cur = start; + + bool needsBalanced = true; + bool isFor = false; + allowSemi = false; + + KateTextLine::Ptr textLine = doc->plainKateTextLine(cur.line()); + + // Handle cases such as } while (s ... by skipping the leading symbol + if (textLine->attribute(cur.col()) == symbolAttrib) + { + cur.moveForward(1); + skipBlanks(cur, end, false); + } + + if (textLine->getChar(cur.col()) == '}') + { + skipBlanks(cur, end, true); + if (cur.line() != start.line()) + textLine = doc->plainKateTextLine(cur.line()); + + if (textLine->stringAtPos(cur.col(), "else")) + cur.setCol(cur.col() + 4); + else + return indentWidth * 2; + + needsBalanced = false; + } + else if (textLine->stringAtPos(cur.col(), "else")) + { + cur.setCol(cur.col() + 4); + needsBalanced = false; + int next = textLine->nextNonSpaceChar(cur.col()); + if (next >= 0 && textLine->stringAtPos(next, "if")) + { + cur.setCol(next + 2); + needsBalanced = true; + } + } + else if (textLine->stringAtPos(cur.col(), "if")) + { + cur.setCol(cur.col() + 2); + } + else if (textLine->stringAtPos(cur.col(), "do")) + { + cur.setCol(cur.col() + 2); + needsBalanced = false; + } + else if (textLine->stringAtPos(cur.col(), "for")) + { + cur.setCol(cur.col() + 3); + isFor = true; + } + else if (textLine->stringAtPos(cur.col(), "while")) + { + cur.setCol(cur.col() + 5); + } + else if (textLine->stringAtPos(cur.col(), "switch")) + { + cur.setCol(cur.col() + 6); + } + else if (textLine->stringAtPos(cur.col(), "using")) + { + cur.setCol(cur.col() + 5); + } + else + { + return indentWidth * 2; + } + + uint openPos = 0; + if (needsBalanced && !isBalanced (cur, end, QChar('('), QChar(')'), openPos)) + { + allowSemi = isFor; + if (openPos > 0) + return (openPos - textLine->firstChar()); + else + return indentWidth * 2; + } + + // Check if this statement ends a line now + skipBlanks(cur, end, false); + if (cur == end) + return indentWidth; + + if (skipBlanks(cur, end, true)) + { + if (cur == end) + return indentWidth; + else + return indentWidth + calcContinue(cur, end); + } + + return 0; +} + +uint KateCSmartIndent::findOpeningBrace(KateDocCursor &start) +{ + KateDocCursor cur = start; + int count = 1; + + // Move backwards 1 by 1 and find the opening brace + // Return the indent of that line + while (cur.moveBackward(1)) + { + if (cur.currentAttrib() == symbolAttrib) + { + QChar ch = cur.currentChar(); + if (ch == '{') + count--; + else if (ch == '}') + count++; + + if (count == 0) + { + KateDocCursor temp(cur.line(), doc->plainKateTextLine(cur.line())->firstChar(), doc); + return measureIndent(temp); + } + } + } + + return 0; +} + +bool KateCSmartIndent::firstOpeningBrace(KateDocCursor &start) +{ + KateDocCursor cur = start; + + // Are we the first opening brace at this level? + while(cur.moveBackward(1)) + { + if (cur.currentAttrib() == symbolAttrib) + { + QChar ch = cur.currentChar(); + if (ch == '{') + return false; + else if (ch == '}' && cur.col() == 0) + break; + } + } + + return true; +} + +uint KateCSmartIndent::findOpeningParen(KateDocCursor &start) +{ + KateDocCursor cur = start; + int count = 1; + + // Move backwards 1 by 1 and find the opening ( + // Return the indent of that line + while (cur.moveBackward(1)) + { + if (cur.currentAttrib() == symbolAttrib) + { + QChar ch = cur.currentChar(); + if (ch == '(') + count--; + else if (ch == ')') + count++; + + if (count == 0) + return measureIndent(cur); + } + } + + return 0; +} + +uint KateCSmartIndent::findOpeningComment(KateDocCursor &start) +{ + KateDocCursor cur = start; + + // Find the line with the opening /* and return the proper indent + do + { + KateTextLine::Ptr textLine = doc->plainKateTextLine(cur.line()); + + int pos = textLine->string().find("/*", false); + if (pos >= 0) + { + KateDocCursor temp(cur.line(), pos, doc); + return measureIndent(temp); + } + + } while (cur.gotoPreviousLine()); + + return 0; +} + +//END + +//BEGIN KatePythonIndent + +QRegExp KatePythonIndent::endWithColon = QRegExp( "^[^#]*:\\s*(#.*)?$" ); +QRegExp KatePythonIndent::stopStmt = QRegExp( "^\\s*(break|continue|raise|return|pass)\\b.*" ); +QRegExp KatePythonIndent::blockBegin = QRegExp( "^\\s*(class|def|if|elif|else|for|while|try)\\b.*" ); + +KatePythonIndent::KatePythonIndent (KateDocument *doc) +: KateNormalIndent (doc) +{ +} +KatePythonIndent::~KatePythonIndent () +{ +} + +void KatePythonIndent::processNewline (KateDocCursor &begin, bool /*newline*/) +{ + int prevLine = begin.line() - 1; + int prevPos = begin.col(); + + while ((prevLine > 0) && (prevPos < 0)) // search a not empty text line + prevPos = doc->plainKateTextLine(--prevLine)->firstChar(); + + int prevBlock = prevLine; + int prevBlockPos = prevPos; + int extraIndent = calcExtra (prevBlock, prevBlockPos, begin); + + int indent = doc->plainKateTextLine(prevBlock)->cursorX(prevBlockPos, tabWidth); + if (extraIndent == 0) + { + if (!stopStmt.exactMatch(doc->plainKateTextLine(prevLine)->string())) + { + if (endWithColon.exactMatch(doc->plainKateTextLine(prevLine)->string())) + indent += indentWidth; + else + indent = doc->plainKateTextLine(prevLine)->cursorX(prevPos, tabWidth); + } + } + else + indent += extraIndent; + + if (indent > 0) + { + QString filler = tabString (indent); + doc->insertText (begin.line(), 0, filler); + begin.setCol(filler.length()); + } + else + begin.setCol(0); +} + +int KatePythonIndent::calcExtra (int &prevBlock, int &pos, KateDocCursor &end) +{ + int nestLevel = 0; + bool levelFound = false; + while ((prevBlock > 0)) + { + if (blockBegin.exactMatch(doc->plainKateTextLine(prevBlock)->string())) + { + if ((!levelFound && nestLevel == 0) || (levelFound && nestLevel - 1 <= 0)) + { + pos = doc->plainKateTextLine(prevBlock)->firstChar(); + break; + } + + nestLevel --; + } + else if (stopStmt.exactMatch(doc->plainKateTextLine(prevBlock)->string())) + { + nestLevel ++; + levelFound = true; + } + + --prevBlock; + } + + KateDocCursor cur (prevBlock, pos, doc); + QChar c; + int extraIndent = 0; + while (cur.line() < end.line()) + { + c = cur.currentChar(); + + if (c == '(') + extraIndent += indentWidth; + else if (c == ')') + extraIndent -= indentWidth; + else if (c == ':') + break; + else if (c == '\'' || c == '"' ) + traverseString( c, cur, end ); + + if (c.isNull() || c == '#') + cur.gotoNextLine(); + else + cur.moveForward(1); + } + + return extraIndent; +} + +void KatePythonIndent::traverseString( const QChar &stringChar, KateDocCursor &cur, KateDocCursor &end ) +{ + QChar c; + bool escape = false; + + cur.moveForward(1); + c = cur.currentChar(); + while ( ( c != stringChar || escape ) && cur.line() < end.line() ) + { + if ( escape ) + escape = false; + else if ( c == '\\' ) + escape = !escape; + + cur.moveForward(1); + c = cur.currentChar(); + } +} + +//END + +//BEGIN KateXmlIndent + +/* Explanation + +The XML indenter simply inherits the indentation of the previous line, +with the first line starting at 0 (of course!). For each element that +is opened on the previous line, the indentation is increased by one +level; for each element that is closed, it is decreased by one. + +We also have a special case of opening an element on one line and then +entering attributes on the following lines, in which case we would like +to see the following layout: +<elem attr="..." + blah="..." /> + +<x><a href="..." + title="..." /> +</x> + +This is accomplished by checking for lines that contain an unclosed open +tag. + +*/ + +const QRegExp KateXmlIndent::startsWithCloseTag("^[ \t]*</"); +const QRegExp KateXmlIndent::unclosedDoctype("<!DOCTYPE[^>]*$"); + +KateXmlIndent::KateXmlIndent (KateDocument *doc) +: KateNormalIndent (doc) +{ +} + +KateXmlIndent::~KateXmlIndent () +{ +} + +void KateXmlIndent::processNewline (KateDocCursor &begin, bool /*newline*/) +{ + begin.setCol(processLine(begin.line())); +} + +void KateXmlIndent::processChar (QChar c) +{ + if(c != '/') return; + + // only alter lines that start with a close element + KateView *view = doc->activeView(); + QString text = doc->plainKateTextLine(view->cursorLine())->string(); + if(text.find(startsWithCloseTag) == -1) return; + + // process it + processLine(view->cursorLine()); +} + +void KateXmlIndent::processLine (KateDocCursor &line) +{ + processLine (line.line()); +} + +void KateXmlIndent::processSection (const KateDocCursor &start, const KateDocCursor &end) +{ + KateDocCursor cur (start); + int endLine = end.line(); + + do { + processLine(cur.line()); + if(!cur.gotoNextLine()) break; + } while(cur.line() < endLine); +} + +void KateXmlIndent::getLineInfo (uint line, uint &prevIndent, int &numTags, + uint &attrCol, bool &unclosedTag) +{ + prevIndent = 0; + int firstChar; + KateTextLine::Ptr prevLine = 0; + + // get the indentation of the first non-empty line + while(true) { + prevLine = doc->plainKateTextLine(line); + if( (firstChar = prevLine->firstChar()) < 0) { + if(!line--) return; + continue; + } + break; + } + prevIndent = prevLine->cursorX(prevLine->firstChar(), tabWidth); + QString text = prevLine->string(); + + // special case: + // <a> + // </a> <!-- indentation *already* decreased --> + // requires that we discount the </a> from the number of closed tags + if(text.find(startsWithCloseTag) != -1) ++numTags; + + // count the number of open and close tags + int lastCh = 0; + uint pos, len = text.length(); + bool seenOpen = false; + for(pos = 0; pos < len; ++pos) { + int ch = text.at(pos).unicode(); + switch(ch) { + case '<': + seenOpen = true; + unclosedTag = true; + attrCol = pos; + ++numTags; + break; + + // don't indent because of DOCTYPE, comment, CDATA, etc. + case '!': + if(lastCh == '<') --numTags; + break; + + // don't indent because of xml decl or PI + case '?': + if(lastCh == '<') --numTags; + break; + + case '>': + if(!seenOpen) { + // we are on a line like the second one here: + // <element attr="val" + // other="val"> + // so we need to set prevIndent to the indent of the first line + // + // however, we need to special case "<!DOCTYPE" because + // it's not an open tag + + prevIndent = 0; + + for(uint backLine = line; backLine; ) { + // find first line with an open tag + KateTextLine::Ptr x = doc->plainKateTextLine(--backLine); + if(x->string().find('<') == -1) continue; + + // recalculate the indent + if(x->string().find(unclosedDoctype) != -1) --numTags; + getLineInfo(backLine, prevIndent, numTags, attrCol, unclosedTag); + break; + } + } + if(lastCh == '/') --numTags; + unclosedTag = false; + break; + + case '/': + if(lastCh == '<') numTags -= 2; // correct for '<', above + break; + } + lastCh = ch; + } + + if(unclosedTag) { + // find the start of the next attribute, so we can align with it + do { + lastCh = text.at(++attrCol).unicode(); + }while(lastCh && lastCh != ' ' && lastCh != '\t'); + + while(lastCh == ' ' || lastCh == '\t') { + lastCh = text.at(++attrCol).unicode(); + } + + attrCol = prevLine->cursorX(attrCol, tabWidth); + } +} + +uint KateXmlIndent::processLine (uint line) +{ + KateTextLine::Ptr kateLine = doc->plainKateTextLine(line); + if(!kateLine) return 0; // sanity check + + // get details from previous line + uint prevIndent = 0, attrCol = 0; + int numTags = 0; + bool unclosedTag = false; // for aligning attributes + + if(line) { + getLineInfo(line - 1, prevIndent, numTags, attrCol, unclosedTag); + } + + // compute new indent + int indent = 0; + if(unclosedTag) indent = attrCol; + else indent = prevIndent + numTags * indentWidth; + if(indent < 0) indent = 0; + + // unindent lines that start with a close tag + if(kateLine->string().find(startsWithCloseTag) != -1) { + indent -= indentWidth; + } + if(indent < 0) indent = 0; + + // apply new indent + doc->removeText(line, 0, line, kateLine->firstChar()); + QString filler = tabString(indent); + doc->insertText(line, 0, filler); + + return filler.length(); +} + +//END + +//BEGIN KateCSAndSIndent + +KateCSAndSIndent::KateCSAndSIndent (KateDocument *doc) +: KateNormalIndent (doc) +{ +} + +void KateCSAndSIndent::updateIndentString() +{ + if( useSpaces ) + indentString.fill( ' ', indentWidth ); + else + indentString = '\t'; +} + +KateCSAndSIndent::~KateCSAndSIndent () +{ +} + +void KateCSAndSIndent::processLine (KateDocCursor &line) +{ + KateTextLine::Ptr textLine = doc->plainKateTextLine(line.line()); + + if (!textLine) + return; + + updateIndentString(); + + const int oldCol = line.col(); + QString whitespace = calcIndent(line); + // strip off existing whitespace + int oldIndent = textLine->firstChar(); + if ( oldIndent < 0 ) + oldIndent = doc->lineLength( line.line() ); + if( oldIndent > 0 ) + doc->removeText(line.line(), 0, line.line(), oldIndent); + // add correct amount + doc->insertText(line.line(), 0, whitespace); + + // try to preserve the cursor position in the line + if ( int(oldCol + whitespace.length()) >= oldIndent ) + line.setCol( oldCol + whitespace.length() - oldIndent ); + else + line.setCol( 0 ); +} + +void KateCSAndSIndent::processSection (const KateDocCursor &begin, const KateDocCursor &end) +{ + QTime t; t.start(); + for( KateDocCursor cur = begin; cur.line() <= end.line(); ) + { + processLine (cur); + if (!cur.gotoNextLine()) + break; + } + kdDebug(13030) << "+++ total: " << t.elapsed() << endl; +} + +/** + * Returns the first @p chars characters of @p line, converted to whitespace. + * If @p convert is set to false, characters at and after the first non-whitespace + * character are removed, not converted. + */ +static QString initialWhitespace(const KateTextLine::Ptr &line, int chars, bool convert = true) +{ + QString text = line->string(0, chars); + if( (int)text.length() < chars ) + { + QString filler; filler.fill(' ',chars - text.length()); + text += filler; + } + for( uint n = 0; n < text.length(); ++n ) + { + if( text[n] != '\t' && text[n] != ' ' ) + { + if( !convert ) + return text.left( n ); + text[n] = ' '; + } + } + return text; +} + +QString KateCSAndSIndent::findOpeningCommentIndentation(const KateDocCursor &start) +{ + KateDocCursor cur = start; + + // Find the line with the opening /* and return the indentation of it + do + { + KateTextLine::Ptr textLine = doc->plainKateTextLine(cur.line()); + + int pos = textLine->string().findRev("/*"); + // FIXME: /* inside /* is possible. This screws up in that case... + if (pos >= 0) + return initialWhitespace(textLine, pos); + } while (cur.gotoPreviousLine()); + + // should never happen. + kdWarning( 13030 ) << " in a comment, but can't find the start of it" << endl; + return QString::null; +} + +bool KateCSAndSIndent::handleDoxygen (KateDocCursor &begin) +{ + // Look backwards for a nonempty line + int line = begin.line(); + int first = -1; + while ((line > 0) && (first < 0)) + first = doc->plainKateTextLine(--line)->firstChar(); + + // no earlier nonempty line + if (first < 0) + return false; + + KateTextLine::Ptr textLine = doc->plainKateTextLine(line); + + // if the line doesn't end with a doxygen comment (that's not closed) + // and doesn't start with a doxygen comment (that's not closed), we don't care. + // note that we do need to check the start of the line, or lines ending with, say, @brief aren't + // recognised. + if ( !(textLine->attribute(textLine->lastChar()) == doxyCommentAttrib && !textLine->endingWith("*/")) && + !(textLine->attribute(textLine->firstChar()) == doxyCommentAttrib && !textLine->string().contains("*/")) ) + return false; + + // our line is inside a doxygen comment. align the *'s and then maybe insert one too ... + textLine = doc->plainKateTextLine(begin.line()); + first = textLine->firstChar(); + QString indent = findOpeningCommentIndentation(begin); + + bool doxygenAutoInsert = doc->config()->configFlags() & KateDocumentConfig::cfDoxygenAutoTyping; + + // starts with *: indent one space more to line up *s + if ( first >= 0 && textLine->stringAtPos(first, "*") ) + indent = indent + " "; + // does not start with *: insert one if user wants that + else if ( doxygenAutoInsert ) + indent = indent + " * "; + // user doesn't want * inserted automatically: put in spaces? + //else + // indent = indent + " "; + + doc->removeText (begin.line(), 0, begin.line(), first); + doc->insertText (begin.line(), 0, indent); + begin.setCol(indent.length()); + + return true; +} + +/** + * @brief User pressed enter. Line has been split; begin is on the new line. + * @param begin Three unrelated variables: the new line number, where the first + * non-whitespace char was on the previous line, and the document. + * @param needContinue Something to do with indenting the current line; always true. + */ +void KateCSAndSIndent::processNewline (KateDocCursor &begin, bool /*needContinue*/) +{ + // in a comment, add a * doxygen-style. + if( handleDoxygen(begin) ) + return; + + // TODO: if the user presses enter in the middle of a label, maybe the first half of the + // label should be indented? + + // where the cursor actually is... + int cursorPos = doc->plainKateTextLine( begin.line() )->firstChar(); + if ( cursorPos < 0 ) + cursorPos = doc->lineLength( begin.line() ); + begin.setCol( cursorPos ); + + processLine( begin ); +} + +/** + * Does the line @p line start with a label? + * @note May also return @c true if the line starts in a continuation. + */ +bool KateCSAndSIndent::startsWithLabel( int line ) +{ + // Get the current line. + KateTextLine::Ptr indentLine = doc->plainKateTextLine(line); + const int indentFirst = indentLine->firstChar(); + + // Not entirely sure what this check does. + int attrib = indentLine->attribute(indentFirst); + if (attrib != 0 && attrib != keywordAttrib && attrib != normalAttrib && attrib != extensionAttrib) + return false; + + // Get the line text. + const QString lineContents = indentLine->string(); + const int indentLast = indentLine->lastChar(); + bool whitespaceFound = false; + for ( int n = indentFirst; n <= indentLast; ++n ) + { + // Get the character as latin1. Can't use QChar::isLetterOrNumber() + // as that includes non 0-9 numbers. + char c = lineContents[n].latin1(); + if ( c == ':' ) + { + // See if the next character is ':' - if so, skip to the character after it. + if ( n < lineContents.length() - 1 ) + { + if ( lineContents[n+1].latin1() == ':' ) + { + n += 2; + continue; + } + } + // Right this is the relevent ':'. + if ( n == indentFirst) + { + // Just a line with a : on it. + return false; + } + // It is a label of some kind! + return true; + } + if (isspace(c)) + { + if (!whitespaceFound) + { + if (lineContents.mid(indentFirst, n - indentFirst) == "case") + return true; + else if (lineContents.mid(indentFirst, n - indentFirst) == "class") + return false; + whitespaceFound = true; + } + } + // All other characters don't indent. + else if ( !isalnum(c) && c != '_' ) + { + return false; + } + } + return false; +} + +template<class T> T min(T a, T b) { return (a < b) ? a : b; } + +int KateCSAndSIndent::lastNonCommentChar( const KateDocCursor &line ) +{ + KateTextLine::Ptr textLine = doc->plainKateTextLine( line.line() ); + QString str = textLine->string(); + + // find a possible start-of-comment + int p = -2; // so the first find starts at position 0 + do p = str.find( "//", p + 2 ); + while ( p >= 0 && textLine->attribute(p) != commentAttrib && textLine->attribute(p) != doxyCommentAttrib ); + + // no // found? use whole string + if ( p < 0 ) + p = str.length(); + + // ignore trailing blanks. p starts one-past-the-end. + while( p > 0 && str[p-1].isSpace() ) --p; + return p - 1; +} + +bool KateCSAndSIndent::inForStatement( int line ) +{ + // does this line end in a for ( ... + // with no closing ) ? + int parens = 0, semicolons = 0; + for ( ; line >= 0; --line ) + { + KateTextLine::Ptr textLine = doc->plainKateTextLine(line); + const int first = textLine->firstChar(); + const int last = textLine->lastChar(); + + // look backwards for a symbol: (){}; + // match ()s, {...; and }...; => not in a for + // ; ; ; => not in a for + // ( ; and ( ; ; => a for + for ( int curr = last; curr >= first; --curr ) + { + if ( textLine->attribute(curr) != symbolAttrib ) + continue; + + switch( textLine->getChar(curr) ) + { + case ';': + if( ++semicolons > 2 ) + return false; + break; + case '{': case '}': + return false; + case ')': + ++parens; + break; + case '(': + if( --parens < 0 ) + return true; + break; + } + } + } + // no useful symbols before the ;? + // not in a for then + return false; +} + + +// is the start of the line containing 'begin' in a statement? +bool KateCSAndSIndent::inStatement( const KateDocCursor &begin ) +{ + // if the current line starts with an open brace, it's not a continuation. + // this happens after a function definition (which is treated as a continuation). + KateTextLine::Ptr textLine = doc->plainKateTextLine(begin.line()); + const int first = textLine->firstChar(); + // note that if we're being called from processChar the attribute has not yet been calculated + // should be reasonably safe to assume that unattributed {s are symbols; if the { is in a comment + // we don't want to touch it anyway. + const int attrib = textLine->attribute(first); + if( first >= 0 && (attrib == 0 || attrib == symbolAttrib) && textLine->getChar(first) == '{' ) + return false; + + int line; + for ( line = begin.line() - 1; line >= 0; --line ) + { + textLine = doc->plainKateTextLine(line); + const int first = textLine->firstChar(); + if ( first == -1 ) + continue; + + // starts with #: in a comment, don't care + // outside a comment: preprocessor, don't care + if ( textLine->getChar( first ) == '#' ) + continue; + KateDocCursor currLine = begin; + currLine.setLine( line ); + const int last = lastNonCommentChar( currLine ); + if ( last < first ) + continue; + + // HACK: if we see a comment, assume boldly that this isn't a continuation. + // detecting comments (using attributes) is HARD, since they may have + // embedded alerts, or doxygen stuff, or just about anything. this is + // wrong, and needs fixing. note that only multi-line comments and + // single-line comments continued with \ are affected. + const int attrib = textLine->attribute(last); + if ( attrib == commentAttrib || attrib == doxyCommentAttrib ) + return false; + + char c = textLine->getChar(last); + + // brace => not a continuation. + if ( attrib == symbolAttrib && c == '{' || c == '}' ) + return false; + + // ; => not a continuation, unless in a for (;;) + if ( attrib == symbolAttrib && c == ';' ) + return inForStatement( line ); + + // found something interesting. maybe it's a label? + if ( attrib == symbolAttrib && c == ':' ) + { + // the : above isn't necessarily the : in the label, eg in + // case 'x': a = b ? c : + // this will say no continuation incorrectly. but continued statements + // starting on a line with a label at the start is Bad Style (tm). + if( startsWithLabel( line ) ) + { + // either starts with a label or a continuation. if the current line + // starts in a continuation, we're still in one. if not, this was + // a label, so we're not in one now. so continue to the next line + // upwards. + continue; + } + } + + // any other character => in a continuation + return true; + } + // no non-comment text found before here - not a continuation. + return false; +} + +QString KateCSAndSIndent::continuationIndent( const KateDocCursor &begin ) +{ + if( !inStatement( begin ) ) + return QString::null; + return indentString; +} + +/** + * Figure out how indented the line containing @p begin should be. + */ +QString KateCSAndSIndent::calcIndent (const KateDocCursor &begin) +{ + KateTextLine::Ptr currLine = doc->plainKateTextLine(begin.line()); + int currLineFirst = currLine->firstChar(); + + // if the line starts inside a comment, no change of indentation. + // FIXME: this unnecessarily copies the current indentation over itself. + // FIXME: on newline, this should copy from the previous line. + if ( currLineFirst >= 0 && + (currLine->attribute(currLineFirst) == commentAttrib || + currLine->attribute(currLineFirst) == doxyCommentAttrib) ) + return currLine->string( 0, currLineFirst ); + + // if the line starts with # (but isn't a c# region thingy), no indentation at all. + if( currLineFirst >= 0 && currLine->getChar(currLineFirst) == '#' ) + { + if( !currLine->stringAtPos( currLineFirst+1, QString::fromLatin1("region") ) && + !currLine->stringAtPos( currLineFirst+1, QString::fromLatin1("endregion") ) ) + return QString::null; + } + + /* Strategy: + * Look for an open bracket or brace, or a keyword opening a new scope, whichever comes latest. + * Found a brace: indent one tab in. + * Found a bracket: indent to the first non-white after it. + * Found a keyword: indent one tab in. for try, catch and switch, if newline is set, also add + * an open brace, a newline, and indent two tabs in. + */ + KateDocCursor cur = begin; + int pos, openBraceCount = 0, openParenCount = 0; + bool lookingForScopeKeywords = true; + const char * const scopeKeywords[] = { "for", "do", "while", "if", "else" }; + const char * const blockScopeKeywords[] = { "try", "catch", "switch" }; + + while (cur.gotoPreviousLine()) + { + KateTextLine::Ptr textLine = doc->plainKateTextLine(cur.line()); + const int lastChar = textLine->lastChar(); + const int firstChar = textLine->firstChar(); + + // look through line backwards for interesting characters + for( pos = lastChar; pos >= firstChar; --pos ) + { + if (textLine->attribute(pos) == symbolAttrib) + { + char tc = textLine->getChar (pos); + switch( tc ) + { + case '(': case '[': + if( ++openParenCount > 0 ) + return calcIndentInBracket( begin, cur, pos ); + break; + case ')': case ']': openParenCount--; break; + case '{': + if( ++openBraceCount > 0 ) + return calcIndentInBrace( begin, cur, pos ); + break; + case '}': openBraceCount--; lookingForScopeKeywords = false; break; + case ';': + if( openParenCount == 0 ) + lookingForScopeKeywords = false; + break; + } + } + + // if we've not had a close brace or a semicolon yet, and we're at the same parenthesis level + // as the cursor, and we're at the start of a scope keyword, indent from it. + if ( lookingForScopeKeywords && openParenCount == 0 && + textLine->attribute(pos) == keywordAttrib && + (pos == 0 || textLine->attribute(pos-1) != keywordAttrib ) ) + { + #define ARRLEN( array ) ( sizeof(array)/sizeof(array[0]) ) + for( uint n = 0; n < ARRLEN(scopeKeywords); ++n ) + if( textLine->stringAtPos(pos, QString::fromLatin1(scopeKeywords[n]) ) ) + return calcIndentAfterKeyword( begin, cur, pos, false ); + for( uint n = 0; n < ARRLEN(blockScopeKeywords); ++n ) + if( textLine->stringAtPos(pos, QString::fromLatin1(blockScopeKeywords[n]) ) ) + return calcIndentAfterKeyword( begin, cur, pos, true ); + #undef ARRLEN + } + } + } + + // no active { in file. + return QString::null; +} + +QString KateCSAndSIndent::calcIndentInBracket(const KateDocCursor &indentCursor, const KateDocCursor &bracketCursor, int bracketPos) +{ + KateTextLine::Ptr indentLine = doc->plainKateTextLine(indentCursor.line()); + KateTextLine::Ptr bracketLine = doc->plainKateTextLine(bracketCursor.line()); + + // FIXME: hard-coded max indent to bracket width - use a kate variable + // FIXME: expand tabs first... + if ( bracketPos > 48 ) + { + // how far to indent? we could look back for a brace or keyword, 2 from that. + // as it is, we just indent one more than the line with the ( on it. + // the potential problem with this is when + // you have code ( which does <-- continuation + start of func call + // something like this ); <-- extra indentation for func call + // then again ( + // it works better than ( + // the other method for ( + // cases like this ))); + // consequently, i think this method wins. + return indentString + initialWhitespace( bracketLine, bracketLine->firstChar() ); + } + + const int indentLineFirst = indentLine->firstChar(); + + int indentTo; + const int attrib = indentLine->attribute(indentLineFirst); + if( indentLineFirst >= 0 && (attrib == 0 || attrib == symbolAttrib) && + ( indentLine->getChar(indentLineFirst) == ')' || indentLine->getChar(indentLineFirst) == ']' ) ) + { + // If the line starts with a close bracket, line it up + indentTo = bracketPos; + } + else + { + // Otherwise, line up with the text after the open bracket + indentTo = bracketLine->nextNonSpaceChar( bracketPos + 1 ); + if( indentTo == -1 ) + indentTo = bracketPos + 2; + } + return initialWhitespace( bracketLine, indentTo ); +} + +QString KateCSAndSIndent::calcIndentAfterKeyword(const KateDocCursor &indentCursor, const KateDocCursor &keywordCursor, int keywordPos, bool blockKeyword) +{ + KateTextLine::Ptr keywordLine = doc->plainKateTextLine(keywordCursor.line()); + KateTextLine::Ptr indentLine = doc->plainKateTextLine(indentCursor.line()); + + QString whitespaceToKeyword = initialWhitespace( keywordLine, keywordPos, false ); + if( blockKeyword ) { + // FIXME: we could add the open brace and subsequent newline here since they're definitely needed. + } + + // If the line starts with an open brace, don't indent... + int first = indentLine->firstChar(); + // if we're being called from processChar attribute won't be set + const int attrib = indentLine->attribute(first); + if( first >= 0 && (attrib == 0 || attrib == symbolAttrib) && indentLine->getChar(first) == '{' ) + return whitespaceToKeyword; + + // don't check for a continuation. rules are simple here: + // if we're in a non-compound statement after a scope keyword, we indent all lines + // once. so: + // if ( some stuff + // goes here ) + // apples, and <-- continuation here is ignored. but this is Bad Style (tm) anyway. + // oranges too; + return indentString + whitespaceToKeyword; +} + +QString KateCSAndSIndent::calcIndentInBrace(const KateDocCursor &indentCursor, const KateDocCursor &braceCursor, int bracePos) +{ + KateTextLine::Ptr braceLine = doc->plainKateTextLine(braceCursor.line()); + const int braceFirst = braceLine->firstChar(); + + QString whitespaceToOpenBrace = initialWhitespace( braceLine, bracePos, false ); + + // if the open brace is the start of a namespace, don't indent... + // FIXME: this is an extremely poor heuristic. it looks on the line with + // the { and the line before to see if they start with a keyword + // beginning 'namespace'. that's 99% of usage, I'd guess. + { + if( braceFirst >= 0 && braceLine->attribute(braceFirst) == keywordAttrib && + braceLine->stringAtPos( braceFirst, QString::fromLatin1( "namespace" ) ) ) + return continuationIndent(indentCursor) + whitespaceToOpenBrace; + + if( braceCursor.line() > 0 ) + { + KateTextLine::Ptr prevLine = doc->plainKateTextLine(braceCursor.line() - 1); + int firstPrev = prevLine->firstChar(); + if( firstPrev >= 0 && prevLine->attribute(firstPrev) == keywordAttrib && + prevLine->stringAtPos( firstPrev, QString::fromLatin1( "namespace" ) ) ) + return continuationIndent(indentCursor) + whitespaceToOpenBrace; + } + } + + KateTextLine::Ptr indentLine = doc->plainKateTextLine(indentCursor.line()); + const int indentFirst = indentLine->firstChar(); + + // if the line starts with a close brace, don't indent... + if( indentFirst >= 0 && indentLine->getChar(indentFirst) == '}' ) + return whitespaceToOpenBrace; + + // if : is the first character (and not followed by another :), this is the start + // of an initialization list, or a continuation of a ?:. either way, indent twice. + if ( indentFirst >= 0 && indentLine->attribute(indentFirst) == symbolAttrib && + indentLine->getChar(indentFirst) == ':' && indentLine->getChar(indentFirst+1) != ':' ) + { + return indentString + indentString + whitespaceToOpenBrace; + } + + const bool continuation = inStatement(indentCursor); + // if the current line starts with a label, don't indent... + if( !continuation && startsWithLabel( indentCursor.line() ) ) + return whitespaceToOpenBrace; + + // the normal case: indent once for the brace, again if it's a continuation + QString continuationIndent = continuation ? indentString : QString::null; + return indentString + continuationIndent + whitespaceToOpenBrace; +} + +void KateCSAndSIndent::processChar(QChar c) +{ + // 'n' trigger is for c# regions. + static const QString triggers("}{)]/:;#n"); + if (triggers.find(c) == -1) + return; + + // for historic reasons, processChar doesn't get a cursor + // to work on. so fabricate one. + KateView *view = doc->activeView(); + KateDocCursor begin(view->cursorLine(), 0, doc); + + KateTextLine::Ptr textLine = doc->plainKateTextLine(begin.line()); + if ( c == 'n' ) + { + int first = textLine->firstChar(); + if( first < 0 || textLine->getChar(first) != '#' ) + return; + } + + if ( textLine->attribute( begin.col() ) == doxyCommentAttrib ) + { + // dominik: if line is "* /", change it to "*/" + if ( c == '/' ) + { + int first = textLine->firstChar(); + // if the first char exists and is a '*', and the next non-space-char + // is already the just typed '/', concatenate it to "*/". + if ( first != -1 + && textLine->getChar( first ) == '*' + && textLine->nextNonSpaceChar( first+1 ) == view->cursorColumnReal()-1 ) + doc->removeText( view->cursorLine(), first+1, view->cursorLine(), view->cursorColumnReal()-1); + } + + // anders: don't change the indent of doxygen lines here. + return; + } + + processLine(begin); +} + +//END + +//BEGIN KateVarIndent +class KateVarIndentPrivate { + public: + QRegExp reIndentAfter, reIndent, reUnindent; + QString triggers; + uint couples; + uchar coupleAttrib; +}; + +KateVarIndent::KateVarIndent( KateDocument *doc ) +: KateNormalIndent( doc ) +{ + d = new KateVarIndentPrivate; + d->reIndentAfter = QRegExp( doc->variable( "var-indent-indent-after" ) ); + d->reIndent = QRegExp( doc->variable( "var-indent-indent" ) ); + d->reUnindent = QRegExp( doc->variable( "var-indent-unindent" ) ); + d->triggers = doc->variable( "var-indent-triggerchars" ); + d->coupleAttrib = 0; + + slotVariableChanged( "var-indent-couple-attribute", doc->variable( "var-indent-couple-attribute" ) ); + slotVariableChanged( "var-indent-handle-couples", doc->variable( "var-indent-handle-couples" ) ); + + // update if a setting is changed + connect( doc, SIGNAL(variableChanged( const QString&, const QString&) ), + this, SLOT(slotVariableChanged( const QString&, const QString& )) ); +} + +KateVarIndent::~KateVarIndent() +{ + delete d; +} + +void KateVarIndent::processNewline ( KateDocCursor &begin, bool /*needContinue*/ ) +{ + // process the line left, as well as the one entered + KateDocCursor left( begin.line()-1, 0, doc ); + processLine( left ); + processLine( begin ); +} + +void KateVarIndent::processChar ( QChar c ) +{ + // process line if the c is in our list, and we are not in comment text + if ( d->triggers.contains( c ) ) + { + KateTextLine::Ptr ln = doc->plainKateTextLine( doc->activeView()->cursorLine() ); + if ( ln->attribute( doc->activeView()->cursorColumn()-1 ) == commentAttrib ) + return; + + KateView *view = doc->activeView(); + KateDocCursor begin( view->cursorLine(), 0, doc ); + kdDebug(13030)<<"variable indenter: process char '"<<c<<", line "<<begin.line()<<endl; + processLine( begin ); + } +} + +void KateVarIndent::processLine ( KateDocCursor &line ) +{ + QString indent; // store the indent string here + + // find the first line with content that is not starting with comment text, + // and take the position from that + int ln = line.line(); + int pos = -1; + KateTextLine::Ptr ktl = doc->plainKateTextLine( ln ); + if ( ! ktl ) return; // no line!? + + // skip blank lines, except for the cursor line + KateView *v = doc->activeView(); + if ( (ktl->firstChar() < 0) && (!v || (int)v->cursorLine() != ln ) ) + return; + + int fc; + if ( ln > 0 ) + do + { + + ktl = doc->plainKateTextLine( --ln ); + fc = ktl->firstChar(); + if ( ktl->attribute( fc ) != commentAttrib ) + pos = fc; + } + while ( (ln > 0) && (pos < 0) ); // search a not empty text line + + if ( pos < 0 ) + pos = 0; + else + pos = ktl->cursorX( pos, tabWidth ); + + int adjustment = 0; + + // try 'couples' for an opening on the above line first. since we only adjust by 1 unit, + // we only need 1 match. + if ( d->couples & Parens && coupleBalance( ln, '(', ')' ) > 0 ) + adjustment++; + else if ( d->couples & Braces && coupleBalance( ln, '{', '}' ) > 0 ) + adjustment++; + else if ( d->couples & Brackets && coupleBalance( ln, '[', ']' ) > 0 ) + adjustment++; + + // Try 'couples' for a closing on this line first. since we only adjust by 1 unit, + // we only need 1 match. For unindenting, we look for a closing character + // *at the beginning of the line* + // NOTE Assume that a closing brace with the configured attribute on the start + // of the line is closing. + // When acting on processChar, the character isn't highlighted. So I could + // either not check, assuming that the first char *is* meant to close, or do a + // match test if the attrib is 0. How ever, doing that is + // a potentially huge job, if the match is several hundred lines away. + // Currently, the check is done. + { + KateTextLine::Ptr tl = doc->plainKateTextLine( line.line() ); + int i = tl->firstChar(); + if ( i > -1 ) + { + QChar ch = tl->getChar( i ); + uchar at = tl->attribute( i ); + kdDebug(13030)<<"attrib is "<<at<<endl; + if ( d->couples & Parens && ch == ')' + && ( at == d->coupleAttrib + || (! at && hasRelevantOpening( KateDocCursor( line.line(), i, doc ) )) + ) + ) + adjustment--; + else if ( d->couples & Braces && ch == '}' + && ( at == d->coupleAttrib + || (! at && hasRelevantOpening( KateDocCursor( line.line(), i, doc ) )) + ) + ) + adjustment--; + else if ( d->couples & Brackets && ch == ']' + && ( at == d->coupleAttrib + || (! at && hasRelevantOpening( KateDocCursor( line.line(), i, doc ) )) + ) + ) + adjustment--; + } + } +#define ISCOMMENTATTR(attr) (attr==commentAttrib||attr==doxyCommentAttrib) +#define ISCOMMENT (ISCOMMENTATTR(ktl->attribute(ktl->firstChar()))||ISCOMMENTATTR(ktl->attribute(matchpos))) + // check if we should indent, unless the line starts with comment text, + // or the match is in comment text + kdDebug(13030)<<"variable indenter: starting indent: "<<pos<<endl; + // check if the above line indicates that we shuld add indentation + int matchpos = 0; + if ( ktl && ! d->reIndentAfter.isEmpty() + && (matchpos = d->reIndentAfter.search( doc->textLine( ln ) )) > -1 + && ! ISCOMMENT ) + adjustment++; + + // else, check if this line should indent unless ... + ktl = doc->plainKateTextLine( line.line() ); + if ( ! d->reIndent.isEmpty() + && (matchpos = d->reIndent.search( doc->textLine( line.line() ) )) > -1 + && ! ISCOMMENT ) + adjustment++; + + // else, check if the current line indicates if we should remove indentation unless ... + if ( ! d->reUnindent.isEmpty() + && (matchpos = d->reUnindent.search( doc->textLine( line.line() ) )) > -1 + && ! ISCOMMENT ) + adjustment--; + + kdDebug(13030)<<"variable indenter: adjusting by "<<adjustment<<" units"<<endl; + + if ( adjustment > 0 ) + pos += indentWidth; + else if ( adjustment < 0 ) + pos -= indentWidth; + + ln = line.line(); + fc = doc->plainKateTextLine( ln )->firstChar(); + + // dont change if there is no change. + // ### should I actually compare the strings? + // FIXME for some odd reason, the document gets marked as changed + // even if we don't change it !? + if ( fc == pos ) + return; + + if ( fc > 0 ) + doc->removeText (ln, 0, ln, fc ); + + if ( pos > 0 ) + indent = tabString( pos ); + + if ( pos > 0 ) + doc->insertText (ln, 0, indent); + + // try to restore cursor ? + line.setCol( pos ); +} + +void KateVarIndent::processSection (const KateDocCursor &begin, const KateDocCursor &end) +{ + KateDocCursor cur = begin; + while (cur.line() <= end.line()) + { + processLine (cur); + if (!cur.gotoNextLine()) + break; + } +} + +void KateVarIndent::slotVariableChanged( const QString &var, const QString &val ) +{ + if ( ! var.startsWith("var-indent") ) + return; + + if ( var == "var-indent-indent-after" ) + d->reIndentAfter.setPattern( val ); + else if ( var == "var-indent-indent" ) + d->reIndent.setPattern( val ); + else if ( var == "var-indent-unindent" ) + d->reUnindent.setPattern( val ); + else if ( var == "var-indent-triggerchars" ) + d->triggers = val; + else if ( var == "var-indent-handle-couples" ) + { + d->couples = 0; + QStringList l = QStringList::split( " ", val ); + if ( l.contains("parens") ) d->couples |= Parens; + if ( l.contains("braces") ) d->couples |= Braces; + if ( l.contains("brackets") ) d->couples |= Brackets; + } + else if ( var == "var-indent-couple-attribute" ) + { + //read a named attribute of the config. + KateHlItemDataList items; + doc->highlight()->getKateHlItemDataListCopy (0, items); + + for (uint i=0; i<items.count(); i++) + { + if ( items.at(i)->name.section( ':', 1 ) == val ) + { + d->coupleAttrib = i; + break; + } + } + } +} + +int KateVarIndent::coupleBalance ( int line, const QChar &open, const QChar &close ) const +{ + int r = 0; + + KateTextLine::Ptr ln = doc->plainKateTextLine( line ); + if ( ! ln || ! ln->length() ) return 0; + + for ( uint z=0; z < ln->length(); z++ ) + { + QChar c = ln->getChar( z ); + if ( ln->attribute(z) == d->coupleAttrib ) + { + kdDebug(13030)<<z<<", "<<c<<endl; + if (c == open) + r++; + else if (c == close) + r--; + } + } + return r; +} + +bool KateVarIndent::hasRelevantOpening( const KateDocCursor &end ) const +{ + KateDocCursor cur = end; + int count = 1; + + QChar close = cur.currentChar(); + QChar opener; + if ( close == '}' ) opener = '{'; + else if ( close = ')' ) opener = '('; + else if (close = ']' ) opener = '['; + else return false; + + //Move backwards 1 by 1 and find the opening partner + while (cur.moveBackward(1)) + { + if (cur.currentAttrib() == d->coupleAttrib) + { + QChar ch = cur.currentChar(); + if (ch == opener) + count--; + else if (ch == close) + count++; + + if (count == 0) + return true; + } + } + + return false; +} + + +//END KateVarIndent + +//BEGIN KateScriptIndent +KateScriptIndent::KateScriptIndent( KateDocument *doc ) + : KateNormalIndent( doc ) +{ + m_script=KateFactory::self()->indentScript ("script-indent-c1-test"); +} + +KateScriptIndent::~KateScriptIndent() +{ +} + +void KateScriptIndent::processNewline( KateDocCursor &begin, bool needContinue ) +{ + kdDebug(13030) << "processNewline" << endl; + KateView *view = doc->activeView(); + + if (view) + { + QString errorMsg; + + QTime t; + t.start(); + kdDebug(13030)<<"calling m_script.processChar"<<endl; + if( !m_script.processNewline( view, begin, needContinue , errorMsg ) ) + { + kdDebug(13030) << "Error in script-indent: " << errorMsg << endl; + } + kdDebug(13030) << "ScriptIndent::TIME in ms: " << t.elapsed() << endl; + } +} + +void KateScriptIndent::processChar( QChar c ) +{ + kdDebug(13030) << "processChar" << endl; + KateView *view = doc->activeView(); + + if (view) + { + QString errorMsg; + + QTime t; + t.start(); + kdDebug(13030)<<"calling m_script.processChar"<<endl; + if( !m_script.processChar( view, c , errorMsg ) ) + { + kdDebug(13030) << "Error in script-indent: " << errorMsg << endl; + } + kdDebug(13030) << "ScriptIndent::TIME in ms: " << t.elapsed() << endl; + } +} + +void KateScriptIndent::processLine (KateDocCursor &line) +{ + kdDebug(13030) << "processLine" << endl; + KateView *view = doc->activeView(); + + if (view) + { + QString errorMsg; + + QTime t; + t.start(); + kdDebug(13030)<<"calling m_script.processLine"<<endl; + if( !m_script.processLine( view, line , errorMsg ) ) + { + kdDebug(13030) << "Error in script-indent: " << errorMsg << endl; + } + kdDebug(13030) << "ScriptIndent::TIME in ms: " << t.elapsed() << endl; + } +} +//END KateScriptIndent + +//BEGIN ScriptIndentConfigPage, THIS IS ONLY A TEST! :) +#include <qlabel.h> +ScriptIndentConfigPage::ScriptIndentConfigPage ( QWidget *parent, const char *name ) + : IndenterConfigPage(parent, name) +{ + QLabel* hello = new QLabel("Hello world! Dummy for testing purpose.", this); + hello->show(); +} + +ScriptIndentConfigPage::~ScriptIndentConfigPage () +{ +} + +void ScriptIndentConfigPage::apply () +{ + kdDebug(13030) << "ScriptIndentConfigPagE::apply() was called, save config options now!" << endl; +} +//END ScriptIndentConfigPage + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateautoindent.h b/kate/part/kateautoindent.h new file mode 100644 index 000000000..76ba14ee6 --- /dev/null +++ b/kate/part/kateautoindent.h @@ -0,0 +1,581 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Jesse Yurkovich <yurkjes@iit.edu> + Copyright (C) 2004 >Anders Lund <anders@alweb.dk> (KateVarIndent class) + Copyright (C) 2005 Dominik Haumann <dhdev@gmx.de> (basic support for config page) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __KATE_AUTO_INDENT_H__ +#define __KATE_AUTO_INDENT_H__ + +#include <qobject.h> + +#include "katecursor.h" +#include "kateconfig.h" +#include "katejscript.h" +class KateDocument; + +/** + * This widget will be embedded into a modal dialog when clicking + * the "Configure..." button in the indentation config page. + * To add a config page for an indenter there are several todos: + * - Derive a class from this class. This widget will be embedded into + * the config dialog. + * - Override the slot @p apply(), which is called when the configuration + * needs to be saved. + * - Override @p KateAutoIndent::configPage() to return an instance of + * this dialog. + * - Return @p true in @p KateAutoIndent::hasConfigPage() for the + * corresponding indenter id. + */ +class IndenterConfigPage : public QWidget +{ + Q_OBJECT + + public: + /** + * Standard constructor + * @param parent parent widget + * @param name name + */ + IndenterConfigPage ( QWidget *parent=0, const char *name=0 ) : QWidget(parent, name) {} + virtual ~IndenterConfigPage () {} + + public slots: + /** + * Apply the changes. Save options here, use @p kapp->config() and + * group [Kate Indenter MyIndenter]. + */ + virtual void apply () = 0; +}; + +/** + * Provides Auto-Indent functionality for katepart. + * This baseclass is a real dummy, does nothing beside remembering the document it belongs too, + * only to have the object around + */ +class KateAutoIndent : public QObject +{ + Q_OBJECT + + /** + * Static methods to create and list indention modes + */ + public: + /** + * Create an indenter + * @param doc document for the indenter + * @param mode indention mode wanted + * @return created autoindention object + */ + static KateAutoIndent *createIndenter (KateDocument *doc, uint mode); + + /** + * List all possible modes by name + * @return list of modes + */ + static QStringList listModes (); + + /** + * Return the mode name given the mode + * @param mode mode index + * @return name for this mode index + */ + static QString modeName (uint mode); + + /** + * Return the mode description + * @param mode mode index + * @return mode index + */ + static QString modeDescription (uint mode); + + /** + * Maps name -> index + * @param name mode name + * @return mode index + */ + static uint modeNumber (const QString &name); + + /** + * Config page support + * @param mode mode index + * @return true, if the indenter @p mode has a configuration page + */ + static bool hasConfigPage (uint mode); + + /** + * Support for a config page. + * @return config page or 0 if not available. + */ + static IndenterConfigPage* configPage(QWidget *parent, uint mode); + + public: + /** + * Constructor + * @param doc parent document + */ + KateAutoIndent (KateDocument *doc); + + /** + * Virtual Destructor for the baseclass + */ + virtual ~KateAutoIndent (); + + public slots: + /** + * Update indenter's configuration (indention width, attributes etc.) + */ + virtual void updateConfig () {}; + + public: + /** + * does this indenter support processNewLine + * @return can you do it? + */ + virtual bool canProcessNewLine () const { return false; } + + /** + * Called every time a newline character is inserted in the document. + * + * @param cur The position to start processing. Contains the new cursor position after the indention. + * @param needContinue Used to determine whether to calculate a continue indent or not. + */ + virtual void processNewline (KateDocCursor &cur, bool needContinue) { Q_UNUSED(cur); Q_UNUSED(needContinue); } + + /** + * Called every time a character is inserted into the document. + * @param c character inserted + */ + virtual void processChar (QChar c) { Q_UNUSED(c); } + + /** + * Aligns/indents the given line to the proper indent position. + */ + virtual void processLine (KateDocCursor &/*line*/) { } + + /** + * Processes a section of text, indenting each line in between. + */ + virtual void processSection (const KateDocCursor &/*begin*/, const KateDocCursor &/*end*/) { } + + /** + * Set to true if an actual implementation of 'processLine' is present. + * This is used to prevent a needless Undo action from being created. + */ + virtual bool canProcessLine() const { return false; } + + /** + * Mode index of this mode + * @return modeNumber + */ + virtual uint modeNumber () const { return KateDocumentConfig::imNone; }; + + protected: + KateDocument *doc; +}; + +/** + * This action provides a list of available indenters and gets plugged + * into the KateView's KActionCollection. + */ +class KateViewIndentationAction : public KActionMenu +{ + Q_OBJECT + + public: + KateViewIndentationAction(KateDocument *_doc, const QString& text, QObject* parent = 0, const char* name = 0); + + ~KateViewIndentationAction(){;}; + + private: + KateDocument* doc; + + public slots: + void slotAboutToShow(); + + private slots: + void setMode (int mode); +}; + +/** + * Provides Auto-Indent functionality for katepart. + */ +class KateNormalIndent : public KateAutoIndent +{ + Q_OBJECT + +public: + /** + * Constructor + * @param doc parent document + */ + KateNormalIndent (KateDocument *doc); + + /** + * Virtual Destructor for the baseclass + */ + virtual ~KateNormalIndent (); + +public slots: + /** + * Update indenter's configuration (indention width, attributes etc.) + */ + virtual void updateConfig (); + +public: + /** + * does this indenter support processNewLine + * @return can you do it? + */ + virtual bool canProcessNewLine () const { return true; } + + /** + * Called every time a newline character is inserted in the document. + * + * @param cur The position to start processing. Contains the new cursor position after the indention. + * @param needContinue Used to determine whether to calculate a continue indent or not. + */ + virtual void processNewline (KateDocCursor &cur, bool needContinue); + + /** + * Called every time a character is inserted into the document. + * @param c character inserted + */ + virtual void processChar (QChar c) { Q_UNUSED(c); } + + /** + * Aligns/indents the given line to the proper indent position. + */ + virtual void processLine (KateDocCursor &/*line*/) { } + + /** + * Processes a section of text, indenting each line in between. + */ + virtual void processSection (const KateDocCursor &/*begin*/, const KateDocCursor &/*end*/) { } + + /** + * Set to true if an actual implementation of 'processLine' is present. + * This is used to prevent a needless Undo action from being created. + */ + virtual bool canProcessLine() const { return false; } + + /** + * Mode index of this mode + * @return modeNumber + */ + virtual uint modeNumber () const { return KateDocumentConfig::imNormal; }; + +protected: + + /** + * Determines if the characters open and close are balanced between @p begin and @p end + * Fills in @p pos with the column position of first opened character if found. + * + * @param begin Beginning cursor position. + * @param end Ending cursor position where the processing will stop. + * @param open The open character. + * @param close The closing character which should be matched against @p open. + * @param pos Contains the position of the first @p open character in the line. + * @return True if @p open and @p close have an equal number of occurances between @p begin and @p end. False otherwise. + */ + bool isBalanced (KateDocCursor &begin, const KateDocCursor &end, QChar open, QChar close, uint &pos) const; + + /** + * Skip all whitespace starting at @p cur and ending at @p max. Spans lines if @p newline is set. + * @p cur is set to the current position afterwards. + * + * @param cur The current cursor position to start from. + * @param max The furthest cursor position that will be used for processing + * @param newline Whether we are allowed to span multiple lines when skipping blanks + * @return True if @p cur < @p max after processing. False otherwise. + */ + bool skipBlanks (KateDocCursor &cur, KateDocCursor &max, bool newline) const; + + /** + * Measures the indention of the current textline marked by cur + * @param cur The cursor position to measure the indent to. + * @return The length of the indention in characters. + */ + uint measureIndent (KateDocCursor &cur) const; + + /** + * Produces a string with the proper indentation characters for its length. + * + * @param length The length of the indention in characters. + * @return A QString representing @p length characters (factoring in tabs and spaces) + */ + QString tabString(uint length) const; + + uint tabWidth; //!< The number of characters simulated for a tab + uint indentWidth; //!< The number of characters used when tabs are replaced by spaces + +public: + // Attributes that we should skip over or otherwise know about + uchar commentAttrib; + uchar doxyCommentAttrib; + uchar regionAttrib; + uchar symbolAttrib; + uchar alertAttrib; + uchar tagAttrib; + uchar wordAttrib; + uchar keywordAttrib; + uchar normalAttrib; + uchar extensionAttrib; + uchar preprocessorAttrib; + uchar stringAttrib; + uchar charAttrib; + +protected: + bool useSpaces; //!< Should we use spaces or tabs to indent + bool mixedIndent; //!< Optimize indent by mixing spaces and tabs, ala emacs + bool keepProfile; //!< Always try to honor the leading whitespace of lines already in the file +}; + +class KateCSmartIndent : public KateNormalIndent +{ + Q_OBJECT + + public: + KateCSmartIndent (KateDocument *doc); + ~KateCSmartIndent (); + + virtual void processNewline (KateDocCursor &cur, bool needContinue); + virtual void processChar (QChar c); + + virtual void processLine (KateDocCursor &line); + virtual void processSection (const KateDocCursor &begin, const KateDocCursor &end); + + virtual bool canProcessLine() const { return true; } + + virtual uint modeNumber () const { return KateDocumentConfig::imCStyle; }; + + private: + uint calcIndent (KateDocCursor &begin, bool needContinue); + uint calcContinue (KateDocCursor &begin, KateDocCursor &end); + uint findOpeningBrace (KateDocCursor &start); + uint findOpeningParen (KateDocCursor &start); + uint findOpeningComment (KateDocCursor &start); + bool firstOpeningBrace (KateDocCursor &start); + bool handleDoxygen (KateDocCursor &begin); + + bool allowSemi; + bool processingBlock; +}; + +class KatePythonIndent : public KateNormalIndent +{ + Q_OBJECT + + public: + KatePythonIndent (KateDocument *doc); + ~KatePythonIndent (); + + virtual void processNewline (KateDocCursor &cur, bool needContinue); + + virtual uint modeNumber () const { return KateDocumentConfig::imPythonStyle; }; + + private: + int calcExtra (int &prevBlock, int &pos, KateDocCursor &end); + void traverseString( const QChar &stringChar, KateDocCursor &cur, KateDocCursor &end ); + + static QRegExp endWithColon; + static QRegExp stopStmt; + static QRegExp blockBegin; +}; + +class KateXmlIndent : public KateNormalIndent +{ + Q_OBJECT + + public: + KateXmlIndent (KateDocument *doc); + ~KateXmlIndent (); + + virtual uint modeNumber () const { return KateDocumentConfig::imXmlStyle; } + virtual void processNewline (KateDocCursor &cur, bool needContinue); + virtual void processChar (QChar c); + virtual void processLine (KateDocCursor &line); + virtual bool canProcessLine() const { return true; } + virtual void processSection (const KateDocCursor &begin, const KateDocCursor &end); + + private: + // sets the indentation of a single line based on previous line + // (returns indentation width) + uint processLine (uint line); + + // gets information about a line + void getLineInfo (uint line, uint &prevIndent, int &numTags, + uint &attrCol, bool &unclosedTag); + + // useful regular expressions + static const QRegExp startsWithCloseTag; + static const QRegExp unclosedDoctype; +}; + +class KateCSAndSIndent : public KateNormalIndent +{ + Q_OBJECT + + public: + KateCSAndSIndent (KateDocument *doc); + ~KateCSAndSIndent (); + + virtual void processNewline (KateDocCursor &begin, bool needContinue); + virtual void processChar (QChar c); + + virtual void processLine (KateDocCursor &line); + virtual void processSection (const KateDocCursor &begin, const KateDocCursor &end); + + virtual bool canProcessLine() const { return true; } + + virtual uint modeNumber () const { return KateDocumentConfig::imCSAndS; }; + + private: + void updateIndentString(); + + bool inForStatement( int line ); + int lastNonCommentChar( const KateDocCursor &line ); + bool startsWithLabel( int line ); + bool inStatement( const KateDocCursor &begin ); + QString continuationIndent( const KateDocCursor &begin ); + + QString calcIndent (const KateDocCursor &begin); + QString calcIndentAfterKeyword(const KateDocCursor &indentCursor, const KateDocCursor &keywordCursor, int keywordPos, bool blockKeyword); + QString calcIndentInBracket(const KateDocCursor &indentCursor, const KateDocCursor &bracketCursor, int bracketPos); + QString calcIndentInBrace(const KateDocCursor &indentCursor, const KateDocCursor &braceCursor, int bracePos); + + bool handleDoxygen (KateDocCursor &begin); + QString findOpeningCommentIndentation (const KateDocCursor &start); + + QString indentString; +}; + +/** + * This indenter uses document variables to determine when to add/remove indents. + * + * It attempts to get the following variables from the document: + * - var-indent-indent-after: A rerular expression which will cause a line to + * be indented by one unit, if the first non-whitespace-only line above matches. + * - var-indent-indent: A regular expression, which will cause a matching line + * to be indented by one unit. + * - var-indent-unindent: A regular expression which will cause the line to be + * unindented by one unit if matching. + * - var-indent-triggerchars: a list of characters that should cause the + * indentiou to be recalculated immediately when typed. + * - var-indent-handle-couples: a list of paren sets to handle. Any combination + * of 'parens' 'braces' and 'brackets'. Each set type is handled + * the following way: If there are unmatched opening instances on the above line, + * one indent unit is added, if there are unmatched closing instances on the + * current line, one indent unit is removed. + * - var-indent-couple-attribute: When looking for unmatched couple openings/closings, + * only characters with this attribute is considered. The value must be the + * attribute name from the syntax xml file, for example "Symbol". If it's not + * specified, attribute 0 is used (usually 'Normal Text'). + * + * The idea is to provide a somewhat intelligent indentation for perl, php, + * bash, scheme and in general formats with humble indentation needs. + */ +class KateVarIndent : public KateNormalIndent +{ + Q_OBJECT + + public: + /** + * Purely for readability, couples we know and love + */ + enum pairs { + Parens=1, + Braces=2, + Brackets=4, + AngleBrackets=8 + }; + + KateVarIndent( KateDocument *doc ); + virtual ~KateVarIndent(); + + virtual void processNewline (KateDocCursor &cur, bool needContinue); + virtual void processChar (QChar c); + + virtual void processLine (KateDocCursor &line); + virtual void processSection (const KateDocCursor &begin, const KateDocCursor &end); + + virtual bool canProcessLine() const { return true; } + + virtual uint modeNumber () const { return KateDocumentConfig::imVarIndent; }; + + private slots: + void slotVariableChanged(const QString&, const QString&); + + private: + /** + * Check if coupled characters are in balance within one line. + * @param line the line to check + * @param open the opening character + * @param close the closing character + * @param attrib the attribute the characters must have, defaults to + * KateAutoIndent::symbolAttrib + */ + int coupleBalance( int line, const QChar &open, const QChar &close ) const; + + /** + * @return true if there is a matching opening with the correct attribute + * @param end a cursor pointing to the closing character + */ + bool hasRelevantOpening( const KateDocCursor &end ) const; + + class KateVarIndentPrivate *d; +}; + +class KateScriptIndent : public KateNormalIndent +{ + Q_OBJECT + + public: + KateScriptIndent( KateDocument *doc ); + ~KateScriptIndent(); + + virtual void processNewline( KateDocCursor &cur, bool needContinue ); + virtual void processChar( QChar c ); + + virtual void processLine (KateDocCursor &line); +// virtual void processSection (const KateDocCursor &begin, const KateDocCursor &end); + + virtual bool canProcessLine() const { return true; } + + virtual uint modeNumber () const { return KateDocumentConfig::imScriptIndent; }; + private: + KateIndentScript m_script; +}; + +class ScriptIndentConfigPage : public IndenterConfigPage +{ + Q_OBJECT + + public: + ScriptIndentConfigPage ( QWidget *parent=0, const char *name=0 ); + virtual ~ScriptIndentConfigPage (); + + public slots: + /** + * Apply changes. + */ + virtual void apply (); +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katebookmarks.cpp b/kate/part/katebookmarks.cpp new file mode 100644 index 000000000..f92fb4282 --- /dev/null +++ b/kate/part/katebookmarks.cpp @@ -0,0 +1,287 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002, 2003, 2004 Anders Lund <anders.lund@lund.tdcadsl.dk> + Copyright (C) 2002 John Firebaugh <jfirebaugh@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "katebookmarks.h" +#include "katebookmarks.moc" + +#include "katedocument.h" +#include "kateview.h" + +#include <klocale.h> +#include <kaction.h> +#include <kpopupmenu.h> +#include <kstringhandler.h> +#include <kxmlguiclient.h> +#include <kxmlguifactory.h> + +#include <qregexp.h> +#include <qmemarray.h> +#include <qevent.h> + +/** + Utility: selection sort + sort a QMemArray<uint> in ascending order. + max it the largest (zerobased) index to sort. + To sort the entire array: ssort( *array, array.size() -1 ); + This is only efficient if ran only once. +*/ +static void ssort( QMemArray<uint> &a, int max ) +{ + uint tmp, j, maxpos; + for ( uint h = max; h >= 1; h-- ) + { + maxpos = 0; + for ( j = 0; j <= h; j++ ) + maxpos = a[j] > a[maxpos] ? j : maxpos; + tmp = a[maxpos]; + a[maxpos] = a[h]; + a[h] = tmp; + } +} + +// TODO add a insort() or bubble_sort - more efficient for aboutToShow() ? + +KateBookmarks::KateBookmarks( KateView* view, Sorting sort ) + : QObject( view, "kate bookmarks" ) + , m_view( view ) + , m_sorting( sort ) +{ + connect (view->getDoc(), SIGNAL(marksChanged()), this, SLOT(marksChanged())); + _tries=0; + m_bookmarksMenu = 0L; +} + +KateBookmarks::~KateBookmarks() +{ +} + +void KateBookmarks::createActions( KActionCollection* ac ) +{ + m_bookmarkToggle = new KToggleAction( + i18n("Set &Bookmark"), "bookmark", CTRL+Key_B, + this, SLOT(toggleBookmark()), + ac, "bookmarks_toggle" ); + m_bookmarkToggle->setWhatsThis(i18n("If a line has no bookmark then add one, otherwise remove it.")); + m_bookmarkToggle->setCheckedState( i18n("Clear &Bookmark") ); + + m_bookmarkClear = new KAction( + i18n("Clear &All Bookmarks"), 0, + this, SLOT(clearBookmarks()), + ac, "bookmarks_clear"); + m_bookmarkClear->setWhatsThis(i18n("Remove all bookmarks of the current document.")); + + m_goNext = new KAction( + i18n("Next Bookmark"), "next", ALT + Key_PageDown, + this, SLOT(goNext()), + ac, "bookmarks_next"); + m_goNext->setWhatsThis(i18n("Go to the next bookmark.")); + + m_goPrevious = new KAction( + i18n("Previous Bookmark"), "previous", ALT + Key_PageUp, + this, SLOT(goPrevious()), + ac, "bookmarks_previous"); + m_goPrevious->setWhatsThis(i18n("Go to the previous bookmark.")); + + m_bookmarksMenu = (new KActionMenu(i18n("&Bookmarks"), ac, "bookmarks"))->popupMenu(); + + //connect the aboutToShow() and aboutToHide() signals with + //the bookmarkMenuAboutToShow() and bookmarkMenuAboutToHide() slots + connect( m_bookmarksMenu, SIGNAL(aboutToShow()), this, SLOT(bookmarkMenuAboutToShow())); + connect( m_bookmarksMenu, SIGNAL(aboutToHide()), this, SLOT(bookmarkMenuAboutToHide()) ); + + marksChanged (); + bookmarkMenuAboutToHide(); + + connect( m_view, SIGNAL( gotFocus( Kate::View * ) ), this, SLOT( slotViewGotFocus( Kate::View * ) ) ); + connect( m_view, SIGNAL( lostFocus( Kate::View * ) ), this, SLOT( slotViewLostFocus( Kate::View * ) ) ); +} + +void KateBookmarks::toggleBookmark () +{ + uint mark = m_view->getDoc()->mark( m_view->cursorLine() ); + if( mark & KTextEditor::MarkInterface::markType01 ) + m_view->getDoc()->removeMark( m_view->cursorLine(), + KTextEditor::MarkInterface::markType01 ); + else + m_view->getDoc()->addMark( m_view->cursorLine(), + KTextEditor::MarkInterface::markType01 ); +} + +void KateBookmarks::clearBookmarks () +{ + + QPtrList<KTextEditor::Mark> m = m_view->getDoc()->marks(); + for (uint i=0; i < m.count(); i++) + m_view->getDoc()->removeMark( m.at(i)->line, KTextEditor::MarkInterface::markType01 ); + + // just to be sure ;) + marksChanged (); +} + +void KateBookmarks::slotViewGotFocus( Kate::View *v ) +{ + if ( v == (Kate::View*)m_view ) + bookmarkMenuAboutToHide(); +} + +void KateBookmarks::slotViewLostFocus( Kate::View *v ) +{ + if ( v == (Kate::View*)m_view ) + m_bookmarksMenu->clear(); +} + +void KateBookmarks::insertBookmarks( QPopupMenu& menu ) +{ + uint line = m_view->cursorLine(); + const QRegExp re("&(?!&)"); + int idx( -1 ); + int old_menu_count = menu.count(); + KTextEditor::Mark *next = 0; + KTextEditor::Mark *prev = 0; + + QPtrList<KTextEditor::Mark> m = m_view->getDoc()->marks(); + QMemArray<uint> sortArray( m.count() ); + QPtrListIterator<KTextEditor::Mark> it( m ); + + if ( it.count() > 0 ) + menu.insertSeparator(); + + for( int i = 0; *it; ++it, ++i ) + { + if( (*it)->type & KTextEditor::MarkInterface::markType01 ) + { + QString bText = KStringHandler::rEmSqueeze + ( m_view->getDoc()->textLine( (*it)->line ), + menu.fontMetrics(), 32 ); + bText.replace(re, "&&"); // kill undesired accellerators! + bText.replace('\t', ' '); // kill tabs, as they are interpreted as shortcuts + + if ( m_sorting == Position ) + { + sortArray[i] = (*it)->line; + ssort( sortArray, i ); + idx = sortArray.find( (*it)->line ) + 3; + } + + menu.insertItem( + QString("%1 - \"%2\"").arg( (*it)->line+1 ).arg( bText ), + m_view, SLOT(gotoLineNumber(int)), 0, (*it)->line, idx ); + + if ( (*it)->line < line ) + { + if ( ! prev || prev->line < (*it)->line ) + prev = (*it); + } + + else if ( (*it)->line > line ) + { + if ( ! next || next->line > (*it)->line ) + next = (*it); + } + } + } + + idx = ++old_menu_count; + if ( next ) + { + m_goNext->setText( i18n("&Next: %1 - \"%2\"").arg( next->line + 1 ) + .arg( KStringHandler::rsqueeze( m_view->getDoc()->textLine( next->line ), 24 ) ) ); + m_goNext->plug( &menu, idx ); + idx++; + } + if ( prev ) + { + m_goPrevious->setText( i18n("&Previous: %1 - \"%2\"").arg(prev->line + 1 ) + .arg( KStringHandler::rsqueeze( m_view->getDoc()->textLine( prev->line ), 24 ) ) ); + m_goPrevious->plug( &menu, idx ); + idx++; + } + if ( next || prev ) + menu.insertSeparator( idx ); + +} + +void KateBookmarks::bookmarkMenuAboutToShow() +{ + + QPtrList<KTextEditor::Mark> m = m_view->getDoc()->marks(); + + m_bookmarksMenu->clear(); + m_bookmarkToggle->setChecked( m_view->getDoc()->mark( m_view->cursorLine() ) + & KTextEditor::MarkInterface::markType01 ); + m_bookmarkToggle->plug( m_bookmarksMenu ); + m_bookmarkClear->plug( m_bookmarksMenu ); + + + insertBookmarks(*m_bookmarksMenu); +} + +/* + Make sure next/prev actions are plugged, and have a clean text +*/ +void KateBookmarks::bookmarkMenuAboutToHide() +{ + m_bookmarkToggle->plug( m_bookmarksMenu ); + m_bookmarkClear->plug( m_bookmarksMenu ); + m_goNext->setText( i18n("Next Bookmark") ); + m_goNext->plug( m_bookmarksMenu ); + m_goPrevious->setText( i18n("Previous Bookmark") ); + m_goPrevious->plug( m_bookmarksMenu ); +} + +void KateBookmarks::goNext() +{ + QPtrList<KTextEditor::Mark> m = m_view->getDoc()->marks(); + if (m.isEmpty()) + return; + + uint line = m_view->cursorLine(); + int found = -1; + + for (uint z=0; z < m.count(); z++) + if ( (m.at(z)->line > line) && ((found == -1) || (uint(found) > m.at(z)->line)) ) + found = m.at(z)->line; + + if (found != -1) + m_view->gotoLineNumber ( found ); +} + +void KateBookmarks::goPrevious() +{ + QPtrList<KTextEditor::Mark> m = m_view->getDoc()->marks(); + if (m.isEmpty()) + return; + + uint line = m_view->cursorLine(); + int found = -1; + + for (uint z=0; z < m.count(); z++) + if ((m.at(z)->line < line) && ((found == -1) || (uint(found) < m.at(z)->line))) + found = m.at(z)->line; + + if (found != -1) + m_view->gotoLineNumber ( found ); +} + +void KateBookmarks::marksChanged () +{ + m_bookmarkClear->setEnabled( !m_view->getDoc()->marks().isEmpty() ); +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katebookmarks.h b/kate/part/katebookmarks.h new file mode 100644 index 000000000..0d72c0ccc --- /dev/null +++ b/kate/part/katebookmarks.h @@ -0,0 +1,86 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002, 2003 Anders Lund <anders.lund@lund.tdcadsl.dk> + Copyright (C) 2002 John Firebaugh <jfirebaugh@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __KATE_BOOKMARKS_H__ +#define __KATE_BOOKMARKS_H__ + +#include <qobject.h> +#include <qptrlist.h> + +class KateView; + +namespace KTextEditor { class Mark; } + +namespace Kate { class View; } + +class KAction; +class KToggleAction; +class KActionCollection; +class QPopupMenu; +class QMenuData; + +class KateBookmarks : public QObject +{ + Q_OBJECT + + public: + enum Sorting { Position, Creation }; + KateBookmarks( KateView* parent, Sorting sort=Position ); + virtual ~KateBookmarks(); + + void createActions( KActionCollection* ); + + KateBookmarks::Sorting sorting() { return m_sorting; }; + void setSorting( Sorting s ) { m_sorting = s; }; + + protected: + void insertBookmarks( QPopupMenu& menu); + + private slots: + void toggleBookmark(); + void clearBookmarks(); + + void slotViewGotFocus( Kate::View * ); + void slotViewLostFocus( Kate::View * ); + + void bookmarkMenuAboutToShow(); + void bookmarkMenuAboutToHide(); + + void goNext(); + void goPrevious(); + + void marksChanged (); + + private: + KateView* m_view; + KToggleAction* m_bookmarkToggle; + KAction* m_bookmarkClear; + KAction* m_goNext; + KAction* m_goPrevious; + + Sorting m_sorting; + QPopupMenu* m_bookmarksMenu; + + uint _tries; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; +// vim: noet ts=2 diff --git a/kate/part/katebuffer.cpp b/kate/part/katebuffer.cpp new file mode 100644 index 000000000..06c919f96 --- /dev/null +++ b/kate/part/katebuffer.cpp @@ -0,0 +1,1660 @@ +/* This file is part of the KDE libraries + Copyright (c) 2000 Waldo Bastian <bastian@kde.org> + Copyright (C) 2002-2004 Christoph Cullmann <cullmann@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "katebuffer.h" +#include "katebuffer.moc" + +#include "katedocument.h" +#include "katehighlight.h" +#include "kateconfig.h" +#include "katefactory.h" +#include "kateautoindent.h" + +#include <kdebug.h> +#include <kglobal.h> +#include <kcharsets.h> + +#include <qpopupmenu.h> +#include <qfile.h> +#include <qtextstream.h> +#include <qtimer.h> +#include <qtextcodec.h> +#include <qcstring.h> +#include <qdatetime.h> + +/** + * loader block size, load 256 kb at once per default + * if file size is smaller, fall back to file size + */ +static const Q_ULONG KATE_FILE_LOADER_BS = 256 * 1024; + +/** + * KATE_AVG_BLOCK_SIZE is in characters ! + * (internaly we calc with approx 80 chars per line !) + * block will max contain around BLOCK_SIZE chars or + * BLOCK_LINES lines (after load, later that won't be tracked) + */ +static const Q_ULONG KATE_AVG_BLOCK_SIZE = 2048 * 80; +static const Q_ULONG KATE_MAX_BLOCK_LINES = 2048; + +/** + * hl will look at the next KATE_HL_LOOKAHEAD lines + * or until the current block ends if a line is requested + * will avoid to run doHighlight too often + */ +static const uint KATE_HL_LOOKAHEAD = 64; + +/** + * KATE_MAX_BLOCKS_LOADED should be at least 4, as some + * methodes will cause heavy trashing, if not at least the + * latest 2-3 used blocks are alive + */ +uint KateBuffer::m_maxLoadedBlocks = 16; + +/** + * Initial value for m_maxDynamicContexts + */ +static const uint KATE_MAX_DYNAMIC_CONTEXTS = 512; + +void KateBuffer::setMaxLoadedBlocks (uint count) +{ + m_maxLoadedBlocks = kMax (4U, count); +} + +class KateFileLoader +{ + public: + KateFileLoader (const QString &filename, QTextCodec *codec, bool removeTrailingSpaces) + : m_file (filename) + , m_buffer (kMin (m_file.size(), KATE_FILE_LOADER_BS)) + , m_codec (codec) + , m_decoder (m_codec->makeDecoder()) + , m_position (0) + , m_lastLineStart (0) + , m_eof (false) // default to not eof + , lastWasEndOfLine (true) // at start of file, we had a virtual newline + , lastWasR (false) // we have not found a \r as last char + , m_eol (-1) // no eol type detected atm + , m_twoByteEncoding (QString(codec->name()) == "ISO-10646-UCS-2") + , m_binary (false) + , m_removeTrailingSpaces (removeTrailingSpaces) + { + kdDebug (13020) << "OPEN USES ENCODING: " << m_codec->name() << endl; + } + + ~KateFileLoader () + { + delete m_decoder; + } + + /** + * open file, read first chunk of data, detect eol + */ + bool open () + { + if (m_file.open (IO_ReadOnly)) + { + int c = m_file.readBlock (m_buffer.data(), m_buffer.size()); + + if (c > 0) + { + // fix utf16 LE, stolen from khtml ;) + if ((c >= 2) && (m_codec->mibEnum() == 1000) && (m_buffer[1] == 0x00)) + { + // utf16LE, we need to put the decoder in LE mode + char reverseUtf16[3] = {0xFF, 0xFE, 0x00}; + m_decoder->toUnicode(reverseUtf16, 2); + } + + processNull (c); + m_text = m_decoder->toUnicode (m_buffer, c); + } + + m_eof = (c == -1) || (c == 0) || (m_text.length() == 0) || m_file.atEnd(); + + for (uint i=0; i < m_text.length(); i++) + { + if (m_text[i] == '\n') + { + m_eol = KateDocumentConfig::eolUnix; + break; + } + else if ((m_text[i] == '\r')) + { + if (((i+1) < m_text.length()) && (m_text[i+1] == '\n')) + { + m_eol = KateDocumentConfig::eolDos; + break; + } + else + { + m_eol = KateDocumentConfig::eolMac; + break; + } + } + } + + return true; + } + + return false; + } + + // no new lines around ? + inline bool eof () const { return m_eof && !lastWasEndOfLine && (m_lastLineStart == m_text.length()); } + + // eol mode ? autodetected on open(), -1 for no eol found in the first block! + inline int eol () const { return m_eol; } + + // binary ? + inline bool binary () const { return m_binary; } + + // should spaces be ignored at end of line? + inline bool removeTrailingSpaces () const { return m_removeTrailingSpaces; } + + // internal unicode data array + inline const QChar *unicode () const { return m_text.unicode(); } + + // read a line, return length + offset in unicode data + void readLine (uint &offset, uint &length) + { + length = 0; + offset = 0; + + while (m_position <= m_text.length()) + { + if (m_position == m_text.length()) + { + // try to load more text if something is around + if (!m_eof) + { + int c = m_file.readBlock (m_buffer.data(), m_buffer.size()); + + uint readString = 0; + if (c > 0) + { + processNull (c); + + QString str (m_decoder->toUnicode (m_buffer, c)); + readString = str.length(); + + m_text = m_text.mid (m_lastLineStart, m_position-m_lastLineStart) + + str; + } + else + m_text = m_text.mid (m_lastLineStart, m_position-m_lastLineStart); + + // is file completly read ? + m_eof = (c == -1) || (c == 0) || (readString == 0) || m_file.atEnd(); + + // recalc current pos and last pos + m_position -= m_lastLineStart; + m_lastLineStart = 0; + } + + // oh oh, end of file, escape ! + if (m_eof && (m_position == m_text.length())) + { + lastWasEndOfLine = false; + + // line data + offset = m_lastLineStart; + length = m_position-m_lastLineStart; + + m_lastLineStart = m_position; + + return; + } + } + + if (m_text[m_position] == '\n') + { + lastWasEndOfLine = true; + + if (lastWasR) + { + m_lastLineStart++; + lastWasR = false; + } + else + { + // line data + offset = m_lastLineStart; + length = m_position-m_lastLineStart; + + m_lastLineStart = m_position+1; + m_position++; + + return; + } + } + else if (m_text[m_position] == '\r') + { + lastWasEndOfLine = true; + lastWasR = true; + + // line data + offset = m_lastLineStart; + length = m_position-m_lastLineStart; + + m_lastLineStart = m_position+1; + m_position++; + + return; + } + else + { + lastWasEndOfLine = false; + lastWasR = false; + } + + m_position++; + } + } + + // this nice methode will kill all 0 bytes (or double bytes) + // and remember if this was a binary or not ;) + void processNull (uint length) + { + if (m_twoByteEncoding) + { + for (uint i=1; i < length; i+=2) + { + if ((m_buffer[i] == 0) && (m_buffer[i-1] == 0)) + { + m_binary = true; + m_buffer[i] = ' '; + } + } + } + else + { + for (uint i=0; i < length; i++) + { + if (m_buffer[i] == 0) + { + m_binary = true; + m_buffer[i] = ' '; + } + } + } + } + + private: + QFile m_file; + QByteArray m_buffer; + QTextCodec *m_codec; + QTextDecoder *m_decoder; + QString m_text; + uint m_position; + uint m_lastLineStart; + bool m_eof; + bool lastWasEndOfLine; + bool lastWasR; + int m_eol; + bool m_twoByteEncoding; + bool m_binary; + bool m_removeTrailingSpaces; +}; + +/** + * Create an empty buffer. (with one block with one empty line) + */ +KateBuffer::KateBuffer(KateDocument *doc) + : QObject (doc), + editSessionNumber (0), + editIsRunning (false), + editTagLineStart (0xffffffff), + editTagLineEnd (0), + editTagLineFrom (false), + editChangesDone (false), + m_doc (doc), + m_lines (0), + m_lastInSyncBlock (0), + m_lastFoundBlock (0), + m_cacheReadError(false), + m_cacheWriteError(false), + m_loadingBorked (false), + m_binary (false), + m_highlight (0), + m_regionTree (this), + m_tabWidth (8), + m_lineHighlightedMax (0), + m_lineHighlighted (0), + m_maxDynamicContexts (KATE_MAX_DYNAMIC_CONTEXTS) +{ + clear(); +} + +/** + * Cleanup on destruction + */ +KateBuffer::~KateBuffer() +{ + // DELETE ALL BLOCKS, will free mem + for (uint i=0; i < m_blocks.size(); i++) + delete m_blocks[i]; + + // release HL + if (m_highlight) + m_highlight->release(); +} + +void KateBuffer::editStart () +{ + editSessionNumber++; + + if (editSessionNumber > 1) + return; + + editIsRunning = true; + + editTagLineStart = 0xffffffff; + editTagLineEnd = 0; + editTagLineFrom = false; + + editChangesDone = false; +} + +void KateBuffer::editEnd () +{ + if (editSessionNumber == 0) + return; + + editSessionNumber--; + + if (editSessionNumber > 0) + return; + + if (editChangesDone) + { + // hl update !!! + if ( m_highlight && !m_highlight->noHighlighting() + && (editTagLineStart <= editTagLineEnd) + && (editTagLineEnd <= m_lineHighlighted)) + { + // look one line too far, needed for linecontinue stuff + editTagLineEnd++; + + // look one line before, needed nearly 100% only for indentation based folding ! + if (editTagLineStart > 0) + editTagLineStart--; + + KateBufBlock *buf2 = 0; + bool needContinue = false; + while ((buf2 = findBlock(editTagLineStart))) + { + needContinue = doHighlight (buf2, + (editTagLineStart > buf2->startLine()) ? editTagLineStart : buf2->startLine(), + (editTagLineEnd > buf2->endLine()) ? buf2->endLine() : editTagLineEnd, + true); + + editTagLineStart = (editTagLineEnd > buf2->endLine()) ? buf2->endLine() : editTagLineEnd; + + if ((editTagLineStart >= m_lines) || (editTagLineStart >= editTagLineEnd)) + break; + } + + if (needContinue) + m_lineHighlighted = editTagLineStart; + + if (editTagLineStart > m_lineHighlightedMax) + m_lineHighlightedMax = editTagLineStart; + } + else if (editTagLineStart < m_lineHighlightedMax) + m_lineHighlightedMax = editTagLineStart; + } + + editIsRunning = false; +} + +void KateBuffer::clear() +{ + m_regionTree.clear(); + + // cleanup the blocks + for (uint i=0; i < m_blocks.size(); i++) + delete m_blocks[i]; + + m_blocks.clear (); + + // create a bufblock with one line, we need that, only in openFile we won't have that + KateBufBlock *block = new KateBufBlock(this, 0, 0); + m_blocks.append (block); + + // reset the state + m_lines = block->lines(); + m_lastInSyncBlock = 0; + m_lastFoundBlock = 0; + m_cacheWriteError = false; + m_cacheReadError = false; + m_loadingBorked = false; + m_binary = false; + + m_lineHighlightedMax = 0; + m_lineHighlighted = 0; +} + +bool KateBuffer::openFile (const QString &m_file) +{ + KateFileLoader file (m_file, m_doc->config()->codec(), m_doc->configFlags() & KateDocument::cfRemoveSpaces); + + bool ok = false; + struct stat sbuf; + if (stat(QFile::encodeName(m_file), &sbuf) == 0) + { + if (S_ISREG(sbuf.st_mode) && file.open()) + ok = true; + } + + if (!ok) + { + clear(); + return false; // Error + } + + // set eol mode, if a eol char was found in the first 256kb block and we allow this at all! + if (m_doc->config()->allowEolDetection() && (file.eol() != -1)) + m_doc->config()->setEol (file.eol()); + + // flush current content + clear (); + + // cleanup the blocks + for (uint i=0; i < m_blocks.size(); i++) + delete m_blocks[i]; + + m_blocks.clear (); + + // do the real work + KateBufBlock *block = 0; + m_lines = 0; + while (!file.eof() && !m_cacheWriteError) + { + block = new KateBufBlock (this, block, 0, &file); + + m_lines = block->endLine (); + + if (m_cacheWriteError || (block->lines() == 0)) + { + delete block; + break; + } + else + m_blocks.append (block); + } + + // we had a cache write error, this load is really borked ! + if (m_cacheWriteError) + m_loadingBorked = true; + + if (m_blocks.isEmpty() || (m_lines == 0)) + { + // file was really empty, clean the buffers + emit the line changed + // loadingBorked will be false for such files, not matter what happened + // before + clear (); + } + else + { + // fix region tree + m_regionTree.fixRoot (m_lines); + } + + // if we have no hl or the "None" hl activated, whole file is correct highlighted + // after loading, which wonder ;) + if (!m_highlight || m_highlight->noHighlighting()) + { + m_lineHighlighted = m_lines; + m_lineHighlightedMax = m_lines; + } + + // binary? + m_binary = file.binary (); + + kdDebug (13020) << "LOADING DONE" << endl; + + return !m_loadingBorked; +} + +bool KateBuffer::canEncode () +{ + QTextCodec *codec = m_doc->config()->codec(); + + kdDebug(13020) << "ENC NAME: " << codec->name() << endl; + + // hardcode some unicode encodings which can encode all chars + if ((QString(codec->name()) == "UTF-8") || (QString(codec->name()) == "ISO-10646-UCS-2")) + return true; + + for (uint i=0; i < m_lines; i++) + { + if (!codec->canEncode (plainLine(i)->string())) + { + kdDebug(13020) << "STRING LINE: " << plainLine(i)->string() << endl; + kdDebug(13020) << "ENC WORKING: FALSE" << endl; + + return false; + } + } + + return true; +} + +bool KateBuffer::saveFile (const QString &m_file) +{ + QFile file (m_file); + QTextStream stream (&file); + + if ( !file.open( IO_WriteOnly ) ) + { + return false; // Error + } + + QTextCodec *codec = m_doc->config()->codec(); + + // disable Unicode headers + stream.setEncoding(QTextStream::RawUnicode); + + // this line sets the mapper to the correct codec + stream.setCodec(codec); + + // our loved eol string ;) + QString eol = m_doc->config()->eolString (); + + // should we strip spaces? + bool removeTrailingSpaces = m_doc->configFlags() & KateDocument::cfRemoveSpaces; + + // just dump the lines out ;) + for (uint i=0; i < m_lines; i++) + { + KateTextLine::Ptr textline = plainLine(i); + + // strip spaces + if (removeTrailingSpaces) + { + int lastChar = textline->lastChar(); + + if (lastChar > -1) + { + stream << QConstString (textline->text(), lastChar+1).string(); + } + } + else // simple, dump the line + stream << textline->string(); + + if ((i+1) < m_lines) + stream << eol; + } + + file.close (); + + m_loadingBorked = false; + + return (file.status() == IO_Ok); +} + +KateTextLine::Ptr KateBuffer::line_internal (KateBufBlock *buf, uint i) +{ + // update hl until this line + max KATE_HL_LOOKAHEAD + KateBufBlock *buf2 = 0; + while ((i >= m_lineHighlighted) && (buf2 = findBlock(m_lineHighlighted))) + { + uint end = kMin(i + KATE_HL_LOOKAHEAD, buf2->endLine()); + + doHighlight ( buf2, + kMax(m_lineHighlighted, buf2->startLine()), + end, + false ); + + m_lineHighlighted = end; + } + + // update hl max + if (m_lineHighlighted > m_lineHighlightedMax) + m_lineHighlightedMax = m_lineHighlighted; + + return buf->line (i - buf->startLine()); +} + +KateBufBlock *KateBuffer::findBlock_internal (uint i, uint *index) +{ + uint lastLine = m_blocks[m_lastInSyncBlock]->endLine (); + + if (lastLine > i) // we are in a allready known area ! + { + while (true) + { + KateBufBlock *buf = m_blocks[m_lastFoundBlock]; + + if ( (buf->startLine() <= i) + && (buf->endLine() > i) ) + { + if (index) + (*index) = m_lastFoundBlock; + + return m_blocks[m_lastFoundBlock]; + } + + if (i < buf->startLine()) + m_lastFoundBlock--; + else + m_lastFoundBlock++; + } + } + else // we need first to resync the startLines ! + { + if ((m_lastInSyncBlock+1) < m_blocks.size()) + m_lastInSyncBlock++; + else + return 0; + + for (; m_lastInSyncBlock < m_blocks.size(); m_lastInSyncBlock++) + { + // get next block + KateBufBlock *buf = m_blocks[m_lastInSyncBlock]; + + // sync startLine ! + buf->setStartLine (lastLine); + + // is it allready the searched block ? + if ((i >= lastLine) && (i < buf->endLine())) + { + // remember this block as last found ! + m_lastFoundBlock = m_lastInSyncBlock; + + if (index) + (*index) = m_lastFoundBlock; + + return buf; + } + + // increase lastLine with blocklinecount + lastLine += buf->lines (); + } + } + + // no block found ! + // index will not be set to any useful value in this case ! + return 0; +} + +void KateBuffer::changeLine(uint i) +{ + KateBufBlock *buf = findBlock(i); + + if (!buf) + return; + + // mark this block dirty + buf->markDirty (); + + // mark buffer changed + editChangesDone = true; + + // tag this line as changed + if (i < editTagLineStart) + editTagLineStart = i; + + if (i > editTagLineEnd) + editTagLineEnd = i; +} + +void KateBuffer::insertLine(uint i, KateTextLine::Ptr line) +{ + uint index = 0; + KateBufBlock *buf; + if (i == m_lines) + buf = findBlock(i-1, &index); + else + buf = findBlock(i, &index); + + if (!buf) + return; + + buf->insertLine(i - buf->startLine(), line); + + if (m_lineHighlightedMax > i) + m_lineHighlightedMax++; + + if (m_lineHighlighted > i) + m_lineHighlighted++; + + m_lines++; + + // last sync block adjust + if (m_lastInSyncBlock > index) + m_lastInSyncBlock = index; + + // last found + if (m_lastInSyncBlock < m_lastFoundBlock) + m_lastFoundBlock = m_lastInSyncBlock; + + // mark buffer changed + editChangesDone = true; + + // tag this line as inserted + if (i < editTagLineStart) + editTagLineStart = i; + + if (i <= editTagLineEnd) + editTagLineEnd++; + + if (i > editTagLineEnd) + editTagLineEnd = i; + + // line inserted + editTagLineFrom = true; + + m_regionTree.lineHasBeenInserted (i); +} + +void KateBuffer::removeLine(uint i) +{ + uint index = 0; + KateBufBlock *buf = findBlock(i, &index); + + if (!buf) + return; + + buf->removeLine(i - buf->startLine()); + + if (m_lineHighlightedMax > i) + m_lineHighlightedMax--; + + if (m_lineHighlighted > i) + m_lineHighlighted--; + + m_lines--; + + // trash away a empty block + if (buf->lines() == 0) + { + // we need to change which block is last in sync + if (m_lastInSyncBlock >= index) + { + m_lastInSyncBlock = index; + + if (buf->next()) + { + if (buf->prev()) + buf->next()->setStartLine (buf->prev()->endLine()); + else + buf->next()->setStartLine (0); + } + } + + // cu block ! + delete buf; + m_blocks.erase (m_blocks.begin()+index); + + // make sure we don't keep a pointer to the deleted block + if( m_lastInSyncBlock >= index ) + m_lastInSyncBlock = index - 1; + } + else + { + // last sync block adjust + if (m_lastInSyncBlock > index) + m_lastInSyncBlock = index; + } + + // last found + if (m_lastInSyncBlock < m_lastFoundBlock) + m_lastFoundBlock = m_lastInSyncBlock; + + // mark buffer changed + editChangesDone = true; + + // tag this line as removed + if (i < editTagLineStart) + editTagLineStart = i; + + if (i < editTagLineEnd) + editTagLineEnd--; + + if (i > editTagLineEnd) + editTagLineEnd = i; + + // line removed + editTagLineFrom = true; + + m_regionTree.lineHasBeenRemoved (i); +} + +void KateBuffer::setTabWidth (uint w) +{ + if ((m_tabWidth != w) && (m_tabWidth > 0)) + { + m_tabWidth = w; + + if (m_highlight && m_highlight->foldingIndentationSensitive()) + invalidateHighlighting(); + } +} + +void KateBuffer::setHighlight(uint hlMode) +{ + KateHighlighting *h = KateHlManager::self()->getHl(hlMode); + + // aha, hl will change + if (h != m_highlight) + { + bool invalidate = !h->noHighlighting(); + + if (m_highlight) + { + m_highlight->release(); + invalidate = true; + } + + h->use(); + + // Clear code folding tree (see bug #124102) + m_regionTree.clear(); + m_regionTree.fixRoot(m_lines); + + // try to set indentation + if (!h->indentation().isEmpty()) + m_doc->config()->setIndentationMode (KateAutoIndent::modeNumber(h->indentation())); + + m_highlight = h; + + if (invalidate) + invalidateHighlighting(); + + // inform the document that the hl was really changed + // needed to update attributes and more ;) + m_doc->bufferHlChanged (); + } +} + +void KateBuffer::invalidateHighlighting() +{ + m_lineHighlightedMax = 0; + m_lineHighlighted = 0; +} + + +void KateBuffer::updatePreviousNotEmptyLine(KateBufBlock *blk,uint current_line,bool addindent,uint deindent) +{ + KateTextLine::Ptr textLine; + do { + if (current_line>0) current_line--; + else + { + uint line=blk->startLine()+current_line; + if (line==0) return; + line--; + blk=findBlock(line); + if (!blk) { + kdDebug(13020)<<"updatePreviousNotEmptyLine: block not found, this must not happen"<<endl; + return; + } + current_line=line-blk->startLine(); + } + textLine = blk->line(current_line); + } while (textLine->firstChar()==-1); + kdDebug(13020)<<"updatePreviousNotEmptyLine: updating line:"<<(blk->startLine()+current_line)<<endl; + QMemArray<uint> foldingList=textLine->foldingListArray(); + while ( (foldingList.size()>0) && ( abs(foldingList[foldingList.size()-2])==1)) { + foldingList.resize(foldingList.size()-2,QGArray::SpeedOptim); + } + addIndentBasedFoldingInformation(foldingList,addindent,deindent); + textLine->setFoldingList(foldingList); + bool retVal_folding = false; + m_regionTree.updateLine (current_line + blk->startLine(), &foldingList, &retVal_folding, true,false); + emit tagLines (blk->startLine()+current_line, blk->startLine()+current_line); +} + +void KateBuffer::addIndentBasedFoldingInformation(QMemArray<uint> &foldingList,bool addindent,uint deindent) +{ + if (addindent) { + //kdDebug(13020)<<"adding indent for line :"<<current_line + buf->startLine()<<" textLine->noIndentBasedFoldingAtStart"<<textLine->noIndentBasedFoldingAtStart()<<endl; + kdDebug(13020)<<"adding ident"<<endl; + foldingList.resize (foldingList.size() + 2, QGArray::SpeedOptim); + foldingList[foldingList.size()-2] = 1; + foldingList[foldingList.size()-1] = 0; + } + kdDebug(13020)<<"DEINDENT: "<<deindent<<endl; + if (deindent > 0) + { + foldingList.resize (foldingList.size() + (deindent*2), QGArray::SpeedOptim); + + for (uint z= foldingList.size()-(deindent*2); z < foldingList.size(); z=z+2) + { + foldingList[z] = -1; + foldingList[z+1] = 0; + } + } +} + +bool KateBuffer::doHighlight (KateBufBlock *buf, uint startLine, uint endLine, bool invalidate) +{ + // no hl around, no stuff to do + if (!m_highlight) + return false; + + /*if (m_highlight->foldingIndentationSensitive()) + { + startLine=0; + endLine=50; + }*/ + + // we tried to start in a line behind this buf block ! + if (startLine >= (buf->startLine()+buf->lines())) + return false; + + //QTime t; + //t.start(); + //kdDebug (13020) << "HIGHLIGHTED START --- NEED HL, LINESTART: " << startLine << " LINEEND: " << endLine << endl; + //kdDebug (13020) << "HL UNTIL LINE: " << m_lineHighlighted << " MAX: " << m_lineHighlightedMax << endl; + //kdDebug (13020) << "HL DYN COUNT: " << KateHlManager::self()->countDynamicCtxs() << " MAX: " << m_maxDynamicContexts << endl; + + // see if there are too many dynamic contexts; if yes, invalidate HL of all documents + if (KateHlManager::self()->countDynamicCtxs() >= m_maxDynamicContexts) + { + { + if (KateHlManager::self()->resetDynamicCtxs()) + { + kdDebug (13020) << "HL invalidated - too many dynamic contexts ( >= " << m_maxDynamicContexts << ")" << endl; + + // avoid recursive invalidation + KateHlManager::self()->setForceNoDCReset(true); + + for (KateDocument *doc = KateFactory::self()->documents()->first(); doc; doc = KateFactory::self()->documents()->next()) + doc->makeAttribs(); + + // doHighlight *shall* do his work. After invalidation, some highlight has + // been recalculated, but *maybe not* until endLine ! So we shall force it manually... + KateBufBlock *buf = 0; + while ((endLine > m_lineHighlighted) && (buf = findBlock(m_lineHighlighted))) + { + uint end = kMin(endLine, buf->endLine()); + + doHighlight ( buf, + kMax(m_lineHighlighted, buf->startLine()), + end, + false ); + + m_lineHighlighted = end; + } + + KateHlManager::self()->setForceNoDCReset(false); + + return false; + } + else + { + m_maxDynamicContexts *= 2; + kdDebug (13020) << "New dynamic contexts limit: " << m_maxDynamicContexts << endl; + } + } + } + + // get the previous line, if we start at the beginning of this block + // take the last line of the previous block + KateTextLine::Ptr prevLine = 0; + + if ((startLine == buf->startLine()) && buf->prev() && (buf->prev()->lines() > 0)) + prevLine = buf->prev()->line (buf->prev()->lines() - 1); + else if ((startLine > buf->startLine()) && (startLine <= buf->endLine())) + prevLine = buf->line(startLine - buf->startLine() - 1); + else + prevLine = new KateTextLine (); + + // does we need to emit a signal for the folding changes ? + bool codeFoldingUpdate = false; + + // here we are atm, start at start line in the block + uint current_line = startLine - buf->startLine(); + + // do we need to continue + bool stillcontinue=false; + bool indentContinueWhitespace=false; + bool indentContinueNextWhitespace=false; + // loop over the lines of the block, from startline to endline or end of block + // if stillcontinue forces us to do so + while ( (current_line < buf->lines()) + && (stillcontinue || ((current_line + buf->startLine()) <= endLine)) ) + { + // current line + KateTextLine::Ptr textLine = buf->line(current_line); + + QMemArray<uint> foldingList; + bool ctxChanged = false; + + m_highlight->doHighlight (prevLine, textLine, &foldingList, &ctxChanged); + + // + // indentation sensitive folding + // + bool indentChanged = false; + if (m_highlight->foldingIndentationSensitive()) + { + // get the indentation array of the previous line to start with ! + QMemArray<unsigned short> indentDepth; + indentDepth.duplicate (prevLine->indentationDepthArray()); + + // current indentation of this line + uint iDepth = textLine->indentDepth(m_tabWidth); + if ((current_line+buf->startLine())==0) + { + indentDepth.resize (1, QGArray::SpeedOptim); + indentDepth[0] = iDepth; + } + + textLine->setNoIndentBasedFoldingAtStart(prevLine->noIndentBasedFolding()); + // this line is empty, beside spaces, or has indentaion based folding disabled, use indentation depth of the previous line ! + kdDebug(13020)<<"current_line:"<<current_line + buf->startLine()<<" textLine->noIndentBasedFoldingAtStart"<<textLine->noIndentBasedFoldingAtStart()<<endl; + if ( (textLine->firstChar() == -1) || textLine->noIndentBasedFoldingAtStart()) + { + // do this to get skipped empty lines indent right, which was given in the indenation array + if (!prevLine->indentationDepthArray().isEmpty()) + { + iDepth = (prevLine->indentationDepthArray())[prevLine->indentationDepthArray().size()-1]; + kdDebug(13020)<<"reusing old depth as current"<<endl; + } + else + { + iDepth = prevLine->indentDepth(m_tabWidth); + kdDebug(13020)<<"creating indentdepth for previous line"<<endl; + } + } + + kdDebug(13020)<<"iDepth:"<<iDepth<<endl; + + // query the next line indentation, if we are at the end of the block + // use the first line of the next buf block + uint nextLineIndentation = 0; + bool nextLineIndentationValid=true; + indentContinueNextWhitespace=false; + if ((current_line+1) < buf->lines()) + { + if (buf->line(current_line+1)->firstChar() == -1) + { + nextLineIndentation = iDepth; + indentContinueNextWhitespace=true; + } + else + nextLineIndentation = buf->line(current_line+1)->indentDepth(m_tabWidth); + } + else + { + KateBufBlock *blk = buf->next(); + + if (blk && (blk->lines() > 0)) + { + if (blk->line (0)->firstChar() == -1) + { + nextLineIndentation = iDepth; + indentContinueNextWhitespace=true; + } + else + nextLineIndentation = blk->line (0)->indentDepth(m_tabWidth); + } + else nextLineIndentationValid=false; + } + + if (!textLine->noIndentBasedFoldingAtStart()) { + + if ((iDepth > 0) && (indentDepth.isEmpty() || (indentDepth[indentDepth.size()-1] < iDepth))) + { + kdDebug(13020)<<"adding depth to \"stack\":"<<iDepth<<endl; + indentDepth.resize (indentDepth.size()+1, QGArray::SpeedOptim); + indentDepth[indentDepth.size()-1] = iDepth; + } else { + if (!indentDepth.isEmpty()) + { + for (int z=indentDepth.size()-1; z > -1; z--) + if (indentDepth[z]>iDepth) + indentDepth.resize(z, QGArray::SpeedOptim); + if ((iDepth > 0) && (indentDepth.isEmpty() || (indentDepth[indentDepth.size()-1] < iDepth))) + { + kdDebug(13020)<<"adding depth to \"stack\":"<<iDepth<<endl; + indentDepth.resize (indentDepth.size()+1, QGArray::SpeedOptim); + indentDepth[indentDepth.size()-1] = iDepth; + if (prevLine->firstChar()==-1) { + + } + } + } + } + } + + if (!textLine->noIndentBasedFolding()) + { + if (nextLineIndentationValid) + { + //if (textLine->firstChar()!=-1) + { + kdDebug(13020)<<"nextLineIndentation:"<<nextLineIndentation<<endl; + bool addindent=false; + uint deindent=0; + if (!indentDepth.isEmpty()) + kdDebug()<<"indentDepth[indentDepth.size()-1]:"<<indentDepth[indentDepth.size()-1]<<endl; + if ((nextLineIndentation>0) && ( indentDepth.isEmpty() || (indentDepth[indentDepth.size()-1]<nextLineIndentation))) + { + kdDebug(13020)<<"addindent==true"<<endl; + addindent=true; + } else { + if ((!indentDepth.isEmpty()) && (indentDepth[indentDepth.size()-1]>nextLineIndentation)) + { + kdDebug(13020)<<"...."<<endl; + for (int z=indentDepth.size()-1; z > -1; z--) + { + kdDebug(13020)<<indentDepth[z]<<" "<<nextLineIndentation<<endl; + if (indentDepth[z]>nextLineIndentation) + deindent++; + } + } + } +/* } + if (textLine->noIndentBasedFolding()) kdDebug(13020)<<"=============================indentation based folding disabled======================"<<endl; + if (!textLine->noIndentBasedFolding()) {*/ + if ((textLine->firstChar()==-1)) { + updatePreviousNotEmptyLine(buf,current_line,addindent,deindent); + codeFoldingUpdate=true; + } + else + { + addIndentBasedFoldingInformation(foldingList,addindent,deindent); + } + } + } + } + indentChanged = !(indentDepth == textLine->indentationDepthArray()); + + // assign the new array to the textline ! + if (indentChanged) + textLine->setIndentationDepth (indentDepth); + + indentContinueWhitespace=textLine->firstChar()==-1; + } + bool foldingColChanged=false; + bool foldingChanged = false; //!(foldingList == textLine->foldingListArray()); + if (foldingList.size()!=textLine->foldingListArray().size()) { + foldingChanged=true; + } else { + QMemArray<uint>::ConstIterator it=foldingList.begin(); + QMemArray<uint>::ConstIterator it1=textLine->foldingListArray(); + bool markerType=true; + for(;it!=foldingList.end();++it,++it1) { + if (markerType) { + if ( ((*it)!=(*it1))) { + foldingChanged=true; + foldingColChanged=false; + break; + } + } else { + if ((*it)!=(*it1)) { + foldingColChanged=true; + } + } + markerType=!markerType; + } + } + + if (foldingChanged || foldingColChanged) { + textLine->setFoldingList(foldingList); + if (foldingChanged==false){ + textLine->setFoldingColumnsOutdated(textLine->foldingColumnsOutdated() | foldingColChanged); + } else textLine->setFoldingColumnsOutdated(false); + } + bool retVal_folding = false; + //perhaps make en enums out of the change flags + m_regionTree.updateLine (current_line + buf->startLine(), &foldingList, &retVal_folding, foldingChanged,foldingColChanged); + + codeFoldingUpdate = codeFoldingUpdate | retVal_folding; + + // need we to continue ? + stillcontinue = ctxChanged || indentChanged || indentContinueWhitespace || indentContinueNextWhitespace; + + // move around the lines + prevLine = textLine; + + // increment line + current_line++; + } + + buf->markDirty (); + + // tag the changed lines ! + if (invalidate) + emit tagLines (startLine, current_line + buf->startLine()); + + // emit that we have changed the folding + if (codeFoldingUpdate) + emit codeFoldingUpdated(); + + //kdDebug (13020) << "HIGHLIGHTED END --- NEED HL, LINESTART: " << startLine << " LINEEND: " << endLine << endl; + //kdDebug (13020) << "HL UNTIL LINE: " << m_lineHighlighted << " MAX: " << m_lineHighlightedMax << endl; + //kdDebug (13020) << "HL DYN COUNT: " << KateHlManager::self()->countDynamicCtxs() << " MAX: " << m_maxDynamicContexts << endl; + //kdDebug (13020) << "TIME TAKEN: " << t.elapsed() << endl; + + // if we are at the last line of the block + we still need to continue + // return the need of that ! + return stillcontinue && ((current_line+1) == buf->lines()); +} + +void KateBuffer::codeFoldingColumnUpdate(unsigned int lineNr) { + KateTextLine::Ptr line=plainLine(lineNr); + if (!line) return; + if (line->foldingColumnsOutdated()) { + line->setFoldingColumnsOutdated(false); + bool tmp; + QMemArray<uint> folding=line->foldingListArray(); + m_regionTree.updateLine(lineNr,&folding,&tmp,true,false); + } +} + +//BEGIN KateBufBlock + +KateBufBlock::KateBufBlock ( KateBuffer *parent, KateBufBlock *prev, KateBufBlock *next, + KateFileLoader *stream ) +: m_state (KateBufBlock::stateDirty), + m_startLine (0), + m_lines (0), + m_vmblock (0), + m_vmblockSize (0), + m_parent (parent), + m_prev (prev), + m_next (next), + list (0), + listPrev (0), + listNext (0) +{ + // init startline + the next pointers of the neighbour blocks + if (m_prev) + { + m_startLine = m_prev->endLine (); + m_prev->m_next = this; + } + + if (m_next) + m_next->m_prev = this; + + // we have a stream, use it to fill the block ! + // this can lead to 0 line blocks which are invalid ! + if (stream) + { + // this we lead to either dirty or swapped state + fillBlock (stream); + } + else // init the block if no stream given ! + { + // fill in one empty line ! + KateTextLine::Ptr textLine = new KateTextLine (); + m_stringList.push_back (textLine); + m_lines++; + + // if we have allready enough blocks around, swap one + if (m_parent->m_loadedBlocks.count() >= KateBuffer::maxLoadedBlocks()) + m_parent->m_loadedBlocks.first()->swapOut(); + + // we are a new nearly empty dirty block + m_state = KateBufBlock::stateDirty; + m_parent->m_loadedBlocks.append (this); + } +} + +KateBufBlock::~KateBufBlock () +{ + // sync prev/next pointers + if (m_prev) + m_prev->m_next = m_next; + + if (m_next) + m_next->m_prev = m_prev; + + // if we have some swapped data allocated, free it now or never + if (m_vmblock) + KateFactory::self()->vm()->free(m_vmblock); + + // remove me from the list I belong + KateBufBlockList::remove (this); +} + +void KateBufBlock::fillBlock (KateFileLoader *stream) +{ + // is allready too much stuff around in mem ? + bool swap = m_parent->m_loadedBlocks.count() >= KateBuffer::maxLoadedBlocks(); + + QByteArray rawData; + + // calcs the approx size for KATE_AVG_BLOCK_SIZE chars ! + if (swap) + rawData.resize ((KATE_AVG_BLOCK_SIZE * sizeof(QChar)) + ((KATE_AVG_BLOCK_SIZE/80) * 8)); + + char *buf = rawData.data (); + uint size = 0; + uint blockSize = 0; + while (!stream->eof() && (blockSize < KATE_AVG_BLOCK_SIZE) && (m_lines < KATE_MAX_BLOCK_LINES)) + { + uint offset = 0, length = 0; + stream->readLine(offset, length); + const QChar *unicodeData = stream->unicode () + offset; + + // strip spaces at end of line + if ( stream->removeTrailingSpaces() ) + { + while (length > 0) + { + if (unicodeData[length-1].isSpace()) + --length; + else + break; + } + } + + blockSize += length; + + if (swap) + { + // create the swapped data on the fly, no need to waste time + // via going over the textline classes and dump them ! + char attr = KateTextLine::flagNoOtherData; + uint pos = size; + + // calc new size + size = size + 1 + sizeof(uint) + (sizeof(QChar)*length); + + if (size > rawData.size ()) + { + rawData.resize (size); + buf = rawData.data (); + } + + memcpy(buf+pos, (char *) &attr, 1); + pos += 1; + + memcpy(buf+pos, (char *) &length, sizeof(uint)); + pos += sizeof(uint); + + memcpy(buf+pos, (char *) unicodeData, sizeof(QChar)*length); + pos += sizeof(QChar)*length; + } + else + { + KateTextLine::Ptr textLine = new KateTextLine (); + textLine->insertText (0, length, unicodeData); + m_stringList.push_back (textLine); + } + + m_lines++; + } + + if (swap) + { + m_vmblock = KateFactory::self()->vm()->allocate(size); + m_vmblockSize = size; + + if (!rawData.isEmpty()) + { + if (!KateFactory::self()->vm()->copyBlock(m_vmblock, rawData.data(), 0, size)) + { + if (m_vmblock) + KateFactory::self()->vm()->free(m_vmblock); + + m_vmblock = 0; + m_vmblockSize = 0; + + m_parent->m_cacheWriteError = true; + } + } + + // fine, we are swapped ! + m_state = KateBufBlock::stateSwapped; + } + else + { + // we are a new dirty block without any swap data + m_state = KateBufBlock::stateDirty; + m_parent->m_loadedBlocks.append (this); + } + + kdDebug (13020) << "A BLOCK LOADED WITH LINES: " << m_lines << endl; +} + +KateTextLine::Ptr KateBufBlock::line(uint i) +{ + // take care that the string list is around !!! + if (m_state == KateBufBlock::stateSwapped) + swapIn (); + + // LRU + if (!m_parent->m_loadedBlocks.isLast(this)) + m_parent->m_loadedBlocks.append (this); + + return m_stringList[i]; +} + +void KateBufBlock::insertLine(uint i, KateTextLine::Ptr line) +{ + // take care that the string list is around !!! + if (m_state == KateBufBlock::stateSwapped) + swapIn (); + + m_stringList.insert (m_stringList.begin()+i, line); + m_lines++; + + markDirty (); +} + +void KateBufBlock::removeLine(uint i) +{ + // take care that the string list is around !!! + if (m_state == KateBufBlock::stateSwapped) + swapIn (); + + m_stringList.erase (m_stringList.begin()+i); + m_lines--; + + markDirty (); +} + +void KateBufBlock::markDirty () +{ + if (m_state != KateBufBlock::stateSwapped) + { + // LRU + if (!m_parent->m_loadedBlocks.isLast(this)) + m_parent->m_loadedBlocks.append (this); + + if (m_state == KateBufBlock::stateClean) + { + // if we have some swapped data allocated which is dirty, free it now + if (m_vmblock) + KateFactory::self()->vm()->free(m_vmblock); + + m_vmblock = 0; + m_vmblockSize = 0; + + // we are dirty + m_state = KateBufBlock::stateDirty; + } + } +} + +void KateBufBlock::swapIn () +{ + if (m_state != KateBufBlock::stateSwapped) + return; + + QByteArray rawData (m_vmblockSize); + + // what to do if that fails ? + if (!KateFactory::self()->vm()->copyBlock(rawData.data(), m_vmblock, 0, rawData.size())) + m_parent->m_cacheReadError = true; + + // reserve mem, keep realloc away on push_back + m_stringList.reserve (m_lines); + + char *buf = rawData.data(); + for (uint i=0; i < m_lines; i++) + { + KateTextLine::Ptr textLine = new KateTextLine (); + buf = textLine->restore (buf); + m_stringList.push_back (textLine); + } + + // if we have allready enough blocks around, swap one + if (m_parent->m_loadedBlocks.count() >= KateBuffer::maxLoadedBlocks()) + m_parent->m_loadedBlocks.first()->swapOut(); + + // fine, we are now clean again, save state + append to clean list + m_state = KateBufBlock::stateClean; + m_parent->m_loadedBlocks.append (this); +} + +void KateBufBlock::swapOut () +{ + if (m_state == KateBufBlock::stateSwapped) + return; + + if (m_state == KateBufBlock::stateDirty) + { + bool haveHl = m_parent->m_highlight && !m_parent->m_highlight->noHighlighting(); + + // Calculate size. + uint size = 0; + for (uint i=0; i < m_lines; i++) + size += m_stringList[i]->dumpSize (haveHl); + + QByteArray rawData (size); + char *buf = rawData.data(); + + // Dump textlines + for (uint i=0; i < m_lines; i++) + buf = m_stringList[i]->dump (buf, haveHl); + + m_vmblock = KateFactory::self()->vm()->allocate(rawData.size()); + m_vmblockSize = rawData.size(); + + if (!rawData.isEmpty()) + { + if (!KateFactory::self()->vm()->copyBlock(m_vmblock, rawData.data(), 0, rawData.size())) + { + if (m_vmblock) + KateFactory::self()->vm()->free(m_vmblock); + + m_vmblock = 0; + m_vmblockSize = 0; + + m_parent->m_cacheWriteError = true; + + return; + } + } + } + + m_stringList.clear(); + + // we are now swapped out, set state + remove us out of the lists ! + m_state = KateBufBlock::stateSwapped; + KateBufBlockList::remove (this); +} + +//END KateBufBlock + +//BEGIN KateBufBlockList + +KateBufBlockList::KateBufBlockList () + : m_count (0), + m_first (0), + m_last (0) +{ +} + +void KateBufBlockList::append (KateBufBlock *buf) +{ + if (buf->list) + buf->list->removeInternal (buf); + + m_count++; + + // append a element + if (m_last) + { + m_last->listNext = buf; + + buf->listPrev = m_last; + buf->listNext = 0; + + m_last = buf; + + buf->list = this; + + return; + } + + // insert the first element + m_last = buf; + m_first = buf; + + buf->listPrev = 0; + buf->listNext = 0; + + buf->list = this; +} + +void KateBufBlockList::removeInternal (KateBufBlock *buf) +{ + if (buf->list != this) + return; + + m_count--; + + if ((buf == m_first) && (buf == m_last)) + { + // last element removed ! + m_first = 0; + m_last = 0; + } + else if (buf == m_first) + { + // first element removed + m_first = buf->listNext; + m_first->listPrev = 0; + } + else if (buf == m_last) + { + // last element removed + m_last = buf->listPrev; + m_last->listNext = 0; + } + else + { + buf->listPrev->listNext = buf->listNext; + buf->listNext->listPrev = buf->listPrev; + } + + buf->listPrev = 0; + buf->listNext = 0; + + buf->list = 0; +} + +//END KateBufBlockList + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katebuffer.h b/kate/part/katebuffer.h new file mode 100644 index 000000000..6b2d48ead --- /dev/null +++ b/kate/part/katebuffer.h @@ -0,0 +1,709 @@ +/* This file is part of the KDE libraries + Copyright (c) 2000 Waldo Bastian <bastian@kde.org> + Copyright (C) 2002-2004 Christoph Cullmann <cullmann@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __KATE_BUFFER_H__ +#define __KATE_BUFFER_H__ + +#include "katetextline.h" +#include "katecodefoldinghelpers.h" + +#include <kvmallocator.h> + +#include <qptrlist.h> +#include <qobject.h> +#include <qtimer.h> +#include <qvaluevector.h> + +class KateLineInfo; +class KateDocument; +class KateHighlighting; +class KateBufBlockList; +class KateBuffer; +class KateFileLoader; + +class QTextCodec; + +/** + * The KateBufBlock class contains an amount of data representing + * a certain number of lines. + * + * @author Waldo Bastian <bastian@kde.org> + * @author Christoph Cullmann <cullmann@kde.org> + */ +class KateBufBlock +{ + friend class KateBufBlockList; + + public: + /** + * Create an empty block. (empty == ONE line) + * @param parent buffer the block belongs to + * @param prev previous bufblock in the list + * @param next next bufblock in the list + * @param stream stream to load the content from, if any given + */ + KateBufBlock ( KateBuffer *parent, KateBufBlock *prev = 0, KateBufBlock *next = 0, + KateFileLoader *stream = 0 ); + + /** + * destroy this block and take care of freeing all mem + */ + ~KateBufBlock (); + + private: + /** + * fill the block with the lines from the given stream + * @param stream stream to load data from + */ + void fillBlock (KateFileLoader *stream); + + public: + /** + * state flags + */ + enum State + { + stateSwapped = 0, + stateClean = 1, + stateDirty = 2 + }; + + /** + * returns the current state of this block + * @return state + */ + State state () const { return m_state; } + + public: + /** + * return line @p i + * The first line of this block is line 0. + * if you modifiy this line, please mark the block as dirty + * @param i line to return + * @return line pointer + */ + KateTextLine::Ptr line(uint i); + + /** + * insert @p line in front of line @p i + * marks the block dirty + * @param i where to insert + * @param line line pointer + */ + void insertLine(uint i, KateTextLine::Ptr line); + + /** + * remove line @p i + * marks the block dirty + * @param i line to remove + */ + void removeLine(uint i); + + /** + * mark this block as dirty, will invalidate the swap data + * insert/removeLine will mark the block dirty itself + */ + void markDirty (); + + public: + /** + * startLine + * @return first line in block + */ + inline uint startLine () const { return m_startLine; }; + + /** + * update the first line, needed to keep it up to date + * @param line new startLine + */ + inline void setStartLine (uint line) { m_startLine = line; } + + /** + * first line behind this block + * @return line behind block + */ + inline uint endLine () const { return m_startLine + m_lines; } + + /** + * lines in this block + * @return lines + */ + inline uint lines () const { return m_lines; } + + /** + * prev block + * @return previous block + */ + inline KateBufBlock *prev () { return m_prev; } + + /** + * next block + * @return next block + */ + inline KateBufBlock *next () { return m_next; } + + /** + * methodes to swap in/out + */ + private: + /** + * swap in the kvmallocater data, create string list + */ + void swapIn (); + + /** + * swap our string list out, delete it ! + */ + void swapOut (); + + private: + /** + * VERY IMPORTANT, state of this block + * this uchar indicates if the block is swapped, loaded, clean or dirty + */ + KateBufBlock::State m_state; + + /** + * IMPORTANT, start line + */ + uint m_startLine; + + /** + * IMPORTANT, line count + */ + uint m_lines; + + /** + * here we swap our stuff + */ + KVMAllocator::Block *m_vmblock; + + /** + * swapped size + */ + uint m_vmblockSize; + + /** + * list of textlines + */ + QValueVector<KateTextLine::Ptr> m_stringList; + + /** + * parent buffer. + */ + KateBuffer* m_parent; + + /** + * prev block + */ + KateBufBlock *m_prev; + + /** + * next block + */ + KateBufBlock *m_next; + + private: + /** + * list pointer, to which list I belong + * list element pointers for the KateBufBlockList ONLY !!! + */ + KateBufBlockList *list; + + /** + * prev list item + */ + KateBufBlock *listPrev; + + /** + * next list item + */ + KateBufBlock *listNext; +}; + +/** + * list which allows O(1) inserts/removes + * will not delete the elements on remove + * will use the next/prevNode pointers in the KateBufBlocks ! + * internal use: loaded/clean/dirty block lists + * + * @author Christoph Cullmann <cullmann@kde.org> + */ +class KateBufBlockList +{ + public: + /** + * Default Constructor + */ + KateBufBlockList (); + + public: + /** + * count of blocks in this list + * @return count of blocks + */ + inline uint count() const { return m_count; } + + /** + * first block in this list or 0 + * @return head of list + */ + inline KateBufBlock *first () { return m_first; }; + + /** + * last block in this list or 0 + * @return end of list + */ + inline KateBufBlock *last () { return m_last; }; + + /** + * is buf the last block? + * @param buf block to test + * @return is this block the first one? + */ + inline bool isFirst (KateBufBlock *buf) { return m_first == buf; }; + + /** + * is buf the last block? + * @param buf block to test + * @return is this block the last one? + */ + inline bool isLast (KateBufBlock *buf) { return m_last == buf; }; + + /** + * append a block to this list ! + * will remove it from the list it belonged before ! + * @param buf block to append + */ + void append (KateBufBlock *buf); + + /** + * remove the block from the list it belongs to ! + * @param buf block to remove + */ + inline static void remove (KateBufBlock *buf) + { + if (buf->list) + buf->list->removeInternal (buf); + } + + private: + /** + * internal helper for remove + * @param buf block to remove + */ + void removeInternal (KateBufBlock *buf); + + private: + /** + * count of blocks in list + */ + uint m_count; + + /** + * first block + */ + KateBufBlock *m_first; + + /** + * last block + */ + KateBufBlock *m_last; +}; + +/** + * The KateBuffer class maintains a collections of lines. + * It allows to maintain state information in a lazy way. + * It handles swapping out of data using secondary storage. + * + * It is designed to handle large amounts of text-data efficiently + * with respect to CPU and memory usage. + * + * @author Waldo Bastian <bastian@kde.org> + * @author Christoph Cullmann <cullmann@kde.org> + */ +class KateBuffer : public QObject +{ + Q_OBJECT + + friend class KateBufBlock; + + public: + /** + * maximal loaded block count + * @return max loaded blocks + */ + inline static uint maxLoadedBlocks () { return m_maxLoadedBlocks; } + + /** + * modifier for max loaded blocks limit + * @param count new limit + */ + static void setMaxLoadedBlocks (uint count); + + private: + /** + * global max loaded blocks limit + */ + static uint m_maxLoadedBlocks; + + public: + /** + * Create an empty buffer. + * @param doc parent document + */ + KateBuffer (KateDocument *doc); + + /** + * Goodbye buffer + */ + ~KateBuffer (); + + public: + /** + * start some editing action + */ + void editStart (); + + /** + * finish some editing action + */ + void editEnd (); + + /** + * were there changes in the current running + * editing session? + * @return changes done? + */ + inline bool editChanged () const { return editChangesDone; } + + /** + * dirty lines start + * @return start line + */ + inline uint editTagStart () const { return editTagLineStart; } + + /** + * dirty lines end + * @return end line + */ + inline uint editTagEnd () const { return editTagLineEnd; } + + /** + * line inserted/removed? + * @return line inserted/removed? + */ + inline bool editTagFrom () const { return editTagLineFrom; } + + private: + /** + * edit session recursion + */ + uint editSessionNumber; + + /** + * is a edit session running + */ + bool editIsRunning; + + /** + * dirty lines start at line + */ + uint editTagLineStart; + + /** + * dirty lines end at line + */ + uint editTagLineEnd; + + /** + * a line was inserted or removed + */ + bool editTagLineFrom; + + /** + * changes done? + */ + bool editChangesDone; + + public: + /** + * Clear the buffer. + */ + void clear(); + + /** + * Open a file, use the given filename + * @param m_file filename to open + * @return success + */ + bool openFile (const QString &m_file); + + /** + * was the last loading broken because of not enough tmp disk space ? + * (will be reseted on successful save of the file, user gets warning if he really wants to do it) + * @return was loading borked? + */ + bool loadingBorked () const { return m_loadingBorked; } + + /** + * is this file a binary? + * @return binary file? + */ + bool binary () const { return m_binary; } + + /** + * Can the current codec handle all chars + * @return chars can be encoded + */ + bool canEncode (); + + /** + * Save the buffer to a file, use the given filename + codec + end of line chars (internal use of qtextstream) + * @param m_file filename to save to + * @return success + */ + bool saveFile (const QString &m_file); + + public: + /** + * Return line @p i + */ + inline KateTextLine::Ptr line(uint i) + { + KateBufBlock *buf = findBlock(i); + if (!buf) + return 0; + + if (i < m_lineHighlighted) + return buf->line (i - buf->startLine()); + + return line_internal (buf, i); + } + + private: + /** + * line needs hl + */ + KateTextLine::Ptr line_internal (KateBufBlock *buf, uint i); + + inline void addIndentBasedFoldingInformation(QMemArray<uint> &foldingList,bool addindent,uint deindent); + inline void updatePreviousNotEmptyLine(KateBufBlock *blk,uint current_line,bool addindent,uint deindent); + public: + /** + * Return line @p i without triggering highlighting + */ + inline KateTextLine::Ptr plainLine(uint i) + { + KateBufBlock *buf = findBlock(i); + if (!buf) + return 0; + + return buf->line(i - buf->startLine()); + } + + /** + * Return the total number of lines in the buffer. + */ + inline uint count() const { return m_lines; } + + private: + /** + * Find the block containing line @p i + * index pointer gets filled with index of block in m_blocks + * index only valid if returned block != 0 ! + */ + KateBufBlock *findBlock (uint i, uint *index = 0) + { + // out of range ! + if (i >= m_lines) + return 0; + + if ((m_blocks[m_lastFoundBlock]->startLine() <= i) && (m_blocks[m_lastFoundBlock]->endLine() > i)) + { + if (index) + (*index) = m_lastFoundBlock; + + return m_blocks[m_lastFoundBlock]; + } + + return findBlock_internal (i, index); + } + + KateBufBlock *findBlock_internal (uint i, uint *index = 0); + + public: + /** + * Mark line @p i as changed ! + */ + void changeLine(uint i); + + /** + * Insert @p line in front of line @p i + */ + void insertLine(uint i, KateTextLine::Ptr line); + + /** + * Remove line @p i + */ + void removeLine(uint i); + + public: + inline uint countVisible () { return m_lines - m_regionTree.getHiddenLinesCount(m_lines); } + + inline uint lineNumber (uint visibleLine) { return m_regionTree.getRealLine (visibleLine); } + + inline uint lineVisibleNumber (uint line) { return m_regionTree.getVirtualLine (line); } + + inline void lineInfo (KateLineInfo *info, unsigned int line) { m_regionTree.getLineInfo(info,line); } + + inline uint tabWidth () const { return m_tabWidth; } + + public: + void setTabWidth (uint w); + + /** + * Use @p highlight for highlighting + * + * @p highlight may be 0 in which case highlighting + * will be disabled. + */ + void setHighlight (uint hlMode); + + KateHighlighting *highlight () { return m_highlight; }; + + /** + * Invalidate highlighting of whole buffer. + */ + void invalidateHighlighting(); + + KateCodeFoldingTree *foldingTree () { return &m_regionTree; }; + + public slots: + void codeFoldingColumnUpdate(unsigned int lineNr); + + private: + /** + * Highlight information needs to be updated. + * + * @param buf The buffer being processed. + * @param startState highlighting state of last line before range + * @param from first line in range + * @param to last line in range + * @param invalidat should the rehighlighted lines be tagged ? + * + * @returns true when the highlighting in the next block needs to be updated, + * false otherwise. + */ + bool doHighlight (KateBufBlock *buf, uint from, uint to, bool invalidate); + + signals: + /** + * Emittend if codefolding returned with a changed list + */ + void codeFoldingUpdated(); + + /** + * Emitted when the highlighting of a certain range has + * changed. + */ + void tagLines(int start, int end); + + private: + /** + * document we belong too + */ + KateDocument *m_doc; + + /** + * current line count + */ + uint m_lines; + + /** + * ALL blocks + * in order of linenumbers + */ + QValueVector<KateBufBlock*> m_blocks; + + /** + * last block where the start/end line is in sync with real life + */ + uint m_lastInSyncBlock; + + /** + * last block found by findBlock, there to make searching faster + */ + uint m_lastFoundBlock; + + /** + * status of the cache read/write errors + * write errors get handled, read errors not really atm + */ + bool m_cacheReadError; + bool m_cacheWriteError; + + /** + * had we cache error while loading ? + */ + bool m_loadingBorked; + + /** + * binary file loaded ? + */ + bool m_binary; + + /** + * highlighting & folding relevant stuff + */ + private: + /** + * current highlighting mode or 0 + */ + KateHighlighting *m_highlight; + + /** + * folding tree + */ + KateCodeFoldingTree m_regionTree; + + // for the scrapty indent sensitive langs + uint m_tabWidth; + + uint m_lineHighlightedMax; + uint m_lineHighlighted; + + /** + * number of dynamic contexts causing a full invalidation + */ + uint m_maxDynamicContexts; + + /** + * only used from the KateBufBlocks ! + */ + private: + /** + * all not swapped blocks ! + */ + KateBufBlockList m_loadedBlocks; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katecmds.cpp b/kate/part/katecmds.cpp new file mode 100644 index 000000000..17846dd7d --- /dev/null +++ b/kate/part/katecmds.cpp @@ -0,0 +1,605 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 - 2005 Anders Lund <anders@alweb.dk> + Copyright (C) 2001-2004 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Charles Samuels <charles@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "katecmds.h" + +#include "katedocument.h" +#include "kateview.h" +#include "kateconfig.h" +#include "kateautoindent.h" +#include "katetextline.h" +#include "katefactory.h" +#include "katejscript.h" +#include "katerenderer.h" + +#include "../interfaces/katecmd.h" + +#include <kdebug.h> +#include <klocale.h> +#include <kurl.h> +#include <kshellcompletion.h> + +#include <qregexp.h> + + +//BEGIN CoreCommands +// syncs a config flag in the document with a boolean value +static void setDocFlag( KateDocumentConfig::ConfigFlags flag, bool enable, + KateDocument *doc ) +{ + doc->config()->setConfigFlags( flag, enable ); +} + +// this returns wheather the string s could be converted to +// a bool value, one of on|off|1|0|true|false. the argument val is +// set to the extracted value in case of success +static bool getBoolArg( QString s, bool *val ) +{ + bool res( false ); + s = s.lower(); + res = (s == "on" || s == "1" || s == "true"); + if ( res ) + { + *val = true; + return true; + } + res = (s == "off" || s == "0" || s == "false"); + if ( res ) + { + *val = false; + return true; + } + return false; +} + +QStringList KateCommands::CoreCommands::cmds() +{ + QStringList l; + l << "indent" << "unindent" << "cleanindent" + << "comment" << "uncomment" << "goto" << "kill-line" + << "set-tab-width" << "set-replace-tabs" << "set-show-tabs" + << "set-remove-trailing-space" + << "set-indent-spaces" << "set-indent-width" << "set-mixed-indent" + << "set-indent-mode" << "set-auto-indent" + << "set-line-numbers" << "set-folding-markers" << "set-icon-border" + << "set-wrap-cursor" + << "set-word-wrap" << "set-word-wrap-column" + << "set-replace-tabs-save" << "set-remove-trailing-space-save" + << "set-highlight" << "run-myself" << "set-show-indent"; + return l; +} + +bool KateCommands::CoreCommands::exec(Kate::View *view, + const QString &_cmd, + QString &errorMsg) +{ +#define KCC_ERR(s) { errorMsg=s; return false; } + // cast it hardcore, we know that it is really a kateview :) + KateView *v = (KateView*) view; + + if ( ! v ) + KCC_ERR( i18n("Could not access view") ); + + //create a list of args + QStringList args( QStringList::split( QRegExp("\\s+"), _cmd ) ); + QString cmd ( args.first() ); + args.remove( args.first() ); + + // ALL commands that takes no arguments. + if ( cmd == "indent" ) + { + v->indent(); + return true; + } + else if ( cmd == "run-myself" ) + { +#ifndef Q_WS_WIN //todo + return KateFactory::self()->jscript()->execute(v, v->doc()->text(), errorMsg); +#else + return 0; +#endif + } + else if ( cmd == "unindent" ) + { + v->unIndent(); + return true; + } + else if ( cmd == "cleanindent" ) + { + v->cleanIndent(); + return true; + } + else if ( cmd == "comment" ) + { + v->comment(); + return true; + } + else if ( cmd == "uncomment" ) + { + v->uncomment(); + return true; + } + else if ( cmd == "kill-line" ) + { + v->killLine(); + return true; + } + else if ( cmd == "set-indent-mode" ) + { + bool ok(false); + int val ( args.first().toInt( &ok ) ); + if ( ok ) + { + if ( val < 0 ) + KCC_ERR( i18n("Mode must be at least 0.") ); + v->doc()->config()->setIndentationMode( val ); + } + else + v->doc()->config()->setIndentationMode( KateAutoIndent::modeNumber( args.first() ) ); + return true; + } + else if ( cmd == "set-highlight" ) + { + QString val = _cmd.section( ' ', 1 ).lower(); + for ( uint i=0; i < v->doc()->hlModeCount(); i++ ) + { + if ( v->doc()->hlModeName( i ).lower() == val ) + { + v->doc()->setHlMode( i ); + return true; + } + } + KCC_ERR( i18n("No such highlight '%1'").arg( args.first() ) ); + } + + // ALL commands that takes exactly one integer argument. + else if ( cmd == "set-tab-width" || + cmd == "set-indent-width" || + cmd == "set-word-wrap-column" || + cmd == "goto" ) + { + // find a integer value > 0 + if ( ! args.count() ) + KCC_ERR( i18n("Missing argument. Usage: %1 <value>").arg( cmd ) ); + bool ok; + int val ( args.first().toInt( &ok ) ); + if ( !ok ) + KCC_ERR( i18n("Failed to convert argument '%1' to integer.") + .arg( args.first() ) ); + + if ( cmd == "set-tab-width" ) + { + if ( val < 1 ) + KCC_ERR( i18n("Width must be at least 1.") ); + v->setTabWidth( val ); + } + else if ( cmd == "set-indent-width" ) + { + if ( val < 1 ) + KCC_ERR( i18n("Width must be at least 1.") ); + v->doc()->config()->setIndentationWidth( val ); + } + else if ( cmd == "set-word-wrap-column" ) + { + if ( val < 2 ) + KCC_ERR( i18n("Column must be at least 1.") ); + v->doc()->setWordWrapAt( val ); + } + else if ( cmd == "goto" ) + { + if ( val < 1 ) + KCC_ERR( i18n("Line must be at least 1") ); + if ( (uint)val > v->doc()->numLines() ) + KCC_ERR( i18n("There is not that many lines in this document") ); + v->gotoLineNumber( val - 1 ); + } + return true; + } + + // ALL commands that takes 1 boolean argument. + else if ( cmd == "set-icon-border" || + cmd == "set-folding-markers" || + cmd == "set-line-numbers" || + cmd == "set-replace-tabs" || + cmd == "set-remove-trailing-space" || + cmd == "set-show-tabs" || + cmd == "set-indent-spaces" || + cmd == "set-mixed-indent" || + cmd == "set-word-wrap" || + cmd == "set-wrap-cursor" || + cmd == "set-replace-tabs-save" || + cmd == "set-remove-trailing-space-save" || + cmd == "set-show-indent" ) + { + if ( ! args.count() ) + KCC_ERR( i18n("Usage: %1 on|off|1|0|true|false").arg( cmd ) ); + bool enable; + if ( getBoolArg( args.first(), &enable ) ) + { + if ( cmd == "set-icon-border" ) + v->setIconBorder( enable ); + else if (cmd == "set-folding-markers") + v->setFoldingMarkersOn( enable ); + else if ( cmd == "set-line-numbers" ) + v->setLineNumbersOn( enable ); + else if ( cmd == "set-show-indent" ) + v->renderer()->setShowIndentLines( enable ); + else if ( cmd == "set-replace-tabs" ) + setDocFlag( KateDocumentConfig::cfReplaceTabsDyn, enable, v->doc() ); + else if ( cmd == "set-remove-trailing-space" ) + setDocFlag( KateDocumentConfig::cfRemoveTrailingDyn, enable, v->doc() ); + else if ( cmd == "set-show-tabs" ) + setDocFlag( KateDocumentConfig::cfShowTabs, enable, v->doc() ); + else if ( cmd == "set-indent-spaces" ) + setDocFlag( KateDocumentConfig::cfSpaceIndent, enable, v->doc() ); + else if ( cmd == "set-mixed-indent" ) + { + // this is special, in that everything is set up -- space-indent is enabled, + // and a indent-width is set if it is 0 (to tabwidth/2) + setDocFlag( KateDocumentConfig::cfMixedIndent, enable, v->doc() ); + if ( enable ) + { + setDocFlag( KateDocumentConfig::cfSpaceIndent, enable, v->doc() ); + if ( ! v->doc()->config()->indentationWidth() ) + v->doc()->config()->setIndentationWidth( v->tabWidth()/2 ); + } + } + else if ( cmd == "set-word-wrap" ) + v->doc()->setWordWrap( enable ); + else if ( cmd == "set-remove-trailing-space-save" ) + setDocFlag( KateDocumentConfig::cfRemoveSpaces, enable, v->doc() ); + else if ( cmd == "set-wrap-cursor" ) + setDocFlag( KateDocumentConfig::cfWrapCursor, enable, v->doc() ); + + return true; + } + else + KCC_ERR( i18n("Bad argument '%1'. Usage: %2 on|off|1|0|true|false") + .arg( args.first() ).arg( cmd ) ); + } + + // unlikely.. + KCC_ERR( i18n("Unknown command '%1'").arg(cmd) ); +} + +KCompletion *KateCommands::CoreCommands::completionObject( const QString &cmd, Kate::View *view ) +{ + if ( cmd == "set-highlight" ) + { + KateView *v = (KateView*)view; + QStringList l; + for ( uint i = 0; i < v->doc()->hlModeCount(); i++ ) + l << v->doc()->hlModeName( i ); + + KateCmdShellCompletion *co = new KateCmdShellCompletion(); + co->setItems( l ); + co->setIgnoreCase( true ); + return co; + } + return 0L; +} +//END CoreCommands + +//BEGIN SedReplace +static void replace(QString &s, const QString &needle, const QString &with) +{ + int pos=0; + while (1) + { + pos=s.find(needle, pos); + if (pos==-1) break; + s.replace(pos, needle.length(), with); + pos+=with.length(); + } + +} + +static int backslashString(const QString &haystack, const QString &needle, int index) +{ + int len=haystack.length(); + int searchlen=needle.length(); + bool evenCount=true; + while (index<len) + { + if (haystack[index]=='\\') + { + evenCount=!evenCount; + } + else + { // isn't a slash + if (!evenCount) + { + if (haystack.mid(index, searchlen)==needle) + return index-1; + } + evenCount=true; + } + index++; + + } + + return -1; +} + +// exchange "\t" for the actual tab character, for example +static void exchangeAbbrevs(QString &str) +{ + // the format is (findreplace)*[nullzero] + const char *magic="a\x07t\tn\n"; + + while (*magic) + { + int index=0; + char replace=magic[1]; + while ((index=backslashString(str, QChar(*magic), index))!=-1) + { + str.replace(index, 2, QChar(replace)); + index++; + } + magic++; + magic++; + } +} + +int KateCommands::SedReplace::sedMagic( KateDocument *doc, int &line, + const QString &find, const QString &repOld, const QString &delim, + bool noCase, bool repeat, + uint startcol, int endcol ) +{ + KateTextLine *ln = doc->kateTextLine( line ); + if ( ! ln || ! ln->length() ) return 0; + + // HANDLING "\n"s in PATTERN + // * Create a list of patterns, splitting PATTERN on (unescaped) "\n" + // * insert $s and ^s to match line ends/beginnings + // * When matching patterhs after the first one, replace \N with the captured + // text. + // * If all patterns in the list match sequentiel lines, there is a match, so + // * remove line/start to line + patterns.count()-1/patterns.last.length + // * handle capatures by putting them in one list. + // * the existing insertion is fine, including the line calculation. + + QStringList patterns = QStringList::split( QRegExp("(^\\\\n|(?![^\\\\])\\\\n)"), find, true ); + + if ( patterns.count() > 1 ) + { + for ( uint i = 0; i < patterns.count(); i++ ) + { + if ( i < patterns.count() - 1 ) + patterns[i].append("$"); + if ( i ) + patterns[i].prepend("^"); + + kdDebug(13025)<<"patterns["<<i<<"] ="<<patterns[i]<<endl; + } + } + + QRegExp matcher(patterns[0], noCase); + + uint len; + int matches = 0; + + while ( ln->searchText( startcol, matcher, &startcol, &len ) ) + { + + if ( endcol >= 0 && startcol + len > (uint)endcol ) + break; + + matches++; + + + QString rep=repOld; + + // now set the backreferences in the replacement + QStringList backrefs=matcher.capturedTexts(); + int refnum=1; + + QStringList::Iterator i = backrefs.begin(); + ++i; + + for (; i!=backrefs.end(); ++i) + { + // I need to match "\\" or "", but not "\" + QString number=QString::number(refnum); + + int index=0; + while (index!=-1) + { + index=backslashString(rep, number, index); + if (index>=0) + { + rep.replace(index, 2, *i); + index+=(*i).length(); + } + } + + refnum++; + } + + replace(rep, "\\\\", "\\"); + replace(rep, "\\" + delim, delim); + + doc->removeText( line, startcol, line, startcol + len ); + doc->insertText( line, startcol, rep ); + + // TODO if replace contains \n, + // change the line number and + // check for text that needs be searched behind the last inserted newline. + int lns = rep.contains('\n'); + if ( lns ) + { + line += lns; + + if ( doc->lineLength( line ) > 0 && ( endcol < 0 || (uint)endcol >= startcol + len ) ) + { + // if ( endcol >= startcol + len ) + endcol -= (startcol + len); + uint sc = rep.length() - rep.findRev('\n') - 1; + matches += sedMagic( doc, line, find, repOld, delim, noCase, repeat, sc, endcol ); + } + } + + if (!repeat) break; + startcol+=rep.length(); + + // sanity check -- avoid infinite loops eg with %s,.*,,g ;) + uint ll = ln->length(); + if ( ! ll || startcol > ll ) + break; + } + + return matches; +} + +bool KateCommands::SedReplace::exec (Kate::View *view, const QString &cmd, QString &msg) +{ + kdDebug(13025)<<"SedReplace::execCmd( "<<cmd<<" )"<<endl; + + QRegExp delim("^[$%]?s\\s*([^\\w\\s])"); + if ( delim.search( cmd ) < 0 ) return false; + + bool fullFile=cmd[0]=='%'; + bool noCase=cmd[cmd.length()-1]=='i' || cmd[cmd.length()-2]=='i'; + bool repeat=cmd[cmd.length()-1]=='g' || cmd[cmd.length()-2]=='g'; + bool onlySelect=cmd[0]=='$'; + + QString d = delim.cap(1); + kdDebug(13025)<<"SedReplace: delimiter is '"<<d<<"'"<<endl; + + QRegExp splitter( QString("^[$%]?s\\s*") + d + "((?:[^\\\\\\" + d + "]|\\\\.)*)\\" + d +"((?:[^\\\\\\" + d + "]|\\\\.)*)\\" + d + "[ig]{0,2}$" ); + if (splitter.search(cmd)<0) return false; + + QString find=splitter.cap(1); + kdDebug(13025)<< "SedReplace: find=" << find.latin1() <<endl; + + QString replace=splitter.cap(2); + exchangeAbbrevs(replace); + kdDebug(13025)<< "SedReplace: replace=" << replace.latin1() <<endl; + + if ( find.contains("\\n") ) + { + msg = i18n("Sorry, but Kate is not able to replace newlines, yet"); + return false; + } + + KateDocument *doc = ((KateView*)view)->doc(); + if ( ! doc ) return false; + + doc->editStart(); + + int res = 0; + + if (fullFile) + { + uint numLines=doc->numLines(); + for (int line=0; (uint)line < numLines; line++) + { + res += sedMagic( doc, line, find, replace, d, !noCase, repeat ); + if ( ! repeat && res ) break; + } + } + else if (onlySelect) + { + int startline = doc->selStartLine(); + uint startcol = doc->selStartCol(); + int endcol = -1; + do { + if ( startline == doc->selEndLine() ) + endcol = doc->selEndCol(); + + res += sedMagic( doc, startline, find, replace, d, !noCase, repeat, startcol, endcol ); + + /*if ( startcol )*/ startcol = 0; + + startline++; + } while ( (int)startline <= doc->selEndLine() ); + } + else // just this line + { + int line=view->cursorLine(); + res += sedMagic(doc, line, find, replace, d, !noCase, repeat); + } + + msg = i18n("1 replacement done", "%n replacements done",res ); + + doc->editEnd(); + + return true; +} +//END SedReplace + +//BEGIN Character +bool KateCommands::Character::exec (Kate::View *view, const QString &_cmd, QString &) +{ + QString cmd = _cmd; + + // hex, octal, base 9+1 + QRegExp num("^char *(0?x[0-9A-Fa-f]{1,4}|0[0-7]{1,6}|[0-9]{1,3})$"); + if (num.search(cmd)==-1) return false; + + cmd=num.cap(1); + + // identify the base + + unsigned short int number=0; + int base=10; + if (cmd[0]=='x' || cmd.left(2)=="0x") + { + cmd.replace(QRegExp("^0?x"), ""); + base=16; + } + else if (cmd[0]=='0') + base=8; + bool ok; + number=cmd.toUShort(&ok, base); + if (!ok || number==0) return false; + if (number<=255) + { + char buf[2]; + buf[0]=(char)number; + buf[1]=0; + view->insertText(QString(buf)); + } + else + { // do the unicode thing + QChar c(number); + view->insertText(QString(&c, 1)); + } + + return true; +} +//END Character + +//BEGIN Date +bool KateCommands::Date::exec (Kate::View *view, const QString &cmd, QString &) +{ + if (cmd.left(4) != "date") + return false; + + if (QDateTime::currentDateTime().toString(cmd.mid(5, cmd.length()-5)).length() > 0) + view->insertText(QDateTime::currentDateTime().toString(cmd.mid(5, cmd.length()-5))); + else + view->insertText(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")); + + return true; +} +//END Date + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katecmds.h b/kate/part/katecmds.h new file mode 100644 index 000000000..84f7919d8 --- /dev/null +++ b/kate/part/katecmds.h @@ -0,0 +1,178 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Anders Lund <anders@alweb.dk> + Copyright (C) 2001-2004 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Charles Samuels <charles@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __KATE_CMDS_H__ +#define __KATE_CMDS_H__ + +#include "../interfaces/document.h" +#include "../interfaces/view.h" + +class KateDocument; +class KCompletion; + +namespace KateCommands +{ + +/** + * This Kate::Command provides access to a lot of the core functionality + * of kate part, settings, utilities, navigation etc. + * it needs to get a kateview pointer, it will cast the kate::view pointer + * hard to kateview + */ +class CoreCommands : public Kate::Command, public Kate::CommandExtension +{ + public: + /** + * execute command + * @param view view to use for execution + * @param cmd cmd string + * @param errorMsg error to return if no success + * @return success + */ + bool exec( class Kate::View *view, const QString &cmd, QString &errorMsg ); + + bool help( class Kate::View *, const QString &, QString & ) {return false;}; + + /** + * supported commands as prefixes + * @return prefix list + */ + QStringList cmds(); + + /** + * override completionObject from interfaces/document.h . + */ + KCompletion *completionObject( const QString &cmd, Kate::View *view ); +}; + +/** + * -- Charles Samuels <charles@kde.org> + * Support vim/sed find and replace + * s/search/replace/ find search, replace with replace on this line + * %s/search/replace/ do the same to the whole file + * s/search/replace/i do the S. and R., but case insensitively + * $s/search/replace/ do the search are replacement to the selection only + * + * $s/// is currently unsupported + **/ +class SedReplace : public Kate::Command +{ + public: + /** + * execute command + * @param view view to use for execution + * @param cmd cmd string + * @param errorMsg error to return if no success + * @return success + */ + bool exec (class Kate::View *view, const QString &cmd, QString &errorMsg); + + bool help (class Kate::View *, const QString &, QString &) { return false; }; + + /** + * supported commands as prefixes + * @return prefix list + */ + QStringList cmds () { QStringList l("s"); l << "%s" << "$s"; return l; }; + + private: + /** + * Searches one line and does the replacement in the document. + * If @p replace contains any newline characters, the reamaining part of the + * line is searched, and the @p line set to the last line number searched. + * @return the number of replacements performed. + * @param doc a pointer to the document to work on + * @param line the number of the line to search. This may be changed by the + * function, if newlines are inserted. + * @param find A regular expression pattern to use for searching + * @param replace a template for replacement. Backspaced integers are + * replaced with captured texts from the regular expression. + * @param delim the delimiter character from the command. In the replacement + * text backsplashes preceeding this character are removed. + * @param nocase parameter for matching the reqular expression. + * @param repeat If false, the search is stopped after the first match. + * @param startcol The position in the line to start the search. + * @param endcol The last collumn in the line allowed in a match. + * If it is -1, the whole line is used. + */ + static int sedMagic(KateDocument *doc, int &line, + const QString &find, const QString &replace, const QString &delim, + bool noCase, bool repeat, + uint startcol=0, int endcol=-1); +}; + +/** + * insert a unicode or ascii character + * base 9+1: 1234 + * hex: 0x1234 or x1234 + * octal: 01231 + * + * prefixed with "char:" + **/ +class Character : public Kate::Command +{ + public: + /** + * execute command + * @param view view to use for execution + * @param cmd cmd string + * @param errorMsg error to return if no success + * @return success + */ + bool exec (class Kate::View *view, const QString &cmd, QString &errorMsg); + + bool help (class Kate::View *, const QString &, QString &) { return false; }; + + /** + * supported commands as prefixes + * @return prefix list + */ + QStringList cmds () { return QStringList("char"); }; +}; + +/** + * insert the current date/time in the given format + */ +class Date : public Kate::Command +{ + public: + /** + * execute command + * @param view view to use for execution + * @param cmd cmd string + * @param errorMsg error to return if no success + * @return success + */ + bool exec (class Kate::View *view, const QString &cmd, QString &errorMsg); + + bool help (class Kate::View *, const QString &, QString &) { return false; }; + + /** + * supported commands as prefixes + * @return prefix list + */ + QStringList cmds () { return QStringList("date"); }; +}; + + +} // namespace KateCommands +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katecodecompletion.cpp b/kate/part/katecodecompletion.cpp new file mode 100644 index 000000000..bbc34dfca --- /dev/null +++ b/kate/part/katecodecompletion.cpp @@ -0,0 +1,566 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2002 John Firebaugh <jfirebaugh@kde.org> + Copyright (C) 2001 by Victor Röder <Victor_Roeder@GMX.de> + Copyright (C) 2002 by Roberto Raggi <roberto@kdevelop.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/******** Partly based on the ArgHintWidget of Qt3 by Trolltech AS *********/ +/* Trolltech doesn't mind, if we license that piece of code as LGPL, because there isn't much + * left from the desigener code */ + +#include "katecodecompletion.h" +#include "katecodecompletion.moc" + +#include "katedocument.h" +#include "kateview.h" +#include "katerenderer.h" +#include "kateconfig.h" +#include "katefont.h" + +#include <kdebug.h> + +#include <qwhatsthis.h> +#include <qvbox.h> +#include <qlistbox.h> +#include <qtimer.h> +#include <qtooltip.h> +#include <qapplication.h> +#include <qsizegrip.h> +#include <qfontmetrics.h> +#include <qlayout.h> +#include <qregexp.h> + +/** + * This class is used as the codecompletion listbox. It can be resized according to its contents, + * therfor the needed size is provided by sizeHint(); + *@short Listbox showing codecompletion + *@author Jonas B. Jacobi <j.jacobi@gmx.de> + */ +class KateCCListBox : public QListBox +{ + public: + /** + @short Create a new CCListBox + */ + KateCCListBox (QWidget* parent = 0, const char* name = 0, WFlags f = 0):QListBox(parent, name, f) + { + } + + QSize sizeHint() const + { + int count = this->count(); + int height = 20; + int tmpwidth = 8; + //FIXME the height is for some reasons at least 3 items heigh, even if there is only one item in the list + if (count > 0) + if(count < 11) + height = count * itemHeight(0); + else { + height = 10 * itemHeight(0); + tmpwidth += verticalScrollBar()->width(); + } + + int maxcount = 0, tmpcount = 0; + for (int i = 0; i < count; ++i) + if ( (tmpcount = fontMetrics().width(text(i)) ) > maxcount) + maxcount = tmpcount; + + if (maxcount > QApplication::desktop()->width()){ + tmpwidth = QApplication::desktop()->width() - 5; + height += horizontalScrollBar()->height(); + } else + tmpwidth += maxcount; + return QSize(tmpwidth,height); + + } +}; + +class KateCompletionItem : public QListBoxText +{ + public: + KateCompletionItem( QListBox* lb, KTextEditor::CompletionEntry entry ) + : QListBoxText( lb ) + , m_entry( entry ) + { + if( entry.postfix == "()" ) { // should be configurable + setText( entry.prefix + " " + entry.text + entry.postfix ); + } else { + setText( entry.prefix + " " + entry.text + " " + entry.postfix); + } + } + + KTextEditor::CompletionEntry m_entry; +}; + + +KateCodeCompletion::KateCodeCompletion( KateView* view ) + : QObject( view, "Kate Code Completion" ) + , m_view( view ) + , m_commentLabel( 0 ) +{ + m_completionPopup = new QVBox( 0, 0, WType_Popup ); + m_completionPopup->setFrameStyle( QFrame::Box | QFrame::Plain ); + m_completionPopup->setLineWidth( 1 ); + + m_completionListBox = new KateCCListBox( m_completionPopup ); + m_completionListBox->setFrameStyle( QFrame::NoFrame ); + //m_completionListBox->setCornerWidget( new QSizeGrip( m_completionListBox) ); + m_completionListBox->setFocusProxy( m_view->m_viewInternal ); + + m_completionListBox->installEventFilter( this ); + + m_completionPopup->resize(m_completionListBox->sizeHint() + QSize(2,2)); + m_completionPopup->installEventFilter( this ); + m_completionPopup->setFocusProxy( m_view->m_viewInternal ); + + m_pArgHint = new KateArgHint( m_view ); + connect( m_pArgHint, SIGNAL(argHintHidden()), + this, SIGNAL(argHintHidden()) ); + + connect( m_view, SIGNAL(cursorPositionChanged()), + this, SLOT(slotCursorPosChanged()) ); +} + +KateCodeCompletion::~KateCodeCompletion() +{ + delete m_completionPopup; +} + +bool KateCodeCompletion::codeCompletionVisible () { + return m_completionPopup->isVisible(); +} + +void KateCodeCompletion::showCompletionBox( + QValueList<KTextEditor::CompletionEntry> complList, int offset, bool casesensitive ) +{ + kdDebug(13035) << "showCompletionBox " << endl; + + if ( codeCompletionVisible() ) return; + + m_caseSensitive = casesensitive; + m_complList = complList; + m_offset = offset; + m_view->cursorPositionReal( &m_lineCursor, &m_colCursor ); + m_colCursor -= offset; + + updateBox( true ); +} + +bool KateCodeCompletion::eventFilter( QObject *o, QEvent *e ) +{ + if ( o != m_completionPopup && + o != m_completionListBox && + o != m_completionListBox->viewport() ) + return false; + + if( e->type() == QEvent::Hide ) + { + //don't use abortCompletion() as aborting here again will send abort signal + //even on successfull completion we will emit completionAborted() twice... + m_completionPopup->hide(); + delete m_commentLabel; + m_commentLabel = 0; + return false; + } + + + if ( e->type() == QEvent::MouseButtonDblClick ) { + doComplete(); + return false; + } + + if ( e->type() == QEvent::MouseButtonPress ) { + QTimer::singleShot(0, this, SLOT(showComment())); + return false; + } + + return false; +} + +void KateCodeCompletion::handleKey (QKeyEvent *e) +{ + // close completion if you move out of range + if ((e->key() == Key_Up) && (m_completionListBox->currentItem() == 0)) + { + abortCompletion(); + m_view->setFocus(); + return; + } + + // keyboard movement + if( (e->key() == Key_Up) || (e->key() == Key_Down ) || + (e->key() == Key_Home ) || (e->key() == Key_End) || + (e->key() == Key_Prior) || (e->key() == Key_Next )) + { + QTimer::singleShot(0,this,SLOT(showComment())); + QApplication::sendEvent( m_completionListBox, (QEvent*)e ); + return; + } + + // update the box + updateBox(); +} + +void KateCodeCompletion::doComplete() +{ + KateCompletionItem* item = static_cast<KateCompletionItem*>( + m_completionListBox->item(m_completionListBox->currentItem())); + + if( item == 0 ) + return; + + QString text = item->m_entry.text; + QString currentLine = m_view->currentTextLine(); + int len = m_view->cursorColumnReal() - m_colCursor; + QString currentComplText = currentLine.mid(m_colCursor,len); + QString add = text.mid(currentComplText.length()); + if( item->m_entry.postfix == "()" ) + add += "("; + + emit filterInsertString(&(item->m_entry),&add); + m_view->insertText(add); + + complete( item->m_entry ); + m_view->setFocus(); +} + +void KateCodeCompletion::abortCompletion() +{ + m_completionPopup->hide(); + delete m_commentLabel; + m_commentLabel = 0; + emit completionAborted(); +} + +void KateCodeCompletion::complete( KTextEditor::CompletionEntry entry ) +{ + m_completionPopup->hide(); + delete m_commentLabel; + m_commentLabel = 0; + emit completionDone( entry ); + emit completionDone(); +} + +void KateCodeCompletion::updateBox( bool ) +{ + if( m_colCursor > m_view->cursorColumnReal() ) { + // the cursor is too far left + kdDebug(13035) << "Aborting Codecompletion after sendEvent" << endl; + kdDebug(13035) << m_view->cursorColumnReal() << endl; + abortCompletion(); + m_view->setFocus(); + return; + } + + m_completionListBox->clear(); + + QString currentLine = m_view->currentTextLine(); + int len = m_view->cursorColumnReal() - m_colCursor; + QString currentComplText = currentLine.mid(m_colCursor,len); +/* No-one really badly wants those, or? + kdDebug(13035) << "Column: " << m_colCursor << endl; + kdDebug(13035) << "Line: " << currentLine << endl; + kdDebug(13035) << "CurrentColumn: " << m_view->cursorColumnReal() << endl; + kdDebug(13035) << "Len: " << len << endl; + kdDebug(13035) << "Text: '" << currentComplText << "'" << endl; + kdDebug(13035) << "Count: " << m_complList.count() << endl; +*/ + QValueList<KTextEditor::CompletionEntry>::Iterator it; + if( m_caseSensitive ) { + for( it = m_complList.begin(); it != m_complList.end(); ++it ) { + if( (*it).text.startsWith(currentComplText) ) { + new KateCompletionItem(m_completionListBox,*it); + } + } + } else { + currentComplText = currentComplText.upper(); + for( it = m_complList.begin(); it != m_complList.end(); ++it ) { + if( (*it).text.upper().startsWith(currentComplText) ) { + new KateCompletionItem(m_completionListBox,*it); + } + } + } + + if( m_completionListBox->count() == 0 || + ( m_completionListBox->count() == 1 && // abort if we equaled the last item + currentComplText == m_completionListBox->text(0).stripWhiteSpace() ) ) { + abortCompletion(); + m_view->setFocus(); + return; + } + + kdDebug(13035)<<"KateCodeCompletion::updateBox: Resizing widget"<<endl; + m_completionPopup->resize(m_completionListBox->sizeHint() + QSize(2,2)); + QPoint p = m_view->mapToGlobal( m_view->cursorCoordinates() ); + int x = p.x(); + int y = p.y() ; + if ( y + m_completionPopup->height() + m_view->renderer()->config()->fontMetrics( )->height() > QApplication::desktop()->height() ) + y -= (m_completionPopup->height() ); + else + y += m_view->renderer()->config()->fontMetrics( )->height(); + + if (x + m_completionPopup->width() > QApplication::desktop()->width()) + x = QApplication::desktop()->width() - m_completionPopup->width(); + + m_completionPopup->move( QPoint(x,y) ); + + m_completionListBox->setCurrentItem( 0 ); + m_completionListBox->setSelected( 0, true ); + m_completionListBox->setFocus(); + m_completionPopup->show(); + + QTimer::singleShot(0,this,SLOT(showComment())); +} + +void KateCodeCompletion::showArgHint ( QStringList functionList, const QString& strWrapping, const QString& strDelimiter ) +{ + unsigned int line, col; + m_view->cursorPositionReal( &line, &col ); + m_pArgHint->reset( line, col ); + m_pArgHint->setArgMarkInfos( strWrapping, strDelimiter ); + + int nNum = 0; + QStringList::Iterator end(functionList.end()); + for( QStringList::Iterator it = functionList.begin(); it != end; ++it ) + { + kdDebug(13035) << "Insert function text: " << *it << endl; + + m_pArgHint->addFunction( nNum, ( *it ) ); + + nNum++; + } + + m_pArgHint->move(m_view->mapToGlobal(m_view->cursorCoordinates() + QPoint(0,m_view->renderer()->config()->fontMetrics( )->height())) ); + m_pArgHint->show(); +} + +void KateCodeCompletion::slotCursorPosChanged() +{ + m_pArgHint->cursorPositionChanged ( m_view, m_view->cursorLine(), m_view->cursorColumnReal() ); +} + +void KateCodeCompletion::showComment() +{ + if (!m_completionPopup->isVisible()) + return; + + KateCompletionItem* item = static_cast<KateCompletionItem*>(m_completionListBox->item(m_completionListBox->currentItem())); + + if( !item ) + return; + + if( item->m_entry.comment.isEmpty() ) + return; + + delete m_commentLabel; + m_commentLabel = new KateCodeCompletionCommentLabel( 0, item->m_entry.comment ); + m_commentLabel->setFont(QToolTip::font()); + m_commentLabel->setPalette(QToolTip::palette()); + + QPoint rightPoint = m_completionPopup->mapToGlobal(QPoint(m_completionPopup->width(),0)); + QPoint leftPoint = m_completionPopup->mapToGlobal(QPoint(0,0)); + QRect screen = QApplication::desktop()->screenGeometry ( m_commentLabel ); + QPoint finalPoint; + if (rightPoint.x()+m_commentLabel->width() > screen.x() + screen.width()) + finalPoint.setX(leftPoint.x()-m_commentLabel->width()); + else + finalPoint.setX(rightPoint.x()); + + m_completionListBox->ensureCurrentVisible(); + + finalPoint.setY( + m_completionListBox->viewport()->mapToGlobal(m_completionListBox->itemRect( + m_completionListBox->item(m_completionListBox->currentItem())).topLeft()).y()); + + m_commentLabel->move(finalPoint); + m_commentLabel->show(); +} + +KateArgHint::KateArgHint( KateView* parent, const char* name ) + : QFrame( parent, name, WType_Popup ) +{ + setBackgroundColor( black ); + setPaletteForegroundColor( Qt::black ); + + labelDict.setAutoDelete( true ); + layout = new QVBoxLayout( this, 1, 2 ); + layout->setAutoAdd( true ); + editorView = parent; + + m_markCurrentFunction = true; + + setFocusPolicy( StrongFocus ); + setFocusProxy( parent ); + + reset( -1, -1 ); +} + +KateArgHint::~KateArgHint() +{ +} + +void KateArgHint::setArgMarkInfos( const QString& wrapping, const QString& delimiter ) +{ + m_wrapping = wrapping; + m_delimiter = delimiter; + m_markCurrentFunction = true; +} + +void KateArgHint::reset( int line, int col ) +{ + m_functionMap.clear(); + m_currentFunction = -1; + labelDict.clear(); + + m_currentLine = line; + m_currentCol = col - 1; +} + +void KateArgHint::slotDone(bool completed) +{ + hide(); + + m_currentLine = m_currentCol = -1; + + emit argHintHidden(); + if (completed) + emit argHintCompleted(); + else + emit argHintAborted(); +} + +void KateArgHint::cursorPositionChanged( KateView* view, int line, int col ) +{ + if( m_currentCol == -1 || m_currentLine == -1 ){ + slotDone(false); + return; + } + + int nCountDelimiter = 0; + int count = 0; + + QString currentTextLine = view->doc()->textLine( line ); + QString text = currentTextLine.mid( m_currentCol, col - m_currentCol ); + QRegExp strconst_rx( "\"[^\"]*\"" ); + QRegExp chrconst_rx( "'[^']*'" ); + + text = text + .replace( strconst_rx, "\"\"" ) + .replace( chrconst_rx, "''" ); + + int index = 0; + while( index < (int)text.length() ){ + if( text[index] == m_wrapping[0] ){ + ++count; + } else if( text[index] == m_wrapping[1] ){ + --count; + } else if( count > 0 && text[index] == m_delimiter[0] ){ + ++nCountDelimiter; + } + ++index; + } + + if( (m_currentLine > 0 && m_currentLine != line) || (m_currentLine < col) || (count == 0) ){ + slotDone(count == 0); + return; + } + + // setCurArg ( nCountDelimiter + 1 ); + +} + +void KateArgHint::addFunction( int id, const QString& prot ) +{ + m_functionMap[ id ] = prot; + QLabel* label = new QLabel( prot.stripWhiteSpace().simplifyWhiteSpace(), this ); + label->setBackgroundColor( QColor(255, 255, 238) ); + label->show(); + labelDict.insert( id, label ); + + if( m_currentFunction < 0 ) + setCurrentFunction( id ); +} + +void KateArgHint::setCurrentFunction( int currentFunction ) +{ + if( m_currentFunction != currentFunction ){ + + if( currentFunction < 0 ) + currentFunction = (int)m_functionMap.size() - 1; + + if( currentFunction > (int)m_functionMap.size()-1 ) + currentFunction = 0; + + if( m_markCurrentFunction && m_currentFunction >= 0 ){ + QLabel* label = labelDict[ m_currentFunction ]; + label->setFont( font() ); + } + + m_currentFunction = currentFunction; + + if( m_markCurrentFunction ){ + QLabel* label = labelDict[ currentFunction ]; + QFont fnt( font() ); + fnt.setBold( true ); + label->setFont( fnt ); + } + + adjustSize(); + } +} + +void KateArgHint::show() +{ + QFrame::show(); + adjustSize(); +} + +bool KateArgHint::eventFilter( QObject*, QEvent* e ) +{ + if( isVisible() && e->type() == QEvent::KeyPress ){ + QKeyEvent* ke = static_cast<QKeyEvent*>( e ); + if( (ke->state() & ControlButton) && ke->key() == Key_Left ){ + setCurrentFunction( currentFunction() - 1 ); + ke->accept(); + return true; + } else if( ke->key() == Key_Escape ){ + slotDone(false); + return false; + } else if( (ke->state() & ControlButton) && ke->key() == Key_Right ){ + setCurrentFunction( currentFunction() + 1 ); + ke->accept(); + return true; + } + } + + return false; +} + +void KateArgHint::adjustSize( ) +{ + QRect screen = QApplication::desktop()->screenGeometry( pos() ); + + QFrame::adjustSize(); + if( width() > screen.width() ) + resize( screen.width(), height() ); + + if( x() + width() > screen.x() + screen.width() ) + move( screen.x() + screen.width() - width(), y() ); +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katecodecompletion.h b/kate/part/katecodecompletion.h new file mode 100644 index 000000000..81279d929 --- /dev/null +++ b/kate/part/katecodecompletion.h @@ -0,0 +1,164 @@ +/* This file is part of the KDE libraries + + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2002 John Firebaugh <jfirebaugh@kde.org> + Copyright (C) 2001 by Victor Röder <Victor_Roeder@GMX.de> + Copyright (C) 2002 by Roberto Raggi <roberto@kdevelop.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/******** Partly based on the ArgHintWidget of Qt3 by Trolltech AS *********/ +/* Trolltech doesn't mind, if we license that piece of code as LGPL, because there isn't much + * left from the desigener code */ + + +#ifndef __KateCodeCompletion_H__ +#define __KateCodeCompletion_H__ + +#include <ktexteditor/codecompletioninterface.h> + +#include <qvaluelist.h> +#include <qstringlist.h> +#include <qlabel.h> +#include <qframe.h> +#include <qmap.h> +#include <qintdict.h> + +class KateView; +class KateArgHint; +class KateCCListBox; + +class QLayout; +class QVBox; + +class KateCodeCompletionCommentLabel : public QLabel +{ + Q_OBJECT + + public: + KateCodeCompletionCommentLabel( QWidget* parent, const QString& text) : QLabel( parent, "toolTipTip", + WStyle_StaysOnTop | WStyle_Customize | WStyle_NoBorder | WStyle_Tool | WX11BypassWM ) + { + setMargin(1); + setIndent(0); + setAutoMask( false ); + setFrameStyle( QFrame::Plain | QFrame::Box ); + setLineWidth( 1 ); + setAlignment( AlignAuto | AlignTop ); + polish(); + setText(text); + adjustSize(); + } +}; + +class KateCodeCompletion : public QObject +{ + Q_OBJECT + + friend class KateViewInternal; + + public: + KateCodeCompletion(KateView *view); + ~KateCodeCompletion(); + + bool codeCompletionVisible (); + + void showArgHint( + QStringList functionList, const QString& strWrapping, const QString& strDelimiter ); + void showCompletionBox( + QValueList<KTextEditor::CompletionEntry> entries, int offset = 0, bool casesensitive = true ); + bool eventFilter( QObject* o, QEvent* e ); + + void handleKey (QKeyEvent *e); + + public slots: + void slotCursorPosChanged(); + void showComment(); + void updateBox () { updateBox(false); } + + signals: + void completionAborted(); + void completionDone(); + void argHintHidden(); + void completionDone(KTextEditor::CompletionEntry); + void filterInsertString(KTextEditor::CompletionEntry*,QString *); + + private: + void doComplete(); + void abortCompletion(); + void complete( KTextEditor::CompletionEntry ); + void updateBox( bool newCoordinate ); + + KateArgHint* m_pArgHint; + KateView* m_view; + QVBox* m_completionPopup; + KateCCListBox* m_completionListBox; + QValueList<KTextEditor::CompletionEntry> m_complList; + uint m_lineCursor; + uint m_colCursor; + int m_offset; + bool m_caseSensitive; + KateCodeCompletionCommentLabel* m_commentLabel; +}; + +class KateArgHint: public QFrame +{ + Q_OBJECT + + public: + KateArgHint( KateView* =0, const char* =0 ); + virtual ~KateArgHint(); + + virtual void setCurrentFunction( int ); + virtual int currentFunction() const { return m_currentFunction; } + + void setArgMarkInfos( const QString&, const QString& ); + + virtual void addFunction( int, const QString& ); + QString functionAt( int id ) const { return m_functionMap[ id ]; } + + virtual void show(); + virtual void adjustSize(); + virtual bool eventFilter( QObject*, QEvent* ); + + signals: + void argHintHidden(); + void argHintCompleted(); + void argHintAborted(); + + public slots: + virtual void reset( int, int ); + virtual void cursorPositionChanged( KateView*, int, int ); + + private slots: + void slotDone(bool completed); + + private: + QMap<int, QString> m_functionMap; + int m_currentFunction; + QString m_wrapping; + QString m_delimiter; + bool m_markCurrentFunction; + int m_currentLine; + int m_currentCol; + KateView* editorView; + QIntDict<QLabel> labelDict; + QLayout* layout; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katecodefoldinghelpers.cpp b/kate/part/katecodefoldinghelpers.cpp new file mode 100644 index 000000000..49090820b --- /dev/null +++ b/kate/part/katecodefoldinghelpers.cpp @@ -0,0 +1,1662 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002 Joseph Wenninger <jowenn@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "katecodefoldinghelpers.h" +#include "katecodefoldinghelpers.moc" + +#include "katebuffer.h" +#include "katecursor.h" +#include <kdebug.h> + +#include <qstring.h> + +#define JW_DEBUG 0 + +bool KateCodeFoldingTree::trueVal = true; + +KateCodeFoldingNode::KateCodeFoldingNode() : + parentNode(0), + startLineRel(0), + endLineRel(0), + startCol(0), + endCol(0), + startLineValid(false), + endLineValid(false), + type(0), + visible(true), + deleteOpening(false), + deleteEnding(false) +{ +}//the endline fields should be initialised to not valid + +KateCodeFoldingNode::KateCodeFoldingNode(KateCodeFoldingNode *par, signed char typ, unsigned int sLRel): + parentNode(par), + startLineRel(sLRel), + endLineRel(10000), + startCol(0), + endCol(0), + startLineValid(true), + endLineValid(false), + type(typ), + visible(true), + deleteOpening(false), + deleteEnding(false) +{ +}//the endline fields should be initialised to not valid + +KateCodeFoldingNode::~KateCodeFoldingNode() +{ + // delete all child nodes + clearChildren (); +} + +bool KateCodeFoldingNode::getBegin(KateCodeFoldingTree *tree, KateTextCursor* begin) { + if (!startLineValid) return false; + unsigned int line=startLineRel; + for (KateCodeFoldingNode *n=parentNode;n;n=n->parentNode) + line+=n->startLineRel; + + tree->m_buffer->codeFoldingColumnUpdate(line); + begin->setLine(line); + begin->setCol(startCol); + + return true; +} + +bool KateCodeFoldingNode::getEnd(KateCodeFoldingTree *tree, KateTextCursor *end) { + if (!endLineValid) return false; + unsigned int line=startLineRel+endLineRel; + for (KateCodeFoldingNode *n=parentNode;n;n=n->parentNode) + line+=n->startLineRel; + + tree->m_buffer->codeFoldingColumnUpdate(line); + end->setLine(line); + end->setCol(endCol); + + return true; +} + +int KateCodeFoldingNode::cmpPos(KateCodeFoldingTree *tree, uint line,uint col) { + KateTextCursor cur(line,col); + KateTextCursor start,end; + kdDebug(13000)<<"KateCodeFoldingNode::cmpPos (1)"<<endl; + bool startValid=getBegin(tree, &start); + kdDebug(13000)<<"KateCodeFoldingNode::cmpPos (2)"<<endl; + bool endValid=getEnd(tree, &end); + kdDebug(13000)<<"KateCodeFoldingNode::cmpPos (3)"<<endl; + if ((!endValid) && startValid) { + return ((start>cur)?-1:0); + } + if ((!startValid) && endValid) { + return ((cur>end)?1:0); + } + //here both have to be valid, both invalid must not happen + Q_ASSERT(startValid && endValid); + return ( (cur<start)?(-1):( (cur>end) ? 1:0)); +} + +void KateCodeFoldingNode::insertChild (uint index, KateCodeFoldingNode *node) +{ + uint s = m_children.size (); + + if (index > s) + return; + + m_children.resize (++s); + + for (uint i=s-1; i > index; --i) + m_children[i] = m_children[i-1]; + + m_children[index] = node; +} + +KateCodeFoldingNode *KateCodeFoldingNode::takeChild (uint index) +{ + uint s = m_children.size (); + + if (index >= s) + return 0; + + KateCodeFoldingNode *n = m_children[index]; + + for (uint i=index; (i+1) < s; ++i) + m_children[i] = m_children[i+1]; + + m_children.resize (s-1); + + return n; +} + +void KateCodeFoldingNode::clearChildren () +{ + for (uint i=0; i < m_children.size(); ++i) + delete m_children[i]; + + m_children.resize (0); +} + +KateCodeFoldingTree::KateCodeFoldingTree(KateBuffer *buffer): QObject(buffer), m_buffer (buffer) +{ + clear(); +} + +void KateCodeFoldingTree::fixRoot(int endLRel) +{ + m_root.endLineRel = endLRel; +} + +void KateCodeFoldingTree::clear() +{ + m_root.clearChildren(); + + // initialize the root "special" node + m_root.startLineValid=true; + m_root.endLineValid=true; // temporary, should be false; + m_root.endLineRel=1; // temporary; + + hiddenLinesCountCacheValid=false; + lineMapping.setAutoDelete(true); + hiddenLines.clear(); + lineMapping.clear(); + nodesForLine.clear(); + markedForDeleting.clear(); + dontIgnoreUnchangedLines.clear(); +} + +KateCodeFoldingTree::~KateCodeFoldingTree() +{ +} + +bool KateCodeFoldingTree::isTopLevel(unsigned int line) +{ + if (m_root.noChildren()) + return true; // no childs + + // look if a given lines belongs to a sub node + for ( uint i=0; i < m_root.childCount(); ++i ) + { + KateCodeFoldingNode *node = m_root.child(i); + + if ((node->startLineRel<=line) && (line<=node->startLineRel+node->endLineRel)) + return false; // the line is within the range of a subnode -> return toplevel=false + } + + return true; // the root node is the only node containing the given line, return toplevel=true +} + +void KateCodeFoldingTree::getLineInfo(KateLineInfo *info, unsigned int line) +{ + // Initialze the returned structure, this will also be returned if the root node has no child nodes + // or the line is not within a childnode's range. + info->topLevel = true; + info->startsVisibleBlock = false; + info->startsInVisibleBlock = false; + info->endsBlock = false; + info->invalidBlockEnd = false; + + if (m_root.noChildren()) + return; + + //let's look for some information + for ( uint i=0; i < m_root.childCount(); ++i ) + { + KateCodeFoldingNode *node = m_root.child(i); + + if ((node->startLineRel<=line) && (line<=node->startLineRel+node->endLineRel)) // we found a node, which contains the given line -> do a complete lookup + { + info->topLevel = false; //we are definitly not toplevel + findAllNodesOpenedOrClosedAt(line); //lookup all nodes, which start or and at the given line + + for ( KateCodeFoldingNode *node = nodesForLine.first(); node; node = nodesForLine.next() ) + { + uint startLine = getStartLine(node); + + // type<0 means, that a region has been closed, but not opened + // eg. parantheses missmatch + if (node->type < 0) + info->invalidBlockEnd=true; + else + { + if (startLine != line) // does the region we look at not start at the given line + info->endsBlock = true; // than it has to be an ending + else + { + // The line starts a new region, now determine, if it's a visible or a hidden region + if (node->visible) + info->startsVisibleBlock=true; + else + info->startsInVisibleBlock=true; + } + } + } + + return; + } + } + + return; +} + + +KateCodeFoldingNode *KateCodeFoldingTree::findNodeForLine(unsigned int line) +{ + if (m_root.noChildren()) // does we have child list + nodes ? + return &m_root; + + // lets look, if given line is within a subnode range, and then return the deepest one. + for ( uint i=0; i < m_root.childCount(); ++i ) + { + KateCodeFoldingNode *node = m_root.child(i); + + if ((node->startLineRel<=line) && (line<=node->startLineRel+node->endLineRel)) + { + // a region surounds the line, look in the next deeper hierarchy step + return findNodeForLineDescending(node,line,0); + } + } + + return &m_root; +} + + +KateCodeFoldingNode *KateCodeFoldingTree::findNodeForLineDescending ( KateCodeFoldingNode *node, + unsigned int line, unsigned int offset, bool oneStepOnly ) +{ + if (node->noChildren()) + return node; + + // calculate the offset, between a subnodes real start line and its relative start + offset += node->startLineRel; + + for ( uint i=0; i < node->childCount(); ++i ) + { + KateCodeFoldingNode *subNode = node->child(i); + + if ((subNode->startLineRel+offset<=line) && (line<=subNode->endLineRel+subNode->startLineRel+offset)) //warning fix me for invalid ends + { + // a subnode contains the line. + // if oneStepOnly is true, we don't want to search for the deepest node, just return the found one + + if (oneStepOnly) + return subNode; + else + return findNodeForLineDescending (subNode,line,offset); // look into the next deeper hierarchy step + } + } + + return node; // the current node has no sub nodes, or the line couldn'te be found within a subregion +} + +KateCodeFoldingNode *KateCodeFoldingTree::findNodeForPosition(unsigned int line, unsigned int column) +{ + KateCodeFoldingNode *node=findNodeForLine(line); + + if (node==&m_root) return &m_root; + + kdDebug(13000)<<"initial cmpPos"<<endl; + + KateCodeFoldingNode *tmp; + int leq=node->cmpPos(this, line,column); + while (true) { + switch (leq) { + case 0: { + if (node->noChildren()) + return node; + else + { + tmp=node; + for ( uint i=0; i < node->childCount(); ++i ) + { + KateCodeFoldingNode *subNode = node->child(i); + kdDebug(13000)<<"cmdPos(case0):calling"<<endl; + leq=subNode->cmpPos(this, line,column); + kdDebug(13000)<<"cmdPos(case0):returned"<<endl; + if (leq==0) { + tmp=subNode; + break; + } else if (leq==-1) break; + } + if (tmp!=node) node=tmp; else return node; + } + break; + } + //this could be optimized a littlebit + case -1: + case 1: { + if (!(node->parentNode)) return &m_root; + kdDebug(13000)<<"current node type"<<node->type<<endl; + node=node->parentNode; + kdDebug(13000)<<"cmdPos(case-1/1):calling:"<<node<<endl; + leq=node->cmpPos(this, line,column); + kdDebug(13000)<<"cmdPos(case-1/1):returned"<<endl; + break; + } + } + + } + Q_ASSERT(false); + return &m_root; +} + +void KateCodeFoldingTree::debugDump() +{ + //dump all nodes for debugging + kdDebug(13000)<<"The parsed region/block tree for code folding"<<endl; + dumpNode(&m_root, ""); +} + +void KateCodeFoldingTree::dumpNode(KateCodeFoldingNode *node, const QString &prefix) +{ + //output node properties + kdDebug(13000)<<prefix<<QString("Type: %1, startLineValid %2, startLineRel %3, endLineValid %4, endLineRel %5, visible %6"). + arg(node->type).arg(node->startLineValid).arg(node->startLineRel).arg(node->endLineValid). + arg(node->endLineRel).arg(node->visible)<<endl; + + //output child node properties recursive + if (node->noChildren()) + return; + + QString newprefix(prefix + " "); + for ( uint i=0; i < node->childCount(); ++i ) + dumpNode (node->child(i),newprefix); +} + +/* + That's one of the most important functions ;) +*/ +void KateCodeFoldingTree::updateLine(unsigned int line, + QMemArray<uint> *regionChanges, bool *updated,bool changed,bool colsChanged) +{ + if ( (!changed) || colsChanged) + { + if (dontIgnoreUnchangedLines.isEmpty()) + return; + + if (dontIgnoreUnchangedLines[line]) + dontIgnoreUnchangedLines.remove(line); + else + return; + } + + something_changed = false; + + findAndMarkAllNodesforRemovalOpenedOrClosedAt(line); + + if (regionChanges->isEmpty()) + { + // KateCodeFoldingNode *node=findNodeForLine(line); + // if (node->type!=0) + // if (getStartLine(node)+node->endLineRel==line) removeEnding(node,line); + } + else + { + for (unsigned int i=0;i<regionChanges->size() / 4;i++) + { + signed char tmp=(*regionChanges)[regionChanges->size()-2-i*2]; + uint tmppos=(*regionChanges)[regionChanges->size()-1-i*2]; + (*regionChanges)[regionChanges->size()-2-i*2]=(*regionChanges)[i*2]; + (*regionChanges)[regionChanges->size()-1-i*2]=(*regionChanges)[i*2+1]; + (*regionChanges)[i*2]=tmp; + (*regionChanges)[i*2+1]=tmppos; + } + + + signed char data= (*regionChanges)[regionChanges->size()-2]; + uint charPos=(*regionChanges)[regionChanges->size()-1]; + regionChanges->resize (regionChanges->size()-2); + + int insertPos=-1; + KateCodeFoldingNode *node = findNodeForLine(line); + + if (data<0) + { + // if (insertPos==-1) + { + unsigned int tmpLine=line-getStartLine(node); + + for ( uint i=0; i < node->childCount(); ++i ) + { + if (node->child(i)->startLineRel >= tmpLine) + { + insertPos=i; + break; + } + } + } + } + else + { + for (; (node->parentNode) && (getStartLine(node->parentNode)==line) && (node->parentNode->type!=0); node=node->parentNode); + + if ((getStartLine(node)==line) && (node->type!=0)) + { + insertPos=node->parentNode->findChild(node); + node = node->parentNode; + } + else + { + for ( uint i=0; i < node->childCount(); ++i ) + { + if (getStartLine(node->child(i))>=line) + { + insertPos=i; + break; + } + } + } + } + + do + { + if (data<0) + { + if (correctEndings(data,node,line,charPos,insertPos)) + { + insertPos=node->parentNode->findChild(node)+1; + node=node->parentNode; + } + else + { + if (insertPos!=-1) insertPos++; + } + } + else + { + int startLine=getStartLine(node); + if ((insertPos==-1) || (insertPos>=(int)node->childCount())) + { + KateCodeFoldingNode *newNode = new KateCodeFoldingNode (node,data,line-startLine); + something_changed = true; + node->appendChild(newNode); + addOpening(newNode, data, regionChanges, line,charPos); + insertPos = node->findChild(newNode)+1; + } + else + { + if (node->child(insertPos)->startLineRel == line-startLine) + { + addOpening(node->child(insertPos), data, regionChanges, line,charPos); + insertPos++; + } + else + { +// kdDebug(13000)<<"ADDING NODE "<<endl; + KateCodeFoldingNode *newNode = new KateCodeFoldingNode (node,data,line-startLine); + something_changed = true; + node->insertChild(insertPos, newNode); + addOpening(newNode, data, regionChanges, line,charPos); + insertPos++; + } + } + } + + if (regionChanges->isEmpty()) + data = 0; + else + { + data = (*regionChanges)[regionChanges->size()-2]; + charPos=(*regionChanges)[regionChanges->size()-1]; + regionChanges->resize (regionChanges->size()-2); + } + } while (data!=0); + } + + cleanupUnneededNodes(line); +// if (something_changed) emit regionBeginEndAddedRemoved(line); + (*updated) = something_changed; +} + + +bool KateCodeFoldingTree::removeOpening(KateCodeFoldingNode *node,unsigned int line) +{ + signed char type; + if ((type=node->type) == 0) + { + dontDeleteOpening(node); + dontDeleteEnding(node); + return false; + } + + if (!node->visible) + { + toggleRegionVisibility(getStartLine(node)); + } + + KateCodeFoldingNode *parent = node->parentNode; + int mypos = parent->findChild(node); + + if (mypos > -1) + { + //move childnodes() up + for(; node->childCount()>0 ;) + { + KateCodeFoldingNode *tmp; + parent->insertChild(mypos, tmp=node->takeChild(0)); + tmp->parentNode = parent; + tmp->startLineRel += node->startLineRel; + mypos++; + } + + // remove the node + //mypos = parent->findChild(node); + bool endLineValid = node->endLineValid; + int endLineRel = node->endLineRel; + uint endCol=node->endCol; + + // removes + deletes + KateCodeFoldingNode *child = parent->takeChild(mypos); + markedForDeleting.removeRef(child); + delete child; + + if ((type>0) && (endLineValid)) + correctEndings(-type, parent, line+endLineRel/*+1*/,endCol, mypos); // why the hell did I add a +1 here ? + } + + return true; +} + +bool KateCodeFoldingTree::removeEnding(KateCodeFoldingNode *node,unsigned int /* line */) +{ + KateCodeFoldingNode *parent = node->parentNode; + + if (!parent) + return false; + + if (node->type == 0) + return false; + + if (node->type < 0) + { + // removes + deletes + int i = parent->findChild (node); + if (i >= 0) + { + KateCodeFoldingNode *child = parent->takeChild(i); + markedForDeleting.removeRef(child); + delete child; + } + + return true; + } + + int mypos = parent->findChild(node); + int count = parent->childCount(); + + for (int i=mypos+1; i<count; i++) + { + if (parent->child(i)->type == -node->type) + { + node->endLineValid = true; + node->endLineRel = parent->child(i)->startLineRel - node->startLineRel; + + KateCodeFoldingNode *child = parent->takeChild(i); + markedForDeleting.removeRef(child); + delete child; + + count = i-mypos-1; + if (count > 0) + { + for (int i=0; i<count; i++) + { + KateCodeFoldingNode *tmp = parent->takeChild(mypos+1); + tmp->startLineRel -= node->startLineRel; + tmp->parentNode = node; //should help 16.04.2002 + node->appendChild(tmp); + } + } + return false; + } + } + + if ( (parent->type == node->type) || /*temporary fix */ (!parent->parentNode)) + { + for (int i=mypos+1; i<(int)parent->childCount(); i++) + { + KateCodeFoldingNode *tmp = parent->takeChild(mypos+1); + tmp->startLineRel -= node->startLineRel; + tmp->parentNode = node; // SHOULD HELP 16.04.2002 + node->appendChild(tmp); + } + + // this should fix the bug of wrongly closed nodes + if (!parent->parentNode) + node->endLineValid=false; + else + node->endLineValid = parent->endLineValid; + + node->endLineRel = parent->endLineRel-node->startLineRel; + + if (node->endLineValid) + return removeEnding(parent, getStartLine(parent)+parent->endLineRel); + + return false; + } + + node->endLineValid = false; + node->endLineRel = parent->endLineRel - node->startLineRel; + + return false; +} + + +bool KateCodeFoldingTree::correctEndings(signed char data, KateCodeFoldingNode *node,unsigned int line,unsigned int endCol,int insertPos) +{ +// if (node->type==0) {kdError()<<"correct Ending should never be called with the root node"<<endl; return true;} + uint startLine = getStartLine(node); + if (data != -node->type) + { +#if JW_DEBUG + kdDebug(13000)<<"data!=-node->type (correctEndings)"<<endl; +#endif + //invalid close -> add to unopend list + dontDeleteEnding(node); + if (data == node->type) { + node->endCol=endCol; + return false; + } + KateCodeFoldingNode *newNode = new KateCodeFoldingNode (node,data,line-startLine); + something_changed = true; + newNode->startLineValid = false; + newNode->endLineValid = true; + newNode->endLineRel = 0; + newNode->endCol=endCol; + + if ((insertPos==-1) || (insertPos==(int)node->childCount())) + node->appendChild(newNode); + else + node->insertChild(insertPos,newNode); + + // find correct position + return false; + } + else + { + something_changed = true; + dontDeleteEnding(node); + + // valid closing region + if (!node->endLineValid) + { + node->endLineValid = true; + node->endLineRel = line - startLine; + node->endCol=endCol; + //moving + + moveSubNodesUp(node); + } + else + { +#if JW_DEBUG + kdDebug(13000)<<"Closing a node which had already a valid end"<<endl; +#endif + // block has already an ending + if (startLine+node->endLineRel == line) + { + node->endCol=endCol; + // we won, just skip +#if JW_DEBUG + kdDebug(13000)<< "We won, just skipping (correctEndings)"<<endl; +#endif + } + else + { + int bakEndLine = node->endLineRel+startLine; + uint bakEndCol = node->endCol; + node->endLineRel = line-startLine; + node->endCol=endCol; + +#if JW_DEBUG + kdDebug(13000)<< "reclosed node had childnodes()"<<endl; + kdDebug(13000)<<"It could be, that childnodes() need to be moved up"<<endl; +#endif + moveSubNodesUp(node); + + if (node->parentNode) + { + correctEndings(data,node->parentNode,bakEndLine, bakEndCol,node->parentNode->findChild(node)+1); // ???? + } + else + { + //add to unopened list (bakEndLine) + } + } + } + } + return true; +} + +void KateCodeFoldingTree::moveSubNodesUp(KateCodeFoldingNode *node) +{ + int mypos = node->parentNode->findChild(node); + int removepos=-1; + int count = node->childCount(); + for (int i=0; i<count; i++) + if (node->child(i)->startLineRel >= node->endLineRel) + { + removepos=i; + break; + } +#if JW_DEBUG + kdDebug(13000)<<QString("remove pos: %1").arg(removepos)<<endl; +#endif + if (removepos>-1) + { +#if JW_DEBUG + kdDebug(13000)<<"Children need to be moved"<<endl; +#endif + KateCodeFoldingNode *moveNode; + if (mypos == (int)node->parentNode->childCount()-1) + { + while (removepos<(int)node->childCount()) + { + node->parentNode->appendChild(moveNode=node->takeChild(removepos)); + moveNode->parentNode = node->parentNode; + moveNode->startLineRel += node->startLineRel; + } + } + else + { + int insertPos=mypos; + while (removepos < (int)node->childCount()) + { + insertPos++; + node->parentNode->insertChild(insertPos, moveNode=node->takeChild(removepos)); + moveNode->parentNode = node->parentNode; // That should solve a crash + moveNode->startLineRel += node->startLineRel; + } + } + } + +} + + + +void KateCodeFoldingTree::addOpening(KateCodeFoldingNode *node,signed char nType, QMemArray<uint>* list,unsigned int line,unsigned int charPos) +{ + uint startLine = getStartLine(node); + if ((startLine==line) && (node->type!=0)) + { +#if JW_DEBUG + kdDebug(13000)<<"startLine equals line"<<endl; +#endif + if (nType == node->type) + { +#if JW_DEBUG + kdDebug(13000)<<"Node exists"<<endl; +#endif + node->deleteOpening = false; + node->startCol=charPos; + KateCodeFoldingNode *parent = node->parentNode; + + if (!node->endLineValid) + { + int current = parent->findChild(node); + int count = parent->childCount()-(current+1); + node->endLineRel = parent->endLineRel - node->startLineRel; + +// EXPERIMENTAL TEST BEGIN +// move this afte the test for unopened, but closed regions within the parent node, or if there are no siblings, bubble up + if (parent) + if (parent->type == node->type) + { + if (parent->endLineValid) + { + removeEnding(parent, line); + node->endLineValid = true; + } + } + +// EXPERIMENTAL TEST BEGIN + + if (current != (int)parent->childCount()-1) + { + //search for an unopened but closed region, even if the parent is of the same type +#ifdef __GNUC__ +#warning "FIXME: why does this seem to work?" +#endif +// if (node->type != parent->type) + { + for (int i=current+1; i<(int)parent->childCount(); i++) + { + if (parent->child(i)->type == -node->type) + { + count = (i-current-1); + node->endLineValid = true; + node->endLineRel = getStartLine(parent->child(i))-line; + node->endCol = parent->child(i)->endCol; + KateCodeFoldingNode *child = parent->takeChild(i); + markedForDeleting.removeRef( child ); + delete child; + break; + } + } + } +// else +// { +// parent->endLineValid = false; +// parent->endLineRel = 20000; +// } + + if (count>0) + { + for (int i=0;i<count;i++) + { + KateCodeFoldingNode *tmp; + node->appendChild(tmp=parent->takeChild(current+1)); + tmp->startLineRel -= node->startLineRel; + tmp->parentNode = node; + } + } + } + + } + + addOpening_further_iterations(node, nType, list, line, 0, startLine,node->startCol); + + } //else ohoh, much work to do same line, but other region type + } + else + { // create a new region + KateCodeFoldingNode *newNode = new KateCodeFoldingNode (node,nType,line-startLine); + something_changed = true; + + int insert_position=-1; + for (int i=0; i<(int)node->childCount(); i++) + { + if (startLine+node->child(i)->startLineRel > line) + { + insert_position=i; + break; + } + } + + int current; + if (insert_position==-1) + { + node->appendChild(newNode); + current = node->childCount()-1; + } + else + { + node->insertChild(insert_position, newNode); + current = insert_position; + } + +// if (node->type==newNode->type) +// { +// newNode->endLineValid=true; +// node->endLineValid=false; +// newNode->endLineRel=node->endLineRel-newNode->startLineRel; +// node->endLineRel=20000; //FIXME + + int count = node->childCount() - (current+1); + newNode->endLineRel -= newNode->startLineRel; + if (current != (int)node->childCount()-1) + { + if (node->type != newNode->type) + { + for (int i=current+1; i<(int)node->childCount(); i++) + { + if (node->child(i)->type == -newNode->type) + { + count = node->childCount() - i - 1; + newNode->endLineValid = true; + newNode->endLineRel = line - getStartLine(node->child(i)); + KateCodeFoldingNode *child = node->takeChild(i); + markedForDeleting.removeRef( child ); + delete child; + break; + } + } + } + else + { + node->endLineValid = false; + node->endLineRel = 10000; + } + if (count > 0) + { + for (int i=0;i<count;i++) + { + KateCodeFoldingNode *tmp; + newNode->appendChild(tmp=node->takeChild(current+1)); + tmp->parentNode=newNode; + } + } +// } + } + + addOpening(newNode, nType, list, line,charPos); + + addOpening_further_iterations(node, node->type, list, line, current, startLine,node->startCol); + } +} + + +void KateCodeFoldingTree::addOpening_further_iterations(KateCodeFoldingNode *node,signed char /* nType */, QMemArray<uint>* + list,unsigned int line,int current, unsigned int startLine,unsigned int charPos) +{ + while (!(list->isEmpty())) + { + if (list->isEmpty()) + return; + else + { + signed char data = (*list)[list->size()-2]; + uint charPos=(*list)[list->size()-1]; + list->resize (list->size()-2); + + if (data<0) + { +#if JW_DEBUG + kdDebug(13000)<<"An ending was found"<<endl; +#endif + + if (correctEndings(data,node,line,charPos,-1)) + return; // -1 ? + +#if 0 + if(data == -nType) + { + if (node->endLineValid) + { + if (node->endLineRel+startLine==line) // We've won again + { + //handle next node; + } + else + { // much moving + node->endLineRel=line-startLine; + node->endLineValid=true; + } + return; // next higher level should do the rest + } + else + { + node->endLineRel=line-startLine; + node->endLineValid=true; + //much moving + } + } //else add to unopened list +#endif + } + else + { + bool needNew = true; + if (current < (int)node->childCount()) + { + if (getStartLine(node->child(current)) == line) + needNew=false; + } + if (needNew) + { + something_changed = true; + KateCodeFoldingNode *newNode = new KateCodeFoldingNode(node, data, line-startLine); + node->insertChild(current, newNode); //find the correct position later + } + + addOpening(node->child(current), data, list, line,charPos); + current++; + //lookup node or create subnode + } + } + } // end while +} + +unsigned int KateCodeFoldingTree::getStartLine(KateCodeFoldingNode *node) +{ + unsigned int lineStart=0; + for (KateCodeFoldingNode *iter=node; iter->type != 0; iter=iter->parentNode) + lineStart += iter->startLineRel; + + return lineStart; +} + + +void KateCodeFoldingTree::lineHasBeenRemoved(unsigned int line) +{ + lineMapping.clear(); + dontIgnoreUnchangedLines.insert(line, &trueVal); + dontIgnoreUnchangedLines.insert(line-1, &trueVal); + dontIgnoreUnchangedLines.insert(line+1, &trueVal); + hiddenLinesCountCacheValid = false; +#if JW_DEBUG + kdDebug(13000)<<QString("KateCodeFoldingTree::lineHasBeenRemoved: %1").arg(line)<<endl; +#endif + +//line ++; + findAndMarkAllNodesforRemovalOpenedOrClosedAt(line); //It's an ugly solution + cleanupUnneededNodes(line); //It's an ugly solution + + KateCodeFoldingNode *node = findNodeForLine(line); +//????? if (node->endLineValid) + { + int startLine = getStartLine(node); + if (startLine == (int)line) + node->startLineRel--; + else + { + if (node->endLineRel == 0) + node->endLineValid = false; + node->endLineRel--; + } + + int count = node->childCount(); + for (int i=0; i<count; i++) + { + if (node->child(i)->startLineRel+startLine >= line) + node->child(i)->startLineRel--; + } + } + + if (node->parentNode) + decrementBy1(node->parentNode, node); + + for (QValueList<KateHiddenLineBlock>::Iterator it=hiddenLines.begin(); it!=hiddenLines.end(); ++it) + { + if ((*it).start > line) + (*it).start--; + else if ((*it).start+(*it).length > line) + (*it).length--; + } +} + + +void KateCodeFoldingTree::decrementBy1(KateCodeFoldingNode *node, KateCodeFoldingNode *after) +{ + if (node->endLineRel == 0) + node->endLineValid = false; + node->endLineRel--; + + for (uint i=node->findChild(after)+1; i < node->childCount(); ++i) + node->child(i)->startLineRel--; + + if (node->parentNode) + decrementBy1(node->parentNode,node); +} + + +void KateCodeFoldingTree::lineHasBeenInserted(unsigned int line) +{ + lineMapping.clear(); + dontIgnoreUnchangedLines.insert(line, &trueVal); + dontIgnoreUnchangedLines.insert(line-1, &trueVal); + dontIgnoreUnchangedLines.insert(line+1, &trueVal); + hiddenLinesCountCacheValid = false; +//return; +#if JW_DEBUG + kdDebug(13000)<<QString("KateCodeFoldingTree::lineHasBeenInserted: %1").arg(line)<<endl; +#endif + +// findAndMarkAllNodesforRemovalOpenedOrClosedAt(line); +// cleanupUnneededNodes(line); + + KateCodeFoldingNode *node = findNodeForLine(line); +// ???????? if (node->endLineValid) + { + int startLine=getStartLine(node); + if (node->type < 0) + node->startLineRel++; + else + node->endLineRel++; + + for (uint i=0; i < node->childCount(); ++i) + { + KateCodeFoldingNode *iter = node->child(i); + + if (iter->startLineRel+startLine >= line) + iter->startLineRel++; + } + } + + if (node->parentNode) + incrementBy1(node->parentNode, node); + + for (QValueList<KateHiddenLineBlock>::Iterator it=hiddenLines.begin(); it!=hiddenLines.end(); ++it) + { + if ((*it).start > line) + (*it).start++; + else if ((*it).start+(*it).length > line) + (*it).length++; + } +} + +void KateCodeFoldingTree::incrementBy1(KateCodeFoldingNode *node, KateCodeFoldingNode *after) +{ + node->endLineRel++; + + for (uint i=node->findChild(after)+1; i < node->childCount(); ++i) + node->child(i)->startLineRel++; + + if (node->parentNode) + incrementBy1(node->parentNode,node); +} + + +void KateCodeFoldingTree::findAndMarkAllNodesforRemovalOpenedOrClosedAt(unsigned int line) +{ +#ifdef __GNUC__ +#warning "FIXME: make this multiple region changes per line save"; +#endif +// return; + markedForDeleting.clear(); + KateCodeFoldingNode *node = findNodeForLine(line); + if (node->type == 0) + return; + + addNodeToRemoveList(node, line); + + while (((node->parentNode) && (node->parentNode->type!=0)) && (getStartLine(node->parentNode)==line)) + { + node = node->parentNode; + addNodeToRemoveList(node, line); + } +#if JW_DEBUG + kdDebug(13000)<<" added line to markedForDeleting list"<<endl; +#endif +} + + +void KateCodeFoldingTree::addNodeToRemoveList(KateCodeFoldingNode *node,unsigned int line) +{ + bool add=false; +#ifdef __GNUC__ +#warning "FIXME: make this multiple region changes per line save"; +#endif + unsigned int startLine=getStartLine(node); + if ((startLine==line) && (node->startLineValid)) + { + add=true; + node->deleteOpening = true; + } + if ((startLine+node->endLineRel==line) || ((node->endLineValid==false) && (node->deleteOpening))) + { + int myPos=node->parentNode->findChild(node); // this has to be implemented nicely + if ((int)node->parentNode->childCount()>myPos+1) + addNodeToRemoveList(node->parentNode->child(myPos+1),line); + add=true; + node->deleteEnding = true; + } + + if(add) + markedForDeleting.append(node); + +} + + +void KateCodeFoldingTree::findAllNodesOpenedOrClosedAt(unsigned int line) +{ + nodesForLine.clear(); + KateCodeFoldingNode *node = findNodeForLine(line); + if (node->type == 0) + return; + + unsigned int startLine = getStartLine(node); + if (startLine == line) + nodesForLine.append(node); + else if ((startLine+node->endLineRel == line)) + nodesForLine.append(node); + + while (node->parentNode) + { + addNodeToFoundList(node->parentNode, line, node->parentNode->findChild(node)); + node = node->parentNode; + } +#if JW_DEBUG + kdDebug(13000)<<" added line to nodesForLine list"<<endl; +#endif +} + + +void KateCodeFoldingTree::addNodeToFoundList(KateCodeFoldingNode *node,unsigned int line,int childpos) +{ + unsigned int startLine = getStartLine(node); + + if ((startLine==line) && (node->type!=0)) + nodesForLine.append(node); + else if ((startLine+node->endLineRel==line) && (node->type!=0)) + nodesForLine.append(node); + + for (int i=childpos+1; i<(int)node->childCount(); i++) + { + KateCodeFoldingNode *child = node->child(i); + + if (startLine+child->startLineRel == line) + { + nodesForLine.append(child); + addNodeToFoundList(child, line, 0); + } + else + break; + } +} + + +void KateCodeFoldingTree::cleanupUnneededNodes(unsigned int line) +{ +#if JW_DEBUG + kdDebug(13000)<<"void KateCodeFoldingTree::cleanupUnneededNodes(unsigned int line)"<<endl; +#endif + +// return; + if (markedForDeleting.isEmpty()) + return; + + for (int i=0; i<(int)markedForDeleting.count(); i++) + { + KateCodeFoldingNode *node = markedForDeleting.at(i); + if (node->deleteOpening) + kdDebug(13000)<<"DELETE OPENING SET"<<endl; + if (node->deleteEnding) + kdDebug(13000)<<"DELETE ENDING SET"<<endl; + + if ((node->deleteOpening) && (node->deleteEnding)) + { +#if JW_DEBUG + kdDebug(13000)<<"Deleting complete node"<<endl; +#endif + if (node->endLineValid) // just delete it, it has been opened and closed on this line + { + int f = node->parentNode->findChild (node); + + if (f >= 0) + delete node->parentNode->takeChild(f); + } + else + { + removeOpening(node, line); + // the node has subnodes which need to be moved up and this one has to be deleted + } + something_changed = true; + } + else + { + if ((node->deleteOpening) && (node->startLineValid)) + { +#if JW_DEBUG + kdDebug(13000)<<"calling removeOpening"<<endl; +#endif + removeOpening(node, line); + something_changed = true; + } + else + { + dontDeleteOpening(node); + + if ((node->deleteEnding) && (node->endLineValid)) + { + dontDeleteEnding(node); + removeEnding(node, line); + something_changed = true; + } + else + dontDeleteEnding(node); + } + } + } +} + +void KateCodeFoldingTree::dontDeleteEnding(KateCodeFoldingNode* node) +{ + node->deleteEnding = false; +} + + +void KateCodeFoldingTree::dontDeleteOpening(KateCodeFoldingNode* node) +{ + node->deleteOpening = false; +} + + +void KateCodeFoldingTree::toggleRegionVisibility(unsigned int line) +{ + // hl whole file + m_buffer->line (m_buffer->count()-1); + + lineMapping.clear(); + hiddenLinesCountCacheValid = false; + kdDebug(13000)<<QString("KateCodeFoldingTree::toggleRegionVisibility() %1").arg(line)<<endl; + + findAllNodesOpenedOrClosedAt(line); + for (int i=0; i<(int)nodesForLine.count(); i++) + { + KateCodeFoldingNode *node=nodesForLine.at(i); + if ( (!node->startLineValid) || (getStartLine(node) != line) ) + { + nodesForLine.remove(i); + i--; + } + } + + if (nodesForLine.isEmpty()) + return; + + nodesForLine.at(0)->visible = !nodesForLine.at(0)->visible; + + if (!nodesForLine.at(0)->visible) + addHiddenLineBlock(nodesForLine.at(0),line); + else + { + for (QValueList<KateHiddenLineBlock>::Iterator it=hiddenLines.begin(); it!=hiddenLines.end();++it) + if ((*it).start == line+1) + { + hiddenLines.remove(it); + break; + } + + updateHiddenSubNodes(nodesForLine.at(0)); + } + + emit regionVisibilityChangedAt(line); +} + +void KateCodeFoldingTree::updateHiddenSubNodes(KateCodeFoldingNode *node) +{ + for (uint i=0; i < node->childCount(); ++i) + { + KateCodeFoldingNode *iter = node->child(i); + + if (!iter->visible) + addHiddenLineBlock(iter, getStartLine(iter)); + else + updateHiddenSubNodes(iter); + } +} + +void KateCodeFoldingTree::addHiddenLineBlock(KateCodeFoldingNode *node,unsigned int line) +{ + KateHiddenLineBlock data; + data.start = line+1; + data.length = node->endLineRel-(existsOpeningAtLineAfter(line+node->endLineRel,node)?1:0); // without -1; + bool inserted = false; + + for (QValueList<KateHiddenLineBlock>::Iterator it=hiddenLines.begin(); it!=hiddenLines.end(); ++it) + { + if (((*it).start>=data.start) && ((*it).start<=data.start+data.length-1)) // another hidden block starting at the within this block already exits -> adapt new block + { + // the existing block can't have lines behind the new one, because a newly hidden + // block has to encapsulate already hidden ones + it=hiddenLines.remove(it); + --it; + } + else + { + if ((*it).start > line) + { + hiddenLines.insert(it, data); + inserted = true; + + break; + } + } + } + + if (!inserted) + hiddenLines.append(data); +} + +bool KateCodeFoldingTree::existsOpeningAtLineAfter(unsigned int line, KateCodeFoldingNode *node) +{ + for(KateCodeFoldingNode *tmp = node->parentNode; tmp; tmp=tmp->parentNode) + { + KateCodeFoldingNode *tmp2; + unsigned int startLine=getStartLine(tmp); + + if ((tmp2 = tmp->child(tmp->findChild(node) + 1)) + && ((tmp2->startLineRel + startLine) == line)) + return true; + + if ((startLine + tmp->endLineRel) > line) + return false; + } + + return false; +} + + +// +// get the real line number for a virtual line +// +unsigned int KateCodeFoldingTree::getRealLine(unsigned int virtualLine) +{ + // he, if nothing is hidden, why look at it ;) + if (hiddenLines.isEmpty()) + return virtualLine; + + // kdDebug(13000)<<QString("VirtualLine %1").arg(virtualLine)<<endl; + + unsigned int *real=lineMapping[virtualLine]; + if (real) + return (*real); + + unsigned int tmp = virtualLine; + for (QValueList<KateHiddenLineBlock>::ConstIterator it=hiddenLines.begin();it!=hiddenLines.end();++it) + { + if ((*it).start<=virtualLine) + virtualLine += (*it).length; + else + break; + } + + // kdDebug(13000)<<QString("Real Line %1").arg(virtualLine)<<endl; + + lineMapping.insert(tmp, new unsigned int(virtualLine)); + return virtualLine; +} + +// +// get the virtual line number for a real line +// +unsigned int KateCodeFoldingTree::getVirtualLine(unsigned int realLine) +{ + // he, if nothing is hidden, why look at it ;) + if (hiddenLines.isEmpty()) + return realLine; + + // kdDebug(13000)<<QString("RealLine--> %1").arg(realLine)<<endl; + + for (QValueList<KateHiddenLineBlock>::ConstIterator it=hiddenLines.fromLast(); it!=hiddenLines.end(); --it) + { + if ((*it).start <= realLine) + realLine -= (*it).length; + // else + // break; + } + + // kdDebug(13000)<<QString("-->virtual Line %1").arg(realLine)<<endl; + + return realLine; +} + +// +// get the number of hidden lines +// +unsigned int KateCodeFoldingTree::getHiddenLinesCount(unsigned int doclen) +{ + // he, if nothing is hidden, why look at it ;) + if (hiddenLines.isEmpty()) + return 0; + + if (hiddenLinesCountCacheValid) + return hiddenLinesCountCache; + + hiddenLinesCountCacheValid = true; + hiddenLinesCountCache = 0; + + for (QValueList<KateHiddenLineBlock>::ConstIterator it=hiddenLines.begin(); it!=hiddenLines.end(); ++it) + { + if ((*it).start+(*it).length<=doclen) + hiddenLinesCountCache += (*it).length; + else + { + hiddenLinesCountCache += ((*it).length- ((*it).length + (*it).start - doclen)); + break; + } + } + + return hiddenLinesCountCache; +} + +void KateCodeFoldingTree::collapseToplevelNodes() +{ + // hl whole file + m_buffer->line (m_buffer->count()-1); + + if (m_root.noChildren ()) + return; + + for ( uint i=0; i < m_root.childCount(); ++i ) + { + KateCodeFoldingNode *node = m_root.child(i); + + if (node->visible && node->startLineValid && node->endLineValid) + { + node->visible=false; + lineMapping.clear(); + hiddenLinesCountCacheValid = false; + addHiddenLineBlock(node,node->startLineRel); + emit regionVisibilityChangedAt(node->startLineRel); + } + } +} + +void KateCodeFoldingTree::expandToplevelNodes(int numLines) +{ + // hl whole file + m_buffer->line (m_buffer->count()-1); + + KateLineInfo line; + for (int i = 0; i < numLines; i++) { + getLineInfo(&line, i); + + if (line.startsInVisibleBlock) + toggleRegionVisibility(i); + } +} + +int KateCodeFoldingTree::collapseOne(int realLine) +{ + // hl whole file + m_buffer->line (m_buffer->count()-1); + + KateLineInfo line; + int unrelatedBlocks = 0; + for (int i = realLine; i >= 0; i--) { + getLineInfo(&line, i); + + if (line.topLevel && !line.endsBlock) + // optimisation + break; + + if (line.endsBlock && ( line.invalidBlockEnd ) && (i != realLine)) { + unrelatedBlocks++; + } + + if (line.startsVisibleBlock) { + unrelatedBlocks--; + if (unrelatedBlocks == -1) { + toggleRegionVisibility(i); + return i; + } + } + } + return -1; +} + +void KateCodeFoldingTree::expandOne(int realLine, int numLines) +{ + // hl whole file + m_buffer->line (m_buffer->count()-1); + + KateLineInfo line; + int blockTrack = 0; + for (int i = realLine; i >= 0; i--) { + getLineInfo(&line, i); + + if (line.topLevel) + // done + break; + + if (line.startsInVisibleBlock && i != realLine) { + if (blockTrack == 0) + toggleRegionVisibility(i); + + blockTrack--; + } + + if (line.endsBlock) + blockTrack++; + + if (blockTrack < 0) + // too shallow + break; + } + + blockTrack = 0; + for (int i = realLine; i < numLines; i++) { + getLineInfo(&line, i); + + if (line.topLevel) + // done + break; + + if (line.startsInVisibleBlock) { + if (blockTrack == 0) + toggleRegionVisibility(i); + + blockTrack++; + } + + if (line.endsBlock) + blockTrack--; + + if (blockTrack < 0) + // too shallow + break; + } +} + +void KateCodeFoldingTree::ensureVisible( uint line ) +{ + // first have a look, if the line is really hidden + bool found=false; + for (QValueList<KateHiddenLineBlock>::ConstIterator it=hiddenLines.begin();it!=hiddenLines.end();++it) + { + if ( ((*it).start<=line) && ((*it).start+(*it).length>line) ) + { + found=true; + break; + } + } + + + if (!found) return; + + kdDebug(13000)<<"line "<<line<<" is really hidden ->show block"<<endl; + + // it looks like we really have to ensure visibility + KateCodeFoldingNode *n = findNodeForLine( line ); + do { + if ( ! n->visible ) + toggleRegionVisibility( getStartLine( n ) ); + n = n->parentNode; + } while( n ); + +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katecodefoldinghelpers.h b/kate/part/katecodefoldinghelpers.h new file mode 100644 index 000000000..bd5e5e8d5 --- /dev/null +++ b/kate/part/katecodefoldinghelpers.h @@ -0,0 +1,222 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002 Joseph Wenninger <jowenn@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef _KATE_CODEFOLDING_HELPERS_ +#define _KATE_CODEFOLDING_HELPERS_ + +//BEGIN INCLUDES + FORWARDS +#include <qptrlist.h> +#include <qvaluelist.h> +#include <qobject.h> +#include <qintdict.h> +#include <qmemarray.h> + +class KateCodeFoldingTree; +class KateTextCursor; +class KateBuffer; + +class QString; +//END + +class KateHiddenLineBlock +{ + public: + unsigned int start; + unsigned int length; +}; + +class KateLineInfo +{ + public: + bool topLevel; + bool startsVisibleBlock; + bool startsInVisibleBlock; + bool endsBlock; + bool invalidBlockEnd; +}; + +class KateCodeFoldingNode +{ + friend class KateCodeFoldingTree; + + public: + KateCodeFoldingNode (); + KateCodeFoldingNode (KateCodeFoldingNode *par, signed char typ, unsigned int sLRel); + + ~KateCodeFoldingNode (); + + inline int nodeType () { return type;} + + inline bool isVisible () {return visible;} + + inline KateCodeFoldingNode *getParentNode () {return parentNode;} + + bool getBegin (KateCodeFoldingTree *tree, KateTextCursor* begin); + bool getEnd (KateCodeFoldingTree *tree, KateTextCursor *end); + + /** + * accessors for the child nodes + */ + protected: + inline bool noChildren () const { return m_children.isEmpty(); } + + inline uint childCount () const { return m_children.size(); } + + inline KateCodeFoldingNode *child (uint index) const { return m_children[index]; } + + inline int findChild (KateCodeFoldingNode *node, uint start = 0) const { return m_children.find (node, start); } + + inline void appendChild (KateCodeFoldingNode *node) { m_children.resize(m_children.size()+1); m_children[m_children.size()-1] = node; } + + void insertChild (uint index, KateCodeFoldingNode *node); + + KateCodeFoldingNode *takeChild (uint index); + + void clearChildren (); + + int cmpPos(KateCodeFoldingTree *tree, uint line, uint col); + + /** + * data members + */ + private: + KateCodeFoldingNode *parentNode; + unsigned int startLineRel; + unsigned int endLineRel; + + unsigned int startCol; + unsigned int endCol; + + bool startLineValid; + bool endLineValid; + + signed char type; // 0 -> toplevel / invalid + bool visible; + bool deleteOpening; + bool deleteEnding; + + QMemArray<KateCodeFoldingNode*> m_children; +}; + +class KateCodeFoldingTree : public QObject +{ + friend class KateCodeFoldingNode; + + Q_OBJECT + + public: + KateCodeFoldingTree (KateBuffer *buffer); + ~KateCodeFoldingTree (); + + KateCodeFoldingNode *findNodeForLine (unsigned int line); + + unsigned int getRealLine (unsigned int virtualLine); + unsigned int getVirtualLine (unsigned int realLine); + unsigned int getHiddenLinesCount (unsigned int docLine); + + bool isTopLevel (unsigned int line); + + void lineHasBeenInserted (unsigned int line); + void lineHasBeenRemoved (unsigned int line); + void debugDump (); + void getLineInfo (KateLineInfo *info,unsigned int line); + + unsigned int getStartLine (KateCodeFoldingNode *node); + + void fixRoot (int endLRel); + void clear (); + + KateCodeFoldingNode *findNodeForPosition(unsigned int line, unsigned int column); + private: + + KateCodeFoldingNode m_root; + + KateBuffer *m_buffer; + + QIntDict<unsigned int> lineMapping; + QIntDict<bool> dontIgnoreUnchangedLines; + + QPtrList<KateCodeFoldingNode> markedForDeleting; + QPtrList<KateCodeFoldingNode> nodesForLine; + QValueList<KateHiddenLineBlock> hiddenLines; + + unsigned int hiddenLinesCountCache; + bool something_changed; + bool hiddenLinesCountCacheValid; + + static bool trueVal; + + KateCodeFoldingNode *findNodeForLineDescending (KateCodeFoldingNode *, unsigned int, unsigned int, bool oneStepOnly=false); + + bool correctEndings (signed char data, KateCodeFoldingNode *node, unsigned int line, unsigned int endCol, int insertPos); + + void dumpNode (KateCodeFoldingNode *node, const QString &prefix); + void addOpening (KateCodeFoldingNode *node, signed char nType,QMemArray<uint>* list, unsigned int line,unsigned int charPos); + void addOpening_further_iterations (KateCodeFoldingNode *node,signed char nType, QMemArray<uint>* + list,unsigned int line,int current,unsigned int startLine,unsigned int charPos); + + void incrementBy1 (KateCodeFoldingNode *node, KateCodeFoldingNode *after); + void decrementBy1 (KateCodeFoldingNode *node, KateCodeFoldingNode *after); + + void cleanupUnneededNodes (unsigned int line); + + /** + * if returns true, this node has been deleted !! + */ + bool removeEnding (KateCodeFoldingNode *node,unsigned int line); + + /** + * if returns true, this node has been deleted !! + */ + bool removeOpening (KateCodeFoldingNode *node,unsigned int line); + + void findAndMarkAllNodesforRemovalOpenedOrClosedAt (unsigned int line); + void findAllNodesOpenedOrClosedAt (unsigned int line); + + void addNodeToFoundList (KateCodeFoldingNode *node,unsigned int line,int childpos); + void addNodeToRemoveList (KateCodeFoldingNode *node,unsigned int line); + void addHiddenLineBlock (KateCodeFoldingNode *node,unsigned int line); + + bool existsOpeningAtLineAfter(unsigned int line, KateCodeFoldingNode *node); + + void dontDeleteEnding (KateCodeFoldingNode*); + void dontDeleteOpening (KateCodeFoldingNode*); + + void updateHiddenSubNodes (KateCodeFoldingNode *node); + void moveSubNodesUp (KateCodeFoldingNode *node); + + public slots: + void updateLine (unsigned int line,QMemArray<uint>* regionChanges, bool *updated, bool changed,bool colschanged); + void toggleRegionVisibility (unsigned int); + void collapseToplevelNodes (); + void expandToplevelNodes (int numLines); + int collapseOne (int realLine); + void expandOne (int realLine, int numLines); + /** + Ensures that all nodes surrounding @p line are open + */ + void ensureVisible( uint line ); + + signals: + void regionVisibilityChangedAt (unsigned int); + void regionBeginEndAddedRemoved (unsigned int); +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateconfig.cpp b/kate/part/kateconfig.cpp new file mode 100644 index 000000000..c580ed5c1 --- /dev/null +++ b/kate/part/kateconfig.cpp @@ -0,0 +1,1429 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Christoph Cullmann <cullmann@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kateconfig.h" + +#include "katefactory.h" +#include "katerenderer.h" +#include "kateview.h" +#include "katedocument.h" +#include "katefont.h" +#include "kateschema.h" + +#include <math.h> + +#include <kapplication.h> +#include <kconfig.h> +#include <kglobalsettings.h> +#include <kcharsets.h> +#include <klocale.h> +#include <kfinddialog.h> +#include <kreplacedialog.h> +#include <kinstance.h> +#include <kstaticdeleter.h> + +#include <qpopupmenu.h> +#include <qtextcodec.h> + +#include <kdebug.h> + +//BEGIN KateConfig +KateConfig::KateConfig () + : configSessionNumber (0), configIsRunning (false) +{ +} + +KateConfig::~KateConfig () +{ +} + +void KateConfig::configStart () +{ + configSessionNumber++; + + if (configSessionNumber > 1) + return; + + configIsRunning = true; +} + +void KateConfig::configEnd () +{ + if (configSessionNumber == 0) + return; + + configSessionNumber--; + + if (configSessionNumber > 0) + return; + + configIsRunning = false; + + updateConfig (); +} +//END + +//BEGIN KateDocumentConfig +KateDocumentConfig *KateDocumentConfig::s_global = 0; +KateViewConfig *KateViewConfig::s_global = 0; +KateRendererConfig *KateRendererConfig::s_global = 0; + +KateDocumentConfig::KateDocumentConfig () + : m_tabWidth (8), + m_indentationWidth (2), + m_wordWrapAt (80), + m_configFlags (0), + m_plugins (KateFactory::self()->plugins().count()), + m_tabWidthSet (true), + m_indentationWidthSet (true), + m_indentationModeSet (true), + m_wordWrapSet (true), + m_wordWrapAtSet (true), + m_pageUpDownMovesCursorSet (true), + m_undoStepsSet (true), + m_configFlagsSet (0xFFFF), + m_encodingSet (true), + m_eolSet (true), + m_allowEolDetectionSet (true), + m_backupFlagsSet (true), + m_searchDirConfigDepthSet (true), + m_backupPrefixSet (true), + m_backupSuffixSet (true), + m_pluginsSet (m_plugins.size()), + m_doc (0) +{ + s_global = this; + + // init plugin array + m_plugins.fill (false); + m_pluginsSet.fill (true); + + // init with defaults from config or really hardcoded ones + KConfig *config = kapp->config(); + config->setGroup("Kate Document Defaults"); + readConfig (config); +} + +KateDocumentConfig::KateDocumentConfig (KateDocument *doc) + : m_configFlags (0), + m_plugins (KateFactory::self()->plugins().count()), + m_tabWidthSet (false), + m_indentationWidthSet (false), + m_indentationModeSet (false), + m_wordWrapSet (false), + m_wordWrapAtSet (false), + m_pageUpDownMovesCursorSet (false), + m_undoStepsSet (false), + m_configFlagsSet (0), + m_encodingSet (false), + m_eolSet (false), + m_allowEolDetectionSet (false), + m_backupFlagsSet (false), + m_searchDirConfigDepthSet (false), + m_backupPrefixSet (false), + m_backupSuffixSet (false), + m_pluginsSet (m_plugins.size()), + m_doc (doc) +{ + // init plugin array + m_plugins.fill (false); + m_pluginsSet.fill (false); +} + +KateDocumentConfig::~KateDocumentConfig () +{ +} + +void KateDocumentConfig::readConfig (KConfig *config) +{ + configStart (); + + setTabWidth (config->readNumEntry("Tab Width", 8)); + + setIndentationWidth (config->readNumEntry("Indentation Width", 2)); + + setIndentationMode (config->readNumEntry("Indentation Mode", KateDocumentConfig::imNone)); + + setWordWrap (config->readBoolEntry("Word Wrap", false)); + setWordWrapAt (config->readNumEntry("Word Wrap Column", 80)); + setPageUpDownMovesCursor (config->readBoolEntry("PageUp/PageDown Moves Cursor", false)); + setUndoSteps(config->readNumEntry("Undo Steps", 0)); + + setConfigFlags (config->readNumEntry("Basic Config Flags", KateDocumentConfig::cfTabIndents + | KateDocumentConfig::cfKeepIndentProfile + | KateDocumentConfig::cfWrapCursor + | KateDocumentConfig::cfShowTabs + | KateDocumentConfig::cfSmartHome + | KateDocumentConfig::cfIndentPastedText)); + + setEncoding (config->readEntry("Encoding", "")); + + setEol (config->readNumEntry("End of Line", 0)); + setAllowEolDetection (config->readBoolEntry("Allow End of Line Detection", true)); + + setBackupFlags (config->readNumEntry("Backup Config Flags", 1)); + + setSearchDirConfigDepth (config->readNumEntry("Search Dir Config Depth", 3)); + + setBackupPrefix (config->readEntry("Backup Prefix", QString (""))); + + setBackupSuffix (config->readEntry("Backup Suffix", QString ("~"))); + + // plugins + for (uint i=0; i<KateFactory::self()->plugins().count(); i++) + setPlugin (i, config->readBoolEntry("KTextEditor Plugin " + (KateFactory::self()->plugins())[i]->library(), false)); + + configEnd (); +} + +void KateDocumentConfig::writeConfig (KConfig *config) +{ + config->writeEntry("Tab Width", tabWidth()); + + config->writeEntry("Indentation Width", indentationWidth()); + config->writeEntry("Indentation Mode", indentationMode()); + + config->writeEntry("Word Wrap", wordWrap()); + config->writeEntry("Word Wrap Column", wordWrapAt()); + + config->writeEntry("PageUp/PageDown Moves Cursor", pageUpDownMovesCursor()); + + config->writeEntry("Undo Steps", undoSteps()); + + config->writeEntry("Basic Config Flags", configFlags()); + + config->writeEntry("Encoding", encoding()); + + config->writeEntry("End of Line", eol()); + config->writeEntry("Allow End of Line Detection", allowEolDetection()); + + config->writeEntry("Backup Config Flags", backupFlags()); + + config->writeEntry("Search Dir Config Depth", searchDirConfigDepth()); + + config->writeEntry("Backup Prefix", backupPrefix()); + + config->writeEntry("Backup Suffix", backupSuffix()); + + // plugins + for (uint i=0; i<KateFactory::self()->plugins().count(); i++) + config->writeEntry("KTextEditor Plugin " + (KateFactory::self()->plugins())[i]->library(), plugin(i)); +} + +void KateDocumentConfig::updateConfig () +{ + if (m_doc) + { + m_doc->updateConfig (); + return; + } + + if (isGlobal()) + { + for (uint z=0; z < KateFactory::self()->documents()->count(); z++) + { + KateFactory::self()->documents()->at(z)->updateConfig (); + } + } +} + +int KateDocumentConfig::tabWidth () const +{ + if (m_tabWidthSet || isGlobal()) + return m_tabWidth; + + return s_global->tabWidth(); +} + +void KateDocumentConfig::setTabWidth (int tabWidth) +{ + if (tabWidth < 1) + return; + + configStart (); + + m_tabWidthSet = true; + m_tabWidth = tabWidth; + + configEnd (); +} + +int KateDocumentConfig::indentationWidth () const +{ + if (m_indentationWidthSet || isGlobal()) + return m_indentationWidth; + + return s_global->indentationWidth(); +} + +void KateDocumentConfig::setIndentationWidth (int indentationWidth) +{ + if (indentationWidth < 1) + return; + + configStart (); + + m_indentationWidthSet = true; + m_indentationWidth = indentationWidth; + + configEnd (); +} + +uint KateDocumentConfig::indentationMode () const +{ + if (m_indentationModeSet || isGlobal()) + return m_indentationMode; + + return s_global->indentationMode(); +} + +void KateDocumentConfig::setIndentationMode (uint indentationMode) +{ + configStart (); + + m_indentationModeSet = true; + m_indentationMode = indentationMode; + + configEnd (); +} + +bool KateDocumentConfig::wordWrap () const +{ + if (m_wordWrapSet || isGlobal()) + return m_wordWrap; + + return s_global->wordWrap(); +} + +void KateDocumentConfig::setWordWrap (bool on) +{ + configStart (); + + m_wordWrapSet = true; + m_wordWrap = on; + + configEnd (); +} + +unsigned int KateDocumentConfig::wordWrapAt () const +{ + if (m_wordWrapAtSet || isGlobal()) + return m_wordWrapAt; + + return s_global->wordWrapAt(); +} + +void KateDocumentConfig::setWordWrapAt (unsigned int col) +{ + if (col < 1) + return; + + configStart (); + + m_wordWrapAtSet = true; + m_wordWrapAt = col; + + configEnd (); +} + +uint KateDocumentConfig::undoSteps () const +{ + if (m_undoStepsSet || isGlobal()) + return m_undoSteps; + + return s_global->undoSteps(); +} + +void KateDocumentConfig::setUndoSteps (uint undoSteps) +{ + configStart (); + + m_undoStepsSet = true; + m_undoSteps = undoSteps; + + configEnd (); +} + +bool KateDocumentConfig::pageUpDownMovesCursor () const +{ + if (m_pageUpDownMovesCursorSet || isGlobal()) + return m_pageUpDownMovesCursor; + + return s_global->pageUpDownMovesCursor(); +} + +void KateDocumentConfig::setPageUpDownMovesCursor (bool on) +{ + configStart (); + + m_pageUpDownMovesCursorSet = true; + m_pageUpDownMovesCursor = on; + + configEnd (); +} + +uint KateDocumentConfig::configFlags () const +{ + if (isGlobal()) + return m_configFlags; + + return ((s_global->configFlags() & ~ m_configFlagsSet) | m_configFlags); +} + +void KateDocumentConfig::setConfigFlags (KateDocumentConfig::ConfigFlags flag, bool enable) +{ + configStart (); + + m_configFlagsSet |= flag; + + if (enable) + m_configFlags = m_configFlags | flag; + else + m_configFlags = m_configFlags & ~ flag; + + configEnd (); +} + +void KateDocumentConfig::setConfigFlags (uint fullFlags) +{ + configStart (); + + m_configFlagsSet = 0xFFFF; + m_configFlags = fullFlags; + + configEnd (); +} + +const QString &KateDocumentConfig::encoding () const +{ + if (m_encodingSet || isGlobal()) + return m_encoding; + + return s_global->encoding(); +} + +QTextCodec *KateDocumentConfig::codec () +{ + if (m_encodingSet || isGlobal()) + { + if (m_encoding.isEmpty() && isGlobal()) + return KGlobal::charsets()->codecForName (QString::fromLatin1(KGlobal::locale()->encoding())); + else if (m_encoding.isEmpty()) + return s_global->codec (); + else + return KGlobal::charsets()->codecForName (m_encoding); + } + + return s_global->codec (); +} + +void KateDocumentConfig::setEncoding (const QString &encoding) +{ + QString enc = encoding; + + if (!enc.isEmpty()) + { + bool found = false; + QTextCodec *codec = KGlobal::charsets()->codecForName (encoding, found); + + if (!found || !codec) + return; + + enc = codec->name(); + } + + configStart (); + + if (isGlobal()) + KateDocument::setDefaultEncoding (enc); + + m_encodingSet = true; + m_encoding = enc; + + configEnd (); +} + +bool KateDocumentConfig::isSetEncoding () const +{ + return m_encodingSet; +} + +int KateDocumentConfig::eol () const +{ + if (m_eolSet || isGlobal()) + return m_eol; + + return s_global->eol(); +} + +QString KateDocumentConfig::eolString () +{ + if (eol() == KateDocumentConfig::eolUnix) + return QString ("\n"); + else if (eol() == KateDocumentConfig::eolDos) + return QString ("\r\n"); + else if (eol() == KateDocumentConfig::eolMac) + return QString ("\r"); + + return QString ("\n"); +} + +void KateDocumentConfig::setEol (int mode) +{ + configStart (); + + m_eolSet = true; + m_eol = mode; + + configEnd (); +} + +bool KateDocumentConfig::allowEolDetection () const +{ + if (m_allowEolDetectionSet || isGlobal()) + return m_allowEolDetection; + + return s_global->allowEolDetection(); +} + +void KateDocumentConfig::setAllowEolDetection (bool on) +{ + configStart (); + + m_allowEolDetectionSet = true; + m_allowEolDetection = on; + + configEnd (); +} + +uint KateDocumentConfig::backupFlags () const +{ + if (m_backupFlagsSet || isGlobal()) + return m_backupFlags; + + return s_global->backupFlags(); +} + +void KateDocumentConfig::setBackupFlags (uint flags) + { + configStart (); + + m_backupFlagsSet = true; + m_backupFlags = flags; + + configEnd (); +} + +const QString &KateDocumentConfig::backupPrefix () const +{ + if (m_backupPrefixSet || isGlobal()) + return m_backupPrefix; + + return s_global->backupPrefix(); +} + +const QString &KateDocumentConfig::backupSuffix () const +{ + if (m_backupSuffixSet || isGlobal()) + return m_backupSuffix; + + return s_global->backupSuffix(); +} + +void KateDocumentConfig::setBackupPrefix (const QString &prefix) +{ + configStart (); + + m_backupPrefixSet = true; + m_backupPrefix = prefix; + + configEnd (); +} + +void KateDocumentConfig::setBackupSuffix (const QString &suffix) +{ + configStart (); + + m_backupSuffixSet = true; + m_backupSuffix = suffix; + + configEnd (); +} + +bool KateDocumentConfig::plugin (uint index) const +{ + if (index >= m_plugins.size()) + return false; + + if (m_pluginsSet.at(index) || isGlobal()) + return m_plugins.at(index); + + return s_global->plugin (index); +} + +void KateDocumentConfig::setPlugin (uint index, bool load) +{ + if (index >= m_plugins.size()) + return; + + configStart (); + + m_pluginsSet.setBit(index); + m_plugins.setBit(index, load); + + configEnd (); +} + +int KateDocumentConfig::searchDirConfigDepth () const +{ + if (m_searchDirConfigDepthSet || isGlobal()) + return m_searchDirConfigDepth; + + return s_global->searchDirConfigDepth (); +} + +void KateDocumentConfig::setSearchDirConfigDepth (int depth) +{ + configStart (); + + m_searchDirConfigDepthSet = true; + m_searchDirConfigDepth = depth; + + configEnd (); +} + +//END + +//BEGIN KateViewConfig +KateViewConfig::KateViewConfig () + : + m_dynWordWrapSet (true), + m_dynWordWrapIndicatorsSet (true), + m_dynWordWrapAlignIndentSet (true), + m_lineNumbersSet (true), + m_scrollBarMarksSet (true), + m_iconBarSet (true), + m_foldingBarSet (true), + m_bookmarkSortSet (true), + m_autoCenterLinesSet (true), + m_searchFlagsSet (true), + m_cmdLineSet (true), + m_defaultMarkTypeSet (true), + m_persistentSelectionSet (true), + m_textToSearchModeSet (true), + m_view (0) +{ + s_global = this; + + // init with defaults from config or really hardcoded ones + KConfig *config = kapp->config(); + config->setGroup("Kate View Defaults"); + readConfig (config); +} + +KateViewConfig::KateViewConfig (KateView *view) + : + m_dynWordWrapSet (false), + m_dynWordWrapIndicatorsSet (false), + m_dynWordWrapAlignIndentSet (false), + m_lineNumbersSet (false), + m_scrollBarMarksSet (false), + m_iconBarSet (false), + m_foldingBarSet (false), + m_bookmarkSortSet (false), + m_autoCenterLinesSet (false), + m_searchFlagsSet (false), + m_cmdLineSet (false), + m_defaultMarkTypeSet (false), + m_persistentSelectionSet (false), + m_textToSearchModeSet (false), + m_view (view) +{ +} + +KateViewConfig::~KateViewConfig () +{ +} + +void KateViewConfig::readConfig (KConfig *config) +{ + configStart (); + + setDynWordWrap (config->readBoolEntry( "Dynamic Word Wrap", true )); + setDynWordWrapIndicators (config->readNumEntry( "Dynamic Word Wrap Indicators", 1 )); + setDynWordWrapAlignIndent (config->readNumEntry( "Dynamic Word Wrap Align Indent", 80 )); + + setLineNumbers (config->readBoolEntry( "Line Numbers", false)); + + setScrollBarMarks (config->readBoolEntry( "Scroll Bar Marks", false)); + + setIconBar (config->readBoolEntry( "Icon Bar", false )); + + setFoldingBar (config->readBoolEntry( "Folding Bar", true)); + + setBookmarkSort (config->readNumEntry( "Bookmark Menu Sorting", 0 )); + + setAutoCenterLines (config->readNumEntry( "Auto Center Lines", 0 )); + + setSearchFlags (config->readNumEntry("Search Config Flags", KFindDialog::FromCursor | KFindDialog::CaseSensitive | KReplaceDialog::PromptOnReplace)); + + setCmdLine (config->readBoolEntry( "Command Line", false)); + + setDefaultMarkType (config->readNumEntry( "Default Mark Type", KTextEditor::MarkInterface::markType01 )); + + setPersistentSelection (config->readNumEntry( "Persistent Selection", false )); + + setTextToSearchMode (config->readNumEntry( "Text To Search Mode", KateViewConfig::SelectionWord)); + + configEnd (); +} + +void KateViewConfig::writeConfig (KConfig *config) +{ + config->writeEntry( "Dynamic Word Wrap", dynWordWrap() ); + config->writeEntry( "Dynamic Word Wrap Indicators", dynWordWrapIndicators() ); + config->writeEntry( "Dynamic Word Wrap Align Indent", dynWordWrapAlignIndent() ); + + config->writeEntry( "Line Numbers", lineNumbers() ); + + config->writeEntry( "Scroll Bar Marks", scrollBarMarks() ); + + config->writeEntry( "Icon Bar", iconBar() ); + + config->writeEntry( "Folding Bar", foldingBar() ); + + config->writeEntry( "Bookmark Menu Sorting", bookmarkSort() ); + + config->writeEntry( "Auto Center Lines", autoCenterLines() ); + + config->writeEntry("Search Config Flags", searchFlags()); + + config->writeEntry("Command Line", cmdLine()); + + config->writeEntry("Default Mark Type", defaultMarkType()); + + config->writeEntry("Persistent Selection", persistentSelection()); + + config->writeEntry("Text To Search Mode", textToSearchMode()); +} + +void KateViewConfig::updateConfig () +{ + if (m_view) + { + m_view->updateConfig (); + return; + } + + if (isGlobal()) + { + for (uint z=0; z < KateFactory::self()->views()->count(); z++) + { + KateFactory::self()->views()->at(z)->updateConfig (); + } + } +} + +bool KateViewConfig::dynWordWrap () const +{ + if (m_dynWordWrapSet || isGlobal()) + return m_dynWordWrap; + + return s_global->dynWordWrap(); +} + +void KateViewConfig::setDynWordWrap (bool wrap) +{ + configStart (); + + m_dynWordWrapSet = true; + m_dynWordWrap = wrap; + + configEnd (); +} + +int KateViewConfig::dynWordWrapIndicators () const +{ + if (m_dynWordWrapIndicatorsSet || isGlobal()) + return m_dynWordWrapIndicators; + + return s_global->dynWordWrapIndicators(); +} + +void KateViewConfig::setDynWordWrapIndicators (int mode) +{ + configStart (); + + m_dynWordWrapIndicatorsSet = true; + m_dynWordWrapIndicators = kMin(80, kMax(0, mode)); + + configEnd (); +} + +int KateViewConfig::dynWordWrapAlignIndent () const +{ + if (m_dynWordWrapAlignIndentSet || isGlobal()) + return m_dynWordWrapAlignIndent; + + return s_global->dynWordWrapAlignIndent(); +} + +void KateViewConfig::setDynWordWrapAlignIndent (int indent) +{ + configStart (); + + m_dynWordWrapAlignIndentSet = true; + m_dynWordWrapAlignIndent = indent; + + configEnd (); +} + +bool KateViewConfig::lineNumbers () const +{ + if (m_lineNumbersSet || isGlobal()) + return m_lineNumbers; + + return s_global->lineNumbers(); +} + +void KateViewConfig::setLineNumbers (bool on) +{ + configStart (); + + m_lineNumbersSet = true; + m_lineNumbers = on; + + configEnd (); +} + +bool KateViewConfig::scrollBarMarks () const +{ + if (m_scrollBarMarksSet || isGlobal()) + return m_scrollBarMarks; + + return s_global->scrollBarMarks(); +} + +void KateViewConfig::setScrollBarMarks (bool on) +{ + configStart (); + + m_scrollBarMarksSet = true; + m_scrollBarMarks = on; + + configEnd (); +} + +bool KateViewConfig::iconBar () const +{ + if (m_iconBarSet || isGlobal()) + return m_iconBar; + + return s_global->iconBar(); +} + +void KateViewConfig::setIconBar (bool on) +{ + configStart (); + + m_iconBarSet = true; + m_iconBar = on; + + configEnd (); +} + +bool KateViewConfig::foldingBar () const +{ + if (m_foldingBarSet || isGlobal()) + return m_foldingBar; + + return s_global->foldingBar(); +} + +void KateViewConfig::setFoldingBar (bool on) +{ + configStart (); + + m_foldingBarSet = true; + m_foldingBar = on; + + configEnd (); +} + +int KateViewConfig::bookmarkSort () const +{ + if (m_bookmarkSortSet || isGlobal()) + return m_bookmarkSort; + + return s_global->bookmarkSort(); +} + +void KateViewConfig::setBookmarkSort (int mode) +{ + configStart (); + + m_bookmarkSortSet = true; + m_bookmarkSort = mode; + + configEnd (); +} + +int KateViewConfig::autoCenterLines () const +{ + if (m_autoCenterLinesSet || isGlobal()) + return m_autoCenterLines; + + return s_global->autoCenterLines(); +} + +void KateViewConfig::setAutoCenterLines (int lines) +{ + if (lines < 0) + return; + + configStart (); + + m_autoCenterLinesSet = true; + m_autoCenterLines = lines; + + configEnd (); +} + +long KateViewConfig::searchFlags () const +{ + if (m_searchFlagsSet || isGlobal()) + return m_searchFlags; + + return s_global->searchFlags(); +} + +void KateViewConfig::setSearchFlags (long flags) + { + configStart (); + + m_searchFlagsSet = true; + m_searchFlags = flags; + + configEnd (); +} + +bool KateViewConfig::cmdLine () const +{ + if (m_cmdLineSet || isGlobal()) + return m_cmdLine; + + return s_global->cmdLine(); +} + +void KateViewConfig::setCmdLine (bool on) +{ + configStart (); + + m_cmdLineSet = true; + m_cmdLine = on; + + configEnd (); +} + +uint KateViewConfig::defaultMarkType () const +{ + if (m_defaultMarkTypeSet || isGlobal()) + return m_defaultMarkType; + + return s_global->defaultMarkType(); +} + +void KateViewConfig::setDefaultMarkType (uint type) +{ + configStart (); + + m_defaultMarkTypeSet = true; + m_defaultMarkType = type; + + configEnd (); +} + +bool KateViewConfig::persistentSelection () const +{ + if (m_persistentSelectionSet || isGlobal()) + return m_persistentSelection; + + return s_global->persistentSelection(); +} + +void KateViewConfig::setPersistentSelection (bool on) +{ + configStart (); + + m_persistentSelectionSet = true; + m_persistentSelection = on; + + configEnd (); +} + +int KateViewConfig::textToSearchMode () const +{ + if (m_textToSearchModeSet || isGlobal()) + return m_textToSearchMode; + + return s_global->textToSearchMode(); +} + +void KateViewConfig::setTextToSearchMode (int mode) +{ + configStart (); + + m_textToSearchModeSet = true; + m_textToSearchMode = mode; + + configEnd (); +} + +//END + +//BEGIN KateRendererConfig +KateRendererConfig::KateRendererConfig () + : + m_font (new KateFontStruct ()), + m_lineMarkerColor (KTextEditor::MarkInterface::reservedMarkersCount()), + m_schemaSet (true), + m_fontSet (true), + m_wordWrapMarkerSet (true), + m_showIndentationLinesSet (true), + m_backgroundColorSet (true), + m_selectionColorSet (true), + m_highlightedLineColorSet (true), + m_highlightedBracketColorSet (true), + m_wordWrapMarkerColorSet (true), + m_tabMarkerColorSet(true), + m_iconBarColorSet (true), + m_lineNumberColorSet (true), + m_lineMarkerColorSet (m_lineMarkerColor.size()), + m_renderer (0) +{ + // init bitarray + m_lineMarkerColorSet.fill (true); + + s_global = this; + + // init with defaults from config or really hardcoded ones + KConfig *config = kapp->config(); + config->setGroup("Kate Renderer Defaults"); + readConfig (config); +} + +KateRendererConfig::KateRendererConfig (KateRenderer *renderer) + : m_font (0), + m_lineMarkerColor (KTextEditor::MarkInterface::reservedMarkersCount()), + m_schemaSet (false), + m_fontSet (false), + m_wordWrapMarkerSet (false), + m_showIndentationLinesSet (false), + m_backgroundColorSet (false), + m_selectionColorSet (false), + m_highlightedLineColorSet (false), + m_highlightedBracketColorSet (false), + m_wordWrapMarkerColorSet (false), + m_tabMarkerColorSet(false), + m_iconBarColorSet (false), + m_lineNumberColorSet (false), + m_lineMarkerColorSet (m_lineMarkerColor.size()), + m_renderer (renderer) +{ + // init bitarray + m_lineMarkerColorSet.fill (false); +} + +KateRendererConfig::~KateRendererConfig () +{ + delete m_font; +} + +void KateRendererConfig::readConfig (KConfig *config) +{ + configStart (); + + setSchema (KateFactory::self()->schemaManager()->number (config->readEntry("Schema", KateSchemaManager::normalSchema()))); + + setWordWrapMarker (config->readBoolEntry("Word Wrap Marker", false )); + + setShowIndentationLines (config->readBoolEntry( "Show Indentation Lines", false)); + + configEnd (); +} + +void KateRendererConfig::writeConfig (KConfig *config) +{ + config->writeEntry ("Schema", KateFactory::self()->schemaManager()->name(schema())); + + config->writeEntry("Word Wrap Marker", wordWrapMarker() ); + + config->writeEntry("Show Indentation Lines", showIndentationLines()); +} + +void KateRendererConfig::updateConfig () +{ + if (m_renderer) + { + m_renderer->updateConfig (); + return; + } + + if (isGlobal()) + { + for (uint z=0; z < KateFactory::self()->renderers()->count(); z++) + { + KateFactory::self()->renderers()->at(z)->updateConfig (); + } + } +} + +uint KateRendererConfig::schema () const +{ + if (m_schemaSet || isGlobal()) + return m_schema; + + return s_global->schema(); +} + +void KateRendererConfig::setSchema (uint schema) +{ + configStart (); + m_schemaSet = true; + m_schema = schema; + setSchemaInternal( schema ); + configEnd (); +} + +void KateRendererConfig::reloadSchema() +{ + if ( isGlobal() ) + for ( uint z=0; z < KateFactory::self()->renderers()->count(); z++ ) + KateFactory::self()->renderers()->at(z)->config()->reloadSchema(); + + else if ( m_renderer && m_schemaSet ) + setSchemaInternal( m_schema ); +} + +void KateRendererConfig::setSchemaInternal( int schema ) +{ + m_schemaSet = true; + m_schema = schema; + + KConfig *config (KateFactory::self()->schemaManager()->schema(schema)); + + QColor tmp0 (KGlobalSettings::baseColor()); + QColor tmp1 (KGlobalSettings::highlightColor()); + QColor tmp2 (KGlobalSettings::alternateBackgroundColor()); + QColor tmp3 ( "#FFFF99" ); + QColor tmp4 (tmp2.dark()); + QColor tmp5 ( KGlobalSettings::textColor() ); + QColor tmp6 ( "#EAE9E8" ); + QColor tmp7 ( "#000000" ); + + m_backgroundColor = config->readColorEntry("Color Background", &tmp0); + m_backgroundColorSet = true; + m_selectionColor = config->readColorEntry("Color Selection", &tmp1); + m_selectionColorSet = true; + m_highlightedLineColor = config->readColorEntry("Color Highlighted Line", &tmp2); + m_highlightedLineColorSet = true; + m_highlightedBracketColor = config->readColorEntry("Color Highlighted Bracket", &tmp3); + m_highlightedBracketColorSet = true; + m_wordWrapMarkerColor = config->readColorEntry("Color Word Wrap Marker", &tmp4); + m_wordWrapMarkerColorSet = true; + m_tabMarkerColor = config->readColorEntry("Color Tab Marker", &tmp5); + m_tabMarkerColorSet = true; + m_iconBarColor = config->readColorEntry("Color Icon Bar", &tmp6); + m_iconBarColorSet = true; + m_lineNumberColor = config->readColorEntry("Color Line Number", &tmp7); + m_lineNumberColorSet = true; + + // same std colors like in KateDocument::markColor + QColor mark[7]; + mark[0] = Qt::blue; + mark[1] = Qt::red; + mark[2] = Qt::yellow; + mark[3] = Qt::magenta; + mark[4] = Qt::gray; + mark[5] = Qt::green; + mark[6] = Qt::red; + + for (int i = 1; i <= KTextEditor::MarkInterface::reservedMarkersCount(); i++) { + QColor col = config->readColorEntry(QString("Color MarkType%1").arg(i), &mark[i - 1]); + int index = i-1; + m_lineMarkerColorSet[index] = true; + m_lineMarkerColor[index] = col; + } + + QFont f (KGlobalSettings::fixedFont()); + + if (!m_fontSet) + { + m_fontSet = true; + m_font = new KateFontStruct (); + } + + m_font->setFont(config->readFontEntry("Font", &f)); +} + +KateFontStruct *KateRendererConfig::fontStruct () +{ + if (m_fontSet || isGlobal()) + return m_font; + + return s_global->fontStruct (); +} + +QFont *KateRendererConfig::font() +{ + return &(fontStruct ()->myFont); +} + +KateFontMetrics *KateRendererConfig::fontMetrics() +{ + return &(fontStruct ()->myFontMetrics); +} + +void KateRendererConfig::setFont(const QFont &font) +{ + configStart (); + + if (!m_fontSet) + { + m_fontSet = true; + m_font = new KateFontStruct (); + } + + m_font->setFont(font); + + configEnd (); +} + +bool KateRendererConfig::wordWrapMarker () const +{ + if (m_wordWrapMarkerSet || isGlobal()) + return m_wordWrapMarker; + + return s_global->wordWrapMarker(); +} + +void KateRendererConfig::setWordWrapMarker (bool on) +{ + configStart (); + + m_wordWrapMarkerSet = true; + m_wordWrapMarker = on; + + configEnd (); +} + +const QColor& KateRendererConfig::backgroundColor() const +{ + if (m_backgroundColorSet || isGlobal()) + return m_backgroundColor; + + return s_global->backgroundColor(); +} + +void KateRendererConfig::setBackgroundColor (const QColor &col) +{ + configStart (); + + m_backgroundColorSet = true; + m_backgroundColor = col; + + configEnd (); +} + +const QColor& KateRendererConfig::selectionColor() const +{ + if (m_selectionColorSet || isGlobal()) + return m_selectionColor; + + return s_global->selectionColor(); +} + +void KateRendererConfig::setSelectionColor (const QColor &col) +{ + configStart (); + + m_selectionColorSet = true; + m_selectionColor = col; + + configEnd (); +} + +const QColor& KateRendererConfig::highlightedLineColor() const +{ + if (m_highlightedLineColorSet || isGlobal()) + return m_highlightedLineColor; + + return s_global->highlightedLineColor(); +} + +void KateRendererConfig::setHighlightedLineColor (const QColor &col) +{ + configStart (); + + m_highlightedLineColorSet = true; + m_highlightedLineColor = col; + + configEnd (); +} + +const QColor& KateRendererConfig::lineMarkerColor(KTextEditor::MarkInterface::MarkTypes type) const +{ + int index = 0; + if (type > 0) { while((type >> index++) ^ 1) {} } + index -= 1; + + if ( index < 0 || index >= KTextEditor::MarkInterface::reservedMarkersCount() ) + { + static QColor dummy; + return dummy; + } + + if (m_lineMarkerColorSet[index] || isGlobal()) + return m_lineMarkerColor[index]; + + return s_global->lineMarkerColor( type ); +} + +void KateRendererConfig::setLineMarkerColor (const QColor &col, KTextEditor::MarkInterface::MarkTypes type) +{ + int index = static_cast<int>( log(static_cast<double>(type)) / log(2.0) ); + Q_ASSERT( index >= 0 && index < KTextEditor::MarkInterface::reservedMarkersCount() ); + configStart (); + + m_lineMarkerColorSet[index] = true; + m_lineMarkerColor[index] = col; + + configEnd (); +} + +const QColor& KateRendererConfig::highlightedBracketColor() const +{ + if (m_highlightedBracketColorSet || isGlobal()) + return m_highlightedBracketColor; + + return s_global->highlightedBracketColor(); +} + +void KateRendererConfig::setHighlightedBracketColor (const QColor &col) +{ + configStart (); + + m_highlightedBracketColorSet = true; + m_highlightedBracketColor = col; + + configEnd (); +} + +const QColor& KateRendererConfig::wordWrapMarkerColor() const +{ + if (m_wordWrapMarkerColorSet || isGlobal()) + return m_wordWrapMarkerColor; + + return s_global->wordWrapMarkerColor(); +} + +void KateRendererConfig::setWordWrapMarkerColor (const QColor &col) +{ + configStart (); + + m_wordWrapMarkerColorSet = true; + m_wordWrapMarkerColor = col; + + configEnd (); +} + +const QColor& KateRendererConfig::tabMarkerColor() const +{ + if (m_tabMarkerColorSet || isGlobal()) + return m_tabMarkerColor; + + return s_global->tabMarkerColor(); +} + +void KateRendererConfig::setTabMarkerColor (const QColor &col) +{ + configStart (); + + m_tabMarkerColorSet = true; + m_tabMarkerColor = col; + + configEnd (); +} + +const QColor& KateRendererConfig::iconBarColor() const +{ + if (m_iconBarColorSet || isGlobal()) + return m_iconBarColor; + + return s_global->iconBarColor(); +} + +void KateRendererConfig::setIconBarColor (const QColor &col) +{ + configStart (); + + m_iconBarColorSet = true; + m_iconBarColor = col; + + configEnd (); +} + +const QColor& KateRendererConfig::lineNumberColor() const +{ + if (m_lineNumberColorSet || isGlobal()) + return m_lineNumberColor; + + return s_global->lineNumberColor(); +} + +void KateRendererConfig::setLineNumberColor (const QColor &col) +{ + configStart (); + + m_lineNumberColorSet = true; + m_lineNumberColor = col; + + configEnd (); +} + +bool KateRendererConfig::showIndentationLines () const +{ + if (m_showIndentationLinesSet || isGlobal()) + return m_showIndentationLines; + + return s_global->showIndentationLines(); +} + +void KateRendererConfig::setShowIndentationLines (bool on) +{ + configStart (); + + m_showIndentationLinesSet = true; + m_showIndentationLines = on; + + configEnd (); +} + +//END + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateconfig.h b/kate/part/kateconfig.h new file mode 100644 index 000000000..e81234826 --- /dev/null +++ b/kate/part/kateconfig.h @@ -0,0 +1,537 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Christoph Cullmann <cullmann@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __KATE_CONFIG_H__ +#define __KATE_CONFIG_H__ + +#include <ktexteditor/markinterface.h> + +#include <qbitarray.h> +#include <qcolor.h> +#include <qobject.h> +#include <qvaluevector.h> + +class KateView; +class KateDocument; +class KateRenderer; +class KateFontStruct; +class KateFontMetrics; + +class KConfig; + +class QFont; +class QTextCodec; + +/** + * Base Class for the Kate Config Classes + */ +class KateConfig +{ + public: + /** + * Default Constructor + */ + KateConfig (); + + /** + * Virtual Destructor + */ + virtual ~KateConfig (); + + public: + /** + * start some config changes + * this method is needed to init some kind of transaction + * for config changes, update will only be done once, at + * configEnd() call + */ + void configStart (); + + /** + * end a config change transaction, update the concerned + * documents/views/renderers + */ + void configEnd (); + + protected: + /** + * do the real update + */ + virtual void updateConfig () = 0; + + private: + /** + * recursion depth + */ + uint configSessionNumber; + + /** + * is a config session running + */ + bool configIsRunning; +}; + +class KateDocumentConfig : public KateConfig +{ + private: + friend class KateFactory; + + /** + * only used in KateFactory for the static global fallback !!! + */ + KateDocumentConfig (); + + public: + /** + * Construct a DocumentConfig + */ + KateDocumentConfig (KateDocument *doc); + + /** + * Cu DocumentConfig + */ + ~KateDocumentConfig (); + + inline static KateDocumentConfig *global () { return s_global; } + + inline bool isGlobal () const { return (this == global()); } + + public: + /** + * Read config from object + */ + void readConfig (KConfig *config); + + /** + * Write config to object + */ + void writeConfig (KConfig *config); + + protected: + void updateConfig (); + + public: + int tabWidth () const; + void setTabWidth (int tabWidth); + + int indentationWidth () const; + void setIndentationWidth (int indentationWidth); + + enum IndentationMode + { + imNone = 0, + imNormal = 1, + imCStyle = 2, + imPythonStyle = 3, + imXmlStyle = 4, + imCSAndS = 5, + imVarIndent = 6, + imScriptIndent = 7 + }; + + uint indentationMode () const; + void setIndentationMode (uint identationMode); + + bool wordWrap () const; + void setWordWrap (bool on); + + unsigned int wordWrapAt () const; + void setWordWrapAt (unsigned int col); + + uint undoSteps () const; + void setUndoSteps ( uint undoSteps ); + + bool pageUpDownMovesCursor () const; + void setPageUpDownMovesCursor (bool on); + + enum ConfigFlags + { + cfBackspaceIndents= 0x2, + cfWordWrap= 0x4, + cfRemoveSpaces = 0x10, + cfWrapCursor= 0x20, + cfAutoBrackets= 0x40, + cfTabIndentsMode = 0x200, + cfOvr= 0x1000, + cfKeepIndentProfile= 0x8000, + cfKeepExtraSpaces= 0x10000, + cfTabIndents= 0x80000, + cfShowTabs= 0x200000, + cfSpaceIndent= 0x400000, + cfSmartHome = 0x800000, + cfTabInsertsTab = 0x1000000, + cfReplaceTabsDyn= 0x2000000, + cfRemoveTrailingDyn=0x4000000, + cfDoxygenAutoTyping=0x8000000 , // Remove for KDE 4.0 (put in indenters) + cfMixedIndent = 0x10000000, + cfIndentPastedText = 0x20000000 + }; + + uint configFlags () const; + void setConfigFlags (KateDocumentConfig::ConfigFlags flag, bool enable); + void setConfigFlags (uint fullFlags); + + const QString &encoding () const; + QTextCodec *codec (); + + void setEncoding (const QString &encoding); + + bool isSetEncoding () const; + + enum Eol + { + eolUnix = 0, + eolDos = 1, + eolMac = 2 + }; + + int eol () const; + QString eolString (); + + void setEol (int mode); + + bool allowEolDetection () const; + void setAllowEolDetection (bool on); + + enum BackupFlags + { + LocalFiles=1, + RemoteFiles=2 + }; + + uint backupFlags () const; + void setBackupFlags (uint flags); + + const QString &backupPrefix () const; + void setBackupPrefix (const QString &prefix); + + const QString &backupSuffix () const; + void setBackupSuffix (const QString &suffix); + + bool plugin (uint index) const; + void setPlugin (uint index, bool load); + + /** + * Should Kate Part search for dir wide config file + * and if, how depth? + * @return search depth (< 0 no search) + */ + int searchDirConfigDepth () const; + + void setSearchDirConfigDepth (int depth); + + private: + int m_tabWidth; + int m_indentationWidth; + uint m_indentationMode; + bool m_wordWrap; + int m_wordWrapAt; + uint m_undoSteps; + bool m_pageUpDownMovesCursor; + uint m_configFlags; + QString m_encoding; + int m_eol; + bool m_allowEolDetection; + uint m_backupFlags; + int m_searchDirConfigDepth; + QString m_backupPrefix; + QString m_backupSuffix; + QBitArray m_plugins; + + bool m_tabWidthSet : 1; + bool m_indentationWidthSet : 1; + bool m_indentationModeSet : 1; + bool m_wordWrapSet : 1; + bool m_wordWrapAtSet : 1; + bool m_pageUpDownMovesCursorSet : 1; + bool m_undoStepsSet : 1; + uint m_configFlagsSet; + bool m_encodingSet : 1; + bool m_eolSet : 1; + bool m_allowEolDetectionSet : 1; + bool m_backupFlagsSet : 1; + bool m_searchDirConfigDepthSet : 1; + bool m_backupPrefixSet : 1; + bool m_backupSuffixSet : 1; + QBitArray m_pluginsSet; + + private: + static KateDocumentConfig *s_global; + KateDocument *m_doc; +}; + +class KateViewConfig : public KateConfig +{ + private: + friend class KateFactory; + + /** + * only used in KateFactory for the static global fallback !!! + */ + KateViewConfig (); + + public: + /** + * Construct a DocumentConfig + */ + KateViewConfig (KateView *view); + + /** + * Cu DocumentConfig + */ + ~KateViewConfig (); + + inline static KateViewConfig *global () { return s_global; } + + inline bool isGlobal () const { return (this == global()); } + + public: + /** + * Read config from object + */ + void readConfig (KConfig *config); + + /** + * Write config to object + */ + void writeConfig (KConfig *config); + + protected: + void updateConfig (); + + public: + bool dynWordWrap () const; + void setDynWordWrap (bool wrap); + + int dynWordWrapIndicators () const; + void setDynWordWrapIndicators (int mode); + + int dynWordWrapAlignIndent () const; + void setDynWordWrapAlignIndent (int indent); + + bool lineNumbers () const; + void setLineNumbers (bool on); + + bool scrollBarMarks () const; + void setScrollBarMarks (bool on); + + bool iconBar () const; + void setIconBar (bool on); + + bool foldingBar () const; + void setFoldingBar (bool on); + + int bookmarkSort () const; + void setBookmarkSort (int mode); + + int autoCenterLines() const; + void setAutoCenterLines (int lines); + + long searchFlags () const; + void setSearchFlags (long flags); + + bool cmdLine () const; + void setCmdLine (bool on); + + uint defaultMarkType () const; + void setDefaultMarkType (uint type); + + bool persistentSelection () const; + void setPersistentSelection (bool on); + + enum TextToSearch + { + Nowhere = 0, + SelectionOnly = 1, + SelectionWord = 2, + WordOnly = 3, + WordSelection = 4 + }; + + int textToSearchMode () const; + void setTextToSearchMode (int mode); + + private: + bool m_dynWordWrap; + int m_dynWordWrapIndicators; + int m_dynWordWrapAlignIndent; + bool m_lineNumbers; + bool m_scrollBarMarks; + bool m_iconBar; + bool m_foldingBar; + int m_bookmarkSort; + int m_autoCenterLines; + long m_searchFlags; + bool m_cmdLine; + uint m_defaultMarkType; + bool m_persistentSelection; + int m_textToSearchMode; + + bool m_dynWordWrapSet : 1; + bool m_dynWordWrapIndicatorsSet : 1; + bool m_dynWordWrapAlignIndentSet : 1; + bool m_lineNumbersSet : 1; + bool m_scrollBarMarksSet : 1; + bool m_iconBarSet : 1; + bool m_foldingBarSet : 1; + bool m_bookmarkSortSet : 1; + bool m_autoCenterLinesSet : 1; + bool m_searchFlagsSet : 1; + bool m_cmdLineSet : 1; + bool m_defaultMarkTypeSet : 1; + bool m_persistentSelectionSet : 1; + bool m_textToSearchModeSet : 1; + + private: + static KateViewConfig *s_global; + KateView *m_view; +}; + +class KateRendererConfig : public KateConfig +{ + private: + friend class KateFactory; + + /** + * only used in KateFactory for the static global fallback !!! + */ + KateRendererConfig (); + + + public: + /** + * Construct a DocumentConfig + */ + KateRendererConfig (KateRenderer *renderer); + + /** + * Cu DocumentConfig + */ + ~KateRendererConfig (); + + inline static KateRendererConfig *global () { return s_global; } + + inline bool isGlobal () const { return (this == global()); } + + public: + /** + * Read config from object + */ + void readConfig (KConfig *config); + + /** + * Write config to object + */ + void writeConfig (KConfig *config); + + protected: + void updateConfig (); + + public: + uint schema () const; + void setSchema (uint schema); + /** + * Reload the schema from the schema manager. + * For the global instance, have all other instances reload. + * Used by the schema config page to apply changes. + */ + void reloadSchema(); + + KateFontStruct *fontStruct (); + QFont *font(); + KateFontMetrics *fontMetrics(); + + void setFont(const QFont &font); + + bool wordWrapMarker () const; + void setWordWrapMarker (bool on); + + const QColor& backgroundColor() const; + void setBackgroundColor (const QColor &col); + + const QColor& selectionColor() const; + void setSelectionColor (const QColor &col); + + const QColor& highlightedLineColor() const; + void setHighlightedLineColor (const QColor &col); + + const QColor& lineMarkerColor(KTextEditor::MarkInterface::MarkTypes type = KTextEditor::MarkInterface::markType01) const; // markType01 == Bookmark + void setLineMarkerColor (const QColor &col, KTextEditor::MarkInterface::MarkTypes type = KTextEditor::MarkInterface::markType01); + + const QColor& highlightedBracketColor() const; + void setHighlightedBracketColor (const QColor &col); + + const QColor& wordWrapMarkerColor() const; + void setWordWrapMarkerColor (const QColor &col); + + const QColor& tabMarkerColor() const; + void setTabMarkerColor (const QColor &col); + + const QColor& iconBarColor() const; + void setIconBarColor (const QColor &col); + + // the line number color is used for the line numbers on the left bar and + // for vertical separator lines and for code folding lines. + const QColor& lineNumberColor() const; + void setLineNumberColor (const QColor &col); + + bool showIndentationLines () const; + void setShowIndentationLines (bool on); + + private: + /** + * Read the schema properties from the config file. + */ + void setSchemaInternal(int schema); + + uint m_schema; + KateFontStruct *m_font; + bool m_wordWrapMarker; + bool m_showIndentationLines; + QColor m_backgroundColor; + QColor m_selectionColor; + QColor m_highlightedLineColor; + QColor m_highlightedBracketColor; + QColor m_wordWrapMarkerColor; + QColor m_tabMarkerColor; + QColor m_iconBarColor; + QColor m_lineNumberColor; + QValueVector<QColor> m_lineMarkerColor; + + bool m_schemaSet : 1; + bool m_fontSet : 1; + bool m_wordWrapMarkerSet : 1; + bool m_showIndentationLinesSet : 1; + bool m_backgroundColorSet : 1; + bool m_selectionColorSet : 1; + bool m_highlightedLineColorSet : 1; + bool m_highlightedBracketColorSet : 1; + bool m_wordWrapMarkerColorSet : 1; + bool m_tabMarkerColorSet : 1; + bool m_iconBarColorSet : 1; + bool m_lineNumberColorSet : 1; + QBitArray m_lineMarkerColorSet; + + private: + static KateRendererConfig *s_global; + KateRenderer *m_renderer; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katecursor.cpp b/kate/part/katecursor.cpp new file mode 100644 index 000000000..a67b7e475 --- /dev/null +++ b/kate/part/katecursor.cpp @@ -0,0 +1,192 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002 Christian Couder <christian@kdevelop.org> + Copyright (C) 2001, 2003 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "katecursor.h" + +#include "katedocument.h" +#include "katetextline.h" + +// +// KateDocCursor implementation +// + +KateDocCursor::KateDocCursor(KateDocument *doc) : KateTextCursor(), m_doc(doc) +{ +} + +KateDocCursor::KateDocCursor(int line, int col, KateDocument *doc) + : KateTextCursor(line, col), m_doc(doc) +{ +} + +bool KateDocCursor::validPosition(uint line, uint col) +{ + return line < m_doc->numLines() && (int)col <= m_doc->lineLength(line); +} + +bool KateDocCursor::validPosition() +{ + return validPosition(line(), col()); +} + +void KateDocCursor::position(uint *pline, uint *pcol) const +{ + if (pline) + *pline = (uint)line(); + + if (pcol) + *pcol = (uint)col(); +} + +bool KateDocCursor::setPosition(uint line, uint col) +{ + bool ok = validPosition(line, col); + + if(ok) + setPos(line, col); + + return ok; +} + +bool KateDocCursor::gotoNextLine() +{ + bool ok = (line() + 1 < (int)m_doc->numLines()); + + if (ok) { + m_line++; + m_col = 0; + } + + return ok; +} + +bool KateDocCursor::gotoPreviousLine() +{ + bool ok = (line() > 0); + + if (ok) { + m_line--; + m_col = 0; + } + + return ok; +} + +bool KateDocCursor::gotoEndOfNextLine() +{ + bool ok = gotoNextLine(); + if(ok) + m_col = m_doc->lineLength(line()); + + return ok; +} + +bool KateDocCursor::gotoEndOfPreviousLine() +{ + bool ok = gotoPreviousLine(); + if(ok) + m_col = m_doc->lineLength(line()); + + return ok; +} + +int KateDocCursor::nbCharsOnLineAfter() +{ + return ((int)m_doc->lineLength(line()) - col()); +} + +bool KateDocCursor::moveForward(uint nbChar) +{ + int nbCharLeft = nbChar - nbCharsOnLineAfter(); + + if(nbCharLeft > 0) { + return gotoNextLine() && moveForward((uint)nbCharLeft); + } else { + m_col += nbChar; + return true; + } +} + +bool KateDocCursor::moveBackward(uint nbChar) +{ + int nbCharLeft = nbChar - m_col; + if(nbCharLeft > 0) { + return gotoEndOfPreviousLine() && moveBackward((uint)nbCharLeft); + } else { + m_col -= nbChar; + return true; + } +} + +bool KateDocCursor::insertText(const QString& s) +{ + return m_doc->insertText(line(), col(), s); +} + +bool KateDocCursor::removeText(uint nbChar) +{ + // Get a cursor at the end of the removed area + KateDocCursor endCursor = *this; + endCursor.moveForward(nbChar); + + // Remove the text + return m_doc->removeText((uint)line(), (uint)col(), + (uint)endCursor.line(), (uint)endCursor.col()); +} + +QChar KateDocCursor::currentChar() const +{ + return m_doc->plainKateTextLine(line())->getChar(col()); +} + +uchar KateDocCursor::currentAttrib() const +{ + return m_doc->plainKateTextLine(line())->attribute(col()); +} + +bool KateDocCursor::nextNonSpaceChar() +{ + for(; m_line < (int)m_doc->numLines(); m_line++) { + m_col = m_doc->plainKateTextLine(line())->nextNonSpaceChar(col()); + if(m_col != -1) + return true; // Next non-space char found + m_col = 0; + } + // No non-space char found + setPos(-1, -1); + return false; +} + +bool KateDocCursor::previousNonSpaceChar() +{ + while (true) { + m_col = m_doc->plainKateTextLine(line())->previousNonSpaceChar(col()); + if(m_col != -1) return true; // Previous non-space char found + if(m_line == 0) return false; + --m_line; + m_col = m_doc->plainKateTextLine(m_line)->length(); + } + // No non-space char found + setPos(-1, -1); + return false; +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katecursor.h b/kate/part/katecursor.h new file mode 100644 index 000000000..20a86d5d9 --- /dev/null +++ b/kate/part/katecursor.h @@ -0,0 +1,250 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002 Christian Couder <christian@kdevelop.org> + Copyright (C) 2001, 2003 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef kate_cursor_h +#define kate_cursor_h + +#include "../interfaces/document.h" +#include <kdebug.h> + +class KateDocument; + +/** + Simple cursor class with no document pointer. +*/ +class KateTextCursor +{ + public: + KateTextCursor() : m_line(0), m_col(0) {}; + KateTextCursor(int line, int col) : m_line(line), m_col(col) {}; + virtual ~KateTextCursor () {}; + + friend bool operator==(const KateTextCursor& c1, const KateTextCursor& c2) + { return c1.m_line == c2.m_line && c1.m_col == c2.m_col; } + + friend bool operator!=(const KateTextCursor& c1, const KateTextCursor& c2) + { return !(c1 == c2); } + + friend bool operator>(const KateTextCursor& c1, const KateTextCursor& c2) + { return c1.m_line > c2.m_line || (c1.m_line == c2.m_line && c1.m_col > c2.m_col); } + + friend bool operator>=(const KateTextCursor& c1, const KateTextCursor& c2) + { return c1.m_line > c2.m_line || (c1.m_line == c2.m_line && c1.m_col >= c2.m_col); } + + friend bool operator<(const KateTextCursor& c1, const KateTextCursor& c2) + { return !(c1 >= c2); } + + friend bool operator<=(const KateTextCursor& c1, const KateTextCursor& c2) + { return !(c1 > c2); } + + friend kdbgstream& operator<<(kdbgstream& stream, const KateTextCursor& c) { + stream << c.m_line << "," << c.m_col; + return stream; + } + +#ifndef Q_WS_WIN //not needed + friend void qSwap(KateTextCursor & c1, KateTextCursor & c2) { + KateTextCursor tmp = c1; + c1 = c2; + c2 = tmp; + } +#endif + + inline void pos(int *pline, int *pcol) const { + if(pline) *pline = m_line; + if(pcol) *pcol = m_col; + } + + inline int line() const { return m_line; }; + inline int col() const { return m_col; }; + + virtual void setLine(int line) { m_line = line; }; + virtual void setCol(int col) { m_col = col; }; + virtual void setPos(const KateTextCursor& pos) { m_line = pos.line(); m_col = pos.col(); }; + virtual void setPos(int line, int col) { m_line = line; m_col = col; }; + + protected: + int m_line; + int m_col; +}; + +/** + Cursor class with a pointer to its document. +*/ +class KateDocCursor : public KateTextCursor +{ + public: + KateDocCursor(KateDocument *doc); + KateDocCursor(int line, int col, KateDocument *doc); + virtual ~KateDocCursor() {}; + + bool validPosition(uint line, uint col); + bool validPosition(); + + bool gotoNextLine(); + bool gotoPreviousLine(); + bool gotoEndOfNextLine(); + bool gotoEndOfPreviousLine(); + + int nbCharsOnLineAfter(); + bool moveForward(uint nbChar); + bool moveBackward(uint nbChar); + + // KTextEditor::Cursor interface + void position(uint *line, uint *col) const; + bool setPosition(uint line, uint col); + bool insertText(const QString& text); + bool removeText(uint numberOfCharacters); + QChar currentChar() const; + + uchar currentAttrib() const; + + /** + Find the position (line and col) of the next char + that is not a space. If found KateDocCursor points to the + found character. Otherwise to a invalid Position such that + validPosition() returns false. + @return True if the specified or a following character is not a space + Otherwise false. + */ + bool nextNonSpaceChar(); + + /** + Find the position (line and col) of the previous char + that is not a space. If found KateDocCursor points to the + found character. Otherwise to a invalid Position such that + validPosition() returns false. + @return True if the specified or a preceding character is not a space + Otherwise false. + */ + bool previousNonSpaceChar(); + + protected: + KateDocument *m_doc; +}; + +class KateRange +{ + public: + KateRange () {}; + virtual ~KateRange () {}; + + virtual bool isValid() const = 0; + virtual KateTextCursor& start() = 0; + virtual KateTextCursor& end() = 0; + virtual const KateTextCursor& start() const = 0; + virtual const KateTextCursor& end() const = 0; +}; + +class KateTextRange : public KateRange +{ + public: + KateTextRange() + : m_valid(false) + { + }; + + KateTextRange(int startline, int startcol, int endline, int endcol) + : m_start(startline, startcol) + , m_end(endline, endcol) + , m_valid(true) + { + normalize(); + }; + + KateTextRange(const KateTextCursor& start, const KateTextCursor& end) + : m_start(start) + , m_end(end) + , m_valid(true) + { + normalize(); + }; + + virtual ~KateTextRange () {}; + + virtual bool isValid() const { return m_valid; }; + void setValid(bool valid) { + m_valid = valid; + if( valid ) + normalize(); + }; + + virtual KateTextCursor& start() { return m_start; }; + virtual KateTextCursor& end() { return m_end; }; + virtual const KateTextCursor& start() const { return m_start; }; + virtual const KateTextCursor& end() const { return m_end; }; + + /* if range is not valid, the result is undefined + if cursor is before start -1 is returned, if cursor is within range 0 is returned if cursor is after end 1 is returned*/ + inline int cursorInRange(const KateTextCursor &cursor) const { + return ((cursor<m_start)?(-1):((cursor>m_end)?1:0)); + } + + inline void normalize() { + if( m_start > m_end ) + qSwap(m_start, m_end); + } + + protected: + KateTextCursor m_start, m_end; + bool m_valid; +}; + + +class KateBracketRange : public KateTextRange +{ + public: + KateBracketRange() + : KateTextRange() + , m_minIndent(0) + { + }; + + KateBracketRange(int startline, int startcol, int endline, int endcol, int minIndent) + : KateTextRange(startline, startcol, endline, endcol) + , m_minIndent(minIndent) + { + }; + + KateBracketRange(const KateTextCursor& start, const KateTextCursor& end, int minIndent) + : KateTextRange(start, end) + , m_minIndent(minIndent) + { + }; + + int getMinIndent() const + { + return m_minIndent; + } + + void setIndentMin(int m) + { + m_minIndent = m; + } + + protected: + int m_minIndent; +}; + + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katedialogs.cpp b/kate/part/katedialogs.cpp new file mode 100644 index 000000000..91da42e8f --- /dev/null +++ b/kate/part/katedialogs.cpp @@ -0,0 +1,1740 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002, 2003 Anders Lund <anders.lund@lund.tdcadsl.dk> + Copyright (C) 2003 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + + Based on work of: + Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +//BEGIN Includes +#include "katedialogs.h" +#include "katedialogs.moc" + +#include "kateautoindent.h" +#include "katebuffer.h" +#include "kateconfig.h" +#include "katedocument.h" +#include "katefactory.h" +#include "kateschema.h" +#include "katesyntaxdocument.h" +#include "kateview.h" + + +#include <ktexteditor/configinterfaceextension.h> +#include <ktexteditor/plugin.h> + +#include <kio/job.h> +#include <kio/jobclasses.h> +#include <kio/netaccess.h> + +#include <kaccel.h> +#include <kapplication.h> +#include <kbuttonbox.h> +#include <kcharsets.h> +#include <kcolorbutton.h> +#include <kcolorcombo.h> +#include <kcolordialog.h> +#include <kcombobox.h> +#include <kconfig.h> +#include <kdebug.h> +#include <kfontdialog.h> +#include <kglobal.h> +#include <kglobalsettings.h> +#include <kiconloader.h> +#include <kkeybutton.h> +#include <kkeydialog.h> +#include <klineedit.h> +#include <klistview.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kmimetypechooser.h> +#include <knuminput.h> +#include <kparts/componentfactory.h> +#include <kpopupmenu.h> +#include <kprocess.h> +#include <kprocio.h> +#include <kregexpeditorinterface.h> +#include <krun.h> +#include <kseparator.h> +#include <kstandarddirs.h> +#include <ktempfile.h> + +#include <qbuttongroup.h> +#include <qcheckbox.h> +#include <qcombobox.h> +#include <qdialog.h> +#include <qdom.h> +#include <qfile.h> +#include <qgrid.h> +#include <qgroupbox.h> +#include <qhbox.h> +#include <qheader.h> +#include <qhgroupbox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qlineedit.h> +#include <qlistbox.h> +#include <qlistview.h> +#include <qmap.h> +#include <qobjectlist.h> +#include <qpainter.h> +#include <qpointarray.h> +#include <qptrcollection.h> +#include <qpushbutton.h> +#include <qradiobutton.h> +#include <qslider.h> +#include <qspinbox.h> +#include <qstringlist.h> +#include <qtabwidget.h> +#include <qtextcodec.h> +#include <qtoolbutton.h> +#include <qvbox.h> +#include <qvgroupbox.h> +#include <qwhatsthis.h> +#include <qwidgetstack.h> + +// trailing slash is important +#define HLDOWNLOADPATH "http://kate.kde.org/syntax/" + +//END + +//BEGIN KateConfigPage +KateConfigPage::KateConfigPage ( QWidget *parent, const char *name ) + : Kate::ConfigPage (parent, name) + , m_changed (false) +{ + connect (this, SIGNAL(changed()), this, SLOT(somethingHasChanged ())); +} + +KateConfigPage::~KateConfigPage () +{ +} + +void KateConfigPage::somethingHasChanged () +{ + m_changed = true; + kdDebug (13000) << "TEST: something changed on the config page: " << this << endl; +} +//END KateConfigPage + +//BEGIN KateIndentConfigTab +const int KateIndentConfigTab::flags[] = { + KateDocument::cfSpaceIndent, + KateDocument::cfKeepIndentProfile, + KateDocument::cfKeepExtraSpaces, + KateDocument::cfTabIndents, + KateDocument::cfBackspaceIndents, + KateDocumentConfig::cfDoxygenAutoTyping, + KateDocumentConfig::cfMixedIndent, + KateDocumentConfig::cfIndentPastedText +}; + +KateIndentConfigTab::KateIndentConfigTab(QWidget *parent) + : KateConfigPage(parent) +{ + QVBoxLayout *layout = new QVBoxLayout(this, 0, KDialog::spacingHint() ); + int configFlags = KateDocumentConfig::global()->configFlags(); + + QVGroupBox *gbAuto = new QVGroupBox(i18n("Automatic Indentation"), this); + + QHBox *indentLayout = new QHBox(gbAuto); + indentLayout->setSpacing(KDialog::spacingHint()); + QLabel *indentLabel = new QLabel(i18n("&Indentation mode:"), indentLayout); + m_indentMode = new KComboBox (indentLayout); + m_indentMode->insertStringList (KateAutoIndent::listModes()); + indentLabel->setBuddy(m_indentMode); + m_configPage = new QPushButton(SmallIconSet("configure"), i18n("Configure..."), indentLayout); + + opt[5] = new QCheckBox(i18n("Insert leading Doxygen \"*\" when typing"), gbAuto); + opt[7] = new QCheckBox(i18n("Adjust indentation of code pasted from the clipboard"), gbAuto); + + QVGroupBox *gbSpaces = new QVGroupBox(i18n("Indentation with Spaces"), this); + QVBox *spaceLayout = new QVBox(gbSpaces); + opt[0] = new QCheckBox(i18n("Use &spaces instead of tabs to indent"), spaceLayout ); + opt[6] = new QCheckBox(i18n("Emacs style mixed mode"), spaceLayout); + + indentationWidth = new KIntNumInput(KateDocumentConfig::global()->indentationWidth(), spaceLayout); + indentationWidth->setRange(1, 16, 1, false); + indentationWidth->setLabel(i18n("Number of spaces:"), AlignVCenter); + + opt[1] = new QCheckBox(i18n("Keep indent &profile"), this); + opt[2] = new QCheckBox(i18n("&Keep extra spaces"), this); + + QVGroupBox *keys = new QVGroupBox(i18n("Keys to Use"), this); + opt[3] = new QCheckBox(i18n("&Tab key indents"), keys); + opt[4] = new QCheckBox(i18n("&Backspace key indents"), keys); + + QRadioButton *rb1, *rb2, *rb3; + m_tabs = new QButtonGroup( 1, Qt::Horizontal, i18n("Tab Key Mode if Nothing Selected"), this ); + m_tabs->setRadioButtonExclusive( true ); + m_tabs->insert( rb1=new QRadioButton( i18n("Insert indent &characters"), m_tabs ), 0 ); + m_tabs->insert( rb2=new QRadioButton( i18n("I&nsert tab character"), m_tabs ), 1 ); + m_tabs->insert( rb3=new QRadioButton( i18n("Indent current &line"), m_tabs ), 2 ); + + opt[0]->setChecked(configFlags & flags[0]); + opt[1]->setChecked(configFlags & flags[1]); + opt[2]->setChecked(configFlags & flags[2]); + opt[3]->setChecked(configFlags & flags[3]); + opt[4]->setChecked(configFlags & flags[4]); + opt[5]->setChecked(configFlags & flags[5]); + opt[6]->setChecked(configFlags & flags[6]); + opt[7]->setChecked(configFlags & flags[7]); + + layout->addWidget(gbAuto); + layout->addWidget(gbSpaces); + layout->addWidget(opt[1]); + layout->addWidget(opt[2]); + layout->addWidget(keys); + layout->addWidget(m_tabs, 0); + + layout->addStretch(); + + // What is this? help + QWhatsThis::add(opt[0], i18n( + "Check this if you want to indent with spaces rather than tabs.")); + QWhatsThis::add(opt[2], i18n( + "Indentations of more than the selected number of spaces will not be " + "shortened.")); + QWhatsThis::add(opt[3], i18n( + "This allows the <b>Tab</b> key to be used to increase the indentation " + "level.")); + QWhatsThis::add(opt[4], i18n( + "This allows the <b>Backspace</b> key to be used to decrease the " + "indentation level.")); + QWhatsThis::add(opt[5], i18n( + "Automatically inserts a leading \"*\" while typing within a Doxygen " + "style comment.")); + QWhatsThis::add( opt[6], i18n( + "Use a mix of tab and space characters for indentation.") ); + QWhatsThis::add( opt[7], i18n( + "If this option is selected, pasted code from the clipboard is indented. " + "Triggering the <b>undo</b>-action removes the indentation.") ); + QWhatsThis::add(indentationWidth, i18n("The number of spaces to indent with.")); + + QWhatsThis::add(m_configPage, i18n( + "If this button is enabled, additional indenter specific options are " + "available and can be configured in an extra dialog.") ); + + reload (); + + // + // after initial reload, connect the stuff for the changed () signal + // + + connect(m_indentMode, SIGNAL(activated(int)), this, SLOT(slotChanged())); + connect(m_indentMode, SIGNAL(activated(int)), this, SLOT(indenterSelected(int))); + + connect( opt[0], SIGNAL(toggled(bool)), this, SLOT(somethingToggled())); + + connect( opt[0], SIGNAL( toggled(bool) ), this, SLOT( slotChanged() ) ); + connect( opt[1], SIGNAL( toggled(bool) ), this, SLOT( slotChanged() ) ); + connect( opt[2], SIGNAL( toggled(bool) ), this, SLOT( slotChanged() ) ); + connect( opt[3], SIGNAL( toggled(bool) ), this, SLOT( slotChanged() ) ); + connect( opt[4], SIGNAL( toggled(bool) ), this, SLOT( slotChanged() ) ); + connect( opt[5], SIGNAL( toggled(bool) ), this, SLOT( slotChanged() ) ); + connect( opt[6], SIGNAL( toggled(bool) ), this, SLOT( slotChanged() ) ); + connect( opt[7], SIGNAL( toggled(bool) ), this, SLOT( slotChanged() ) ); + + connect(indentationWidth, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); + + connect(rb1, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); + connect(rb2, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); + connect(rb3, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); + + connect(m_configPage, SIGNAL(clicked()), this, SLOT(configPage())); +} + +void KateIndentConfigTab::somethingToggled() { + indentationWidth->setEnabled(opt[0]->isChecked()); + opt[6]->setEnabled(opt[0]->isChecked()); +} + +void KateIndentConfigTab::indenterSelected (int index) +{ + if (index == KateDocumentConfig::imCStyle || index == KateDocumentConfig::imCSAndS) + opt[5]->setEnabled(true); + else + opt[5]->setEnabled(false); + + m_configPage->setEnabled( KateAutoIndent::hasConfigPage(index) ); +} + +void KateIndentConfigTab::configPage() +{ + uint index = m_indentMode->currentItem(); + if ( KateAutoIndent::hasConfigPage(index) ) + { + KDialogBase dlg(this, "indenter_config_dialog", true, i18n("Configure Indenter"), + KDialogBase::Ok|KDialogBase::Cancel, KDialogBase::Cancel, true); + + QVBox *box = new QVBox(&dlg); + box->setSpacing( KDialog::spacingHint() ); + dlg.setMainWidget(box); + new QLabel("<qt><b>" + KateAutoIndent::modeDescription(index) + "</b></qt>", box); + new KSeparator(KSeparator::HLine, box); + + IndenterConfigPage* page = KateAutoIndent::configPage(box, index); + + if (!page) return; + box->setStretchFactor(page, 1); + + connect( &dlg, SIGNAL(okClicked()), page, SLOT(apply()) ); + + dlg.resize(400, 300); + dlg.exec(); + } +} + +void KateIndentConfigTab::apply () +{ + // nothing changed, no need to apply stuff + if (!changed()) + return; + m_changed = false; + + KateDocumentConfig::global()->configStart (); + + int configFlags, z; + + configFlags = KateDocumentConfig::global()->configFlags(); + for (z = 0; z < numFlags; z++) { + configFlags &= ~flags[z]; + if (opt[z]->isChecked()) configFlags |= flags[z]; + } + + KateDocumentConfig::global()->setConfigFlags(configFlags); + KateDocumentConfig::global()->setIndentationWidth(indentationWidth->value()); + + KateDocumentConfig::global()->setIndentationMode(m_indentMode->currentItem()); + + KateDocumentConfig::global()->setConfigFlags (KateDocumentConfig::cfTabIndentsMode, 2 == m_tabs->id (m_tabs->selected())); + KateDocumentConfig::global()->setConfigFlags (KateDocumentConfig::cfTabInsertsTab, 1 == m_tabs->id (m_tabs->selected())); + + KateDocumentConfig::global()->configEnd (); +} + +void KateIndentConfigTab::reload () +{ + if (KateDocumentConfig::global()->configFlags() & KateDocumentConfig::cfTabIndentsMode) + m_tabs->setButton (2); + else if (KateDocumentConfig::global()->configFlags() & KateDocumentConfig::cfTabInsertsTab) + m_tabs->setButton (1); + else + m_tabs->setButton (0); + + m_indentMode->setCurrentItem (KateDocumentConfig::global()->indentationMode()); + + somethingToggled (); + indenterSelected (m_indentMode->currentItem()); +} +//END KateIndentConfigTab + +//BEGIN KateSelectConfigTab +const int KateSelectConfigTab::flags[] = {}; + +KateSelectConfigTab::KateSelectConfigTab(QWidget *parent) + : KateConfigPage(parent) +{ + int configFlags = KateDocumentConfig::global()->configFlags(); + + QVBoxLayout *layout = new QVBoxLayout(this, 0, KDialog::spacingHint() ); + + QVGroupBox *gbCursor = new QVGroupBox(i18n("Text Cursor Movement"), this); + + opt[0] = new QCheckBox(i18n("Smart ho&me and smart end"), gbCursor); + opt[0]->setChecked(configFlags & KateDocumentConfig::cfSmartHome); + connect(opt[0], SIGNAL(toggled(bool)), this, SLOT(slotChanged())); + + opt[1] = new QCheckBox(i18n("Wrap c&ursor"), gbCursor); + opt[1]->setChecked(configFlags & KateDocumentConfig::cfWrapCursor); + connect(opt[1], SIGNAL(toggled(bool)), this, SLOT(slotChanged())); + + e6 = new QCheckBox(i18n("&PageUp/PageDown moves cursor"), gbCursor); + e6->setChecked(KateDocumentConfig::global()->pageUpDownMovesCursor()); + connect(e6, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); + + e4 = new KIntNumInput(KateViewConfig::global()->autoCenterLines(), gbCursor); + e4->setRange(0, 1000000, 1, false); + e4->setLabel(i18n("Autocenter cursor (lines):"), AlignVCenter); + connect(e4, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); + + layout->addWidget(gbCursor); + + QRadioButton *rb1, *rb2; + + m_tabs = new QButtonGroup( 1, Qt::Horizontal, i18n("Selection Mode"), this ); + layout->add (m_tabs); + + m_tabs->setRadioButtonExclusive( true ); + m_tabs->insert( rb1=new QRadioButton( i18n("&Normal"), m_tabs ), 0 ); + m_tabs->insert( rb2=new QRadioButton( i18n("&Persistent"), m_tabs ), 1 ); + + layout->addStretch(); + + QWhatsThis::add(rb1, i18n( + "Selections will be overwritten by typed text and will be lost on " + "cursor movement.")); + QWhatsThis::add(rb2, i18n( + "Selections will stay even after cursor movement and typing.")); + + QWhatsThis::add(e4, i18n( + "Sets the number of lines to maintain visible above and below the " + "cursor when possible.")); + + QWhatsThis::add(opt[0], i18n( + "When selected, pressing the home key will cause the cursor to skip " + "whitespace and go to the start of a line's text. " + "The same applies for the end key.")); + + QWhatsThis::add(opt[1], i18n( + "When on, moving the insertion cursor using the <b>Left</b> and " + "<b>Right</b> keys will go on to previous/next line at beginning/end of " + "the line, similar to most editors.<p>When off, the insertion cursor " + "cannot be moved left of the line start, but it can be moved off the " + "line end, which can be very handy for programmers.")); + + QWhatsThis::add(e6, i18n("Selects whether the PageUp and PageDown keys should alter the vertical position of the cursor relative to the top of the view.")); + + + reload (); + + // + // after initial reload, connect the stuff for the changed () signal + // + + connect(rb1, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); + connect(rb2, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); +} + +void KateSelectConfigTab::apply () +{ + // nothing changed, no need to apply stuff + if (!changed()) + return; + m_changed = false; + + KateViewConfig::global()->configStart (); + KateDocumentConfig::global()->configStart (); + + int configFlags = KateDocumentConfig::global()->configFlags(); + + configFlags &= ~KateDocumentConfig::cfSmartHome; + configFlags &= ~KateDocumentConfig::cfWrapCursor; + + if (opt[0]->isChecked()) configFlags |= KateDocumentConfig::cfSmartHome; + if (opt[1]->isChecked()) configFlags |= KateDocumentConfig::cfWrapCursor; + + KateDocumentConfig::global()->setConfigFlags(configFlags); + + KateViewConfig::global()->setAutoCenterLines(kMax(0, e4->value())); + KateDocumentConfig::global()->setPageUpDownMovesCursor(e6->isChecked()); + + KateViewConfig::global()->setPersistentSelection (m_tabs->id (m_tabs->selected()) == 1); + + KateDocumentConfig::global()->configEnd (); + KateViewConfig::global()->configEnd (); +} + +void KateSelectConfigTab::reload () +{ + if (KateViewConfig::global()->persistentSelection()) + m_tabs->setButton (1); + else + m_tabs->setButton (0); +} +//END KateSelectConfigTab + +//BEGIN KateEditConfigTab +const int KateEditConfigTab::flags[] = {KateDocument::cfWordWrap, + KateDocument::cfAutoBrackets, KateDocument::cfShowTabs, + KateDocumentConfig::cfReplaceTabsDyn, KateDocumentConfig::cfRemoveTrailingDyn}; + +KateEditConfigTab::KateEditConfigTab(QWidget *parent) + : KateConfigPage(parent) +{ + QVBoxLayout *mainLayout = new QVBoxLayout(this, 0, KDialog::spacingHint() ); + int configFlags = KateDocumentConfig::global()->configFlags(); + + QVGroupBox *gbWhiteSpace = new QVGroupBox(i18n("Tabulators"), this); + + opt[3] = new QCheckBox( i18n("&Insert spaces instead of tabulators"), gbWhiteSpace ); + opt[3]->setChecked( configFlags & KateDocumentConfig::cfReplaceTabsDyn ); + connect( opt[3], SIGNAL(toggled(bool)), this, SLOT(slotChanged()) ); + + opt[2] = new QCheckBox(i18n("&Show tabulators"), gbWhiteSpace); + opt[2]->setChecked(configFlags & flags[2]); + connect(opt[2], SIGNAL(toggled(bool)), this, SLOT(slotChanged())); + + e2 = new KIntNumInput(KateDocumentConfig::global()->tabWidth(), gbWhiteSpace); + e2->setRange(1, 16, 1, false); + e2->setLabel(i18n("Tab width:"), AlignVCenter); + connect(e2, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); + + mainLayout->addWidget(gbWhiteSpace); + + QVGroupBox *gbWordWrap = new QVGroupBox(i18n("Static Word Wrap"), this); + + opt[0] = new QCheckBox(i18n("Enable static &word wrap"), gbWordWrap); + opt[0]->setChecked(KateDocumentConfig::global()->wordWrap()); + connect(opt[0], SIGNAL(toggled(bool)), this, SLOT(slotChanged())); + + m_wwmarker = new QCheckBox( i18n("&Show static word wrap marker (if applicable)"), gbWordWrap ); + m_wwmarker->setChecked( KateRendererConfig::global()->wordWrapMarker() ); + connect(m_wwmarker, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); + + e1 = new KIntNumInput(KateDocumentConfig::global()->wordWrapAt(), gbWordWrap); + e1->setRange(20, 200, 1, false); + e1->setLabel(i18n("Wrap words at:"), AlignVCenter); + connect(e1, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); + + mainLayout->addWidget(gbWordWrap); + + opt[4] = new QCheckBox( i18n("Remove &trailing spaces"), this ); + mainLayout->addWidget( opt[4] ); + opt[4]->setChecked( configFlags & KateDocumentConfig::cfRemoveTrailingDyn ); + connect( opt[4], SIGNAL(toggled(bool)), this, SLOT(slotChanged()) ); + + opt[1] = new QCheckBox(i18n("Auto &brackets"), this); + mainLayout->addWidget(opt[1]); + opt[1]->setChecked(configFlags & flags[1]); + connect(opt[1], SIGNAL(toggled(bool)), this, SLOT(slotChanged())); + + e3 = new KIntNumInput(e2, KateDocumentConfig::global()->undoSteps(), this); + e3->setRange(0, 1000000, 1, false); + e3->setSpecialValueText( i18n("Unlimited") ); + e3->setLabel(i18n("Maximum undo steps:"), AlignVCenter); + mainLayout->addWidget(e3); + connect(e3, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); + + QHBoxLayout *e5Layout = new QHBoxLayout(mainLayout); + QLabel *e5Label = new QLabel(i18n("Smart search t&ext from:"), this); + e5Layout->addWidget(e5Label); + e5 = new KComboBox (this); + e5->insertItem( i18n("Nowhere") ); + e5->insertItem( i18n("Selection Only") ); + e5->insertItem( i18n("Selection, then Current Word") ); + e5->insertItem( i18n("Current Word Only") ); + e5->insertItem( i18n("Current Word, then Selection") ); + e5->setCurrentItem(KateViewConfig::global()->textToSearchMode()); + e5Layout->addWidget(e5); + e5Label->setBuddy(e5); + connect(e5, SIGNAL(activated(int)), this, SLOT(slotChanged())); + + mainLayout->addStretch(); + + // What is this? help + QWhatsThis::add(opt[0], i18n( + "Automatically start a new line of text when the current line exceeds " + "the length specified by the <b>Wrap words at:</b> option." + "<p>This option does not wrap existing lines of text - use the <b>Apply " + "Static Word Wrap</b> option in the <b>Tools</b> menu for that purpose." + "<p>If you want lines to be <i>visually wrapped</i> instead, according " + "to the width of the view, enable <b>Dynamic Word Wrap</b> in the " + "<b>View Defaults</b> config page.")); + QWhatsThis::add(e1, i18n( + "If the Word Wrap option is selected this entry determines the length " + "(in characters) at which the editor will automatically start a new line.")); + QWhatsThis::add(opt[1], i18n( + "When the user types a left bracket ([,(, or {) KateView automatically " + "enters the right bracket (}, ), or ]) to the right of the cursor.")); + QWhatsThis::add(opt[2], i18n( + "The editor will display a symbol to indicate the presence of a tab in " + "the text.")); + + QWhatsThis::add(e3, i18n( + "Sets the number of undo/redo steps to record. More steps uses more memory.")); + + QString gstfwt = i18n( + "This determines where KateView will get the search text from " + "(this will be automatically entered into the Find Text dialog): " + "<br>" + "<ul>" + "<li><b>Nowhere:</b> Don't guess the search text." + "</li>" + "<li><b>Selection Only:</b> Use the current text selection, " + "if available." + "</li>" + "<li><b>Selection, then Current Word:</b> Use the current " + "selection if available, otherwise use the current word." + "</li>" + "<li><b>Current Word Only:</b> Use the word that the cursor " + "is currently resting on, if available." + "</li>" + "<li><b>Current Word, then Selection:</b> Use the current " + "word if available, otherwise use the current selection." + "</li>" + "</ul>" + "Note that, in all the above modes, if a search string has " + "not been or cannot be determined, then the Find Text Dialog " + "will fall back to the last search text."); + QWhatsThis::add(e5Label, gstfwt); + QWhatsThis::add(e5, gstfwt); + QWhatsThis::add( opt[3], i18n( + "If this is enabled, the editor will calculate the number of spaces up to " + "the next tab position as defined by the tab width, and insert that number " + "of spaces instead of a TAB character." ) ); + QWhatsThis::add( opt[4], i18n( + "If this is enabled, the editor will remove any trailing whitespace on " + "lines when they are left by the insertion cursor.") ); + QWhatsThis::add( m_wwmarker, i18n( + "<p>If this option is checked, a vertical line will be drawn at the word " + "wrap column as defined in the <strong>Editing</strong> properties." + "<p>Note that the word wrap marker is only drawn if you use a fixed " + "pitch font." )); +} + +void KateEditConfigTab::apply () +{ + // nothing changed, no need to apply stuff + if (!changed()) + return; + m_changed = false; + + KateViewConfig::global()->configStart (); + KateDocumentConfig::global()->configStart (); + + int configFlags, z; + + configFlags = KateDocumentConfig::global()->configFlags(); + for (z = 1; z < numFlags; z++) { + configFlags &= ~flags[z]; + if (opt[z]->isChecked()) configFlags |= flags[z]; + } + KateDocumentConfig::global()->setConfigFlags(configFlags); + + KateDocumentConfig::global()->setWordWrapAt(e1->value()); + KateDocumentConfig::global()->setWordWrap (opt[0]->isChecked()); + KateDocumentConfig::global()->setTabWidth(e2->value()); + + if (e3->value() <= 0) + KateDocumentConfig::global()->setUndoSteps(0); + else + KateDocumentConfig::global()->setUndoSteps(e3->value()); + + KateViewConfig::global()->setTextToSearchMode(e5->currentItem()); + + KateRendererConfig::global()->setWordWrapMarker (m_wwmarker->isChecked()); + + KateDocumentConfig::global()->configEnd (); + KateViewConfig::global()->configEnd (); +} + +void KateEditConfigTab::reload () +{ +} +//END KateEditConfigTab + +//BEGIN KateViewDefaultsConfig +KateViewDefaultsConfig::KateViewDefaultsConfig(QWidget *parent) + :KateConfigPage(parent) +{ + QRadioButton *rb1; + QRadioButton *rb2; + + QVBoxLayout *blay=new QVBoxLayout(this,0,KDialog::spacingHint()); + + QVGroupBox *gbWordWrap = new QVGroupBox(i18n("Word Wrap"), this); + + m_dynwrap=new QCheckBox(i18n("&Dynamic word wrap"),gbWordWrap); + + QHBox *m_dynwrapIndicatorsLay = new QHBox (gbWordWrap); + m_dynwrapIndicatorsLabel = new QLabel( i18n("Dynamic word wrap indicators (if applicable):"), m_dynwrapIndicatorsLay ); + m_dynwrapIndicatorsCombo = new KComboBox( m_dynwrapIndicatorsLay ); + m_dynwrapIndicatorsCombo->insertItem( i18n("Off") ); + m_dynwrapIndicatorsCombo->insertItem( i18n("Follow Line Numbers") ); + m_dynwrapIndicatorsCombo->insertItem( i18n("Always On") ); + m_dynwrapIndicatorsLabel->setBuddy(m_dynwrapIndicatorsCombo); + + m_dynwrapAlignLevel = new KIntNumInput(gbWordWrap); + m_dynwrapAlignLevel->setLabel(i18n("Vertically align dynamically wrapped lines to indentation depth:")); + m_dynwrapAlignLevel->setRange(0, 80, 10); + // xgettext:no-c-format + m_dynwrapAlignLevel->setSuffix(i18n("% of View Width")); + m_dynwrapAlignLevel->setSpecialValueText(i18n("Disabled")); + + blay->addWidget(gbWordWrap); + + QVGroupBox *gbFold = new QVGroupBox(i18n("Code Folding"), this); + + m_folding=new QCheckBox(i18n("Show &folding markers (if available)"), gbFold ); + m_collapseTopLevel = new QCheckBox( i18n("Collapse toplevel folding nodes"), gbFold ); + m_collapseTopLevel->hide (); + + blay->addWidget(gbFold); + + QVGroupBox *gbBar = new QVGroupBox(i18n("Borders"), this); + + m_icons=new QCheckBox(i18n("Show &icon border"),gbBar); + m_line=new QCheckBox(i18n("Show &line numbers"),gbBar); + m_scrollBarMarks=new QCheckBox(i18n("Show &scrollbar marks"),gbBar); + + blay->addWidget(gbBar); + + m_bmSort = new QButtonGroup( 1, Qt::Horizontal, i18n("Sort Bookmarks Menu"), this ); + m_bmSort->setRadioButtonExclusive( true ); + m_bmSort->insert( rb1=new QRadioButton( i18n("By &position"), m_bmSort ), 0 ); + m_bmSort->insert( rb2=new QRadioButton( i18n("By c&reation"), m_bmSort ), 1 ); + + blay->addWidget(m_bmSort, 0 ); + + m_showIndentLines = new QCheckBox(i18n("Show indentation lines"), this); + m_showIndentLines->setChecked(KateRendererConfig::global()->showIndentationLines()); + blay->addWidget(m_showIndentLines); + + blay->addStretch(1000); + + QWhatsThis::add(m_dynwrap,i18n( + "If this option is checked, the text lines will be wrapped at the view " + "border on the screen.")); + QString wtstr = i18n("Choose when the Dynamic Word Wrap Indicators should be displayed"); + QWhatsThis::add(m_dynwrapIndicatorsLabel, wtstr); + QWhatsThis::add(m_dynwrapIndicatorsCombo, wtstr); + // xgettext:no-c-format + QWhatsThis::add(m_dynwrapAlignLevel, i18n( + "<p>Enables the start of dynamically wrapped lines to be aligned " + "vertically to the indentation level of the first line. This can help " + "to make code and markup more readable.</p><p>Additionally, this allows " + "you to set a maximum width of the screen, as a percentage, after which " + "dynamically wrapped lines will no longer be vertically aligned. For " + "example, at 50%, lines whose indentation levels are deeper than 50% of " + "the width of the screen will not have vertical alignment applied to " + "subsequent wrapped lines.</p>")); + QWhatsThis::add(m_line,i18n( + "If this option is checked, every new view will display line numbers " + "on the left hand side.")); + QWhatsThis::add(m_icons,i18n( + "If this option is checked, every new view will display an icon border " + "on the left hand side.<br><br>The icon border shows bookmark signs, " + "for instance.")); + QWhatsThis::add(m_scrollBarMarks,i18n( + "If this option is checked, every new view will show marks on the " + "vertical scrollbar.<br><br>These marks will, for instance, show " + "bookmarks.")); + QWhatsThis::add(m_folding,i18n( + "If this option is checked, every new view will display marks for code " + "folding, if code folding is available.")); + QWhatsThis::add(m_bmSort,i18n( + "Choose how the bookmarks should be ordered in the <b>Bookmarks</b> menu.")); + QWhatsThis::add(rb1,i18n( + "The bookmarks will be ordered by the line numbers they are placed at.")); + QWhatsThis::add(rb2,i18n( + "Each new bookmark will be added to the bottom, independently from " + "where it is placed in the document.")); + QWhatsThis::add(m_showIndentLines, i18n( + "If this is enabled, the editor will display vertical lines to help " + "identify indent lines.") ); + + reload(); + + // + // after initial reload, connect the stuff for the changed () signal + // + + connect(m_dynwrap, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); + connect(m_dynwrapIndicatorsCombo, SIGNAL(activated(int)), this, SLOT(slotChanged())); + connect(m_dynwrapAlignLevel, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); + connect(m_icons, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); + connect(m_scrollBarMarks, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); + connect(m_line, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); + connect(m_folding, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); + connect(m_collapseTopLevel, SIGNAL(toggled(bool)), this, SLOT(slotChanged()) ); + connect(rb1, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); + connect(rb2, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); + connect(m_showIndentLines, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); +} + +KateViewDefaultsConfig::~KateViewDefaultsConfig() +{ +} + +void KateViewDefaultsConfig::apply () +{ + // nothing changed, no need to apply stuff + if (!changed()) + return; + m_changed = false; + + KateViewConfig::global()->configStart (); + KateRendererConfig::global()->configStart (); + + KateViewConfig::global()->setDynWordWrap (m_dynwrap->isChecked()); + KateViewConfig::global()->setDynWordWrapIndicators (m_dynwrapIndicatorsCombo->currentItem ()); + KateViewConfig::global()->setDynWordWrapAlignIndent(m_dynwrapAlignLevel->value()); + KateViewConfig::global()->setLineNumbers (m_line->isChecked()); + KateViewConfig::global()->setIconBar (m_icons->isChecked()); + KateViewConfig::global()->setScrollBarMarks (m_scrollBarMarks->isChecked()); + KateViewConfig::global()->setFoldingBar (m_folding->isChecked()); + KateViewConfig::global()->setBookmarkSort (m_bmSort->id (m_bmSort->selected())); + + KateRendererConfig::global()->setShowIndentationLines(m_showIndentLines->isChecked()); + + KateRendererConfig::global()->configEnd (); + KateViewConfig::global()->configEnd (); +} + +void KateViewDefaultsConfig::reload () +{ + m_dynwrap->setChecked(KateViewConfig::global()->dynWordWrap()); + m_dynwrapIndicatorsCombo->setCurrentItem( KateViewConfig::global()->dynWordWrapIndicators() ); + m_dynwrapAlignLevel->setValue(KateViewConfig::global()->dynWordWrapAlignIndent()); + m_line->setChecked(KateViewConfig::global()->lineNumbers()); + m_icons->setChecked(KateViewConfig::global()->iconBar()); + m_scrollBarMarks->setChecked(KateViewConfig::global()->scrollBarMarks()); + m_folding->setChecked(KateViewConfig::global()->foldingBar()); + m_bmSort->setButton( KateViewConfig::global()->bookmarkSort() ); + m_showIndentLines->setChecked(KateRendererConfig::global()->showIndentationLines()); +} + +void KateViewDefaultsConfig::reset () {;} + +void KateViewDefaultsConfig::defaults (){;} +//END KateViewDefaultsConfig + +//BEGIN KateEditKeyConfiguration + +KateEditKeyConfiguration::KateEditKeyConfiguration( QWidget* parent, KateDocument* doc ) + : KateConfigPage( parent ) +{ + m_doc = doc; + m_ready = false; +} + +void KateEditKeyConfiguration::showEvent ( QShowEvent * ) +{ + if (!m_ready) + { + (new QVBoxLayout(this))->setAutoAdd(true); + KateView* view = (KateView*)m_doc->views().at(0); + m_ac = view->editActionCollection(); + m_keyChooser = new KKeyChooser( m_ac, this, false ); + connect( m_keyChooser, SIGNAL( keyChange() ), this, SLOT( slotChanged() ) ); + m_keyChooser->show (); + + m_ready = true; + } + + QWidget::show (); +} + +void KateEditKeyConfiguration::apply() +{ + if ( ! changed() ) + return; + m_changed = false; + + if (m_ready) + { + m_keyChooser->commitChanges(); + m_ac->writeShortcutSettings( "Katepart Shortcuts" ); + } +} +//END KateEditKeyConfiguration + +//BEGIN KateSaveConfigTab +KateSaveConfigTab::KateSaveConfigTab( QWidget *parent ) + : KateConfigPage( parent ) +{ + int configFlags = KateDocumentConfig::global()->configFlags(); + QVBoxLayout *layout = new QVBoxLayout(this, 0, KDialog::spacingHint() ); + + QVGroupBox *gbEnc = new QVGroupBox(i18n("File Format"), this); + layout->addWidget( gbEnc ); + + QHBox *e5Layout = new QHBox(gbEnc); + QLabel *e5Label = new QLabel(i18n("&Encoding:"), e5Layout); + m_encoding = new KComboBox (e5Layout); + e5Label->setBuddy(m_encoding); + + e5Layout = new QHBox(gbEnc); + e5Label = new QLabel(i18n("End &of line:"), e5Layout); + m_eol = new KComboBox (e5Layout); + e5Label->setBuddy(m_eol); + + allowEolDetection = new QCheckBox(i18n("&Automatic end of line detection"), gbEnc); + + m_eol->insertItem (i18n("UNIX")); + m_eol->insertItem (i18n("DOS/Windows")); + m_eol->insertItem (i18n("Macintosh")); + + QVGroupBox *gbMem = new QVGroupBox(i18n("Memory Usage"), this); + layout->addWidget( gbMem ); + + e5Layout = new QHBox(gbMem); + e5Layout->setSpacing (32); + blockCountLabel = new QLabel(i18n("Maximum loaded &blocks per file:"), e5Layout); + blockCount = new QSpinBox (4, 512, 4, e5Layout); + + blockCount->setValue (KateBuffer::maxLoadedBlocks()); + blockCountLabel->setBuddy(blockCount); + + QVGroupBox *gbWhiteSpace = new QVGroupBox(i18n("Automatic Cleanups on Load/Save"), this); + layout->addWidget( gbWhiteSpace ); + + removeSpaces = new QCheckBox(i18n("Re&move trailing spaces"), gbWhiteSpace); + removeSpaces->setChecked(configFlags & KateDocument::cfRemoveSpaces); + + QVGroupBox *dirConfigBox = new QVGroupBox(i18n("Folder Config File"), this); + layout->addWidget( dirConfigBox ); + + dirSearchDepth = new KIntNumInput(KateDocumentConfig::global()->searchDirConfigDepth(), dirConfigBox); + dirSearchDepth->setRange(-1, 64, 1, false); + dirSearchDepth->setSpecialValueText( i18n("Do not use config file") ); + dirSearchDepth->setLabel(i18n("Se&arch depth for config file:"), AlignVCenter); + + QGroupBox *gb = new QGroupBox( 1, Qt::Horizontal, i18n("Backup on Save"), this ); + layout->addWidget( gb ); + cbLocalFiles = new QCheckBox( i18n("&Local files"), gb ); + cbRemoteFiles = new QCheckBox( i18n("&Remote files"), gb ); + + QHBox *hbBuPrefix = new QHBox( gb ); + QLabel *lBuPrefix = new QLabel( i18n("&Prefix:"), hbBuPrefix ); + leBuPrefix = new QLineEdit( hbBuPrefix ); + lBuPrefix->setBuddy( leBuPrefix ); + + QHBox *hbBuSuffix = new QHBox( gb ); + QLabel *lBuSuffix = new QLabel( i18n("&Suffix:"), hbBuSuffix ); + leBuSuffix = new QLineEdit( hbBuSuffix ); + lBuSuffix->setBuddy( leBuSuffix ); + + layout->addStretch(); + + QWhatsThis::add(removeSpaces, i18n( + "The editor will automatically eliminate extra spaces at the ends of " + "lines of text while loading/saving the file.")); + QWhatsThis::add( gb, i18n( + "<p>Backing up on save will cause Kate to copy the disk file to " + "'<prefix><filename><suffix>' before saving changes." + "<p>The suffix defaults to <strong>~</strong> and prefix is empty by default" ) ); + QWhatsThis::add( allowEolDetection, i18n( + "Check this if you want the editor to autodetect the end of line type." + "The first found end of line type will be used for the whole file.") ); + QWhatsThis::add( cbLocalFiles, i18n( + "Check this if you want backups of local files when saving") ); + QWhatsThis::add( cbRemoteFiles, i18n( + "Check this if you want backups of remote files when saving") ); + QWhatsThis::add( leBuPrefix, i18n( + "Enter the prefix to prepend to the backup file names" ) ); + QWhatsThis::add( leBuSuffix, i18n( + "Enter the suffix to add to the backup file names" ) ); + QWhatsThis::add(dirSearchDepth, i18n( + "The editor will search the given number of folder levels upwards for .kateconfig file" + " and load the settings line from it." )); + QWhatsThis::add(blockCount, i18n( + "The editor will load given number of blocks (of around 2048 lines) of text into memory;" + " if the filesize is bigger than this the other blocks are swapped " + " to disk and loaded transparently as-needed.<br>" + " This can cause little delays while navigating in the document; a larger block count" + " increases the editing speed at the cost of memory. <br>For normal usage, just choose the highest possible" + " block count: limit it only if you have problems with the memory usage.")); + + reload(); + + // + // after initial reload, connect the stuff for the changed () signal + // + + connect(m_encoding, SIGNAL(activated(int)), this, SLOT(slotChanged())); + connect(m_eol, SIGNAL(activated(int)), this, SLOT(slotChanged())); + connect( allowEolDetection, SIGNAL( toggled(bool) ), this, SLOT( slotChanged() ) ); + connect(blockCount, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); + connect(removeSpaces, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); + connect( cbLocalFiles, SIGNAL( toggled(bool) ), this, SLOT( slotChanged() ) ); + connect( cbRemoteFiles, SIGNAL( toggled(bool) ), this, SLOT( slotChanged() ) ); + connect(dirSearchDepth, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); + connect( leBuPrefix, SIGNAL( textChanged ( const QString & ) ), this, SLOT( slotChanged() ) ); + connect( leBuSuffix, SIGNAL( textChanged ( const QString & ) ), this, SLOT( slotChanged() ) ); +} + +void KateSaveConfigTab::apply() +{ + // nothing changed, no need to apply stuff + if (!changed()) + return; + m_changed = false; + + KateBuffer::setMaxLoadedBlocks (blockCount->value()); + + KateDocumentConfig::global()->configStart (); + + if ( leBuSuffix->text().isEmpty() && leBuPrefix->text().isEmpty() ) { + KMessageBox::information( + this, + i18n("You did not provide a backup suffix or prefix. Using default suffix: '~'"), + i18n("No Backup Suffix or Prefix") + ); + leBuSuffix->setText( "~" ); + } + + uint f( 0 ); + if ( cbLocalFiles->isChecked() ) + f |= KateDocumentConfig::LocalFiles; + if ( cbRemoteFiles->isChecked() ) + f |= KateDocumentConfig::RemoteFiles; + + KateDocumentConfig::global()->setBackupFlags(f); + KateDocumentConfig::global()->setBackupPrefix(leBuPrefix->text()); + KateDocumentConfig::global()->setBackupSuffix(leBuSuffix->text()); + + KateDocumentConfig::global()->setSearchDirConfigDepth(dirSearchDepth->value()); + + int configFlags = KateDocumentConfig::global()->configFlags(); + + configFlags &= ~KateDocument::cfRemoveSpaces; // clear flag + if (removeSpaces->isChecked()) configFlags |= KateDocument::cfRemoveSpaces; // set flag if checked + + KateDocumentConfig::global()->setConfigFlags(configFlags); + + KateDocumentConfig::global()->setEncoding((m_encoding->currentItem() == 0) ? "" : KGlobal::charsets()->encodingForName(m_encoding->currentText())); + + KateDocumentConfig::global()->setEol(m_eol->currentItem()); + KateDocumentConfig::global()->setAllowEolDetection(allowEolDetection->isChecked()); + + KateDocumentConfig::global()->configEnd (); +} + +void KateSaveConfigTab::reload() +{ + // encoding + m_encoding->clear (); + m_encoding->insertItem (i18n("KDE Default")); + m_encoding->setCurrentItem(0); + QStringList encodings (KGlobal::charsets()->descriptiveEncodingNames()); + int insert = 1; + for (uint i=0; i < encodings.count(); i++) + { + bool found = false; + QTextCodec *codecForEnc = KGlobal::charsets()->codecForName(KGlobal::charsets()->encodingForName(encodings[i]), found); + + if (found) + { + m_encoding->insertItem (encodings[i]); + + if ( codecForEnc->name() == KateDocumentConfig::global()->encoding() ) + { + m_encoding->setCurrentItem(insert); + } + + insert++; + } + } + + // eol + m_eol->setCurrentItem(KateDocumentConfig::global()->eol()); + allowEolDetection->setChecked(KateDocumentConfig::global()->allowEolDetection()); + + dirSearchDepth->setValue(KateDocumentConfig::global()->searchDirConfigDepth()); + + // other stuff + uint f ( KateDocumentConfig::global()->backupFlags() ); + cbLocalFiles->setChecked( f & KateDocumentConfig::LocalFiles ); + cbRemoteFiles->setChecked( f & KateDocumentConfig::RemoteFiles ); + leBuPrefix->setText( KateDocumentConfig::global()->backupPrefix() ); + leBuSuffix->setText( KateDocumentConfig::global()->backupSuffix() ); +} + +void KateSaveConfigTab::reset() +{ +} + +void KateSaveConfigTab::defaults() +{ + cbLocalFiles->setChecked( true ); + cbRemoteFiles->setChecked( false ); + leBuPrefix->setText( "" ); + leBuSuffix->setText( "~" ); +} + +//END KateSaveConfigTab + +//BEGIN PluginListItem +class KatePartPluginListItem : public QCheckListItem +{ + public: + KatePartPluginListItem(bool checked, uint i, const QString &name, QListView *parent); + uint pluginIndex () const { return index; } + + protected: + void stateChange(bool); + + private: + uint index; + bool silentStateChange; +}; + +KatePartPluginListItem::KatePartPluginListItem(bool checked, uint i, const QString &name, QListView *parent) + : QCheckListItem(parent, name, CheckBox) + , index(i) + , silentStateChange(false) +{ + silentStateChange = true; + setOn(checked); + silentStateChange = false; +} + +void KatePartPluginListItem::stateChange(bool b) +{ + if(!silentStateChange) + static_cast<KatePartPluginListView *>(listView())->stateChanged(this, b); +} +//END + +//BEGIN PluginListView +KatePartPluginListView::KatePartPluginListView(QWidget *parent, const char *name) + : KListView(parent, name) +{ +} + +void KatePartPluginListView::stateChanged(KatePartPluginListItem *item, bool b) +{ + emit stateChange(item, b); +} +//END + +//BEGIN KatePartPluginConfigPage +KatePartPluginConfigPage::KatePartPluginConfigPage (QWidget *parent) : KateConfigPage (parent, "") +{ + // sizemanagment + QGridLayout *grid = new QGridLayout( this, 1, 1 ); + grid->setSpacing( KDialogBase::spacingHint() ); + + listView = new KatePartPluginListView(this); + listView->addColumn(i18n("Name")); + listView->addColumn(i18n("Comment")); + + grid->addWidget( listView, 0, 0); + + for (uint i=0; i<KateFactory::self()->plugins().count(); i++) + { + KatePartPluginListItem *item = new KatePartPluginListItem(KateDocumentConfig::global()->plugin(i), i, (KateFactory::self()->plugins())[i]->name(), listView); + item->setText(0, (KateFactory::self()->plugins())[i]->name()); + item->setText(1, (KateFactory::self()->plugins())[i]->comment()); + + m_items.append (item); + } + + // configure button + + btnConfigure = new QPushButton( i18n("Configure..."), this ); + btnConfigure->setEnabled( false ); + grid->addWidget( btnConfigure, 1, 0, Qt::AlignRight ); + connect( btnConfigure, SIGNAL(clicked()), this, SLOT(slotConfigure()) ); + + connect( listView, SIGNAL(selectionChanged(QListViewItem*)), this, SLOT(slotCurrentChanged(QListViewItem*)) ); + connect( listView, SIGNAL(stateChange(KatePartPluginListItem *, bool)), + this, SLOT(slotStateChanged(KatePartPluginListItem *, bool))); + connect(listView, SIGNAL(stateChange(KatePartPluginListItem *, bool)), this, SLOT(slotChanged())); +} + +KatePartPluginConfigPage::~KatePartPluginConfigPage () +{ +} + +void KatePartPluginConfigPage::apply () +{ + // nothing changed, no need to apply stuff + if (!changed()) + return; + m_changed = false; + + KateDocumentConfig::global()->configStart (); + + for (uint i=0; i < m_items.count(); i++) + KateDocumentConfig::global()->setPlugin (m_items.at(i)->pluginIndex(), m_items.at(i)->isOn()); + + KateDocumentConfig::global()->configEnd (); +} + +void KatePartPluginConfigPage::slotStateChanged( KatePartPluginListItem *item, bool b ) +{ + if ( b ) + slotCurrentChanged( (QListViewItem*)item ); +} + +void KatePartPluginConfigPage::slotCurrentChanged( QListViewItem* i ) +{ + KatePartPluginListItem *item = static_cast<KatePartPluginListItem *>(i); + if ( ! item ) return; + + bool b = false; + if ( item->isOn() ) + { + + // load this plugin, and see if it has config pages + KTextEditor::Plugin *plugin = KTextEditor::createPlugin(QFile::encodeName((KateFactory::self()->plugins())[item->pluginIndex()]->library())); + if ( plugin ) { + KTextEditor::ConfigInterfaceExtension *cie = KTextEditor::configInterfaceExtension( plugin ); + b = ( cie && cie->configPages() ); + } + + } + btnConfigure->setEnabled( b ); +} + +void KatePartPluginConfigPage::slotConfigure() +{ + KatePartPluginListItem *item = static_cast<KatePartPluginListItem*>(listView->currentItem()); + KTextEditor::Plugin *plugin = + KTextEditor::createPlugin(QFile::encodeName((KateFactory::self()->plugins())[item->pluginIndex()]->library())); + + if ( ! plugin ) return; + + KTextEditor::ConfigInterfaceExtension *cife = + KTextEditor::configInterfaceExtension( plugin ); + + if ( ! cife ) + return; + + if ( ! cife->configPages() ) + return; + + // If we have only one page, we use a simple dialog, else an icon list type + KDialogBase::DialogType dt = + cife->configPages() > 1 ? + KDialogBase::IconList : // still untested + KDialogBase::Plain; + + QString name = (KateFactory::self()->plugins())[item->pluginIndex()]->name(); + KDialogBase *kd = new KDialogBase ( dt, + i18n("Configure %1").arg( name ), + KDialogBase::Ok | KDialogBase::Cancel | KDialogBase::Help, + KDialogBase::Ok, + this ); + + QPtrList<KTextEditor::ConfigPage> editorPages; + + for (uint i = 0; i < cife->configPages (); i++) + { + QWidget *page; + if ( dt == KDialogBase::IconList ) + { + QStringList path; + path.clear(); + path << cife->configPageName( i ); + page = kd->addVBoxPage( path, cife->configPageFullName (i), + cife->configPagePixmap(i, KIcon::SizeMedium) ); + } + else + { + page = kd->plainPage(); + QVBoxLayout *_l = new QVBoxLayout( page ); + _l->setAutoAdd( true ); + } + + editorPages.append( cife->configPage( i, page ) ); + } + + if (kd->exec()) + { + + for( uint i=0; i<editorPages.count(); i++ ) + { + editorPages.at( i )->apply(); + } + } + + delete kd; +} +//END KatePartPluginConfigPage + +//BEGIN KateHlConfigPage +KateHlConfigPage::KateHlConfigPage (QWidget *parent, KateDocument *doc) + : KateConfigPage (parent, "") + , hlData (0) + , m_doc (doc) +{ + QVBoxLayout *layout = new QVBoxLayout(this, 0, KDialog::spacingHint() ); + + // hl chooser + QHBox *hbHl = new QHBox( this ); + layout->add (hbHl); + + hbHl->setSpacing( KDialog::spacingHint() ); + QLabel *lHl = new QLabel( i18n("H&ighlight:"), hbHl ); + hlCombo = new QComboBox( false, hbHl ); + lHl->setBuddy( hlCombo ); + connect( hlCombo, SIGNAL(activated(int)), + this, SLOT(hlChanged(int)) ); + + for( int i = 0; i < KateHlManager::self()->highlights(); i++) { + if (KateHlManager::self()->hlSection(i).length() > 0) + hlCombo->insertItem(KateHlManager::self()->hlSection(i) + QString ("/") + KateHlManager::self()->hlNameTranslated(i)); + else + hlCombo->insertItem(KateHlManager::self()->hlNameTranslated(i)); + } + + QGroupBox *gbInfo = new QGroupBox( 1, Qt::Horizontal, i18n("Information"), this ); + layout->add (gbInfo); + + // author + QHBox *hb1 = new QHBox( gbInfo); + new QLabel( i18n("Author:"), hb1 ); + author = new QLabel (hb1); + author->setTextFormat (Qt::RichText); + + // license + QHBox *hb2 = new QHBox( gbInfo); + new QLabel( i18n("License:"), hb2 ); + license = new QLabel (hb2); + + QGroupBox *gbProps = new QGroupBox( 1, Qt::Horizontal, i18n("Properties"), this ); + layout->add (gbProps); + + // file & mime types + QHBox *hbFE = new QHBox( gbProps); + QLabel *lFileExts = new QLabel( i18n("File e&xtensions:"), hbFE ); + wildcards = new QLineEdit( hbFE ); + lFileExts->setBuddy( wildcards ); + + QHBox *hbMT = new QHBox( gbProps ); + QLabel *lMimeTypes = new QLabel( i18n("MIME &types:"), hbMT); + mimetypes = new QLineEdit( hbMT ); + lMimeTypes->setBuddy( mimetypes ); + + QHBox *hbMT2 = new QHBox( gbProps ); + QLabel *lprio = new QLabel( i18n("Prio&rity:"), hbMT2); + priority = new KIntNumInput( hbMT2 ); + + lprio->setBuddy( priority ); + + QToolButton *btnMTW = new QToolButton(hbMT); + btnMTW->setIconSet(QIconSet(SmallIcon("wizard"))); + connect(btnMTW, SIGNAL(clicked()), this, SLOT(showMTDlg())); + + // download/new buttons + QHBox *hbBtns = new QHBox( this ); + layout->add (hbBtns); + + ((QBoxLayout*)hbBtns->layout())->addStretch(1); // hmm. + hbBtns->setSpacing( KDialog::spacingHint() ); + QPushButton *btnDl = new QPushButton(i18n("Do&wnload..."), hbBtns); + connect( btnDl, SIGNAL(clicked()), this, SLOT(hlDownload()) ); + + int currentHl = m_doc ? m_doc->hlMode() : 0; + hlCombo->setCurrentItem( currentHl ); + hlChanged( currentHl ); + + QWhatsThis::add( hlCombo, i18n( + "Choose a <em>Syntax Highlight mode</em> from this list to view its " + "properties below.") ); + QWhatsThis::add( wildcards, i18n( + "The list of file extensions used to determine which files to highlight " + "using the current syntax highlight mode.") ); + QWhatsThis::add( mimetypes, i18n( + "The list of Mime Types used to determine which files to highlight " + "using the current highlight mode.<p>Click the wizard button on the " + "left of the entry field to display the MimeType selection dialog.") ); + QWhatsThis::add( btnMTW, i18n( + "Display a dialog with a list of all available mime types to choose from." + "<p>The <strong>File Extensions</strong> entry will automatically be " + "edited as well.") ); + QWhatsThis::add( btnDl, i18n( + "Click this button to download new or updated syntax highlight " + "descriptions from the Kate website.") ); + + layout->addStretch (); + + connect( wildcards, SIGNAL( textChanged ( const QString & ) ), this, SLOT( slotChanged() ) ); + connect( mimetypes, SIGNAL( textChanged ( const QString & ) ), this, SLOT( slotChanged() ) ); + connect( priority, SIGNAL( valueChanged ( int ) ), this, SLOT( slotChanged() ) ); +} + +KateHlConfigPage::~KateHlConfigPage () +{ +} + +void KateHlConfigPage::apply () +{ + // nothing changed, no need to apply stuff + if (!changed()) + return; + m_changed = false; + + writeback(); + + for ( QIntDictIterator<KateHlData> it( hlDataDict ); it.current(); ++it ) + KateHlManager::self()->getHl( it.currentKey() )->setData( it.current() ); + + KateHlManager::self()->getKConfig()->sync (); +} + +void KateHlConfigPage::reload () +{ +} + +void KateHlConfigPage::hlChanged(int z) +{ + writeback(); + + KateHighlighting *hl = KateHlManager::self()->getHl( z ); + + if (!hl) + { + hlData = 0; + return; + } + + if ( !hlDataDict.find( z ) ) + hlDataDict.insert( z, hl->getData() ); + + hlData = hlDataDict.find( z ); + wildcards->setText(hlData->wildcards); + mimetypes->setText(hlData->mimetypes); + priority->setValue(hlData->priority); + + // split author string if needed into multiple lines ! + QStringList l= QStringList::split (QRegExp("[,;]"), hl->author()); + author->setText (l.join ("<br>")); + + license->setText (hl->license()); +} + +void KateHlConfigPage::writeback() +{ + if (hlData) + { + hlData->wildcards = wildcards->text(); + hlData->mimetypes = mimetypes->text(); + hlData->priority = priority->value(); + } +} + +void KateHlConfigPage::hlDownload() +{ + KateHlDownloadDialog diag(this,"hlDownload",true); + diag.exec(); +} + +void KateHlConfigPage::showMTDlg() +{ + QString text = i18n("Select the MimeTypes you want highlighted using the '%1' syntax highlight rules.\nPlease note that this will automatically edit the associated file extensions as well.").arg( hlCombo->currentText() ); + QStringList list = QStringList::split( QRegExp("\\s*;\\s*"), mimetypes->text() ); + KMimeTypeChooserDialog d( i18n("Select Mime Types"), text, list, "text", this ); + + if ( d.exec() == KDialogBase::Accepted ) { + // do some checking, warn user if mime types or patterns are removed. + // if the lists are empty, and the fields not, warn. + wildcards->setText(d.chooser()->patterns().join(";")); + mimetypes->setText(d.chooser()->mimeTypes().join(";")); + } +} +//END KateHlConfigPage + +//BEGIN KateHlDownloadDialog +KateHlDownloadDialog::KateHlDownloadDialog(QWidget *parent, const char *name, bool modal) + :KDialogBase(KDialogBase::Swallow, i18n("Highlight Download"), User1|Close, User1, parent, name, modal, true, i18n("&Install")) +{ + QVBox* vbox = new QVBox(this); + setMainWidget(vbox); + vbox->setSpacing(spacingHint()); + new QLabel(i18n("Select the syntax highlighting files you want to update:"), vbox); + list = new QListView(vbox); + list->addColumn(""); + list->addColumn(i18n("Name")); + list->addColumn(i18n("Installed")); + list->addColumn(i18n("Latest")); + list->setSelectionMode(QListView::Multi); + list->setAllColumnsShowFocus(true); + + new QLabel(i18n("<b>Note:</b> New versions are selected automatically."), vbox); + actionButton (User1)->setIconSet(SmallIconSet("ok")); + + transferJob = KIO::get( + KURL(QString(HLDOWNLOADPATH) + + QString("update-") + + QString(KATEPART_VERSION) + + QString(".xml")), true, true ); + connect(transferJob, SIGNAL(data(KIO::Job *, const QByteArray &)), + this, SLOT(listDataReceived(KIO::Job *, const QByteArray &))); +// void data( KIO::Job *, const QByteArray &data); + resize(450, 400); +} + +KateHlDownloadDialog::~KateHlDownloadDialog(){} + +void KateHlDownloadDialog::listDataReceived(KIO::Job *, const QByteArray &data) +{ + if (!transferJob || transferJob->isErrorPage()) + { + actionButton(User1)->setEnabled(false); + return; + } + + listData+=QString(data); + kdDebug(13000)<<QString("CurrentListData: ")<<listData<<endl<<endl; + kdDebug(13000)<<QString("Data length: %1").arg(data.size())<<endl; + kdDebug(13000)<<QString("listData length: %1").arg(listData.length())<<endl; + if (data.size()==0) + { + if (listData.length()>0) + { + QString installedVersion; + KateHlManager *hlm=KateHlManager::self(); + QDomDocument doc; + doc.setContent(listData); + QDomElement DocElem=doc.documentElement(); + QDomNode n=DocElem.firstChild(); + KateHighlighting *hl = 0; + + if (n.isNull()) kdDebug(13000)<<"There is no usable childnode"<<endl; + while (!n.isNull()) + { + installedVersion=" --"; + + QDomElement e=n.toElement(); + if (!e.isNull()) + kdDebug(13000)<<QString("NAME: ")<<e.tagName()<<QString(" - ")<<e.attribute("name")<<endl; + n=n.nextSibling(); + + QString Name=e.attribute("name"); + + for (int i=0;i<hlm->highlights();i++) + { + hl=hlm->getHl(i); + if (hl && hl->name()==Name) + { + installedVersion=" "+hl->version(); + break; + } + else hl = 0; + } + + // autoselect entry if new or updated. + QListViewItem* entry = new QListViewItem( + list, "", e.attribute("name"), installedVersion, + e.attribute("version"),e.attribute("url")); + if (!hl || hl->version() < e.attribute("version")) + { + entry->setSelected(true); + entry->setPixmap(0, SmallIcon(("knewstuff"))); + } + } + } + } +} + +void KateHlDownloadDialog::slotUser1() +{ + QString destdir=KGlobal::dirs()->saveLocation("data","katepart/syntax/"); + for (QListViewItem *it=list->firstChild();it;it=it->nextSibling()) + { + if (list->isSelected(it)) + { + KURL src(it->text(4)); + QString filename=src.fileName(false); + QString dest = destdir+filename; + + KIO::NetAccess::download(src,dest, this); + } + } + + // update Config !! + KateSyntaxDocument doc (true); +} +//END KateHlDownloadDialog + +//BEGIN KateGotoLineDialog +KateGotoLineDialog::KateGotoLineDialog(QWidget *parent, int line, int max) + : KDialogBase(parent, 0L, true, i18n("Go to Line"), Ok | Cancel, Ok) { + + QWidget *page = new QWidget(this); + setMainWidget(page); + + QVBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() ); + e1 = new KIntNumInput(line, page); + e1->setRange(1, max); + e1->setEditFocus(true); + + QLabel *label = new QLabel( e1,i18n("&Go to line:"), page ); + topLayout->addWidget(label); + topLayout->addWidget(e1); + topLayout->addSpacing(spacingHint()); // A little bit extra space + topLayout->addStretch(10); + e1->setFocus(); +} + +int KateGotoLineDialog::getLine() { + return e1->value(); +} +//END KateGotoLineDialog + +//BEGIN KateModOnHdPrompt +KateModOnHdPrompt::KateModOnHdPrompt( KateDocument *doc, + int modtype, + const QString &reason, + QWidget *parent ) + : KDialogBase( parent, "", true, "", Ok|Apply|Cancel|User1 ), + m_doc( doc ), + m_modtype ( modtype ), + m_tmpfile( 0 ) +{ + QString title, btnOK, whatisok; + if ( modtype == 3 ) // deleted + { + title = i18n("File Was Deleted on Disk"); + btnOK = i18n("&Save File As..."); + whatisok = i18n("Lets you select a location and save the file again."); + } else { + title = i18n("File Changed on Disk"); + btnOK = i18n("&Reload File"); + whatisok = i18n("Reload the file from disk. If you have unsaved changes, " + "they will be lost."); + } + + setButtonText( Ok, btnOK); + setButtonText( Apply, i18n("&Ignore") ); + + setButtonWhatsThis( Ok, whatisok ); + setButtonWhatsThis( Apply, i18n("Ignore the changes. You will not be prompted again.") ); + setButtonWhatsThis( Cancel, i18n("Do nothing. Next time you focus the file, " + "or try to save it or close it, you will be prompted again.") ); + + enableButtonSeparator( true ); + setCaption( title ); + + QFrame *w = makeMainWidget(); + QVBoxLayout *lo = new QVBoxLayout( w ); + QHBoxLayout *lo1 = new QHBoxLayout( lo ); + QLabel *icon = new QLabel( w ); + icon->setPixmap( DesktopIcon("messagebox_warning" ) ); + lo1->addWidget( icon ); + lo1->addWidget( new QLabel( reason + "\n\n" + i18n("What do you want to do?"), w ) ); + + // If the file isn't deleted, present a diff button, and a overwrite action. + if ( modtype != 3 ) + { + QHBoxLayout *lo2 = new QHBoxLayout( lo ); + QPushButton *btnDiff = new QPushButton( i18n("&View Difference"), w ); + lo2->addStretch( 1 ); + lo2->addWidget( btnDiff ); + connect( btnDiff, SIGNAL(clicked()), this, SLOT(slotDiff()) ); + QWhatsThis::add( btnDiff, i18n( + "Calculates the difference between the editor contents and the disk " + "file using diff(1) and opens the diff file with the default application " + "for that.") ); + + setButtonText( User1, i18n("Overwrite") ); + setButtonWhatsThis( User1, i18n("Overwrite the disk file with the editor content.") ); + } + else + showButton( User1, false ); +} + +KateModOnHdPrompt::~KateModOnHdPrompt() +{ +} + +void KateModOnHdPrompt::slotDiff() +{ + // Start a KProcess that creates a diff + KProcIO *p = new KProcIO(); + p->setComm( KProcess::All ); + *p << "diff" << "-u" << "-" << m_doc->url().path(); + connect( p, SIGNAL(processExited(KProcess*)), this, SLOT(slotPDone(KProcess*)) ); + connect( p, SIGNAL(readReady(KProcIO*)), this, SLOT(slotPRead(KProcIO*)) ); + + setCursor( WaitCursor ); + + p->start( KProcess::NotifyOnExit, true ); + + uint lastln = m_doc->numLines(); + for ( uint l = 0; l < lastln; l++ ) + p->writeStdin( m_doc->textLine( l ) ); + + p->closeWhenDone(); +} + +void KateModOnHdPrompt::slotPRead( KProcIO *p) +{ + // create a file for the diff if we haven't one allready + if ( ! m_tmpfile ) + m_tmpfile = new KTempFile(); + // put all the data we have in it + QString stmp; + bool dataRead = false; + while ( p->readln( stmp, false ) > -1 ) + { + *m_tmpfile->textStream() << stmp << endl; + dataRead = true; + } + + // dominik: only ackRead(), when we *really* read data, otherwise, this slot + // is called initity times, which leads to a crash + if( dataRead ) + p->ackRead(); +} + +void KateModOnHdPrompt::slotPDone( KProcess *p ) +{ + setCursor( ArrowCursor ); + if( ! m_tmpfile ) + { + // dominik: there were only whitespace changes, so that the diff returned by + // diff(1) has 0 bytes. So slotPRead() is never called, as there is + // no data, so that m_tmpfile was never created and thus is NULL. + // NOTE: would be nice, if we could produce a fake-diff, so that kompare + // tells us "The files are identical". Right now, we get an ugly + // "Could not parse diff output". + m_tmpfile = new KTempFile(); + } + m_tmpfile->close(); + + if ( ! p->normalExit() /*|| p->exitStatus()*/ ) + { + KMessageBox::sorry( this, + i18n("The diff command failed. Please make sure that " + "diff(1) is installed and in your PATH."), + i18n("Error Creating Diff") ); + delete m_tmpfile; + m_tmpfile = 0; + return; + } + + KRun::runURL( m_tmpfile->name(), "text/x-diff", true ); + delete m_tmpfile; + m_tmpfile = 0; +} + +void KateModOnHdPrompt::slotApply() +{ + if ( KMessageBox::warningContinueCancel( + this, + i18n("Ignoring means that you will not be warned again (unless " + "the disk file changes once more): if you save the document, you " + "will overwrite the file on disk; if you do not save then the disk file " + "(if present) is what you have."), + i18n("You Are on Your Own"), + KStdGuiItem::cont(), + "kate_ignore_modonhd" ) != KMessageBox::Continue ) + return; + + done(Ignore); +} + +void KateModOnHdPrompt::slotOk() +{ + done( m_modtype == 3 ? Save : Reload ); +} + +void KateModOnHdPrompt::slotUser1() +{ + done( Overwrite ); +} + +//END KateModOnHdPrompt + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katedialogs.h b/kate/part/katedialogs.h new file mode 100644 index 000000000..ba03d5eff --- /dev/null +++ b/kate/part/katedialogs.h @@ -0,0 +1,406 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002, 2003 Anders Lund <anders.lund@lund.tdcadsl.dk> + Copyright (C) 2003 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + + Based on work of: + Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __KATE_DIALOGS_H__ +#define __KATE_DIALOGS_H__ + +#include "katehighlight.h" +#include "kateattribute.h" + +#include "../interfaces/document.h" + +#include <klistview.h> +#include <kdialogbase.h> +#include <kmimetype.h> + +#include <qstringlist.h> +#include <qcolor.h> +#include <qintdict.h> +#include <qvbox.h> +#include <qtabwidget.h> + +class KatePartPluginListItem; + +struct syntaxContextData; + +class KateDocument; +class KateView; + +namespace KIO +{ + class Job; + class TransferJob; +} + +class KAccel; +class KColorButton; +class KComboBox; +class KIntNumInput; +class KKeyButton; +class KKeyChooser; +class KMainWindow; +class KPushButton; +class KRegExpDialog; +class KIntNumInput; + +class QButtonGroup; +class QCheckBox; +class QHBoxLayout; +class QLabel; +class QLineEdit; +class QPushButton; +class QRadioButton; +class QSpinBox; +class QListBoxItem; +class QWidgetStack; +class QVBox; +class QListViewItem; +class QCheckBox; + +class KateConfigPage : public Kate::ConfigPage +{ + Q_OBJECT + + public: + KateConfigPage ( QWidget *parent=0, const char *name=0 ); + virtual ~KateConfigPage (); + + public: + bool changed () { return m_changed; } + + private slots: + void somethingHasChanged (); + + protected: + bool m_changed; +}; + +class KateGotoLineDialog : public KDialogBase +{ + Q_OBJECT + + public: + + KateGotoLineDialog(QWidget *parent, int line, int max); + int getLine(); + + protected: + + KIntNumInput *e1; + QPushButton *btnOK; +}; + +class KateIndentConfigTab : public KateConfigPage +{ + Q_OBJECT + + public: + KateIndentConfigTab(QWidget *parent); + + protected slots: + void somethingToggled(); + void indenterSelected (int); + + protected: + enum { numFlags = 8 }; + static const int flags[numFlags]; + QCheckBox *opt[numFlags]; + KIntNumInput *indentationWidth; + QButtonGroup *m_tabs; + KComboBox *m_indentMode; + QPushButton *m_configPage; + + public slots: + void configPage(); + + void apply (); + void reload (); + void reset () {}; + void defaults () {}; +}; + +class KateSelectConfigTab : public KateConfigPage +{ + Q_OBJECT + + public: + KateSelectConfigTab(QWidget *parent); + + protected: + enum { numFlags = 2 }; + static const int flags[numFlags]; + QCheckBox *opt[numFlags]; + + QButtonGroup *m_tabs; + KIntNumInput *e4; + QCheckBox *e6; + + public slots: + void apply (); + void reload (); + void reset () {}; + void defaults () {}; +}; + +class KateEditConfigTab : public KateConfigPage +{ + Q_OBJECT + + public: + KateEditConfigTab(QWidget *parent); + + protected: + enum { numFlags = 5 }; + static const int flags[numFlags]; + QCheckBox *opt[numFlags]; + + KIntNumInput *e1; + KIntNumInput *e2; + KIntNumInput *e3; + KComboBox *e5; + QCheckBox *m_wwmarker; + + public slots: + void apply (); + void reload (); + void reset () {}; + void defaults () {}; +}; + +class KateViewDefaultsConfig : public KateConfigPage +{ + Q_OBJECT + + public: + KateViewDefaultsConfig( QWidget *parent ); + ~KateViewDefaultsConfig(); + + private: + QCheckBox *m_line; + QCheckBox *m_folding; + QCheckBox *m_collapseTopLevel; + QCheckBox *m_icons; + QCheckBox *m_scrollBarMarks; + QCheckBox *m_dynwrap; + QCheckBox *m_showIndentLines; + KIntNumInput *m_dynwrapAlignLevel; + QLabel *m_dynwrapIndicatorsLabel; + KComboBox *m_dynwrapIndicatorsCombo; + QButtonGroup *m_bmSort; + + public slots: + void apply (); + void reload (); + void reset (); + void defaults (); +}; + +class KateEditKeyConfiguration: public KateConfigPage +{ + Q_OBJECT + + public: + KateEditKeyConfiguration( QWidget* parent, KateDocument* doc ); + + public slots: + void apply(); + void reload() {}; + void reset() {}; + void defaults() {}; + + protected: + void showEvent ( QShowEvent * ); + + private: + bool m_ready; + class KateDocument *m_doc; + KKeyChooser* m_keyChooser; + class KActionCollection *m_ac; +}; + +class KateSaveConfigTab : public KateConfigPage +{ + Q_OBJECT + public: + KateSaveConfigTab( QWidget *parent ); + + public slots: + void apply(); + void reload(); + void reset(); + void defaults(); + + protected: + KComboBox *m_encoding, *m_eol; + QCheckBox *cbLocalFiles, *cbRemoteFiles; + QCheckBox *replaceTabs, *removeSpaces, *allowEolDetection; + QLineEdit *leBuPrefix; + QLineEdit *leBuSuffix; + KIntNumInput *dirSearchDepth; + class QSpinBox *blockCount; + class QLabel *blockCountLabel; +}; + +class KatePartPluginListItem; + +class KatePartPluginListView : public KListView +{ + Q_OBJECT + + friend class KatePartPluginListItem; + + public: + KatePartPluginListView (QWidget *parent = 0, const char *name = 0); + + signals: + void stateChange(KatePartPluginListItem *, bool); + + private: + void stateChanged(KatePartPluginListItem *, bool); +}; + +class QListViewItem; +class KatePartPluginConfigPage : public KateConfigPage +{ + Q_OBJECT + + public: + KatePartPluginConfigPage (QWidget *parent); + ~KatePartPluginConfigPage (); + + public slots: + void apply (); + void reload () {}; + void reset () {}; + void defaults () {}; + + private slots: + void slotCurrentChanged( QListViewItem * ); + void slotConfigure(); + void slotStateChanged( KatePartPluginListItem *, bool ); + + private: + KatePartPluginListView *listView; + QPtrList<KatePartPluginListItem> m_items; + class QPushButton *btnConfigure; +}; + +class KateHlConfigPage : public KateConfigPage +{ + Q_OBJECT + + public: + KateHlConfigPage (QWidget *parent, KateDocument *doc); + ~KateHlConfigPage (); + + public slots: + void apply (); + void reload (); + void reset () {}; + void defaults () {}; + + protected slots: + void hlChanged(int); + void hlDownload(); + void showMTDlg(); + + private: + void writeback (); + + QComboBox *hlCombo; + QLineEdit *wildcards; + QLineEdit *mimetypes; + class KIntNumInput *priority; + class QLabel *author, *license; + + QIntDict<KateHlData> hlDataDict; + KateHlData *hlData; + + KateDocument *m_doc; +}; + +class KateHlDownloadDialog: public KDialogBase +{ + Q_OBJECT + + public: + KateHlDownloadDialog(QWidget *parent, const char *name, bool modal); + ~KateHlDownloadDialog(); + + private: + class QListView *list; + class QString listData; + KIO::TransferJob *transferJob; + + private slots: + void listDataReceived(KIO::Job *, const QByteArray &data); + + public slots: + void slotUser1(); +}; + +class KProcIO; +class KProcess; +/** + * This dialog will prompt the user for what do with a file that is + * modified on disk. + * If the file wasn't deleted, it has a 'diff' button, which will create + * a diff file (uing diff(1)) and launch that using KRun. + */ +class KateModOnHdPrompt : public KDialogBase +{ + Q_OBJECT + public: + enum Status { + Reload=1, // 0 is KDialogBase::Cancel + Save, + Overwrite, + Ignore + }; + KateModOnHdPrompt( KateDocument *doc, int modtype, const QString &reason, QWidget *parent ); + ~KateModOnHdPrompt(); + + public slots: + /** + * Show a diff between the document text and the disk file. + * This will not close the dialog, since we still need a + * decision from the user. + */ + void slotDiff(); + + void slotOk(); + void slotApply(); + void slotUser1(); + + private slots: + void slotPRead(KProcIO*); ///< Read from the diff process + void slotPDone(KProcess*); ///< Runs the diff file when done + + private: + KateDocument *m_doc; + int m_modtype; + class KTempFile *m_tmpfile; ///< The diff file. Deleted by KRun when the viewer is exited. + +}; + +#endif diff --git a/kate/part/katedocument.cpp b/kate/part/katedocument.cpp new file mode 100644 index 000000000..68374ebe1 --- /dev/null +++ b/kate/part/katedocument.cpp @@ -0,0 +1,5174 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001-2004 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02111-13020, USA. +*/ + +//BEGIN includes +#include "katedocument.h" +#include "katedocument.moc" +#include "katekeyinterceptorfunctor.h" +#include "katefactory.h" +#include "katedialogs.h" +#include "katehighlight.h" +#include "kateview.h" +#include "katesearch.h" +#include "kateautoindent.h" +#include "katetextline.h" +#include "katedocumenthelpers.h" +#include "kateprinter.h" +#include "katelinerange.h" +#include "katesupercursor.h" +#include "katearbitraryhighlight.h" +#include "katerenderer.h" +#include "kateattribute.h" +#include "kateconfig.h" +#include "katefiletype.h" +#include "kateschema.h" +#include "katetemplatehandler.h" +#include <ktexteditor/plugin.h> + +#include <kio/job.h> +#include <kio/netaccess.h> +#include <kio/kfileitem.h> + + +#include <kparts/event.h> + +#include <klocale.h> +#include <kglobal.h> +#include <kapplication.h> +#include <kpopupmenu.h> +#include <kconfig.h> +#include <kfiledialog.h> +#include <kmessagebox.h> +#include <kstdaction.h> +#include <kiconloader.h> +#include <kxmlguifactory.h> +#include <kdialogbase.h> +#include <kdebug.h> +#include <kglobalsettings.h> +#include <klibloader.h> +#include <kdirwatch.h> +#include <kwin.h> +#include <kencodingfiledialog.h> +#include <ktempfile.h> +#include <kmdcodec.h> +#include <kstandarddirs.h> + +#include <qtimer.h> +#include <qfile.h> +#include <qclipboard.h> +#include <qtextstream.h> +#include <qtextcodec.h> +#include <qmap.h> +//END includes + +//BEGIN PRIVATE CLASSES +class KatePartPluginItem +{ + public: + KTextEditor::Plugin *plugin; +}; +//END PRIVATE CLASSES + +//BEGIN d'tor, c'tor +// +// KateDocument Constructor +// +KateDocument::KateDocument ( bool bSingleViewMode, bool bBrowserView, + bool bReadOnly, QWidget *parentWidget, + const char *widgetName, QObject *parent, const char *name) +: Kate::Document(parent, name), + m_plugins (KateFactory::self()->plugins().count()), + m_undoDontMerge(false), + m_undoIgnoreCancel(false), + lastUndoGroupWhenSaved( 0 ), + lastRedoGroupWhenSaved( 0 ), + docWasSavedWhenUndoWasEmpty( true ), + docWasSavedWhenRedoWasEmpty( true ), + m_modOnHd (false), + m_modOnHdReason (0), + m_job (0), + m_tempFile (0), + m_tabInterceptor(0) +{ + m_undoComplexMerge=false; + m_isInUndo = false; + // my dcop object + setObjId ("KateDocument#"+documentDCOPSuffix()); + + // ktexteditor interfaces + setBlockSelectionInterfaceDCOPSuffix (documentDCOPSuffix()); + setConfigInterfaceDCOPSuffix (documentDCOPSuffix()); + setConfigInterfaceExtensionDCOPSuffix (documentDCOPSuffix()); + setCursorInterfaceDCOPSuffix (documentDCOPSuffix()); + setEditInterfaceDCOPSuffix (documentDCOPSuffix()); + setEncodingInterfaceDCOPSuffix (documentDCOPSuffix()); + setHighlightingInterfaceDCOPSuffix (documentDCOPSuffix()); + setMarkInterfaceDCOPSuffix (documentDCOPSuffix()); + setMarkInterfaceExtensionDCOPSuffix (documentDCOPSuffix()); + setPrintInterfaceDCOPSuffix (documentDCOPSuffix()); + setSearchInterfaceDCOPSuffix (documentDCOPSuffix()); + setSelectionInterfaceDCOPSuffix (documentDCOPSuffix()); + setSelectionInterfaceExtDCOPSuffix (documentDCOPSuffix()); + setSessionConfigInterfaceDCOPSuffix (documentDCOPSuffix()); + setUndoInterfaceDCOPSuffix (documentDCOPSuffix()); + setWordWrapInterfaceDCOPSuffix (documentDCOPSuffix()); + + // init local plugin array + m_plugins.fill (0); + + // register doc at factory + KateFactory::self()->registerDocument (this); + + m_reloading = false; + m_loading = false; + m_encodingSticky = false; + + m_buffer = new KateBuffer (this); + + // init the config object, be careful not to use it + // until the initial readConfig() call is done + m_config = new KateDocumentConfig (this); + + // init some more vars ! + m_activeView = 0L; + + hlSetByUser = false; + m_fileType = -1; + m_fileTypeSetByUser = false; + setInstance( KateFactory::self()->instance() ); + + editSessionNumber = 0; + editIsRunning = false; + m_editCurrentUndo = 0L; + editWithUndo = false; + + m_docNameNumber = 0; + + m_bSingleViewMode = bSingleViewMode; + m_bBrowserView = bBrowserView; + m_bReadOnly = bReadOnly; + + m_marks.setAutoDelete( true ); + m_markPixmaps.setAutoDelete( true ); + m_markDescriptions.setAutoDelete( true ); + setMarksUserChangable( markType01 ); + + m_undoMergeTimer = new QTimer(this); + connect(m_undoMergeTimer, SIGNAL(timeout()), SLOT(undoCancel())); + + clearMarks (); + clearUndo (); + clearRedo (); + setModified (false); + docWasSavedWhenUndoWasEmpty = true; + + // normal hl + m_buffer->setHighlight (0); + + m_extension = new KateBrowserExtension( this ); + m_arbitraryHL = new KateArbitraryHighlight(); + m_indenter = KateAutoIndent::createIndenter ( this, 0 ); + + m_indenter->updateConfig (); + + // some nice signals from the buffer + connect(m_buffer, SIGNAL(tagLines(int,int)), this, SLOT(tagLines(int,int))); + connect(m_buffer, SIGNAL(codeFoldingUpdated()),this,SIGNAL(codeFoldingUpdated())); + + // if the user changes the highlight with the dialog, notify the doc + connect(KateHlManager::self(),SIGNAL(changed()),SLOT(internalHlChanged())); + + // signal for the arbitrary HL + connect(m_arbitraryHL, SIGNAL(tagLines(KateView*, KateSuperRange*)), SLOT(tagArbitraryLines(KateView*, KateSuperRange*))); + + // signals for mod on hd + connect( KateFactory::self()->dirWatch(), SIGNAL(dirty (const QString &)), + this, SLOT(slotModOnHdDirty (const QString &)) ); + + connect( KateFactory::self()->dirWatch(), SIGNAL(created (const QString &)), + this, SLOT(slotModOnHdCreated (const QString &)) ); + + connect( KateFactory::self()->dirWatch(), SIGNAL(deleted (const QString &)), + this, SLOT(slotModOnHdDeleted (const QString &)) ); + + // update doc name + setDocName (""); + + // if single view mode, like in the konqui embedding, create a default view ;) + if ( m_bSingleViewMode ) + { + KTextEditor::View *view = createView( parentWidget, widgetName ); + insertChildClient( view ); + view->show(); + setWidget( view ); + } + + connect(this,SIGNAL(sigQueryClose(bool *, bool*)),this,SLOT(slotQueryClose_save(bool *, bool*))); + + m_isasking = 0; + + // plugins + for (uint i=0; i<KateFactory::self()->plugins().count(); i++) + { + if (config()->plugin (i)) + loadPlugin (i); + } +} + +// +// KateDocument Destructor +// +KateDocument::~KateDocument() +{ + // remove file from dirwatch + deactivateDirWatch (); + + if (!singleViewMode()) + { + // clean up remaining views + m_views.setAutoDelete( true ); + m_views.clear(); + } + + delete m_editCurrentUndo; + + delete m_arbitraryHL; + + // cleanup the undo items, very important, truee :/ + undoItems.setAutoDelete(true); + undoItems.clear(); + + // clean up plugins + unloadAllPlugins (); + + delete m_config; + delete m_indenter; + KateFactory::self()->deregisterDocument (this); +} +//END + +//BEGIN Plugins +void KateDocument::unloadAllPlugins () +{ + for (uint i=0; i<m_plugins.count(); i++) + unloadPlugin (i); +} + +void KateDocument::enableAllPluginsGUI (KateView *view) +{ + for (uint i=0; i<m_plugins.count(); i++) + enablePluginGUI (m_plugins[i], view); +} + +void KateDocument::disableAllPluginsGUI (KateView *view) +{ + for (uint i=0; i<m_plugins.count(); i++) + disablePluginGUI (m_plugins[i], view); +} + +void KateDocument::loadPlugin (uint pluginIndex) +{ + if (m_plugins[pluginIndex]) return; + + m_plugins[pluginIndex] = KTextEditor::createPlugin (QFile::encodeName((KateFactory::self()->plugins())[pluginIndex]->library()), this); + + enablePluginGUI (m_plugins[pluginIndex]); +} + +void KateDocument::unloadPlugin (uint pluginIndex) +{ + if (!m_plugins[pluginIndex]) return; + + disablePluginGUI (m_plugins[pluginIndex]); + + delete m_plugins[pluginIndex]; + m_plugins[pluginIndex] = 0L; +} + +void KateDocument::enablePluginGUI (KTextEditor::Plugin *plugin, KateView *view) +{ + if (!plugin) return; + if (!KTextEditor::pluginViewInterface(plugin)) return; + + KXMLGUIFactory *factory = view->factory(); + if ( factory ) + factory->removeClient( view ); + + KTextEditor::pluginViewInterface(plugin)->addView(view); + + if ( factory ) + factory->addClient( view ); +} + +void KateDocument::enablePluginGUI (KTextEditor::Plugin *plugin) +{ + if (!plugin) return; + if (!KTextEditor::pluginViewInterface(plugin)) return; + + for (uint i=0; i< m_views.count(); i++) + enablePluginGUI (plugin, m_views.at(i)); +} + +void KateDocument::disablePluginGUI (KTextEditor::Plugin *plugin, KateView *view) +{ + if (!plugin) return; + if (!KTextEditor::pluginViewInterface(plugin)) return; + + KXMLGUIFactory *factory = view->factory(); + if ( factory ) + factory->removeClient( view ); + + KTextEditor::pluginViewInterface( plugin )->removeView( view ); + + if ( factory ) + factory->addClient( view ); +} + +void KateDocument::disablePluginGUI (KTextEditor::Plugin *plugin) +{ + if (!plugin) return; + if (!KTextEditor::pluginViewInterface(plugin)) return; + + for (uint i=0; i< m_views.count(); i++) + disablePluginGUI (plugin, m_views.at(i)); +} +//END + +//BEGIN KTextEditor::Document stuff + +KTextEditor::View *KateDocument::createView( QWidget *parent, const char *name ) +{ + KateView* newView = new KateView( this, parent, name); + connect(newView, SIGNAL(cursorPositionChanged()), SLOT(undoCancel())); + if ( s_fileChangedDialogsActivated ) + connect( newView, SIGNAL(gotFocus( Kate::View * )), this, SLOT(slotModifiedOnDisk()) ); + return newView; +} + +QPtrList<KTextEditor::View> KateDocument::views () const +{ + return m_textEditViews; +} + +void KateDocument::setActiveView( KateView *view ) +{ + if ( m_activeView == view ) return; + + m_activeView = view; +} +//END + +//BEGIN KTextEditor::ConfigInterfaceExtension stuff + +uint KateDocument::configPages () const +{ + return 10; +} + +KTextEditor::ConfigPage *KateDocument::configPage (uint number, QWidget *parent, const char * ) +{ + switch( number ) + { + case 0: + return new KateViewDefaultsConfig (parent); + + case 1: + return new KateSchemaConfigPage (parent, this); + + case 2: + return new KateSelectConfigTab (parent); + + case 3: + return new KateEditConfigTab (parent); + + case 4: + return new KateIndentConfigTab (parent); + + case 5: + return new KateSaveConfigTab (parent); + + case 6: + return new KateHlConfigPage (parent, this); + + case 7: + return new KateFileTypeConfigTab (parent); + + case 8: + return new KateEditKeyConfiguration (parent, this); + + case 9: + return new KatePartPluginConfigPage (parent); + + default: + return 0; + } + + return 0; +} + +QString KateDocument::configPageName (uint number) const +{ + switch( number ) + { + case 0: + return i18n ("Appearance"); + + case 1: + return i18n ("Fonts & Colors"); + + case 2: + return i18n ("Cursor & Selection"); + + case 3: + return i18n ("Editing"); + + case 4: + return i18n ("Indentation"); + + case 5: + return i18n("Open/Save"); + + case 6: + return i18n ("Highlighting"); + + case 7: + return i18n("Filetypes"); + + case 8: + return i18n ("Shortcuts"); + + case 9: + return i18n ("Plugins"); + + default: + return QString (""); + } + + return QString (""); +} + +QString KateDocument::configPageFullName (uint number) const +{ + switch( number ) + { + case 0: + return i18n("Appearance"); + + case 1: + return i18n ("Font & Color Schemas"); + + case 2: + return i18n ("Cursor & Selection Behavior"); + + case 3: + return i18n ("Editing Options"); + + case 4: + return i18n ("Indentation Rules"); + + case 5: + return i18n("File Opening & Saving"); + + case 6: + return i18n ("Highlighting Rules"); + + case 7: + return i18n("Filetype Specific Settings"); + + case 8: + return i18n ("Shortcuts Configuration"); + + case 9: + return i18n ("Plugin Manager"); + + default: + return QString (""); + } + + return QString (""); +} + +QPixmap KateDocument::configPagePixmap (uint number, int size) const +{ + switch( number ) + { + case 0: + return BarIcon("view_text",size); + + case 1: + return BarIcon("colorize", size); + + case 2: + return BarIcon("frame_edit", size); + + case 3: + return BarIcon("edit", size); + + case 4: + return BarIcon("rightjust", size); + + case 5: + return BarIcon("filesave", size); + + case 6: + return BarIcon("source", size); + + case 7: + return BarIcon("edit", size); + + case 8: + return BarIcon("key_enter", size); + + case 9: + return BarIcon("connect_established", size); + + default: + return BarIcon("edit", size); + } + + return BarIcon("edit", size); +} +//END + +//BEGIN KTextEditor::EditInterface stuff + +QString KateDocument::text() const +{ + QString s; + + for (uint i = 0; i < m_buffer->count(); i++) + { + KateTextLine::Ptr textLine = m_buffer->plainLine(i); + + if (textLine) + { + s.append (textLine->string()); + + if ((i+1) < m_buffer->count()) + s.append('\n'); + } + } + + return s; +} + +QString KateDocument::text ( uint startLine, uint startCol, uint endLine, uint endCol ) const +{ + return text(startLine, startCol, endLine, endCol, false); +} + +QString KateDocument::text ( uint startLine, uint startCol, uint endLine, uint endCol, bool blockwise) const +{ + if ( blockwise && (startCol > endCol) ) + return QString (); + + QString s; + + if (startLine == endLine) + { + if (startCol > endCol) + return QString (); + + KateTextLine::Ptr textLine = m_buffer->plainLine(startLine); + + if ( !textLine ) + return QString (); + + return textLine->string(startCol, endCol-startCol); + } + else + { + + for (uint i = startLine; (i <= endLine) && (i < m_buffer->count()); i++) + { + KateTextLine::Ptr textLine = m_buffer->plainLine(i); + + if ( !blockwise ) + { + if (i == startLine) + s.append (textLine->string(startCol, textLine->length()-startCol)); + else if (i == endLine) + s.append (textLine->string(0, endCol)); + else + s.append (textLine->string()); + } + else + { + s.append( textLine->string( startCol, endCol-startCol)); + } + + if ( i < endLine ) + s.append('\n'); + } + } + + return s; +} + +QString KateDocument::textLine( uint line ) const +{ + KateTextLine::Ptr l = m_buffer->plainLine(line); + + if (!l) + return QString(); + + return l->string(); +} + +bool KateDocument::setText(const QString &s) +{ + if (!isReadWrite()) + return false; + + QPtrList<KTextEditor::Mark> m = marks (); + QValueList<KTextEditor::Mark> msave; + + for (uint i=0; i < m.count(); i++) + msave.append (*m.at(i)); + + editStart (); + + // delete the text + clear(); + + // insert the new text + insertText (0, 0, s); + + editEnd (); + + for (uint i=0; i < msave.count(); i++) + setMark (msave[i].line, msave[i].type); + + return true; +} + +bool KateDocument::clear() +{ + if (!isReadWrite()) + return false; + + for (KateView * view = m_views.first(); view != 0L; view = m_views.next() ) { + view->clear(); + view->tagAll(); + view->update(); + } + + clearMarks (); + + return removeText (0,0,lastLine()+1, 0); +} + +bool KateDocument::insertText( uint line, uint col, const QString &s) +{ + return insertText (line, col, s, false); +} + +bool KateDocument::insertText( uint line, uint col, const QString &s, bool blockwise ) +{ + if (!isReadWrite()) + return false; + + if (s.isEmpty()) + return true; + + if (line == numLines()) + editInsertLine(line,""); + else if (line > lastLine()) + return false; + + editStart (); + + uint insertPos = col; + uint len = s.length(); + + QString buf; + + bool replacetabs = ( config()->configFlags() & KateDocumentConfig::cfReplaceTabsDyn && ! m_isInUndo ); + uint tw = config()->tabWidth(); + uint insertPosExpanded = insertPos; + KateTextLine::Ptr l = m_buffer->line( line ); + if (l != 0) + insertPosExpanded = l->cursorX( insertPos, tw ); + + for (uint pos = 0; pos < len; pos++) + { + QChar ch = s[pos]; + + if (ch == '\n') + { + editInsertText (line, insertPos, buf); + + if ( !blockwise ) + { + editWrapLine (line, insertPos + buf.length()); + insertPos = insertPosExpanded = 0; + } + else + { + if ( line == lastLine() ) + editWrapLine (line, insertPos + buf.length()); + } + + line++; + buf.truncate(0); + l = m_buffer->line( line ); + if (l) + insertPosExpanded = l->cursorX( insertPos, tw ); + } + else + { + if ( replacetabs && ch == '\t' ) + { + uint tr = tw - ( insertPosExpanded+buf.length() )%tw; + for ( uint i=0; i < tr; i++ ) + buf += ' '; + } + else + buf += ch; // append char to buffer + } + } + + editInsertText (line, insertPos, buf); + + editEnd (); + emit textInserted(line,insertPos); + return true; +} + +bool KateDocument::removeText ( uint startLine, uint startCol, uint endLine, uint endCol ) +{ + return removeText (startLine, startCol, endLine, endCol, false); +} + +bool KateDocument::removeText ( uint startLine, uint startCol, uint endLine, uint endCol, bool blockwise) +{ + if (!isReadWrite()) + return false; + + if ( blockwise && (startCol > endCol) ) + return false; + + if ( startLine > endLine ) + return false; + + if ( startLine > lastLine() ) + return false; + + if (!blockwise) { + emit aboutToRemoveText(KateTextRange(startLine,startCol,endLine,endCol)); + } + editStart (); + + if ( !blockwise ) + { + if ( endLine > lastLine() ) + { + endLine = lastLine()+1; + endCol = 0; + } + + if (startLine == endLine) + { + editRemoveText (startLine, startCol, endCol-startCol); + } + else if ((startLine+1) == endLine) + { + if ( (m_buffer->plainLine(startLine)->length()-startCol) > 0 ) + editRemoveText (startLine, startCol, m_buffer->plainLine(startLine)->length()-startCol); + + editRemoveText (startLine+1, 0, endCol); + editUnWrapLine (startLine); + } + else + { + for (uint line = endLine; line >= startLine; line--) + { + if ((line > startLine) && (line < endLine)) + { + editRemoveLine (line); + } + else + { + if (line == endLine) + { + if ( endLine <= lastLine() ) + editRemoveText (line, 0, endCol); + } + else + { + if ( (m_buffer->plainLine(line)->length()-startCol) > 0 ) + editRemoveText (line, startCol, m_buffer->plainLine(line)->length()-startCol); + + editUnWrapLine (startLine); + } + } + + if ( line == 0 ) + break; + } + } + } // if ( ! blockwise ) + else + { + if ( endLine > lastLine() ) + endLine = lastLine (); + + for (uint line = endLine; line >= startLine; line--) + { + + editRemoveText (line, startCol, endCol-startCol); + + if ( line == 0 ) + break; + } + } + + editEnd (); + emit textRemoved(); + return true; +} + +bool KateDocument::insertLine( uint l, const QString &str ) +{ + if (!isReadWrite()) + return false; + + if (l > numLines()) + return false; + + return editInsertLine (l, str); +} + +bool KateDocument::removeLine( uint line ) +{ + if (!isReadWrite()) + return false; + + if (line > lastLine()) + return false; + + return editRemoveLine (line); +} + +uint KateDocument::length() const +{ + uint l = 0; + + for (uint i = 0; i < m_buffer->count(); i++) + { + KateTextLine::Ptr line = m_buffer->plainLine(i); + + if (line) + l += line->length(); + } + + return l; +} + +uint KateDocument::numLines() const +{ + return m_buffer->count(); +} + +uint KateDocument::numVisLines() const +{ + return m_buffer->countVisible (); +} + +int KateDocument::lineLength ( uint line ) const +{ + KateTextLine::Ptr l = m_buffer->plainLine(line); + + if (!l) + return -1; + + return l->length(); +} +//END + +//BEGIN KTextEditor::EditInterface internal stuff +// +// Starts an edit session with (or without) undo, update of view disabled during session +// +void KateDocument::editStart (bool withUndo) +{ + editSessionNumber++; + + if (editSessionNumber > 1) + return; + + editIsRunning = true; + editWithUndo = withUndo; + + if (editWithUndo) + undoStart(); + else + undoCancel(); + + for (uint z = 0; z < m_views.count(); z++) + { + m_views.at(z)->editStart (); + } + + m_buffer->editStart (); +} + +void KateDocument::undoStart() +{ + if (m_editCurrentUndo || (m_activeView && m_activeView->imComposeEvent())) return; + + // Make sure the buffer doesn't get bigger than requested + if ((config()->undoSteps() > 0) && (undoItems.count() > config()->undoSteps())) + { + undoItems.setAutoDelete(true); + undoItems.removeFirst(); + undoItems.setAutoDelete(false); + docWasSavedWhenUndoWasEmpty = false; + } + + // new current undo item + m_editCurrentUndo = new KateUndoGroup(this); +} + +void KateDocument::undoEnd() +{ + if (m_activeView && m_activeView->imComposeEvent()) + return; + + if (m_editCurrentUndo) + { + bool changedUndo = false; + + if (m_editCurrentUndo->isEmpty()) + delete m_editCurrentUndo; + else if (!m_undoDontMerge && undoItems.last() && undoItems.last()->merge(m_editCurrentUndo,m_undoComplexMerge)) + delete m_editCurrentUndo; + else + { + undoItems.append(m_editCurrentUndo); + changedUndo = true; + } + + m_undoDontMerge = false; + m_undoIgnoreCancel = true; + + m_editCurrentUndo = 0L; + + // (Re)Start the single-shot timer to cancel the undo merge + // the user has 5 seconds to input more data, or undo merging gets canceled for the current undo item. + m_undoMergeTimer->start(5000, true); + + if (changedUndo) + emit undoChanged(); + } +} + +void KateDocument::undoCancel() +{ + if (m_undoIgnoreCancel) { + m_undoIgnoreCancel = false; + return; + } + + m_undoDontMerge = true; + + Q_ASSERT(!m_editCurrentUndo); + + // As you can see by the above assert, neither of these should really be required + delete m_editCurrentUndo; + m_editCurrentUndo = 0L; +} + +void KateDocument::undoSafePoint() { + Q_ASSERT(m_editCurrentUndo); + if (!m_editCurrentUndo) return; + m_editCurrentUndo->safePoint(); +} + +// +// End edit session and update Views +// +void KateDocument::editEnd () +{ + if (editSessionNumber == 0) + return; + + // wrap the new/changed text, if something really changed! + if (m_buffer->editChanged() && (editSessionNumber == 1)) + if (editWithUndo && config()->wordWrap()) + wrapText (m_buffer->editTagStart(), m_buffer->editTagEnd()); + + editSessionNumber--; + + if (editSessionNumber > 0) + return; + + // end buffer edit, will trigger hl update + // this will cause some possible adjustment of tagline start/end + m_buffer->editEnd (); + + if (editWithUndo) + undoEnd(); + + // edit end for all views !!!!!!!!! + for (uint z = 0; z < m_views.count(); z++) + m_views.at(z)->editEnd (m_buffer->editTagStart(), m_buffer->editTagEnd(), m_buffer->editTagFrom()); + + if (m_buffer->editChanged()) + { + setModified(true); + emit textChanged (); + } + + editIsRunning = false; +} + +bool KateDocument::wrapText (uint startLine, uint endLine) +{ + uint col = config()->wordWrapAt(); + + if (col == 0) + return false; + + editStart (); + + for (uint line = startLine; (line <= endLine) && (line < numLines()); line++) + { + KateTextLine::Ptr l = m_buffer->line(line); + + if (!l) + return false; + + kdDebug (13020) << "try wrap line: " << line << endl; + + if (l->lengthWithTabs(m_buffer->tabWidth()) > col) + { + KateTextLine::Ptr nextl = m_buffer->line(line+1); + + kdDebug (13020) << "do wrap line: " << line << endl; + + const QChar *text = l->text(); + uint eolPosition = l->length()-1; + + // take tabs into account here, too + uint x = 0; + const QString & t = l->string(); + uint z2 = 0; + for ( ; z2 < l->length(); z2++) + { + if (t[z2] == QChar('\t')) + x += m_buffer->tabWidth() - (x % m_buffer->tabWidth()); + else + x++; + + if (x > col) + break; + } + + uint searchStart = kMin (z2, l->length()-1); + + // If where we are wrapping is an end of line and is a space we don't + // want to wrap there + if (searchStart == eolPosition && text[searchStart].isSpace()) + searchStart--; + + // Scan backwards looking for a place to break the line + // We are not interested in breaking at the first char + // of the line (if it is a space), but we are at the second + // anders: if we can't find a space, try breaking on a word + // boundry, using KateHighlight::canBreakAt(). + // This could be a priority (setting) in the hl/filetype/document + int z = 0; + uint nw = 0; // alternative position, a non word character + for (z=searchStart; z > 0; z--) + { + if (text[z].isSpace()) break; + if ( ! nw && highlight()->canBreakAt( text[z] , l->attribute(z) ) ) + nw = z; + } + + if (z > 0) + { + // cu space + editRemoveText (line, z, 1); + } + else + { + // There was no space to break at so break at a nonword character if + // found, or at the wrapcolumn ( that needs be configurable ) + // Don't try and add any white space for the break + if ( nw && nw < col ) nw++; // break on the right side of the character + z = nw ? nw : col; + } + + if (nextl && !nextl->isAutoWrapped()) + { + editWrapLine (line, z, true); + editMarkLineAutoWrapped (line+1, true); + + endLine++; + } + else + { + if (nextl && (nextl->length() > 0) && !nextl->getChar(0).isSpace() && ((l->length() < 1) || !l->getChar(l->length()-1).isSpace())) + editInsertText (line+1, 0, QString (" ")); + + bool newLineAdded = false; + editWrapLine (line, z, false, &newLineAdded); + + editMarkLineAutoWrapped (line+1, true); + + endLine++; + } + } + } + + editEnd (); + + return true; +} + +void KateDocument::editAddUndo (KateUndoGroup::UndoType type, uint line, uint col, uint len, const QString &text) +{ + if (editIsRunning && editWithUndo && m_editCurrentUndo) { + m_editCurrentUndo->addItem(type, line, col, len, text); + + // Clear redo buffer + if (redoItems.count()) { + redoItems.setAutoDelete(true); + redoItems.clear(); + redoItems.setAutoDelete(false); + } + } +} + +bool KateDocument::editInsertText ( uint line, uint col, const QString &str ) +{ + if (!isReadWrite()) + return false; + + QString s = str; + + KateTextLine::Ptr l = m_buffer->line(line); + + if (!l) + return false; + + if ( config()->configFlags() & KateDocumentConfig::cfReplaceTabsDyn && ! m_isInUndo ) + { + uint tw = config()->tabWidth(); + int pos = 0; + uint l = 0; + while ( (pos = s.find('\t')) > -1 ) + { + l = tw - ( (col + pos)%tw ); + s.replace( pos, 1, QString().fill( ' ', l ) ); + } + } + + editStart (); + + editAddUndo (KateUndoGroup::editInsertText, line, col, s.length(), s); + + l->insertText (col, s.length(), s.unicode()); +// removeTrailingSpace(line); // ### nessecary? + + m_buffer->changeLine(line); + + for( QPtrListIterator<KateSuperCursor> it (m_superCursors); it.current(); ++it ) + it.current()->editTextInserted (line, col, s.length()); + + editEnd (); + + return true; +} + +bool KateDocument::editRemoveText ( uint line, uint col, uint len ) +{ + if (!isReadWrite()) + return false; + + KateTextLine::Ptr l = m_buffer->line(line); + + if (!l) + return false; + + editStart (); + + editAddUndo (KateUndoGroup::editRemoveText, line, col, len, l->string().mid(col, len)); + + l->removeText (col, len); + removeTrailingSpace( line ); + + m_buffer->changeLine(line); + + for( QPtrListIterator<KateSuperCursor> it (m_superCursors); it.current(); ++it ) + it.current()->editTextRemoved (line, col, len); + + editEnd (); + + return true; +} + +bool KateDocument::editMarkLineAutoWrapped ( uint line, bool autowrapped ) +{ + if (!isReadWrite()) + return false; + + KateTextLine::Ptr l = m_buffer->line(line); + + if (!l) + return false; + + editStart (); + + editAddUndo (KateUndoGroup::editMarkLineAutoWrapped, line, autowrapped ? 1 : 0, 0, QString::null); + + l->setAutoWrapped (autowrapped); + + m_buffer->changeLine(line); + + editEnd (); + + return true; +} + +bool KateDocument::editWrapLine ( uint line, uint col, bool newLine, bool *newLineAdded) +{ + if (!isReadWrite()) + return false; + + KateTextLine::Ptr l = m_buffer->line(line); + + if (!l) + return false; + + editStart (); + + KateTextLine::Ptr nextLine = m_buffer->line(line+1); + + int pos = l->length() - col; + + if (pos < 0) + pos = 0; + + editAddUndo (KateUndoGroup::editWrapLine, line, col, pos, (!nextLine || newLine) ? "1" : "0"); + + if (!nextLine || newLine) + { + KateTextLine::Ptr textLine = new KateTextLine(); + + textLine->insertText (0, pos, l->text()+col, l->attributes()+col); + l->truncate(col); + + m_buffer->insertLine (line+1, textLine); + m_buffer->changeLine(line); + + QPtrList<KTextEditor::Mark> list; + for( QIntDictIterator<KTextEditor::Mark> it( m_marks ); it.current(); ++it ) + { + if( it.current()->line >= line ) + { + if ((col == 0) || (it.current()->line > line)) + list.append( it.current() ); + } + } + + for( QPtrListIterator<KTextEditor::Mark> it( list ); it.current(); ++it ) + { + KTextEditor::Mark* mark = m_marks.take( it.current()->line ); + mark->line++; + m_marks.insert( mark->line, mark ); + } + + if( !list.isEmpty() ) + emit marksChanged(); + + // yes, we added a new line ! + if (newLineAdded) + (*newLineAdded) = true; + } + else + { + nextLine->insertText (0, pos, l->text()+col, l->attributes()+col); + l->truncate(col); + + m_buffer->changeLine(line); + m_buffer->changeLine(line+1); + + // no, no new line added ! + if (newLineAdded) + (*newLineAdded) = false; + } + + for( QPtrListIterator<KateSuperCursor> it (m_superCursors); it.current(); ++it ) + it.current()->editLineWrapped (line, col, !nextLine || newLine); + + editEnd (); + + return true; +} + +bool KateDocument::editUnWrapLine ( uint line, bool removeLine, uint length ) +{ + if (!isReadWrite()) + return false; + + KateTextLine::Ptr l = m_buffer->line(line); + KateTextLine::Ptr nextLine = m_buffer->line(line+1); + + if (!l || !nextLine) + return false; + + editStart (); + + uint col = l->length (); + + editAddUndo (KateUndoGroup::editUnWrapLine, line, col, length, removeLine ? "1" : "0"); + + if (removeLine) + { + l->insertText (col, nextLine->length(), nextLine->text(), nextLine->attributes()); + + m_buffer->changeLine(line); + m_buffer->removeLine(line+1); + } + else + { + l->insertText (col, (nextLine->length() < length) ? nextLine->length() : length, + nextLine->text(), nextLine->attributes()); + nextLine->removeText (0, (nextLine->length() < length) ? nextLine->length() : length); + + m_buffer->changeLine(line); + m_buffer->changeLine(line+1); + } + + QPtrList<KTextEditor::Mark> list; + for( QIntDictIterator<KTextEditor::Mark> it( m_marks ); it.current(); ++it ) + { + if( it.current()->line >= line+1 ) + list.append( it.current() ); + + if ( it.current()->line == line+1 ) + { + KTextEditor::Mark* mark = m_marks.take( line ); + + if (mark) + { + it.current()->type |= mark->type; + } + } + } + + for( QPtrListIterator<KTextEditor::Mark> it( list ); it.current(); ++it ) + { + KTextEditor::Mark* mark = m_marks.take( it.current()->line ); + mark->line--; + m_marks.insert( mark->line, mark ); + } + + if( !list.isEmpty() ) + emit marksChanged(); + + for( QPtrListIterator<KateSuperCursor> it (m_superCursors); it.current(); ++it ) + it.current()->editLineUnWrapped (line, col, removeLine, length); + + editEnd (); + + return true; +} + +bool KateDocument::editInsertLine ( uint line, const QString &s ) +{ + if (!isReadWrite()) + return false; + + if ( line > numLines() ) + return false; + + editStart (); + + editAddUndo (KateUndoGroup::editInsertLine, line, 0, s.length(), s); + + removeTrailingSpace( line ); // old line + + KateTextLine::Ptr tl = new KateTextLine(); + tl->insertText (0, s.length(), s.unicode(), 0); + m_buffer->insertLine(line, tl); + m_buffer->changeLine(line); + + removeTrailingSpace( line ); // new line + + QPtrList<KTextEditor::Mark> list; + for( QIntDictIterator<KTextEditor::Mark> it( m_marks ); it.current(); ++it ) + { + if( it.current()->line >= line ) + list.append( it.current() ); + } + + for( QPtrListIterator<KTextEditor::Mark> it( list ); it.current(); ++it ) + { + KTextEditor::Mark* mark = m_marks.take( it.current()->line ); + mark->line++; + m_marks.insert( mark->line, mark ); + } + + if( !list.isEmpty() ) + emit marksChanged(); + + for( QPtrListIterator<KateSuperCursor> it (m_superCursors); it.current(); ++it ) + it.current()->editLineInserted (line); + + editEnd (); + + return true; +} + +bool KateDocument::editRemoveLine ( uint line ) +{ + if (!isReadWrite()) + return false; + + if ( line > lastLine() ) + return false; + + if ( numLines() == 1 ) + return editRemoveText (0, 0, m_buffer->line(0)->length()); + + editStart (); + + editAddUndo (KateUndoGroup::editRemoveLine, line, 0, lineLength(line), textLine(line)); + + m_buffer->removeLine(line); + + QPtrList<KTextEditor::Mark> list; + KTextEditor::Mark* rmark = 0; + for( QIntDictIterator<KTextEditor::Mark> it( m_marks ); it.current(); ++it ) + { + if ( (it.current()->line > line) ) + list.append( it.current() ); + else if ( (it.current()->line == line) ) + rmark = it.current(); + } + + if (rmark) + delete (m_marks.take (rmark->line)); + + for( QPtrListIterator<KTextEditor::Mark> it( list ); it.current(); ++it ) + { + KTextEditor::Mark* mark = m_marks.take( it.current()->line ); + mark->line--; + m_marks.insert( mark->line, mark ); + } + + if( !list.isEmpty() ) + emit marksChanged(); + + for( QPtrListIterator<KateSuperCursor> it (m_superCursors); it.current(); ++it ) + it.current()->editLineRemoved (line); + + editEnd(); + + return true; +} +//END + +//BEGIN KTextEditor::UndoInterface stuff + +uint KateDocument::undoCount () const +{ + return undoItems.count (); +} + +uint KateDocument::redoCount () const +{ + return redoItems.count (); +} + +uint KateDocument::undoSteps () const +{ + return m_config->undoSteps(); +} + +void KateDocument::setUndoSteps(uint steps) +{ + m_config->setUndoSteps (steps); +} + +void KateDocument::undo() +{ + m_isInUndo = true; + if ((undoItems.count() > 0) && undoItems.last()) + { + clearSelection (); + + undoItems.last()->undo(); + redoItems.append (undoItems.last()); + undoItems.removeLast (); + updateModified(); + + emit undoChanged (); + } + m_isInUndo = false; +} + +void KateDocument::redo() +{ + m_isInUndo = true; + if ((redoItems.count() > 0) && redoItems.last()) + { + clearSelection (); + + redoItems.last()->redo(); + undoItems.append (redoItems.last()); + redoItems.removeLast (); + updateModified(); + + emit undoChanged (); + } + m_isInUndo = false; +} + +void KateDocument::updateModified() +{ + /* + How this works: + + After noticing that there where to many scenarios to take into + consideration when using 'if's to toggle the "Modified" flag + I came up with this baby, flexible and repetitive calls are + minimal. + + A numeric unique pattern is generated by toggleing a set of bits, + each bit symbolizes a different state in the Undo Redo structure. + + undoItems.isEmpty() != null BIT 1 + redoItems.isEmpty() != null BIT 2 + docWasSavedWhenUndoWasEmpty == true BIT 3 + docWasSavedWhenRedoWasEmpty == true BIT 4 + lastUndoGroupWhenSavedIsLastUndo BIT 5 + lastUndoGroupWhenSavedIsLastRedo BIT 6 + lastRedoGroupWhenSavedIsLastUndo BIT 7 + lastRedoGroupWhenSavedIsLastRedo BIT 8 + + If you find a new pattern, please add it to the patterns array + */ + + unsigned char currentPattern = 0; + const unsigned char patterns[] = {5,16,24,26,88,90,93,133,144,149,165}; + const unsigned char patternCount = sizeof(patterns); + KateUndoGroup* undoLast = 0; + KateUndoGroup* redoLast = 0; + + if (undoItems.isEmpty()) + { + currentPattern |= 1; + } + else + { + undoLast = undoItems.last(); + } + + if (redoItems.isEmpty()) + { + currentPattern |= 2; + } + else + { + redoLast = redoItems.last(); + } + + if (docWasSavedWhenUndoWasEmpty) currentPattern |= 4; + if (docWasSavedWhenRedoWasEmpty) currentPattern |= 8; + if (lastUndoGroupWhenSaved == undoLast) currentPattern |= 16; + if (lastUndoGroupWhenSaved == redoLast) currentPattern |= 32; + if (lastRedoGroupWhenSaved == undoLast) currentPattern |= 64; + if (lastRedoGroupWhenSaved == redoLast) currentPattern |= 128; + + // This will print out the pattern information + + kdDebug(13020) << k_funcinfo + << "Pattern:" << static_cast<unsigned int>(currentPattern) << endl; + + for (uint patternIndex = 0; patternIndex < patternCount; ++patternIndex) + { + if ( currentPattern == patterns[patternIndex] ) + { + setModified( false ); + kdDebug(13020) << k_funcinfo << "setting modified to false!" << endl; + break; + } + } +} + +void KateDocument::clearUndo() +{ + undoItems.setAutoDelete (true); + undoItems.clear (); + undoItems.setAutoDelete (false); + + lastUndoGroupWhenSaved = 0; + docWasSavedWhenUndoWasEmpty = false; + + emit undoChanged (); +} + +void KateDocument::clearRedo() +{ + redoItems.setAutoDelete (true); + redoItems.clear (); + redoItems.setAutoDelete (false); + + lastRedoGroupWhenSaved = 0; + docWasSavedWhenRedoWasEmpty = false; + + emit undoChanged (); +} + +QPtrList<KTextEditor::Cursor> KateDocument::cursors () const +{ + return myCursors; +} +//END + +//BEGIN KTextEditor::SearchInterface stuff + +bool KateDocument::searchText (unsigned int startLine, unsigned int startCol, const QString &text, unsigned int *foundAtLine, unsigned int *foundAtCol, unsigned int *matchLen, bool casesensitive, bool backwards) +{ + if (text.isEmpty()) + return false; + + int line = startLine; + int col = startCol; + + if (!backwards) + { + int searchEnd = lastLine(); + + while (line <= searchEnd) + { + KateTextLine::Ptr textLine = m_buffer->plainLine(line); + + if (!textLine) + return false; + + uint foundAt, myMatchLen; + bool found = textLine->searchText (col, text, &foundAt, &myMatchLen, casesensitive, false); + + if (found) + { + (*foundAtLine) = line; + (*foundAtCol) = foundAt; + (*matchLen) = myMatchLen; + return true; + } + + col = 0; + line++; + } + } + else + { + // backward search + int searchEnd = 0; + + while (line >= searchEnd) + { + KateTextLine::Ptr textLine = m_buffer->plainLine(line); + + if (!textLine) + return false; + + uint foundAt, myMatchLen; + bool found = textLine->searchText (col, text, &foundAt, &myMatchLen, casesensitive, true); + + if (found) + { + /* if ((uint) line == startLine && foundAt + myMatchLen >= (uint) col + && line == selectStart.line() && foundAt == (uint) selectStart.col() + && line == selectEnd.line() && foundAt + myMatchLen == (uint) selectEnd.col()) + { + // To avoid getting stuck at one match we skip a match if it is already + // selected (most likely because it has just been found). + if (foundAt > 0) + col = foundAt - 1; + else { + if (--line >= 0) + col = lineLength(line); + } + continue; + }*/ + + (*foundAtLine) = line; + (*foundAtCol) = foundAt; + (*matchLen) = myMatchLen; + return true; + } + + if (line >= 1) + col = lineLength(line-1); + + line--; + } + } + + return false; +} + +bool KateDocument::searchText (unsigned int startLine, unsigned int startCol, const QRegExp ®exp, unsigned int *foundAtLine, unsigned int *foundAtCol, unsigned int *matchLen, bool backwards) +{ + kdDebug(13020)<<"KateDocument::searchText( "<<startLine<<", "<<startCol<<", "<<regexp.pattern()<<", "<<backwards<<" )"<<endl; + if (regexp.isEmpty() || !regexp.isValid()) + return false; + + int line = startLine; + int col = startCol; + + if (!backwards) + { + int searchEnd = lastLine(); + + while (line <= searchEnd) + { + KateTextLine::Ptr textLine = m_buffer->plainLine(line); + + if (!textLine) + return false; + + uint foundAt, myMatchLen; + bool found = textLine->searchText (col, regexp, &foundAt, &myMatchLen, false); + + if (found) + { + // A special case which can only occur when searching with a regular expression consisting + // only of a lookahead (e.g. ^(?=\{) for a function beginning without selecting '{'). + if (myMatchLen == 0 && (uint) line == startLine && foundAt == (uint) col) + { + if (col < lineLength(line)) + col++; + else { + line++; + col = 0; + } + continue; + } + + (*foundAtLine) = line; + (*foundAtCol) = foundAt; + (*matchLen) = myMatchLen; + return true; + } + + col = 0; + line++; + } + } + else + { + // backward search + int searchEnd = 0; + + while (line >= searchEnd) + { + KateTextLine::Ptr textLine = m_buffer->plainLine(line); + + if (!textLine) + return false; + + uint foundAt, myMatchLen; + bool found = textLine->searchText (col, regexp, &foundAt, &myMatchLen, true); + + if (found) + { + /*if ((uint) line == startLine && foundAt + myMatchLen >= (uint) col + && line == selectStart.line() && foundAt == (uint) selectStart.col() + && line == selectEnd.line() && foundAt + myMatchLen == (uint) selectEnd.col()) + { + // To avoid getting stuck at one match we skip a match if it is already + // selected (most likely because it has just been found). + if (foundAt > 0) + col = foundAt - 1; + else { + if (--line >= 0) + col = lineLength(line); + } + continue; + }*/ + + (*foundAtLine) = line; + (*foundAtCol) = foundAt; + (*matchLen) = myMatchLen; + return true; + } + + if (line >= 1) + col = lineLength(line-1); + + line--; + } + } + + return false; +} +//END + +//BEGIN KTextEditor::HighlightingInterface stuff + +uint KateDocument::hlMode () +{ + return KateHlManager::self()->findHl(highlight()); +} + +bool KateDocument::setHlMode (uint mode) +{ + m_buffer->setHighlight (mode); + + if (true) + { + setDontChangeHlOnSave(); + return true; + } + + return false; +} + +void KateDocument::bufferHlChanged () +{ + // update all views + makeAttribs(false); + + emit hlChanged(); +} + +uint KateDocument::hlModeCount () +{ + return KateHlManager::self()->highlights(); +} + +QString KateDocument::hlModeName (uint mode) +{ + return KateHlManager::self()->hlName (mode); +} + +QString KateDocument::hlModeSectionName (uint mode) +{ + return KateHlManager::self()->hlSection (mode); +} + +void KateDocument::setDontChangeHlOnSave() +{ + hlSetByUser = true; +} +//END + +//BEGIN KTextEditor::ConfigInterface stuff +void KateDocument::readConfig(KConfig *config) +{ + config->setGroup("Kate Document Defaults"); + + // read max loadable blocks, more blocks will be swapped out + KateBuffer::setMaxLoadedBlocks (config->readNumEntry("Maximal Loaded Blocks", KateBuffer::maxLoadedBlocks())); + + KateDocumentConfig::global()->readConfig (config); + + config->setGroup("Kate View Defaults"); + KateViewConfig::global()->readConfig (config); + + config->setGroup("Kate Renderer Defaults"); + KateRendererConfig::global()->readConfig (config); +} + +void KateDocument::writeConfig(KConfig *config) +{ + config->setGroup("Kate Document Defaults"); + + // write max loadable blocks, more blocks will be swapped out + config->writeEntry("Maximal Loaded Blocks", KateBuffer::maxLoadedBlocks()); + + KateDocumentConfig::global()->writeConfig (config); + + config->setGroup("Kate View Defaults"); + KateViewConfig::global()->writeConfig (config); + + config->setGroup("Kate Renderer Defaults"); + KateRendererConfig::global()->writeConfig (config); +} + +void KateDocument::readConfig() +{ + KConfig *config = kapp->config(); + readConfig (config); +} + +void KateDocument::writeConfig() +{ + KConfig *config = kapp->config(); + writeConfig (config); + config->sync(); +} + +void KateDocument::readSessionConfig(KConfig *kconfig) +{ + // restore the url + KURL url (kconfig->readEntry("URL")); + + // get the encoding + QString tmpenc=kconfig->readEntry("Encoding"); + if (!tmpenc.isEmpty() && (tmpenc != encoding())) + setEncoding(tmpenc); + + // open the file if url valid + if (!url.isEmpty() && url.isValid()) + openURL (url); + + // restore the hl stuff + m_buffer->setHighlight(KateHlManager::self()->nameFind(kconfig->readEntry("Highlighting"))); + + if (hlMode() > 0) + hlSetByUser = true; + + // indent mode + config()->setIndentationMode( (uint)kconfig->readNumEntry("Indentation Mode", config()->indentationMode() ) ); + + // Restore Bookmarks + QValueList<int> marks = kconfig->readIntListEntry("Bookmarks"); + for( uint i = 0; i < marks.count(); i++ ) + addMark( marks[i], KateDocument::markType01 ); +} + +void KateDocument::writeSessionConfig(KConfig *kconfig) +{ + if ( m_url.isLocalFile() && !KGlobal::dirs()->relativeLocation("tmp", m_url.path()).startsWith("/")) + return; + // save url + kconfig->writeEntry("URL", m_url.prettyURL() ); + + // save encoding + kconfig->writeEntry("Encoding",encoding()); + + // save hl + kconfig->writeEntry("Highlighting", highlight()->name()); + + kconfig->writeEntry("Indentation Mode", config()->indentationMode() ); + + // Save Bookmarks + QValueList<int> marks; + for( QIntDictIterator<KTextEditor::Mark> it( m_marks ); + it.current() && it.current()->type & KTextEditor::MarkInterface::markType01; + ++it ) + marks << it.current()->line; + + kconfig->writeEntry( "Bookmarks", marks ); +} + +void KateDocument::configDialog() +{ + KDialogBase *kd = new KDialogBase ( KDialogBase::IconList, + i18n("Configure"), + KDialogBase::Ok | KDialogBase::Cancel | KDialogBase::Help, + KDialogBase::Ok, + kapp->mainWidget() ); + +#ifndef Q_WS_WIN //TODO: reenable + KWin::setIcons( kd->winId(), kapp->icon(), kapp->miniIcon() ); +#endif + + QPtrList<KTextEditor::ConfigPage> editorPages; + + for (uint i = 0; i < KTextEditor::configInterfaceExtension (this)->configPages (); i++) + { + QStringList path; + path.clear(); + path << KTextEditor::configInterfaceExtension (this)->configPageName (i); + QVBox *page = kd->addVBoxPage(path, KTextEditor::configInterfaceExtension (this)->configPageFullName (i), + KTextEditor::configInterfaceExtension (this)->configPagePixmap(i, KIcon::SizeMedium) ); + + editorPages.append (KTextEditor::configInterfaceExtension (this)->configPage(i, page)); + } + + if (kd->exec()) + { + KateDocumentConfig::global()->configStart (); + KateViewConfig::global()->configStart (); + KateRendererConfig::global()->configStart (); + + for (uint i=0; i<editorPages.count(); i++) + { + editorPages.at(i)->apply(); + } + + KateDocumentConfig::global()->configEnd (); + KateViewConfig::global()->configEnd (); + KateRendererConfig::global()->configEnd (); + + writeConfig (); + } + + delete kd; +} + +uint KateDocument::mark( uint line ) +{ + if( !m_marks[line] ) + return 0; + return m_marks[line]->type; +} + +void KateDocument::setMark( uint line, uint markType ) +{ + clearMark( line ); + addMark( line, markType ); +} + +void KateDocument::clearMark( uint line ) +{ + if( line > lastLine() ) + return; + + if( !m_marks[line] ) + return; + + KTextEditor::Mark* mark = m_marks.take( line ); + emit markChanged( *mark, MarkRemoved ); + emit marksChanged(); + delete mark; + tagLines( line, line ); + repaintViews(true); +} + +void KateDocument::addMark( uint line, uint markType ) +{ + if( line > lastLine()) + return; + + if( markType == 0 ) + return; + + if( m_marks[line] ) { + KTextEditor::Mark* mark = m_marks[line]; + + // Remove bits already set + markType &= ~mark->type; + + if( markType == 0 ) + return; + + // Add bits + mark->type |= markType; + } else { + KTextEditor::Mark *mark = new KTextEditor::Mark; + mark->line = line; + mark->type = markType; + m_marks.insert( line, mark ); + } + + // Emit with a mark having only the types added. + KTextEditor::Mark temp; + temp.line = line; + temp.type = markType; + emit markChanged( temp, MarkAdded ); + + emit marksChanged(); + tagLines( line, line ); + repaintViews(true); +} + +void KateDocument::removeMark( uint line, uint markType ) +{ + if( line > lastLine() ) + return; + if( !m_marks[line] ) + return; + + KTextEditor::Mark* mark = m_marks[line]; + + // Remove bits not set + markType &= mark->type; + + if( markType == 0 ) + return; + + // Subtract bits + mark->type &= ~markType; + + // Emit with a mark having only the types removed. + KTextEditor::Mark temp; + temp.line = line; + temp.type = markType; + emit markChanged( temp, MarkRemoved ); + + if( mark->type == 0 ) + m_marks.remove( line ); + + emit marksChanged(); + tagLines( line, line ); + repaintViews(true); +} + +QPtrList<KTextEditor::Mark> KateDocument::marks() +{ + QPtrList<KTextEditor::Mark> list; + + for( QIntDictIterator<KTextEditor::Mark> it( m_marks ); + it.current(); ++it ) { + list.append( it.current() ); + } + + return list; +} + +void KateDocument::clearMarks() +{ + for( QIntDictIterator<KTextEditor::Mark> it( m_marks ); + it.current(); ++it ) { + KTextEditor::Mark* mark = it.current(); + emit markChanged( *mark, MarkRemoved ); + tagLines( mark->line, mark->line ); + } + + m_marks.clear(); + + emit marksChanged(); + repaintViews(true); +} + +void KateDocument::setPixmap( MarkInterface::MarkTypes type, const QPixmap& pixmap ) +{ + m_markPixmaps.replace( type, new QPixmap( pixmap ) ); +} + +void KateDocument::setDescription( MarkInterface::MarkTypes type, const QString& description ) +{ + m_markDescriptions.replace( type, new QString( description ) ); +} + +QPixmap *KateDocument::markPixmap( MarkInterface::MarkTypes type ) +{ + return m_markPixmaps[type]; +} + +QColor KateDocument::markColor( MarkInterface::MarkTypes type ) +{ + uint reserved = (0x1 << KTextEditor::MarkInterface::reservedMarkersCount()) - 1; + if ((uint)type >= (uint)markType01 && (uint)type <= reserved) { + return KateRendererConfig::global()->lineMarkerColor(type); + } else { + return QColor(); + } +} + +QString KateDocument::markDescription( MarkInterface::MarkTypes type ) +{ + if( m_markDescriptions[type] ) + return *m_markDescriptions[type]; + return QString::null; +} + +void KateDocument::setMarksUserChangable( uint markMask ) +{ + m_editableMarks = markMask; +} + +uint KateDocument::editableMarks() +{ + return m_editableMarks; +} +//END + +//BEGIN KTextEditor::PrintInterface stuff +bool KateDocument::printDialog () +{ + return KatePrinter::print (this); +} + +bool KateDocument::print () +{ + return KatePrinter::print (this); +} +//END + +//BEGIN KTextEditor::DocumentInfoInterface (### unfinished) +QString KateDocument::mimeType() +{ + KMimeType::Ptr result = KMimeType::defaultMimeTypePtr(); + + // if the document has a URL, try KMimeType::findByURL + if ( ! m_url.isEmpty() ) + result = KMimeType::findByURL( m_url ); + + else if ( m_url.isEmpty() || ! m_url.isLocalFile() ) + result = mimeTypeForContent(); + + return result->name(); +} + +// TODO implement this -- how to calculate? +long KateDocument::fileSize() +{ + return 0; +} + +// TODO implement this +QString KateDocument::niceFileSize() +{ + return "UNKNOWN"; +} + +KMimeType::Ptr KateDocument::mimeTypeForContent() +{ + QByteArray buf (1024); + uint bufpos = 0; + + for (uint i=0; i < numLines(); i++) + { + QString line = textLine( i ); + uint len = line.length() + 1; + + if (bufpos + len > 1024) + len = 1024 - bufpos; + + memcpy(&buf[bufpos], (line + "\n").latin1(), len); + + bufpos += len; + + if (bufpos >= 1024) + break; + } + buf.resize( bufpos ); + + int accuracy = 0; + return KMimeType::findByContent( buf, &accuracy ); +} +//END KTextEditor::DocumentInfoInterface + + +//BEGIN KParts::ReadWrite stuff + +bool KateDocument::openURL( const KURL &url ) +{ +// kdDebug(13020)<<"KateDocument::openURL( "<<url.prettyURL()<<")"<<endl; + // no valid URL + if ( !url.isValid() ) + return false; + + // could not close old one + if ( !closeURL() ) + return false; + + // set my url + m_url = url; + + if ( m_url.isLocalFile() ) + { + // local mode, just like in kpart + + m_file = m_url.path(); + + emit started( 0 ); + + if (openFile()) + { + emit completed(); + emit setWindowCaption( m_url.prettyURL() ); + + return true; + } + + return false; + } + else + { + // remote mode + + m_bTemp = true; + + m_tempFile = new KTempFile (); + m_file = m_tempFile->name(); + + m_job = KIO::get ( url, false, isProgressInfoEnabled() ); + + // connect to slots + connect( m_job, SIGNAL( data( KIO::Job*, const QByteArray& ) ), + SLOT( slotDataKate( KIO::Job*, const QByteArray& ) ) ); + + connect( m_job, SIGNAL( result( KIO::Job* ) ), + SLOT( slotFinishedKate( KIO::Job* ) ) ); + + QWidget *w = widget (); + if (!w && !m_views.isEmpty ()) + w = m_views.first(); + + if (w) + m_job->setWindow (w->topLevelWidget()); + + emit started( m_job ); + + return true; + } +} + +void KateDocument::slotDataKate ( KIO::Job *, const QByteArray &data ) +{ +// kdDebug(13020) << "KateDocument::slotData" << endl; + + if (!m_tempFile || !m_tempFile->file()) + return; + + m_tempFile->file()->writeBlock (data); +} + +void KateDocument::slotFinishedKate ( KIO::Job * job ) +{ +// kdDebug(13020) << "KateDocument::slotJobFinished" << endl; + + if (!m_tempFile) + return; + + delete m_tempFile; + m_tempFile = 0; + m_job = 0; + + if (job->error()) + emit canceled( job->errorString() ); + else + { + if ( openFile(job) ) + emit setWindowCaption( m_url.prettyURL() ); + emit completed(); + } +} + +void KateDocument::abortLoadKate() +{ + if ( m_job ) + { + kdDebug(13020) << "Aborting job " << m_job << endl; + m_job->kill(); + m_job = 0; + } + + delete m_tempFile; + m_tempFile = 0; +} + +bool KateDocument::openFile() +{ + return openFile (0); +} + +bool KateDocument::openFile(KIO::Job * job) +{ + m_loading = true; + // add new m_file to dirwatch + activateDirWatch (); + + // + // use metadata + // + if (job) + { + QString metaDataCharset = job->queryMetaData("charset"); + + // only overwrite config if nothing set + if (!metaDataCharset.isEmpty () && (!m_config->isSetEncoding() || m_config->encoding().isEmpty())) + setEncoding (metaDataCharset); + } + + // + // service type magic to get encoding right + // + QString serviceType = m_extension->urlArgs().serviceType.simplifyWhiteSpace(); + int pos = serviceType.find(';'); + if (pos != -1) + setEncoding (serviceType.mid(pos+1)); + + // if the encoding is set here - on the command line/from the dialog/from KIO + // we prevent file type and document variables from changing it + bool encodingSticky = m_encodingSticky; + m_encodingSticky = m_config->isSetEncoding(); + + // Try getting the filetype here, so that variables does not have to be reset. + int fileTypeFound = KateFactory::self()->fileTypeManager()->fileType (this); + if ( fileTypeFound > -1 ) + updateFileType( fileTypeFound ); + + // do we have success ? + bool success = m_buffer->openFile (m_file); + // + // yeah, success + // + m_loading = false; // done reading file. + if (success) + { + /*if (highlight() && !m_url.isLocalFile()) { + // The buffer's highlighting gets nuked by KateBuffer::clear() + m_buffer->setHighlight(m_highlight); + }*/ + + // update our hl type if needed + if (!hlSetByUser) + { + int hl (KateHlManager::self()->detectHighlighting (this)); + + if (hl >= 0) + m_buffer->setHighlight(hl); + } + + // update file type if we haven't allready done so. + if ( fileTypeFound < 0 ) + updateFileType (KateFactory::self()->fileTypeManager()->fileType (this)); + + // read dir config (if possible and wanted) + readDirConfig (); + + // read vars + readVariables(); + + // update the md5 digest + createDigest( m_digest ); + } + + // + // update views + // + for (KateView * view = m_views.first(); view != 0L; view = m_views.next() ) + { + view->updateView(true); + } + + // + // emit the signal we need for example for kate app + // + emit fileNameChanged (); + + // + // set doc name, dummy value as arg, don't need it + // + setDocName (QString::null); + + // + // to houston, we are not modified + // + if (m_modOnHd) + { + m_modOnHd = false; + m_modOnHdReason = 0; + emit modifiedOnDisc (this, m_modOnHd, 0); + } + + // + // display errors + // + if (s_openErrorDialogsActivated) + { + if (!success && m_buffer->loadingBorked()) + KMessageBox::error (widget(), i18n ("The file %1 could not be loaded completely, as there is not enough temporary disk storage for it.").arg(m_url.url())); + else if (!success) + KMessageBox::error (widget(), i18n ("The file %1 could not be loaded, as it was not possible to read from it.\n\nCheck if you have read access to this file.").arg(m_url.url())); + } + + // warn -> opened binary file!!!!!!! + if (m_buffer->binary()) + { + // this file can't be saved again without killing it + setReadWrite( false ); + + KMessageBox::information (widget() + , i18n ("The file %1 is a binary, saving it will result in a corrupt file.").arg(m_url.url()) + , i18n ("Binary File Opened") + , "Binary File Opened Warning"); + } + + m_encodingSticky = encodingSticky; + + // + // return the success + // + return success; +} + +bool KateDocument::save() +{ + bool l ( url().isLocalFile() ); + + if ( ( l && config()->backupFlags() & KateDocumentConfig::LocalFiles ) + || ( ! l && config()->backupFlags() & KateDocumentConfig::RemoteFiles ) ) + { + KURL u( url() ); + u.setFileName( config()->backupPrefix() + url().fileName() + config()->backupSuffix() ); + + kdDebug () << "backup src file name: " << url() << endl; + kdDebug () << "backup dst file name: " << u << endl; + + // get the right permissions, start with safe default + mode_t perms = 0600; + KIO::UDSEntry fentry; + if (KIO::NetAccess::stat (url(), fentry, kapp->mainWidget())) + { + kdDebug () << "stating succesfull: " << url() << endl; + KFileItem item (fentry, url()); + perms = item.permissions(); + } + + // first del existing file if any, than copy over the file we have + // failure if a: the existing file could not be deleted, b: the file could not be copied + if ( (!KIO::NetAccess::exists( u, false, kapp->mainWidget() ) || KIO::NetAccess::del( u, kapp->mainWidget() )) + && KIO::NetAccess::file_copy( url(), u, perms, true, false, kapp->mainWidget() ) ) + { + kdDebug(13020)<<"backing up successfull ("<<url().prettyURL()<<" -> "<<u.prettyURL()<<")"<<endl; + } + else + { + kdDebug(13020)<<"backing up failed ("<<url().prettyURL()<<" -> "<<u.prettyURL()<<")"<<endl; + // FIXME: notify user for real ;) + } + } + + return KParts::ReadWritePart::save(); +} + +bool KateDocument::saveFile() +{ + // + // we really want to save this file ? + // + if (m_buffer->loadingBorked() && (KMessageBox::warningContinueCancel(widget(), + i18n("This file could not be loaded correctly due to lack of temporary disk space. Saving it could cause data loss.\n\nDo you really want to save it?"),i18n("Possible Data Loss"),i18n("Save Nevertheless")) != KMessageBox::Continue)) + return false; + + // + // warn -> try to save binary file!!!!!!! + // + if (m_buffer->binary() && (KMessageBox::warningContinueCancel (widget() + , i18n ("The file %1 is a binary, saving it will result in a corrupt file.").arg(m_url.url()) + , i18n ("Trying to Save Binary File") + , i18n("Save Nevertheless"), "Binary File Save Warning") != KMessageBox::Continue)) + return false; + + if ( !url().isEmpty() ) + { + if (s_fileChangedDialogsActivated && m_modOnHd) + { + QString str = reasonedMOHString() + "\n\n"; + + if (!isModified()) + { + if (KMessageBox::warningContinueCancel(0, + str + i18n("Do you really want to save this unmodified file? You could overwrite changed data in the file on disk."),i18n("Trying to Save Unmodified File"),i18n("Save Nevertheless")) != KMessageBox::Continue) + return false; + } + else + { + if (KMessageBox::warningContinueCancel(0, + str + i18n("Do you really want to save this file? Both your open file and the file on disk were changed. There could be some data lost."),i18n("Possible Data Loss"),i18n("Save Nevertheless")) != KMessageBox::Continue) + return false; + } + } + } + + // + // can we encode it if we want to save it ? + // + if (!m_buffer->canEncode () + && (KMessageBox::warningContinueCancel(0, + i18n("The selected encoding cannot encode every unicode character in this document. Do you really want to save it? There could be some data lost."),i18n("Possible Data Loss"),i18n("Save Nevertheless")) != KMessageBox::Continue)) + { + return false; + } + + // remove file from dirwatch + deactivateDirWatch (); + + // + // try to save + // + bool success = m_buffer->saveFile (m_file); + + // update the md5 digest + createDigest( m_digest ); + + // add m_file again to dirwatch + activateDirWatch (); + + // + // hurray, we had success, do stuff we need + // + if (success) + { + // update our hl type if needed + if (!hlSetByUser) + { + int hl (KateHlManager::self()->detectHighlighting (this)); + + if (hl >= 0) + m_buffer->setHighlight(hl); + } + + // read our vars + readVariables(); + } + + // + // we are not modified + // + if (success && m_modOnHd) + { + m_modOnHd = false; + m_modOnHdReason = 0; + emit modifiedOnDisc (this, m_modOnHd, 0); + } + + // + // display errors + // + if (!success) + KMessageBox::error (widget(), i18n ("The document could not be saved, as it was not possible to write to %1.\n\nCheck that you have write access to this file or that enough disk space is available.").arg(m_url.url())); + + // + // return success + // + return success; +} + +bool KateDocument::saveAs( const KURL &u ) +{ + QString oldDir = url().directory(); + + if ( KParts::ReadWritePart::saveAs( u ) ) + { + // null means base on filename + setDocName( QString::null ); + + if ( u.directory() != oldDir ) + readDirConfig(); + + emit fileNameChanged(); + emit nameChanged((Kate::Document *) this); + + return true; + } + + return false; +} + +void KateDocument::readDirConfig () +{ + int depth = config()->searchDirConfigDepth (); + + if (m_url.isLocalFile() && (depth > -1)) + { + QString currentDir = QFileInfo (m_file).dirPath(); + + // only search as deep as specified or not at all ;) + while (depth > -1) + { + kdDebug (13020) << "search for config file in path: " << currentDir << endl; + + // try to open config file in this dir + QFile f (currentDir + "/.kateconfig"); + + if (f.open (IO_ReadOnly)) + { + QTextStream stream (&f); + + uint linesRead = 0; + QString line = stream.readLine(); + while ((linesRead < 32) && !line.isNull()) + { + readVariableLine( line ); + + line = stream.readLine(); + + linesRead++; + } + + break; + } + + QString newDir = QFileInfo (currentDir).dirPath(); + + // bail out on looping (for example reached /) + if (currentDir == newDir) + break; + + currentDir = newDir; + --depth; + } + } +} + +void KateDocument::activateDirWatch () +{ + // same file as we are monitoring, return + if (m_file == m_dirWatchFile) + return; + + // remove the old watched file + deactivateDirWatch (); + + // add new file if needed + if (m_url.isLocalFile() && !m_file.isEmpty()) + { + KateFactory::self()->dirWatch ()->addFile (m_file); + m_dirWatchFile = m_file; + } +} + +void KateDocument::deactivateDirWatch () +{ + if (!m_dirWatchFile.isEmpty()) + KateFactory::self()->dirWatch ()->removeFile (m_dirWatchFile); + + m_dirWatchFile = QString::null; +} + +bool KateDocument::closeURL() +{ + abortLoadKate(); + + // + // file mod on hd + // + if ( !m_reloading && !url().isEmpty() ) + { + if (s_fileChangedDialogsActivated && m_modOnHd) + { + if (!(KMessageBox::warningContinueCancel( + widget(), + reasonedMOHString() + "\n\n" + i18n("Do you really want to continue to close this file? Data loss may occur."), + i18n("Possible Data Loss"), i18n("Close Nevertheless"), + QString("kate_close_modonhd_%1").arg( m_modOnHdReason ) ) == KMessageBox::Continue)) + return false; + } + } + + // + // first call the normal kparts implementation + // + if (!KParts::ReadWritePart::closeURL ()) + return false; + + // remove file from dirwatch + deactivateDirWatch (); + + // + // empty url + filename + // + m_url = KURL (); + m_file = QString::null; + + // we are not modified + if (m_modOnHd) + { + m_modOnHd = false; + m_modOnHdReason = 0; + emit modifiedOnDisc (this, m_modOnHd, 0); + } + + // clear the buffer + m_buffer->clear(); + + // remove all marks + clearMarks (); + + // clear undo/redo history + clearUndo(); + clearRedo(); + + // no, we are no longer modified + setModified(false); + + // we have no longer any hl + m_buffer->setHighlight(0); + + // update all our views + for (KateView * view = m_views.first(); view != 0L; view = m_views.next() ) + { + // Explicitly call the internal version because we don't want this to look like + // an external request (and thus have the view not QWidget::scroll()ed. + view->setCursorPositionInternal(0, 0, 1, false); + view->clearSelection(); + view->updateView(true); + } + + // uh, filename changed + emit fileNameChanged (); + + // update doc name + setDocName (QString::null); + + // success + return true; +} + +void KateDocument::setReadWrite( bool rw ) +{ + if (isReadWrite() != rw) + { + KParts::ReadWritePart::setReadWrite (rw); + + for( KateView* view = m_views.first(); view != 0L; view = m_views.next() ) + { + view->slotUpdate(); + view->slotReadWriteChanged (); + } + } +} + +void KateDocument::setModified(bool m) { + + if (isModified() != m) { + KParts::ReadWritePart::setModified (m); + + for( KateView* view = m_views.first(); view != 0L; view = m_views.next() ) + { + view->slotUpdate(); + } + + emit modifiedChanged (); + emit modStateChanged ((Kate::Document *)this); + } + if ( m == false ) + { + if ( ! undoItems.isEmpty() ) + { + lastUndoGroupWhenSaved = undoItems.last(); + } + + if ( ! redoItems.isEmpty() ) + { + lastRedoGroupWhenSaved = redoItems.last(); + } + + docWasSavedWhenUndoWasEmpty = undoItems.isEmpty(); + docWasSavedWhenRedoWasEmpty = redoItems.isEmpty(); + } +} +//END + +//BEGIN Kate specific stuff ;) + +void KateDocument::makeAttribs(bool needInvalidate) +{ + for (uint z = 0; z < m_views.count(); z++) + m_views.at(z)->renderer()->updateAttributes (); + + if (needInvalidate) + m_buffer->invalidateHighlighting(); + + tagAll (); +} + +// the attributes of a hl have changed, update +void KateDocument::internalHlChanged() +{ + makeAttribs(); +} + +void KateDocument::addView(KTextEditor::View *view) { + if (!view) + return; + + m_views.append( (KateView *) view ); + m_textEditViews.append( view ); + + // apply the view & renderer vars from the file type + const KateFileType *t = 0; + if ((m_fileType > -1) && (t = KateFactory::self()->fileTypeManager()->fileType(m_fileType))) + readVariableLine (t->varLine, true); + + // apply the view & renderer vars from the file + readVariables (true); + + m_activeView = (KateView *) view; +} + +void KateDocument::removeView(KTextEditor::View *view) { + if (!view) + return; + + if (m_activeView == view) + m_activeView = 0L; + + m_views.removeRef( (KateView *) view ); + m_textEditViews.removeRef( view ); +} + +void KateDocument::addSuperCursor(KateSuperCursor *cursor, bool privateC) { + if (!cursor) + return; + + m_superCursors.append( cursor ); + + if (!privateC) + myCursors.append( cursor ); +} + +void KateDocument::removeSuperCursor(KateSuperCursor *cursor, bool privateC) { + if (!cursor) + return; + + if (!privateC) + myCursors.removeRef( cursor ); + + m_superCursors.removeRef( cursor ); +} + +bool KateDocument::ownedView(KateView *view) { + // do we own the given view? + return (m_views.containsRef(view) > 0); +} + +bool KateDocument::isLastView(int numViews) { + return ((int) m_views.count() == numViews); +} + +uint KateDocument::currentColumn( const KateTextCursor& cursor ) +{ + KateTextLine::Ptr textLine = m_buffer->plainLine(cursor.line()); + + if (textLine) + return textLine->cursorX(cursor.col(), config()->tabWidth()); + else + return 0; +} + +bool KateDocument::typeChars ( KateView *view, const QString &chars ) +{ + KateTextLine::Ptr textLine = m_buffer->plainLine(view->cursorLine ()); + + if (!textLine) + return false; + + bool bracketInserted = false; + QString buf; + QChar c; + + for( uint z = 0; z < chars.length(); z++ ) + { + QChar ch = c = chars[z]; + if (ch.isPrint() || ch == '\t') + { + buf.append (ch); + + if (!bracketInserted && (config()->configFlags() & KateDocument::cfAutoBrackets)) + { + QChar end_ch; + bool complete = true; + QChar prevChar = textLine->getChar(view->cursorColumnReal()-1); + QChar nextChar = textLine->getChar(view->cursorColumnReal()); + switch(ch) { + case '(': end_ch = ')'; break; + case '[': end_ch = ']'; break; + case '{': end_ch = '}'; break; + case '\'':end_ch = '\'';break; + case '"': end_ch = '"'; break; + default: complete = false; + } + if (complete) + { + if (view->hasSelection()) + { // there is a selection, enclose the selection + buf.append (view->selection()); + buf.append (end_ch); + bracketInserted = true; + } + else + { // no selection, check whether we should better refuse to complete + if ( ( (ch == '\'' || ch == '"') && + (prevChar.isLetterOrNumber() || prevChar == ch) ) + || nextChar.isLetterOrNumber() + || (nextChar == end_ch && prevChar != ch) ) + { + kdDebug(13020) << "AutoBracket refused before: " << nextChar << "\n"; + } + else + { + buf.append (end_ch); + bracketInserted = true; + } + } + } + } + } + } + + if (buf.isEmpty()) + return false; + + editStart (); + + if (!view->config()->persistentSelection() && view->hasSelection() ) + view->removeSelectedText(); + + int oldLine = view->cursorLine (); + int oldCol = view->cursorColumnReal (); + + + if (config()->configFlags() & KateDocument::cfOvr) + removeText (view->cursorLine(), view->cursorColumnReal(), view->cursorLine(), kMin( view->cursorColumnReal()+buf.length(), textLine->length() ) ); + + insertText (view->cursorLine(), view->cursorColumnReal(), buf); + m_indenter->processChar(c); + + editEnd (); + + if (bracketInserted) + view->setCursorPositionInternal (view->cursorLine(), view->cursorColumnReal()-1); + + emit charactersInteractivelyInserted (oldLine, oldCol, chars); + + return true; +} + +void KateDocument::newLine( KateTextCursor& c, KateViewInternal *v ) +{ + editStart(); + + if( !v->view()->config()->persistentSelection() && v->view()->hasSelection() ) + v->view()->removeSelectedText(); + + // temporary hack to get the cursor pos right !!!!!!!!! + c = v->getCursor (); + + if (c.line() > (int)lastLine()) + c.setLine(lastLine()); + + if ( c.line() < 0 ) + c.setLine( 0 ); + + uint ln = c.line(); + + KateTextLine::Ptr textLine = kateTextLine(c.line()); + + if (c.col() > (int)textLine->length()) + c.setCol(textLine->length()); + + if (m_indenter->canProcessNewLine ()) + { + int pos = textLine->firstChar(); + + // length should do the job better + if (pos < 0) + pos = textLine->length(); + + if (c.col() < pos) + c.setCol(pos); // place cursor on first char if before + + editWrapLine (c.line(), c.col()); + + KateDocCursor cursor (c.line() + 1, pos, this); + m_indenter->processNewline(cursor, true); + + c.setPos(cursor); + } + else + { + editWrapLine (c.line(), c.col()); + c.setPos(c.line() + 1, 0); + } + + removeTrailingSpace( ln ); + + editEnd(); +} + +void KateDocument::transpose( const KateTextCursor& cursor) +{ + KateTextLine::Ptr textLine = m_buffer->plainLine(cursor.line()); + + if (!textLine || (textLine->length() < 2)) + return; + + uint col = cursor.col(); + + if (col > 0) + col--; + + if ((textLine->length() - col) < 2) + return; + + uint line = cursor.line(); + QString s; + + //clever swap code if first character on the line swap right&left + //otherwise left & right + s.append (textLine->getChar(col+1)); + s.append (textLine->getChar(col)); + //do the swap + + // do it right, never ever manipulate a textline + editStart (); + editRemoveText (line, col, 2); + editInsertText (line, col, s); + editEnd (); +} + +void KateDocument::backspace( KateView *view, const KateTextCursor& c ) +{ + if ( !view->config()->persistentSelection() && view->hasSelection() ) { + view->removeSelectedText(); + return; + } + + uint col = kMax( c.col(), 0 ); + uint line = kMax( c.line(), 0 ); + + if ((col == 0) && (line == 0)) + return; + + int complement = 0; + if (col > 0) + { + if (config()->configFlags() & KateDocument::cfAutoBrackets) + { + // if inside empty (), {}, [], '', "" delete both + KateTextLine::Ptr tl = m_buffer->plainLine(line); + if(!tl) return; + QChar prevChar = tl->getChar(col-1); + QChar nextChar = tl->getChar(col); + + if ( (prevChar == '"' && nextChar == '"') || + (prevChar == '\'' && nextChar == '\'') || + (prevChar == '(' && nextChar == ')') || + (prevChar == '[' && nextChar == ']') || + (prevChar == '{' && nextChar == '}') ) + { + complement = 1; + } + } + if (!(config()->configFlags() & KateDocument::cfBackspaceIndents)) + { + // ordinary backspace + //c.cursor.col--; + removeText(line, col-1, line, col+complement); + } + else + { + // backspace indents: erase to next indent position + KateTextLine::Ptr textLine = m_buffer->plainLine(line); + + // don't forget this check!!!! really!!!! + if (!textLine) + return; + + int colX = textLine->cursorX(col, config()->tabWidth()); + int pos = textLine->firstChar(); + if (pos > 0) + pos = textLine->cursorX(pos, config()->tabWidth()); + + if (pos < 0 || pos >= (int)colX) + { + // only spaces on left side of cursor + indent( view, line, -1); + } + else + removeText(line, col-1, line, col+complement); + } + } + else + { + // col == 0: wrap to previous line + if (line >= 1) + { + KateTextLine::Ptr textLine = m_buffer->plainLine(line-1); + + // don't forget this check!!!! really!!!! + if (!textLine) + return; + + if (config()->wordWrap() && textLine->endingWith(QString::fromLatin1(" "))) + { + // gg: in hard wordwrap mode, backspace must also eat the trailing space + removeText (line-1, textLine->length()-1, line, 0); + } + else + removeText (line-1, textLine->length(), line, 0); + } + } + + emit backspacePressed(); +} + +void KateDocument::del( KateView *view, const KateTextCursor& c ) +{ + if ( !view->config()->persistentSelection() && view->hasSelection() ) { + view->removeSelectedText(); + return; + } + + if( c.col() < (int) m_buffer->plainLine(c.line())->length()) + { + removeText(c.line(), c.col(), c.line(), c.col()+1); + } + else if ( (uint)c.line() < lastLine() ) + { + removeText(c.line(), c.col(), c.line()+1, 0); + } +} + +void KateDocument::paste ( KateView* view ) +{ + QString s = QApplication::clipboard()->text(); + + if (s.isEmpty()) + return; + + uint lines = s.contains (QChar ('\n')); + + m_undoDontMerge = true; + + editStart (); + + if (!view->config()->persistentSelection() && view->hasSelection() ) + view->removeSelectedText(); + + uint line = view->cursorLine (); + uint column = view->cursorColumnReal (); + + insertText ( line, column, s, view->blockSelectionMode() ); + + editEnd(); + + // move cursor right for block select, as the user is moved right internal + // even in that case, but user expects other behavior in block selection + // mode ! + if (view->blockSelectionMode()) + view->setCursorPositionInternal (line+lines, column); + + if (m_indenter->canProcessLine() + && config()->configFlags() & KateDocumentConfig::cfIndentPastedText) + { + editStart(); + + KateDocCursor begin(line, 0, this); + KateDocCursor end(line + lines, 0, this); + + m_indenter->processSection (begin, end); + + editEnd(); + } + + if (!view->blockSelectionMode()) emit charactersSemiInteractivelyInserted (line, column, s); + m_undoDontMerge = true; +} + +void KateDocument::insertIndentChars ( KateView *view ) +{ + editStart (); + + QString s; + if (config()->configFlags() & KateDocument::cfSpaceIndent) + { + int width = config()->indentationWidth(); + s.fill (' ', width - (view->cursorColumnReal() % width)); + } + else + s.append ('\t'); + + insertText (view->cursorLine(), view->cursorColumnReal(), s); + + editEnd (); +} + +void KateDocument::indent ( KateView *v, uint line, int change) +{ + editStart (); + + if (!hasSelection()) + { + // single line + optimizeLeadingSpace(line, config()->configFlags(), change); + } + else + { + int sl = v->selStartLine(); + int el = v->selEndLine(); + int ec = v->selEndCol(); + + if ((ec == 0) && ((el-1) >= 0)) + { + el--; /* */ + } + + if (config()->configFlags() & KateDocument::cfKeepIndentProfile && change < 0) { + // unindent so that the existing indent profile doesn't get screwed + // if any line we may unindent is already full left, don't do anything + int adjustedChange = -change; + + for (line = sl; (int) line <= el && adjustedChange > 0; line++) { + KateTextLine::Ptr textLine = m_buffer->plainLine(line); + int firstChar = textLine->firstChar(); + if (firstChar >= 0 && (v->lineSelected(line) || v->lineHasSelected(line))) { + int maxUnindent = textLine->cursorX(firstChar, config()->tabWidth()) / config()->indentationWidth(); + if (maxUnindent < adjustedChange) + adjustedChange = maxUnindent; + } + } + + change = -adjustedChange; + } + + const bool rts = config()->configFlags() & KateDocumentConfig::cfRemoveTrailingDyn; + for (line = sl; (int) line <= el; line++) { + if ((v->lineSelected(line) || v->lineHasSelected(line)) + && (!rts || lineLength(line) > 0)) { + optimizeLeadingSpace(line, config()->configFlags(), change); + } + } + } + + editEnd (); +} + +void KateDocument::align(KateView *view, uint line) +{ + if (m_indenter->canProcessLine()) + { + editStart (); + + if (!view->hasSelection()) + { + KateDocCursor curLine(line, 0, this); + m_indenter->processLine (curLine); + editEnd (); + activeView()->setCursorPosition (line, curLine.col()); + } + else + { + m_indenter->processSection (view->selStart(), view->selEnd()); + editEnd (); + } + } +} + +/* + Optimize the leading whitespace for a single line. + If change is > 0, it adds indentation units (indentationChars) + if change is == 0, it only optimizes + If change is < 0, it removes indentation units + This will be used to indent, unindent, and optimal-fill a line. + If excess space is removed depends on the flag cfKeepExtraSpaces + which has to be set by the user +*/ +void KateDocument::optimizeLeadingSpace(uint line, int flags, int change) +{ + KateTextLine::Ptr textline = m_buffer->plainLine(line); + + int first_char = textline->firstChar(); + + int w = 0; + if (flags & KateDocument::cfSpaceIndent) + w = config()->indentationWidth(); + else + w = config()->tabWidth(); + + if (first_char < 0) + first_char = textline->length(); + + int space = textline->cursorX(first_char, config()->tabWidth()) + change * w; + if (space < 0) + space = 0; + + if (!(flags & KateDocument::cfKeepExtraSpaces)) + { + uint extra = space % w; + + space -= extra; + if (extra && change < 0) { + // otherwise it unindents too much (e.g. 12 chars when indentation is 8 chars wide) + space += w; + } + } + + //kdDebug(13020) << "replace With Op: " << line << " " << first_char << " " << space << endl; + replaceWithOptimizedSpace(line, first_char, space, flags); +} + +void KateDocument::replaceWithOptimizedSpace(uint line, uint upto_column, uint space, int flags) +{ + uint length; + QString new_space; + + if (flags & KateDocument::cfSpaceIndent && ! (flags & KateDocumentConfig::cfMixedIndent) ) { + length = space; + new_space.fill(' ', length); + } + else { + length = space / config()->tabWidth(); + new_space.fill('\t', length); + + QString extra_space; + extra_space.fill(' ', space % config()->tabWidth()); + length += space % config()->tabWidth(); + new_space += extra_space; + } + + KateTextLine::Ptr textline = m_buffer->plainLine(line); + uint change_from; + for (change_from = 0; change_from < upto_column && change_from < length; change_from++) { + if (textline->getChar(change_from) != new_space[change_from]) + break; + } + + editStart(); + + if (change_from < upto_column) + removeText(line, change_from, line, upto_column); + + if (change_from < length) + insertText(line, change_from, new_space.right(length - change_from)); + + editEnd(); +} + +/* + Remove a given string at the begining + of the current line. +*/ +bool KateDocument::removeStringFromBegining(int line, QString &str) +{ + KateTextLine::Ptr textline = m_buffer->plainLine(line); + + int index = 0; + bool there = false; + + if (textline->startingWith(str)) + there = true; + else + { + index = textline->firstChar (); + + if ((index >= 0) && (textline->length() >= (index + str.length())) && (textline->string(index, str.length()) == str)) + there = true; + } + + if (there) + { + // Remove some chars + removeText (line, index, line, index+str.length()); + } + + return there; +} + +/* + Remove a given string at the end + of the current line. +*/ +bool KateDocument::removeStringFromEnd(int line, QString &str) +{ + KateTextLine::Ptr textline = m_buffer->plainLine(line); + + int index = 0; + bool there = false; + + if(textline->endingWith(str)) + { + index = textline->length() - str.length(); + there = true; + } + else + { + index = textline->lastChar ()-str.length()+1; + + if ((index >= 0) && (textline->length() >= (index + str.length())) && (textline->string(index, str.length()) == str)) + there = true; + } + + if (there) + { + // Remove some chars + removeText (line, index, line, index+str.length()); + } + + return there; +} + +/* + Add to the current line a comment line mark at + the begining. +*/ +void KateDocument::addStartLineCommentToSingleLine( int line, int attrib ) +{ + if (highlight()->getCommentSingleLinePosition(attrib)==KateHighlighting::CSLPosColumn0) + { + QString commentLineMark = highlight()->getCommentSingleLineStart( attrib ) + " "; + insertText (line, 0, commentLineMark); + } + else + { + QString commentLineMark=highlight()->getCommentSingleLineStart(attrib); + KateTextLine::Ptr l = m_buffer->line(line); + int pos=l->firstChar(); + if (pos >=0) + insertText(line,pos,commentLineMark); + } +} + +/* + Remove from the current line a comment line mark at + the begining if there is one. +*/ +bool KateDocument::removeStartLineCommentFromSingleLine( int line, int attrib ) +{ + QString shortCommentMark = highlight()->getCommentSingleLineStart( attrib ); + QString longCommentMark = shortCommentMark + " "; + + editStart(); + + // Try to remove the long comment mark first + bool removed = (removeStringFromBegining(line, longCommentMark) + || removeStringFromBegining(line, shortCommentMark)); + + editEnd(); + + return removed; +} + +/* + Add to the current line a start comment mark at the + begining and a stop comment mark at the end. +*/ +void KateDocument::addStartStopCommentToSingleLine( int line, int attrib ) +{ + QString startCommentMark = highlight()->getCommentStart( attrib ) + " "; + QString stopCommentMark = " " + highlight()->getCommentEnd( attrib ); + + editStart(); + + // Add the start comment mark + insertText (line, 0, startCommentMark); + + // Go to the end of the line + int col = m_buffer->plainLine(line)->length(); + + // Add the stop comment mark + insertText (line, col, stopCommentMark); + + editEnd(); +} + +/* + Remove from the current line a start comment mark at + the begining and a stop comment mark at the end. +*/ +bool KateDocument::removeStartStopCommentFromSingleLine( int line, int attrib ) +{ + QString shortStartCommentMark = highlight()->getCommentStart( attrib ); + QString longStartCommentMark = shortStartCommentMark + " "; + QString shortStopCommentMark = highlight()->getCommentEnd( attrib ); + QString longStopCommentMark = " " + shortStopCommentMark; + + editStart(); + +#ifdef __GNUC__ +#warning "that's a bad idea, can lead to stray endings, FIXME" +#endif + // Try to remove the long start comment mark first + bool removedStart = (removeStringFromBegining(line, longStartCommentMark) + || removeStringFromBegining(line, shortStartCommentMark)); + + bool removedStop = false; + if (removedStart) + { + // Try to remove the long stop comment mark first + removedStop = (removeStringFromEnd(line, longStopCommentMark) + || removeStringFromEnd(line, shortStopCommentMark)); + } + + editEnd(); + + return (removedStart || removedStop); +} + +/* + Add to the current selection a start comment + mark at the begining and a stop comment mark + at the end. +*/ +void KateDocument::addStartStopCommentToSelection( KateView *view, int attrib ) +{ + QString startComment = highlight()->getCommentStart( attrib ); + QString endComment = highlight()->getCommentEnd( attrib ); + + int sl = view->selStartLine(); + int el = view->selEndLine(); + int sc = view->selStartCol(); + int ec = view->selEndCol(); + + if ((ec == 0) && ((el-1) >= 0)) + { + el--; + ec = m_buffer->plainLine (el)->length(); + } + + editStart(); + + insertText (el, ec, endComment); + insertText (sl, sc, startComment); + + editEnd (); + + // Set the new selection + ec += endComment.length() + ( (el == sl) ? startComment.length() : 0 ); + view->setSelection(sl, sc, el, ec); +} + +/* + Add to the current selection a comment line + mark at the begining of each line. +*/ +void KateDocument::addStartLineCommentToSelection( KateView *view, int attrib ) +{ + QString commentLineMark = highlight()->getCommentSingleLineStart( attrib ) + " "; + + int sl = view->selStartLine(); + int el = view->selEndLine(); + + if ((view->selEndCol() == 0) && ((el-1) >= 0)) + { + el--; + } + + editStart(); + + // For each line of the selection + for (int z = el; z >= sl; z--) { + //insertText (z, 0, commentLineMark); + addStartLineCommentToSingleLine(z, attrib ); + } + + editEnd (); + + // Set the new selection + + KateDocCursor end (view->selEnd()); + end.setCol(view->selEndCol() + ((el == view->selEndLine()) ? commentLineMark.length() : 0) ); + + view->setSelection(view->selStartLine(), 0, end.line(), end.col()); +} + +bool KateDocument::nextNonSpaceCharPos(int &line, int &col) +{ + for(; line < (int)m_buffer->count(); line++) { + KateTextLine::Ptr textLine = m_buffer->plainLine(line); + + if (!textLine) + break; + + col = textLine->nextNonSpaceChar(col); + if(col != -1) + return true; // Next non-space char found + col = 0; + } + // No non-space char found + line = -1; + col = -1; + return false; +} + +bool KateDocument::previousNonSpaceCharPos(int &line, int &col) +{ + while(true) + { + KateTextLine::Ptr textLine = m_buffer->plainLine(line); + + if (!textLine) + break; + + col = textLine->previousNonSpaceChar(col); + if(col != -1) return true; + if(line == 0) return false; + --line; + col = textLine->length(); +} + // No non-space char found + line = -1; + col = -1; + return false; +} + +/* + Remove from the selection a start comment mark at + the begining and a stop comment mark at the end. +*/ +bool KateDocument::removeStartStopCommentFromSelection( KateView *view, int attrib ) +{ + QString startComment = highlight()->getCommentStart( attrib ); + QString endComment = highlight()->getCommentEnd( attrib ); + + int sl = kMax<int> (0, view->selStartLine()); + int el = kMin<int> (view->selEndLine(), lastLine()); + int sc = view->selStartCol(); + int ec = view->selEndCol(); + + // The selection ends on the char before selectEnd + if (ec != 0) { + ec--; + } else { + if (el > 0) { + el--; + ec = m_buffer->plainLine(el)->length() - 1; + } + } + + int startCommentLen = startComment.length(); + int endCommentLen = endComment.length(); + + // had this been perl or sed: s/^\s*$startComment(.+?)$endComment\s*/$1/ + + bool remove = nextNonSpaceCharPos(sl, sc) + && m_buffer->plainLine(sl)->stringAtPos(sc, startComment) + && previousNonSpaceCharPos(el, ec) + && ( (ec - endCommentLen + 1) >= 0 ) + && m_buffer->plainLine(el)->stringAtPos(ec - endCommentLen + 1, endComment); + + if (remove) { + editStart(); + + removeText (el, ec - endCommentLen + 1, el, ec + 1); + removeText (sl, sc, sl, sc + startCommentLen); + + editEnd (); + // set new selection not necessary, as the selection cursors are KateSuperCursors + } + + return remove; +} + +bool KateDocument::removeStartStopCommentFromRegion(const KateTextCursor &start,const KateTextCursor &end,int attrib) +{ + QString startComment = highlight()->getCommentStart( attrib ); + QString endComment = highlight()->getCommentEnd( attrib ); + int startCommentLen = startComment.length(); + int endCommentLen = endComment.length(); + + bool remove = m_buffer->plainLine(start.line())->stringAtPos(start.col(), startComment) + && ( (end.col() - endCommentLen ) >= 0 ) + && m_buffer->plainLine(end.line())->stringAtPos(end.col() - endCommentLen , endComment); + if (remove) { + editStart(); + removeText(end.line(),end.col()-endCommentLen,end.line(),end.col()); + removeText(start.line(),start.col(),start.line(),start.col()+startCommentLen); + editEnd(); + } + return remove; +} + +/* + Remove from the begining of each line of the + selection a start comment line mark. +*/ +bool KateDocument::removeStartLineCommentFromSelection( KateView *view, int attrib ) +{ + QString shortCommentMark = highlight()->getCommentSingleLineStart( attrib ); + QString longCommentMark = shortCommentMark + " "; + + int sl = view->selStartLine(); + int el = view->selEndLine(); + + if ((view->selEndCol() == 0) && ((el-1) >= 0)) + { + el--; + } + + // Find out how many char will be removed from the last line + int removeLength = 0; + if (m_buffer->plainLine(el)->startingWith(longCommentMark)) + removeLength = longCommentMark.length(); + else if (m_buffer->plainLine(el)->startingWith(shortCommentMark)) + removeLength = shortCommentMark.length(); + + bool removed = false; + + editStart(); + + // For each line of the selection + for (int z = el; z >= sl; z--) + { + // Try to remove the long comment mark first + removed = (removeStringFromBegining(z, longCommentMark) + || removeStringFromBegining(z, shortCommentMark) + || removed); + } + + editEnd(); + // updating selection already done by the KateSuperCursors + return removed; +} + +/* + Comment or uncomment the selection or the current + line if there is no selection. +*/ +void KateDocument::comment( KateView *v, uint line,uint column, int change) +{ + // We need to check that we can sanely comment the selectino or region. + // It is if the attribute of the first and last character of the range to + // comment belongs to the same language definition. + // for lines with no text, we need the attribute for the lines context. + bool hassel = v->hasSelection(); + int startAttrib, endAttrib; + if ( hassel ) + { + KateTextLine::Ptr ln = kateTextLine( v->selStartLine() ); + int l = v->selStartLine(), c = v->selStartCol(); + startAttrib = nextNonSpaceCharPos( l, c ) ? kateTextLine( l )->attribute( c ) : 0; + + ln = kateTextLine( v->selEndLine() ); + l = v->selEndLine(), c = v->selEndCol(); + endAttrib = previousNonSpaceCharPos( l, c ) ? kateTextLine( l )->attribute( c ) : 0; + } + else + { + KateTextLine::Ptr ln = kateTextLine( line ); + if ( ln->length() ) + { + startAttrib = ln->attribute( ln->firstChar() ); + endAttrib = ln->attribute( ln->lastChar() ); + } + else + { + int l = line, c = 0; + if ( nextNonSpaceCharPos( l, c ) || previousNonSpaceCharPos( l, c ) ) + startAttrib = endAttrib = kateTextLine( l )->attribute( c ); + else + startAttrib = endAttrib = 0; + } + } + + if ( ! highlight()->canComment( startAttrib, endAttrib ) ) + { + kdDebug(13020)<<"canComment( "<<startAttrib<<", "<<endAttrib<<" ) returned false!"<<endl; + return; + } + + bool hasStartLineCommentMark = !(highlight()->getCommentSingleLineStart( startAttrib ).isEmpty()); + bool hasStartStopCommentMark = ( !(highlight()->getCommentStart( startAttrib ).isEmpty()) + && !(highlight()->getCommentEnd( endAttrib ).isEmpty()) ); + + bool removed = false; + + if (change > 0) // comment + { + if ( !hassel ) + { + if ( hasStartLineCommentMark ) + addStartLineCommentToSingleLine( line, startAttrib ); + else if ( hasStartStopCommentMark ) + addStartStopCommentToSingleLine( line, startAttrib ); + } + else + { + // anders: prefer single line comment to avoid nesting probs + // If the selection starts after first char in the first line + // or ends before the last char of the last line, we may use + // multiline comment markers. + // TODO We should try to detect nesting. + // - if selection ends at col 0, most likely she wanted that + // line ignored + if ( hasStartStopCommentMark && + ( !hasStartLineCommentMark || ( + ( v->selStartCol() > m_buffer->plainLine( v->selStartLine() )->firstChar() ) || + ( v->selEndCol() < ((int)m_buffer->plainLine( v->selEndLine() )->length()) ) + ) ) ) + addStartStopCommentToSelection( v, startAttrib ); + else if ( hasStartLineCommentMark ) + addStartLineCommentToSelection( v, startAttrib ); + } + } + else // uncomment + { + if ( !hassel ) + { + removed = ( hasStartLineCommentMark + && removeStartLineCommentFromSingleLine( line, startAttrib ) ) + || ( hasStartStopCommentMark + && removeStartStopCommentFromSingleLine( line, startAttrib ) ); + if ((!removed) && foldingTree()) { + kdDebug(13020)<<"easy approach for uncommenting did not work, trying harder (folding tree)"<<endl; + int commentRegion=(highlight()->commentRegion(startAttrib)); + if (commentRegion){ + KateCodeFoldingNode *n=foldingTree()->findNodeForPosition(line,column); + if (n) { + KateTextCursor start,end; + if ((n->nodeType()==commentRegion) && n->getBegin(foldingTree(), &start) && n->getEnd(foldingTree(), &end)) { + kdDebug(13020)<<"Enclosing region found:"<<start.col()<<"/"<<start.line()<<"-"<<end.col()<<"/"<<end.line()<<endl; + removeStartStopCommentFromRegion(start,end,startAttrib); + } else { + kdDebug(13020)<<"Enclosing region found, but not valid"<<endl; + kdDebug(13020)<<"Region found: "<<n->nodeType()<<" region needed: "<<commentRegion<<endl; + } + //perhaps nested regions should be hadled here too... + } else kdDebug(13020)<<"No enclosing region found"<<endl; + } else kdDebug(13020)<<"No comment region specified for current hl"<<endl; + } + } + else + { + // anders: this seems like it will work with above changes :) + removed = ( hasStartLineCommentMark + && removeStartLineCommentFromSelection( v, startAttrib ) ) + || ( hasStartStopCommentMark + && removeStartStopCommentFromSelection( v, startAttrib ) ); + } + } +} + +void KateDocument::transform( KateView *v, const KateTextCursor &c, + KateDocument::TextTransform t ) +{ + editStart(); + uint cl( c.line() ), cc( c.col() ); + bool selectionRestored = false; + + if ( hasSelection() ) + { + // cache the selection and cursor, so we can be sure to restore. + KateTextCursor selstart = v->selStart(); + KateTextCursor selend = v->selEnd(); + + int ln = v->selStartLine(); + while ( ln <= selend.line() ) + { + uint start, end; + start = (ln == selstart.line() || v->blockSelectionMode()) ? + selstart.col() : 0; + end = (ln == selend.line() || v->blockSelectionMode()) ? + selend.col() : lineLength( ln ); + if ( start > end ) + { + uint t = start; + start = end; + end = t; + } + QString s = text( ln, start, ln, end ); + QString o = s; + + if ( t == Uppercase ) + s = s.upper(); + else if ( t == Lowercase ) + s = s.lower(); + else // Capitalize + { + KateTextLine::Ptr l = m_buffer->plainLine( ln ); + uint p ( 0 ); + while( p < s.length() ) + { + // If bol or the character before is not in a word, up this one: + // 1. if both start and p is 0, upper char. + // 2. if blockselect or first line, and p == 0 and start-1 is not in a word, upper + // 3. if p-1 is not in a word, upper. + if ( ( ! start && ! p ) || + ( ( ln == selstart.line() || v->blockSelectionMode() ) && + ! p && ! highlight()->isInWord( l->getChar( start - 1 )) ) || + ( p && ! highlight()->isInWord( s.at( p-1 ) ) ) + ) + s[p] = s.at(p).upper(); + p++; + } + } + + if ( o != s ) + { + removeText( ln, start, ln, end ); + insertText( ln, start, s ); + } + + ln++; + } + + // restore selection + v->setSelection( selstart, selend ); + selectionRestored = true; + + } else { // no selection + QString o = text( cl, cc, cl, cc + 1 ); + QString s; + int n ( cc ); + switch ( t ) { + case Uppercase: + s = o.upper(); + break; + case Lowercase: + s = o.lower(); + break; + case Capitalize: + { + KateTextLine::Ptr l = m_buffer->plainLine( cl ); + while ( n > 0 && highlight()->isInWord( l->getChar( n-1 ), l->attribute( n-1 ) ) ) + n--; + o = text( cl, n, cl, n + 1 ); + s = o.upper(); + } + break; + default: + break; + } + + if ( s != o ) + { + removeText( cl, n, cl, n+1 ); + insertText( cl, n, s ); + } + } + editEnd(); + + if ( ! selectionRestored ) + v->setCursorPosition( cl, cc ); +} + +void KateDocument::joinLines( uint first, uint last ) +{ +// if ( first == last ) last += 1; + editStart(); + int line( first ); + while ( first < last ) + { + // Normalize the whitespace in the joined lines by making sure there's + // always exactly one space between the joined lines + // This cannot be done in editUnwrapLine, because we do NOT want this + // behaviour when deleting from the start of a line, just when explicitly + // calling the join command + KateTextLine::Ptr l = m_buffer->line( line ); + KateTextLine::Ptr tl = m_buffer->line( line + 1 ); + + if ( !l || !tl ) + { + editEnd(); + return; + } + + int pos = tl->firstChar(); + if ( pos >= 0 ) + { + if (pos != 0) + editRemoveText( line + 1, 0, pos ); + if ( !( l->length() == 0 || l->getChar( l->length() - 1 ).isSpace() ) ) + editInsertText( line + 1, 0, " " ); + } + else + { + // Just remove the whitespace and let Kate handle the rest + editRemoveText( line + 1, 0, tl->length() ); + } + + editUnWrapLine( line ); + first++; + } + editEnd(); +} + +QString KateDocument::getWord( const KateTextCursor& cursor ) { + int start, end, len; + + KateTextLine::Ptr textLine = m_buffer->plainLine(cursor.line()); + len = textLine->length(); + start = end = cursor.col(); + if (start > len) // Probably because of non-wrapping cursor mode. + return QString(""); + + while (start > 0 && highlight()->isInWord(textLine->getChar(start - 1), textLine->attribute(start - 1))) start--; + while (end < len && highlight()->isInWord(textLine->getChar(end), textLine->attribute(end))) end++; + len = end - start; + return QString(&textLine->text()[start], len); +} + +void KateDocument::tagLines(int start, int end) +{ + for (uint z = 0; z < m_views.count(); z++) + m_views.at(z)->tagLines (start, end, true); +} + +void KateDocument::tagLines(KateTextCursor start, KateTextCursor end) +{ + // May need to switch start/end cols if in block selection mode + if (blockSelectionMode() && start.col() > end.col()) { + int sc = start.col(); + start.setCol(end.col()); + end.setCol(sc); + } + + for (uint z = 0; z < m_views.count(); z++) + m_views.at(z)->tagLines(start, end, true); +} + +void KateDocument::repaintViews(bool paintOnlyDirty) +{ + for (uint z = 0; z < m_views.count(); z++) + m_views.at(z)->repaintText(paintOnlyDirty); +} + +void KateDocument::tagAll() +{ + for (uint z = 0; z < m_views.count(); z++) + { + m_views.at(z)->tagAll(); + m_views.at(z)->updateView (true); + } +} + +uint KateDocument::configFlags () +{ + return config()->configFlags(); +} + +void KateDocument::setConfigFlags (uint flags) +{ + config()->setConfigFlags(flags); +} + +inline bool isStartBracket( const QChar& c ) { return c == '{' || c == '[' || c == '('; } +inline bool isEndBracket ( const QChar& c ) { return c == '}' || c == ']' || c == ')'; } +inline bool isBracket ( const QChar& c ) { return isStartBracket( c ) || isEndBracket( c ); } + +/* + Bracket matching uses the following algorithm: + If in overwrite mode, match the bracket currently underneath the cursor. + Otherwise, if the character to the right of the cursor is an starting bracket, + match it. Otherwise if the character to the left of the cursor is a + ending bracket, match it. Otherwise, if the the character to the left + of the cursor is an starting bracket, match it. Otherwise, if the character + to the right of the cursor is an ending bracket, match it. Otherwise, don't + match anything. +*/ +void KateDocument::newBracketMark( const KateTextCursor& cursor, KateBracketRange& bm, int maxLines ) +{ + bm.setValid(false); + + bm.start() = cursor; + + if( !findMatchingBracket( bm.start(), bm.end(), maxLines ) ) + return; + + bm.setValid(true); + + const int tw = config()->tabWidth(); + const int indentStart = m_buffer->plainLine(bm.start().line())->indentDepth(tw); + const int indentEnd = m_buffer->plainLine(bm.end().line())->indentDepth(tw); + bm.setIndentMin(kMin(indentStart, indentEnd)); +} + +bool KateDocument::findMatchingBracket( KateTextCursor& start, KateTextCursor& end, int maxLines ) +{ + KateTextLine::Ptr textLine = m_buffer->plainLine( start.line() ); + if( !textLine ) + return false; + + QChar right = textLine->getChar( start.col() ); + QChar left = textLine->getChar( start.col() - 1 ); + QChar bracket; + + if ( config()->configFlags() & cfOvr ) { + if( isBracket( right ) ) { + bracket = right; + } else { + return false; + } + } else if ( isStartBracket( right ) ) { + bracket = right; + } else if ( isEndBracket( left ) ) { + start.setCol(start.col() - 1); + bracket = left; + } else if ( isBracket( left ) ) { + start.setCol(start.col() - 1); + bracket = left; + } else if ( isBracket( right ) ) { + bracket = right; + } else { + return false; + } + + QChar opposite; + + switch( bracket ) { + case '{': opposite = '}'; break; + case '}': opposite = '{'; break; + case '[': opposite = ']'; break; + case ']': opposite = '['; break; + case '(': opposite = ')'; break; + case ')': opposite = '('; break; + default: return false; + } + + bool forward = isStartBracket( bracket ); + int startAttr = textLine->attribute( start.col() ); + uint count = 0; + int lines = 0; + end = start; + + while( true ) { + /* Increment or decrement, check base cases */ + if( forward ) { + end.setCol(end.col() + 1); + if( end.col() >= lineLength( end.line() ) ) { + if( end.line() >= (int)lastLine() ) + return false; + end.setPos(end.line() + 1, 0); + textLine = m_buffer->plainLine( end.line() ); + lines++; + } + } else { + end.setCol(end.col() - 1); + if( end.col() < 0 ) { + if( end.line() <= 0 ) + return false; + end.setLine(end.line() - 1); + end.setCol(lineLength( end.line() ) - 1); + textLine = m_buffer->plainLine( end.line() ); + lines++; + } + } + + if ((maxLines != -1) && (lines > maxLines)) + return false; + + /* Easy way to skip comments */ + if( textLine->attribute( end.col() ) != startAttr ) + continue; + + /* Check for match */ + QChar c = textLine->getChar( end.col() ); + if( c == bracket ) { + count++; + } else if( c == opposite ) { + if( count == 0 ) + return true; + count--; + } + + } +} + +void KateDocument::guiActivateEvent( KParts::GUIActivateEvent *ev ) +{ + KParts::ReadWritePart::guiActivateEvent( ev ); + if ( ev->activated() ) + emit selectionChanged(); +} + +void KateDocument::setDocName (QString name ) +{ + if ( name == m_docName ) + return; + + if ( !name.isEmpty() ) + { + // TODO check for similarly named documents + m_docName = name; + updateFileType (KateFactory::self()->fileTypeManager()->fileType (this)); + emit nameChanged((Kate::Document *) this); + return; + } + + // if the name is set, and starts with FILENAME, it should not be changed! + if ( ! url().isEmpty() && m_docName.startsWith( url().filename() ) ) return; + + int count = -1; + + for (uint z=0; z < KateFactory::self()->documents()->count(); z++) + { + if ( (KateFactory::self()->documents()->at(z) != this) && (KateFactory::self()->documents()->at(z)->url().filename() == url().filename()) ) + if ( KateFactory::self()->documents()->at(z)->m_docNameNumber > count ) + count = KateFactory::self()->documents()->at(z)->m_docNameNumber; + } + + m_docNameNumber = count + 1; + + m_docName = url().filename(); + + if (m_docName.isEmpty()) + m_docName = i18n ("Untitled"); + + if (m_docNameNumber > 0) + m_docName = QString(m_docName + " (%1)").arg(m_docNameNumber+1); + + updateFileType (KateFactory::self()->fileTypeManager()->fileType (this)); + emit nameChanged ((Kate::Document *) this); +} + +void KateDocument::slotModifiedOnDisk( Kate::View * /*v*/ ) +{ + if ( m_isasking < 0 ) + { + m_isasking = 0; + return; + } + + if ( !s_fileChangedDialogsActivated || m_isasking ) + return; + + if (m_modOnHd && !url().isEmpty()) + { + m_isasking = 1; + + KateModOnHdPrompt p( this, m_modOnHdReason, reasonedMOHString(), widget() ); + switch ( p.exec() ) + { + case KateModOnHdPrompt::Save: + { + m_modOnHd = false; + KEncodingFileDialog::Result res=KEncodingFileDialog::getSaveURLAndEncoding(config()->encoding(), + url().url(),QString::null,widget(),i18n("Save File")); + + kdDebug(13020)<<"got "<<res.URLs.count()<<" URLs"<<endl; + if( ! res.URLs.isEmpty() && ! res.URLs.first().isEmpty() && checkOverwrite( res.URLs.first() ) ) + { + setEncoding( res.encoding ); + + if( ! saveAs( res.URLs.first() ) ) + { + KMessageBox::error( widget(), i18n("Save failed") ); + m_modOnHd = true; + } + else + emit modifiedOnDisc( this, false, 0 ); + } + else // the save as dialog was cancelled, we are still modified on disk + { + m_modOnHd = true; + } + + m_isasking = 0; + break; + } + + case KateModOnHdPrompt::Reload: + m_modOnHd = false; + emit modifiedOnDisc( this, false, 0 ); + reloadFile(); + m_isasking = 0; + break; + + case KateModOnHdPrompt::Ignore: + m_modOnHd = false; + emit modifiedOnDisc( this, false, 0 ); + m_isasking = 0; + break; + + case KateModOnHdPrompt::Overwrite: + m_modOnHd = false; + emit modifiedOnDisc( this, false, 0 ); + m_isasking = 0; + save(); + break; + + default: // cancel: ignore next focus event + m_isasking = -1; + } + } +} + +void KateDocument::setModifiedOnDisk( int reason ) +{ + m_modOnHdReason = reason; + m_modOnHd = (reason > 0); + emit modifiedOnDisc( this, (reason > 0), reason ); +} + +class KateDocumentTmpMark +{ + public: + QString line; + KTextEditor::Mark mark; +}; + +void KateDocument::reloadFile() +{ + if ( !url().isEmpty() ) + { + if (m_modOnHd && s_fileChangedDialogsActivated) + { + int i = KMessageBox::warningYesNoCancel + (0, reasonedMOHString() + "\n\n" + i18n("What do you want to do?"), + i18n("File Was Changed on Disk"), i18n("&Reload File"), i18n("&Ignore Changes")); + + if ( i != KMessageBox::Yes) + { + if (i == KMessageBox::No) + { + m_modOnHd = false; + m_modOnHdReason = 0; + emit modifiedOnDisc (this, m_modOnHd, 0); + } + + return; + } + } + + QValueList<KateDocumentTmpMark> tmp; + + for( QIntDictIterator<KTextEditor::Mark> it( m_marks ); it.current(); ++it ) + { + KateDocumentTmpMark m; + + m.line = textLine (it.current()->line); + m.mark = *it.current(); + + tmp.append (m); + } + + uint mode = hlMode (); + bool byUser = hlSetByUser; + + m_storedVariables.clear(); + + m_reloading = true; + + QValueList<int> lines, cols; + for ( uint i=0; i < m_views.count(); i++ ) + { + lines.append( m_views.at( i )->cursorLine() ); + cols.append( m_views.at( i )->cursorColumn() ); + } + + KateDocument::openURL( url() ); + + for ( uint i=0; i < m_views.count(); i++ ) + m_views.at( i )->setCursorPositionInternal( lines[ i ], cols[ i ], m_config->tabWidth(), false ); + + m_reloading = false; + + for ( QValueList<int>::size_type z=0; z < tmp.size(); z++ ) + { + if (z < numLines()) + { + if (textLine(tmp[z].mark.line) == tmp[z].line) + setMark (tmp[z].mark.line, tmp[z].mark.type); + } + } + + if (byUser) + setHlMode (mode); + } +} + +void KateDocument::flush () +{ + closeURL (); +} + +void KateDocument::setWordWrap (bool on) +{ + config()->setWordWrap (on); +} + +bool KateDocument::wordWrap () +{ + return config()->wordWrap (); +} + +void KateDocument::setWordWrapAt (uint col) +{ + config()->setWordWrapAt (col); +} + +unsigned int KateDocument::wordWrapAt () +{ + return config()->wordWrapAt (); +} + +void KateDocument::applyWordWrap () +{ + // dummy to make the API happy +} + +void KateDocument::setPageUpDownMovesCursor (bool on) +{ + config()->setPageUpDownMovesCursor (on); +} + +bool KateDocument::pageUpDownMovesCursor () +{ + return config()->pageUpDownMovesCursor (); +} + +void KateDocument::dumpRegionTree() +{ + m_buffer->foldingTree()->debugDump(); +} +//END + +//BEGIN KTextEditor::CursorInterface stuff + +KTextEditor::Cursor *KateDocument::createCursor ( ) +{ + return new KateSuperCursor (this, false, 0, 0, this); +} + +void KateDocument::tagArbitraryLines(KateView* view, KateSuperRange* range) +{ + if (view) + view->tagLines(range->start(), range->end()); + else + tagLines(range->start(), range->end()); +} + +void KateDocument::lineInfo (KateLineInfo *info, unsigned int line) +{ + m_buffer->lineInfo(info,line); +} + +KateCodeFoldingTree *KateDocument::foldingTree () +{ + return m_buffer->foldingTree(); +} + +void KateDocument::setEncoding (const QString &e) +{ + if ( m_encodingSticky ) + return; + + QString ce = m_config->encoding().lower(); + if ( e.lower() == ce ) + return; + + m_config->setEncoding( e ); + if ( ! m_loading ) + reloadFile(); +} + +QString KateDocument::encoding() const +{ + return m_config->encoding(); +} + +void KateDocument::updateConfig () +{ + emit undoChanged (); + tagAll(); + + for (KateView * view = m_views.first(); view != 0L; view = m_views.next() ) + { + view->updateDocumentConfig (); + } + + // switch indenter if needed + if (m_indenter->modeNumber() != m_config->indentationMode()) + { + delete m_indenter; + m_indenter = KateAutoIndent::createIndenter ( this, m_config->indentationMode() ); + } + + m_indenter->updateConfig(); + + m_buffer->setTabWidth (config()->tabWidth()); + + // plugins + for (uint i=0; i<KateFactory::self()->plugins().count(); i++) + { + if (config()->plugin (i)) + loadPlugin (i); + else + unloadPlugin (i); + } +} + +//BEGIN Variable reader +// "local variable" feature by anders, 2003 +/* TODO + add config options (how many lines to read, on/off) + add interface for plugins/apps to set/get variables + add view stuff +*/ +QRegExp KateDocument::kvLine = QRegExp("kate:(.*)"); +QRegExp KateDocument::kvLineWildcard = QRegExp("kate-wildcard\\((.*)\\):(.*)"); +QRegExp KateDocument::kvLineMime = QRegExp("kate-mimetype\\((.*)\\):(.*)"); +QRegExp KateDocument::kvVar = QRegExp("([\\w\\-]+)\\s+([^;]+)"); + +void KateDocument::readVariables(bool onlyViewAndRenderer) +{ + if (!onlyViewAndRenderer) + m_config->configStart(); + + // views! + KateView *v; + for (v = m_views.first(); v != 0L; v= m_views.next() ) + { + v->config()->configStart(); + v->renderer()->config()->configStart(); + } + // read a number of lines in the top/bottom of the document + for (uint i=0; i < kMin( 9U, numLines() ); ++i ) + { + readVariableLine( textLine( i ), onlyViewAndRenderer ); + } + if ( numLines() > 10 ) + { + for ( uint i = kMax(10U, numLines() - 10); i < numLines(); ++i ) + { + readVariableLine( textLine( i ), onlyViewAndRenderer ); + } + } + + if (!onlyViewAndRenderer) + m_config->configEnd(); + + for (v = m_views.first(); v != 0L; v= m_views.next() ) + { + v->config()->configEnd(); + v->renderer()->config()->configEnd(); + } +} + +void KateDocument::readVariableLine( QString t, bool onlyViewAndRenderer ) +{ + // simple check first, no regex + // no kate inside, no vars, simple... + if (t.find("kate") < 0) + return; + + // found vars, if any + QString s; + + if ( kvLine.search( t ) > -1 ) + { + s = kvLine.cap(1); + + kdDebug (13020) << "normal variable line kate: matched: " << s << endl; + } + else if (kvLineWildcard.search( t ) > -1) // regex given + { + QStringList wildcards (QStringList::split(';', kvLineWildcard.cap(1))); + QString nameOfFile = url().fileName(); + + bool found = false; + for (QStringList::size_type i = 0; !found && i < wildcards.size(); ++i) + { + QRegExp wildcard (wildcards[i], true/*Qt::CaseSensitive*/, true/*QRegExp::Wildcard*/); + + found = wildcard.exactMatch (nameOfFile); + } + + // nothing usable found... + if (!found) + return; + + s = kvLineWildcard.cap(2); + + kdDebug (13020) << "guarded variable line kate-wildcard: matched: " << s << endl; + } + else if (kvLineMime.search( t ) > -1) // mime-type given + { + QStringList types (QStringList::split(';', kvLineMime.cap(1))); + + // no matching type found + if (!types.contains (mimeType ())) + return; + + s = kvLineMime.cap(2); + + kdDebug (13020) << "guarded variable line kate-mimetype: matched: " << s << endl; + } + else // nothing found + { + return; + } + + QStringList vvl; // view variable names + vvl << "dynamic-word-wrap" << "dynamic-word-wrap-indicators" + << "line-numbers" << "icon-border" << "folding-markers" + << "bookmark-sorting" << "auto-center-lines" + << "icon-bar-color" + // renderer + << "background-color" << "selection-color" + << "current-line-color" << "bracket-highlight-color" + << "word-wrap-marker-color" + << "font" << "font-size" << "scheme"; + int p( 0 ); + + QString var, val; + while ( (p = kvVar.search( s, p )) > -1 ) + { + p += kvVar.matchedLength(); + var = kvVar.cap( 1 ); + val = kvVar.cap( 2 ).stripWhiteSpace(); + bool state; // store booleans here + int n; // store ints here + + // only apply view & renderer config stuff + if (onlyViewAndRenderer) + { + if ( vvl.contains( var ) ) // FIXME define above + setViewVariable( var, val ); + } + else + { + // BOOL SETTINGS + if ( var == "word-wrap" && checkBoolValue( val, &state ) ) + setWordWrap( state ); // ??? FIXME CHECK + else if ( var == "block-selection" && checkBoolValue( val, &state ) ) + setBlockSelectionMode( state ); + // KateConfig::configFlags + // FIXME should this be optimized to only a few calls? how? + else if ( var == "backspace-indents" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfBackspaceIndents, state ); + else if ( var == "replace-tabs" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfReplaceTabsDyn, state ); + else if ( var == "remove-trailing-space" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfRemoveTrailingDyn, state ); + else if ( var == "wrap-cursor" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfWrapCursor, state ); + else if ( var == "auto-brackets" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfAutoBrackets, state ); + else if ( var == "overwrite-mode" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfOvr, state ); + else if ( var == "keep-indent-profile" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfKeepIndentProfile, state ); + else if ( var == "keep-extra-spaces" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfKeepExtraSpaces, state ); + else if ( var == "tab-indents" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfTabIndents, state ); + else if ( var == "show-tabs" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfShowTabs, state ); + else if ( var == "space-indent" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfSpaceIndent, state ); + else if ( var == "smart-home" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfSmartHome, state ); + else if ( var == "replace-trailing-space-save" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfRemoveSpaces, state ); + else if ( var == "auto-insert-doxygen" && checkBoolValue( val, &state) ) + m_config->setConfigFlags( KateDocumentConfig::cfDoxygenAutoTyping, state); + else if ( var == "mixed-indent" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfMixedIndent, state ); + + // INTEGER SETTINGS + else if ( var == "tab-width" && checkIntValue( val, &n ) ) + m_config->setTabWidth( n ); + else if ( var == "indent-width" && checkIntValue( val, &n ) ) + m_config->setIndentationWidth( n ); + else if ( var == "indent-mode" ) + { + if ( checkIntValue( val, &n ) ) + m_config->setIndentationMode( n ); + else + m_config->setIndentationMode( KateAutoIndent::modeNumber( val) ); + } + else if ( var == "word-wrap-column" && checkIntValue( val, &n ) && n > 0 ) // uint, but hard word wrap at 0 will be no fun ;) + m_config->setWordWrapAt( n ); + else if ( var == "undo-steps" && checkIntValue( val, &n ) && n >= 0 ) + setUndoSteps( n ); + + // STRING SETTINGS + else if ( var == "eol" || var == "end-of-line" ) + { + QStringList l; + l << "unix" << "dos" << "mac"; + if ( (n = l.findIndex( val.lower() )) != -1 ) + m_config->setEol( n ); + } + else if ( var == "encoding" ) + m_config->setEncoding( val ); + else if ( var == "syntax" || var == "hl" ) + { + for ( uint i=0; i < hlModeCount(); i++ ) + { + if ( hlModeName( i ).lower() == val.lower() ) + { + setHlMode( i ); + break; + } + } + } + + // VIEW SETTINGS + else if ( vvl.contains( var ) ) + setViewVariable( var, val ); + else + { + m_storedVariables.insert( var, val ); + emit variableChanged( var, val ); + } + } + } +} + +void KateDocument::setViewVariable( QString var, QString val ) +{ + KateView *v; + bool state; + int n; + QColor c; + for (v = m_views.first(); v != 0L; v= m_views.next() ) + { + if ( var == "dynamic-word-wrap" && checkBoolValue( val, &state ) ) + v->config()->setDynWordWrap( state ); + else if ( var == "persistent-selection" && checkBoolValue( val, &state ) ) + v->config()->setPersistentSelection( state ); + //else if ( var = "dynamic-word-wrap-indicators" ) + else if ( var == "line-numbers" && checkBoolValue( val, &state ) ) + v->config()->setLineNumbers( state ); + else if (var == "icon-border" && checkBoolValue( val, &state ) ) + v->config()->setIconBar( state ); + else if (var == "folding-markers" && checkBoolValue( val, &state ) ) + v->config()->setFoldingBar( state ); + else if ( var == "auto-center-lines" && checkIntValue( val, &n ) ) + v->config()->setAutoCenterLines( n ); // FIXME uint, > N ?? + else if ( var == "icon-bar-color" && checkColorValue( val, c ) ) + v->renderer()->config()->setIconBarColor( c ); + // RENDERER + else if ( var == "background-color" && checkColorValue( val, c ) ) + v->renderer()->config()->setBackgroundColor( c ); + else if ( var == "selection-color" && checkColorValue( val, c ) ) + v->renderer()->config()->setSelectionColor( c ); + else if ( var == "current-line-color" && checkColorValue( val, c ) ) + v->renderer()->config()->setHighlightedLineColor( c ); + else if ( var == "bracket-highlight-color" && checkColorValue( val, c ) ) + v->renderer()->config()->setHighlightedBracketColor( c ); + else if ( var == "word-wrap-marker-color" && checkColorValue( val, c ) ) + v->renderer()->config()->setWordWrapMarkerColor( c ); + else if ( var == "font" || ( var == "font-size" && checkIntValue( val, &n ) ) ) + { + QFont _f( *v->renderer()->config()->font( ) ); + + if ( var == "font" ) + { + _f.setFamily( val ); + _f.setFixedPitch( QFont( val ).fixedPitch() ); + } + else + _f.setPointSize( n ); + + v->renderer()->config()->setFont( _f ); + } + else if ( var == "scheme" ) + { + v->renderer()->config()->setSchema( KateFactory::self()->schemaManager()->number( val ) ); + } + } +} + +bool KateDocument::checkBoolValue( QString val, bool *result ) +{ + val = val.stripWhiteSpace().lower(); + QStringList l; + l << "1" << "on" << "true"; + if ( l.contains( val ) ) + { + *result = true; + return true; + } + l.clear(); + l << "0" << "off" << "false"; + if ( l.contains( val ) ) + { + *result = false; + return true; + } + return false; +} + +bool KateDocument::checkIntValue( QString val, int *result ) +{ + bool ret( false ); + *result = val.toInt( &ret ); + return ret; +} + +bool KateDocument::checkColorValue( QString val, QColor &c ) +{ + c.setNamedColor( val ); + return c.isValid(); +} + +// KTextEditor::variable +QString KateDocument::variable( const QString &name ) const +{ + if ( m_storedVariables.contains( name ) ) + return m_storedVariables[ name ]; + + return ""; +} + +//END + +void KateDocument::slotModOnHdDirty (const QString &path) +{ + if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != 1)) + { + // compare md5 with the one we have (if we have one) + if ( ! m_digest.isEmpty() ) + { + QCString tmp; + if ( createDigest( tmp ) && tmp == m_digest ) + return; + } + + m_modOnHd = true; + m_modOnHdReason = 1; + + // reenable dialog if not running atm + if (m_isasking == -1) + m_isasking = false; + + emit modifiedOnDisc (this, m_modOnHd, m_modOnHdReason); + } +} + +void KateDocument::slotModOnHdCreated (const QString &path) +{ + if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != 2)) + { + m_modOnHd = true; + m_modOnHdReason = 2; + + // reenable dialog if not running atm + if (m_isasking == -1) + m_isasking = false; + + emit modifiedOnDisc (this, m_modOnHd, m_modOnHdReason); + } +} + +void KateDocument::slotModOnHdDeleted (const QString &path) +{ + if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != 3)) + { + m_modOnHd = true; + m_modOnHdReason = 3; + + // reenable dialog if not running atm + if (m_isasking == -1) + m_isasking = false; + + emit modifiedOnDisc (this, m_modOnHd, m_modOnHdReason); + } +} + +bool KateDocument::createDigest( QCString &result ) +{ + bool ret = false; + result = ""; + if ( url().isLocalFile() ) + { + QFile f ( url().path() ); + if ( f.open( IO_ReadOnly) ) + { + KMD5 md5; + ret = md5.update( f ); + md5.hexDigest( result ); + f.close(); + ret = true; + } + } + return ret; +} + +QString KateDocument::reasonedMOHString() const +{ + switch( m_modOnHdReason ) + { + case 1: + return i18n("The file '%1' was modified by another program.").arg( url().prettyURL() ); + break; + case 2: + return i18n("The file '%1' was created by another program.").arg( url().prettyURL() ); + break; + case 3: + return i18n("The file '%1' was deleted by another program.").arg( url().prettyURL() ); + break; + default: + return QString(); + } +} + +void KateDocument::removeTrailingSpace( uint line ) +{ + // remove trailing spaces from left line if required + if ( config()->configFlags() & KateDocumentConfig::cfRemoveTrailingDyn ) + { + KateTextLine::Ptr ln = kateTextLine( line ); + + if ( ! ln ) return; + + if ( line == activeView()->cursorLine() + && activeView()->cursorColumnReal() >= (uint)kMax(0,ln->lastChar()) ) + return; + + if ( ln->length() ) + { + uint p = ln->lastChar() + 1; + uint l = ln->length() - p; + if ( l ) + editRemoveText( line, p, l); + } + } +} + +void KateDocument::updateFileType (int newType, bool user) +{ + if (user || !m_fileTypeSetByUser) + { + const KateFileType *t = 0; + if ((newType == -1) || (t = KateFactory::self()->fileTypeManager()->fileType (newType))) + { + m_fileType = newType; + + if (t) + { + m_config->configStart(); + // views! + KateView *v; + for (v = m_views.first(); v != 0L; v= m_views.next() ) + { + v->config()->configStart(); + v->renderer()->config()->configStart(); + } + + readVariableLine( t->varLine ); + + m_config->configEnd(); + for (v = m_views.first(); v != 0L; v= m_views.next() ) + { + v->config()->configEnd(); + v->renderer()->config()->configEnd(); + } + } + } + } +} + +uint KateDocument::documentNumber () const +{ + return KTextEditor::Document::documentNumber (); +} + + + + +void KateDocument::slotQueryClose_save(bool *handled, bool* abortClosing) { + *handled=true; + *abortClosing=true; + if (m_url.isEmpty()) + { + KEncodingFileDialog::Result res=KEncodingFileDialog::getSaveURLAndEncoding(config()->encoding(), + QString::null,QString::null,0,i18n("Save File")); + + if( res.URLs.isEmpty() || !checkOverwrite( res.URLs.first() ) ) { + *abortClosing=true; + return; + } + setEncoding( res.encoding ); + saveAs( res.URLs.first() ); + *abortClosing=false; + } + else + { + save(); + *abortClosing=false; + } + +} + +bool KateDocument::checkOverwrite( KURL u ) +{ + if( !u.isLocalFile() ) + return true; + + QFileInfo info( u.path() ); + if( !info.exists() ) + return true; + + return KMessageBox::Cancel != KMessageBox::warningContinueCancel( 0, + i18n( "A file named \"%1\" already exists. " + "Are you sure you want to overwrite it?" ).arg( info.fileName() ), + i18n( "Overwrite File?" ), + i18n( "&Overwrite" ) ); +} + +void KateDocument::setDefaultEncoding (const QString &encoding) +{ + s_defaultEncoding = encoding; +} + +//BEGIN KTextEditor::TemplateInterface +bool KateDocument::insertTemplateTextImplementation ( uint line, uint column, const QString &templateString, const QMap<QString,QString> &initialValues, QWidget *) { + return (new KateTemplateHandler(this,line,column,templateString,initialValues))->initOk(); +} + +void KateDocument::testTemplateCode() { + int col=activeView()->cursorColumn(); + int line=activeView()->cursorLine(); + insertTemplateText(line,col,"for ${index} \\${NOPLACEHOLDER} ${index} ${blah} ${fullname} \\$${Placeholder} \\${${PLACEHOLDER2}}\n next line:${ANOTHERPLACEHOLDER} $${DOLLARBEFOREPLACEHOLDER} {NOTHING} {\n${cursor}\n}",QMap<QString,QString>()); +} + +bool KateDocument::invokeTabInterceptor(KKey key) { + if (m_tabInterceptor) return (*m_tabInterceptor)(key); + return false; +} + +bool KateDocument::setTabInterceptor(KateKeyInterceptorFunctor *interceptor) { + if (m_tabInterceptor) return false; + m_tabInterceptor=interceptor; + return true; +} + +bool KateDocument::removeTabInterceptor(KateKeyInterceptorFunctor *interceptor) { + if (m_tabInterceptor!=interceptor) return false; + m_tabInterceptor=0; + return true; +} +//END KTextEditor::TemplateInterface + +//BEGIN DEPRECATED STUFF + bool KateDocument::setSelection ( uint startLine, uint startCol, uint endLine, uint endCol ) +{ if (m_activeView) return m_activeView->setSelection (startLine, startCol, endLine, endCol); return false; } + + bool KateDocument::clearSelection () + { if (m_activeView) return m_activeView->clearSelection(); return false; } + + bool KateDocument::hasSelection () const + { if (m_activeView) return m_activeView->hasSelection (); return false; } + + QString KateDocument::selection () const + { if (m_activeView) return m_activeView->selection (); return QString(""); } + + bool KateDocument::removeSelectedText () + { if (m_activeView) return m_activeView->removeSelectedText (); return false; } + + bool KateDocument::selectAll() + { if (m_activeView) return m_activeView->selectAll (); return false; } + + int KateDocument::selStartLine() + { if (m_activeView) return m_activeView->selStartLine (); return 0; } + + int KateDocument::selStartCol() + { if (m_activeView) return m_activeView->selStartCol (); return 0; } + + int KateDocument::selEndLine() + { if (m_activeView) return m_activeView->selEndLine (); return 0; } + + int KateDocument::selEndCol() + { if (m_activeView) return m_activeView->selEndCol (); return 0; } + + bool KateDocument::blockSelectionMode () + { if (m_activeView) return m_activeView->blockSelectionMode (); return false; } + +bool KateDocument::setBlockSelectionMode (bool on) + { if (m_activeView) return m_activeView->setBlockSelectionMode (on); return false; } + +bool KateDocument::toggleBlockSelectionMode () + { if (m_activeView) return m_activeView->toggleBlockSelectionMode (); return false; } +//END DEPRECATED + +//END DEPRECATED STUFF + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katedocument.h b/kate/part/katedocument.h new file mode 100644 index 000000000..c1c5ab169 --- /dev/null +++ b/kate/part/katedocument.h @@ -0,0 +1,1073 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001-2004 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef _KATE_DOCUMENT_H_ +#define _KATE_DOCUMENT_H_ + +#include "katesupercursor.h" +#include "katetextline.h" +#include "kateundo.h" +#include "katebuffer.h" +#include "katecodefoldinghelpers.h" + +#include "../interfaces/document.h" + +#include <ktexteditor/configinterfaceextension.h> +#include <ktexteditor/encodinginterface.h> +#include <ktexteditor/sessionconfiginterface.h> +#include <ktexteditor/editinterfaceext.h> +#include <ktexteditor/templateinterface.h> + +#include <dcopobject.h> + +#include <kmimetype.h> +#include <klocale.h> + +#include <qintdict.h> +#include <qmap.h> +#include <qdatetime.h> + +namespace KTextEditor { class Plugin; } + +namespace KIO { class TransferJob; } + +class KateUndoGroup; +class KateCmd; +class KateAttribute; +class KateAutoIndent; +class KateCodeFoldingTree; +class KateBuffer; +class KateView; +class KateViewInternal; +class KateArbitraryHighlight; +class KateSuperRange; +class KateLineInfo; +class KateBrowserExtension; +class KateDocumentConfig; +class KateHighlighting; +class KatePartPluginItem; +class KatePartPluginInfo; + +class KTempFile; + +class QTimer; + +class KateKeyInterceptorFunctor; + +// +// Kate KTextEditor::Document class (and even KTextEditor::Editor ;) +// +class KateDocument : public Kate::Document, + public Kate::DocumentExt, + public KTextEditor::ConfigInterfaceExtension, + public KTextEditor::EncodingInterface, + public KTextEditor::SessionConfigInterface, + public KTextEditor::EditInterfaceExt, + public KTextEditor::TemplateInterface, + public DCOPObject +{ + K_DCOP + Q_OBJECT + + friend class KateViewInternal; + friend class KateRenderer; + + public: + KateDocument (bool bSingleViewMode=false, bool bBrowserView=false, bool bReadOnly=false, + QWidget *parentWidget = 0, const char *widgetName = 0, QObject * = 0, const char * = 0); + ~KateDocument (); + + bool closeURL(); + + // + // Plugins section + // + public: + void unloadAllPlugins (); + + void enableAllPluginsGUI (KateView *view); + void disableAllPluginsGUI (KateView *view); + + void loadPlugin (uint pluginIndex); + void unloadPlugin (uint pluginIndex); + + void enablePluginGUI (KTextEditor::Plugin *plugin, KateView *view); + void enablePluginGUI (KTextEditor::Plugin *plugin); + + void disablePluginGUI (KTextEditor::Plugin *plugin, KateView *view); + void disablePluginGUI (KTextEditor::Plugin *plugin); + + private: + QMemArray<KTextEditor::Plugin *> m_plugins; + + public: + bool readOnly () const { return m_bReadOnly; } + bool browserView () const { return m_bBrowserView; } + bool singleViewMode () const { return m_bSingleViewMode; } + KateBrowserExtension *browserExtension () { return m_extension; } + + private: + // only to make part work, don't change it ! + bool m_bSingleViewMode; + bool m_bBrowserView; + bool m_bReadOnly; + KateBrowserExtension *m_extension; + + // + // KTextEditor::Document stuff + // + public: + KTextEditor::View *createView( QWidget *parent, const char *name ); + QPtrList<KTextEditor::View> views () const; + + inline KateView *activeView () const { return m_activeView; } + + private: + QPtrList<KateView> m_views; + QPtrList<KTextEditor::View> m_textEditViews; + KateView *m_activeView; + + /** + * set the active view. + * + * If @p view is allready the active view, nothing is done. + * + * If the document is modified on disk, ask the user what to do. + * + * @since Kate 2.4 + */ + void setActiveView( KateView *view ); + + // + // KTextEditor::ConfigInterfaceExtension stuff + // + public slots: + uint configPages () const; + KTextEditor::ConfigPage *configPage (uint number = 0, QWidget *parent = 0, const char *name=0 ); + QString configPageName (uint number = 0) const; + QString configPageFullName (uint number = 0) const; + QPixmap configPagePixmap (uint number = 0, int size = KIcon::SizeSmall) const; + + // + // KTextEditor::EditInterface stuff + // + public slots: + QString text() const; + + QString text ( uint startLine, uint startCol, uint endLine, uint endCol ) const; + QString text ( uint startLine, uint startCol, uint endLine, uint endCol, bool blockwise ) const; + + QString textLine ( uint line ) const; + + bool setText(const QString &); + bool clear (); + + bool insertText ( uint line, uint col, const QString &s ); + bool insertText ( uint line, uint col, const QString &s, bool blockwise ); + + bool removeText ( uint startLine, uint startCol, uint endLine, uint endCol ); + bool removeText ( uint startLine, uint startCol, uint endLine, uint endCol, bool blockwise ); + + bool insertLine ( uint line, const QString &s ); + bool removeLine ( uint line ); + + uint numLines() const; + uint numVisLines() const; + uint length () const; + int lineLength ( uint line ) const; + + signals: + void textChanged (); + void charactersInteractivelyInserted(int ,int ,const QString&); + void charactersSemiInteractivelyInserted(int ,int ,const QString&); + void backspacePressed(); + + public: +//BEGIN editStart/editEnd (start, end, undo, cursor update, view update) + /** + * Enclose editor actions with @p editStart() and @p editEnd() to group + * them. + * @param withUndo if true, add undo history + */ + void editStart (bool withUndo = true); + /** Same as editStart() with undo */ + void editBegin () { editStart(); } + /** + * End a editor operation. + * @see editStart() + */ + void editEnd (); + private: + bool m_isInUndo; ///< set to true in undo/redo + +//END editStart/editEnd + +//BEGIN LINE BASED INSERT/REMOVE STUFF (editStart() and editEnd() included) + public: + /** + * Add a string in the given line/column + * @param line line number + * @param col column + * @param s string to be inserted + * @return true on success + */ + bool editInsertText ( uint line, uint col, const QString &s ); + /** + * Remove a string in the given line/column + * @param line line number + * @param col column + * @param len length of text to be removed + * @return true on success + */ + bool editRemoveText ( uint line, uint col, uint len ); + + /** + * Mark @p line as @p autowrapped. This is necessary if static word warp is + * enabled, because we have to know whether to insert a new line or add the + * wrapped words to the followin line. + * @param line line number + * @param autowrapped autowrapped? + * @return true on success + */ + bool editMarkLineAutoWrapped ( uint line, bool autowrapped ); + + /** + * Wrap @p line. If @p newLine is true, ignore the textline's flag + * KateTextLine::flagAutoWrapped and force a new line. Whether a new line + * was needed/added you can grab with @p newLineAdded. + * @param line line number + * @param col column + * @param newLine if true, force a new line + * @param newLineAdded return value is true, if new line was added (may be 0) + * @return true on success + */ + bool editWrapLine ( uint line, uint col, bool newLine = true, bool *newLineAdded = 0 ); + /** + * Unwrap @p line. If @p removeLine is true, we force to join the lines. If + * @p removeLine is true, @p length is ignored (eg not needed). + * @param line line number + * @param removeLine if true, force to remove the next line + * @param length length of the line + * @return true on success + */ + bool editUnWrapLine ( uint line, bool removeLine = true, uint length = 0 ); + + /** + * Insert a string at the given line. + * @param line line number + * @param s string to insert + * @return true on success + */ + bool editInsertLine ( uint line, const QString &s ); + /** + * Remove a line + * @param line line number + * @return true on success + */ + bool editRemoveLine ( uint line ); + + /** + * Remove a line + * @param startLine line to begin wrapping + * @param endLine line to stop wrapping + * @return true on success + */ + bool wrapText (uint startLine, uint endLine); +//END LINE BASED INSERT/REMOVE STUFF + + signals: + /** + * Emitted each time text is inserted into a pre-existing line, including appends. + * Does not include newly inserted lines at the moment. ### needed? + */ + void editTextInserted ( uint line, uint col, uint len); + + /** + * Emitted each time text is removed from a line, including truncates and space removal. + */ + void editTextRemoved ( uint line, uint col, uint len); + + /** + * Emmitted when text from @p line was wrapped at position pos onto line @p nextLine. + */ + void editLineWrapped ( uint line, uint col, uint len ); + + /** + * Emitted each time text from @p nextLine was upwrapped onto @p line. + */ + void editLineUnWrapped ( uint line, uint col ); + + /** + * Emitted whenever a line is inserted before @p line, becoming itself line @ line. + */ + void editLineInserted ( uint line ); + + /** + * Emitted when a line is deleted. + */ + void editLineRemoved ( uint line ); + + private: + void undoStart(); + void undoEnd(); + void undoSafePoint(); + + private slots: + void undoCancel(); + + private: + void editAddUndo (KateUndoGroup::UndoType type, uint line, uint col, uint len, const QString &text); + + uint editSessionNumber; + bool editIsRunning; + bool editWithUndo; + bool m_undoComplexMerge; + KateUndoGroup* m_editCurrentUndo; + + // + // KTextEditor::UndoInterface stuff + // + public slots: + void undo (); + void redo (); + void clearUndo (); + void clearRedo (); + + uint undoCount () const; + uint redoCount () const; + + uint undoSteps () const; + void setUndoSteps ( uint steps ); + + private: + friend class KateTemplateHandler; + + private: + QPtrList<KateSuperCursor> m_superCursors; + + // + // some internals for undo/redo + // + QPtrList<KateUndoGroup> undoItems; + QPtrList<KateUndoGroup> redoItems; + bool m_undoDontMerge; //create a setter later on and remove the friend declaration + bool m_undoIgnoreCancel; + QTimer* m_undoMergeTimer; + // these two variables are for resetting the document to + // non-modified if all changes have been undone... + KateUndoGroup* lastUndoGroupWhenSaved; + KateUndoGroup* lastRedoGroupWhenSaved; + bool docWasSavedWhenUndoWasEmpty; + bool docWasSavedWhenRedoWasEmpty; + + // this sets + void updateModified(); + + signals: + void undoChanged (); + void textInserted(int line,int column); + + // + // KTextEditor::CursorInterface stuff + // + public slots: + KTextEditor::Cursor *createCursor (); + QPtrList<KTextEditor::Cursor> cursors () const; + + private: + QPtrList<KTextEditor::Cursor> myCursors; + + // + // KTextEditor::SearchInterface stuff + // + public slots: + bool searchText (unsigned int startLine, unsigned int startCol, + const QString &text, unsigned int *foundAtLine, unsigned int *foundAtCol, + unsigned int *matchLen, bool casesensitive = true, bool backwards = false); + bool searchText (unsigned int startLine, unsigned int startCol, + const QRegExp ®exp, unsigned int *foundAtLine, unsigned int *foundAtCol, + unsigned int *matchLen, bool backwards = false); + + // + // KTextEditor::HighlightingInterface stuff + // + public slots: + uint hlMode (); + bool setHlMode (uint mode); + uint hlModeCount (); + QString hlModeName (uint mode); + QString hlModeSectionName (uint mode); + + public: + void bufferHlChanged (); + + private: + void setDontChangeHlOnSave(); + + signals: + void hlChanged (); + + // + // Kate::ArbitraryHighlightingInterface stuff + // + public: + KateArbitraryHighlight* arbitraryHL() const { return m_arbitraryHL; }; + + private slots: + void tagArbitraryLines(KateView* view, KateSuperRange* range); + + // + // KTextEditor::ConfigInterface stuff + // + public slots: + void readConfig (); + void writeConfig (); + void readConfig (KConfig *); + void writeConfig (KConfig *); + void readSessionConfig (KConfig *); + void writeSessionConfig (KConfig *); + void configDialog (); + + // + // KTextEditor::MarkInterface and MarkInterfaceExtension + // + public slots: + uint mark( uint line ); + + void setMark( uint line, uint markType ); + void clearMark( uint line ); + + void addMark( uint line, uint markType ); + void removeMark( uint line, uint markType ); + + QPtrList<KTextEditor::Mark> marks(); + void clearMarks(); + + void setPixmap( MarkInterface::MarkTypes, const QPixmap& ); + void setDescription( MarkInterface::MarkTypes, const QString& ); + QString markDescription( MarkInterface::MarkTypes ); + QPixmap *markPixmap( MarkInterface::MarkTypes ); + QColor markColor( MarkInterface::MarkTypes ); + + void setMarksUserChangable( uint markMask ); + uint editableMarks(); + + signals: + void marksChanged(); + void markChanged( KTextEditor::Mark, KTextEditor::MarkInterfaceExtension::MarkChangeAction ); + + private: + QIntDict<KTextEditor::Mark> m_marks; + QIntDict<QPixmap> m_markPixmaps; + QIntDict<QString> m_markDescriptions; + uint m_editableMarks; + + // + // KTextEditor::PrintInterface + // + public slots: + bool printDialog (); + bool print (); + + // + // KTextEditor::DocumentInfoInterface ( ### unfinished ) + // + public: + /** + * @return the name of the mimetype for the document. + * + * This method is using KMimeType::findByURL, and if the pointer + * is then still the default MimeType for a nonlocal or unsaved file, + * uses mimeTypeForContent(). + * + * @since Kate 2.3 + */ + QString mimeType(); + + /** + * @return the calculated size in bytes that the document would have when saved to + * disk. + * + * @since Kate 2.3 + * @todo implement this (it returns 0 right now) + */ + long fileSize(); + + /** + * @return the calculated size the document would have when saved to disk + * as a human readable string. + * + * @since Kate 2.3 + * @todo implement this (it returns "UNKNOWN") + */ + QString niceFileSize(); + + /** + * @return a pointer to the KMimeType for this document, found by analyzing the + * actual content. + * + * Note that this method is *not* part of the DocumentInfoInterface. + * + * @since Kate 2.3 + */ + KMimeType::Ptr mimeTypeForContent(); + + // + // KTextEditor::VariableInterface + // + public: + QString variable( const QString &name ) const; + + signals: + void variableChanged( const QString &, const QString & ); + + private: + QMap<QString, QString> m_storedVariables; + + // + // KParts::ReadWrite stuff + // + public: + bool openURL( const KURL &url ); + + /* Anders: + I reimplemented this, since i need to check if backup succeeded + if requested */ + bool save(); + + /* Anders: Reimplemented to do kate specific stuff */ + bool saveAs( const KURL &url ); + + bool openFile (KIO::Job * job); + bool openFile (); + + bool saveFile (); + + void setReadWrite ( bool readwrite = true ); + + void setModified( bool m ); + + private slots: + void slotDataKate ( KIO::Job* kio_job, const QByteArray &data ); + void slotFinishedKate ( KIO::Job * job ); + + private: + void abortLoadKate(); + + void activateDirWatch (); + void deactivateDirWatch (); + + QString m_dirWatchFile; + + // + // Kate::Document stuff, this is all deprecated!!!!!!!!!! + // + public: + Kate::ConfigPage *colorConfigPage (QWidget *) { return 0; } + Kate::ConfigPage *fontConfigPage (QWidget *) { return 0; } + Kate::ConfigPage *indentConfigPage (QWidget *) { return 0; } + Kate::ConfigPage *selectConfigPage (QWidget *) { return 0; } + Kate::ConfigPage *editConfigPage (QWidget *) { return 0; } + Kate::ConfigPage *keysConfigPage (QWidget *) { return 0; } + Kate::ConfigPage *hlConfigPage (QWidget *) { return 0; } + Kate::ConfigPage *viewDefaultsConfigPage (QWidget *) { return 0; } + Kate::ConfigPage *saveConfigPage( QWidget * ) { return 0; } + + Kate::ActionMenu *hlActionMenu (const QString& /* text */, QObject* /* parent */ = 0, const char* /* name */ = 0) { return 0; } + Kate::ActionMenu *exportActionMenu (const QString& /* text */, QObject* /* parent */ = 0, const char* /* name */ = 0) { return 0; } + + public: + /** + * Type chars in a view + */ + bool typeChars ( KateView *type, const QString &chars ); + + /** + * gets the last line number (numLines() -1) + */ + inline uint lastLine() const { return numLines()-1; } + + uint configFlags (); + void setConfigFlags (uint flags); + + // Repaint all of all of the views + void repaintViews(bool paintOnlyDirty = true); + + inline KateHighlighting *highlight () { return m_buffer->highlight(); } + + inline KateHighlighting *highlight () const { return m_buffer->highlight(); } + + public slots: //please keep prototypes and implementations in same order + void tagLines(int start, int end); + void tagLines(KateTextCursor start, KateTextCursor end); + + //export feature, obsolute + public slots: + void exportAs(const QString&) { }; + + signals: + void modifiedChanged (); + void preHighlightChanged(uint); + + private slots: + void internalHlChanged(); + + public: + void addView(KTextEditor::View *); + void removeView(KTextEditor::View *); + + void addSuperCursor(class KateSuperCursor *, bool privateC); + void removeSuperCursor(class KateSuperCursor *, bool privateC); + + bool ownedView(KateView *); + bool isLastView(int numViews); + + uint currentColumn( const KateTextCursor& ); + void newLine( KateTextCursor&, KateViewInternal * ); // Changes input + void backspace( KateView *view, const KateTextCursor& ); + void del( KateView *view, const KateTextCursor& ); + void transpose( const KateTextCursor& ); + + void paste ( KateView* view ); + + public: + void insertIndentChars ( KateView *view ); + + void indent ( KateView *view, uint line, int change ); + void comment ( KateView *view, uint line, uint column, int change ); + void align ( KateView *view, uint line ); + + enum TextTransform { Uppercase, Lowercase, Capitalize }; + + /** + Handling uppercase, lowercase and capitalize for the view. + + If there is a selection, that is transformed, otherwise for uppercase or + lowercase the character right of the cursor is transformed, for capitalize + the word under the cursor is transformed. + */ + void transform ( KateView *view, const KateTextCursor &, TextTransform ); + /** + Unwrap a range of lines. + */ + void joinLines( uint first, uint last ); + + private: + void optimizeLeadingSpace( uint line, int flags, int change ); + void replaceWithOptimizedSpace( uint line, uint upto_column, uint space, int flags ); + + bool removeStringFromBegining(int line, QString &str); + bool removeStringFromEnd(int line, QString &str); + + /** + Find the position (line and col) of the next char + that is not a space. If found line and col point to the found character. + Otherwise they have both the value -1. + @param line Line of the character which is examined first. + @param col Column of the character which is examined first. + @return True if the specified or a following character is not a space + Otherwise false. + */ + bool nextNonSpaceCharPos(int &line, int &col); + + /** + Find the position (line and col) of the previous char + that is not a space. If found line and col point to the found character. + Otherwise they have both the value -1. + @return True if the specified or a preceding character is not a space. + Otherwise false. + */ + bool previousNonSpaceCharPos(int &line, int &col); + + /** + * Sets a comment marker as defined by the language providing the attribute + * @p attrib on the line @p line + */ + void addStartLineCommentToSingleLine(int line, int attrib=0); + /** + * Removes a comment marker as defined by the language providing the attribute + * @p attrib on the line @p line + */ + bool removeStartLineCommentFromSingleLine(int line, int attrib=0); + + /** + * @see addStartLineCommentToSingleLine. + */ + void addStartStopCommentToSingleLine(int line, int attrib=0); + /** + *@see removeStartLineCommentFromSingleLine. + */ + bool removeStartStopCommentFromSingleLine(int line, int attrib=0); + /** + *@see removeStartLineCommentFromSingleLine. + */ + bool removeStartStopCommentFromRegion(const KateTextCursor &start, const KateTextCursor &end, int attrib=0); + + /** + * Add a comment marker as defined by the language providing the attribute + * @p attrib to each line in the selection. + */ + void addStartStopCommentToSelection( KateView *view, int attrib=0 ); + /** + * @see addStartStopCommentToSelection. + */ + void addStartLineCommentToSelection( KateView *view, int attrib=0 ); + + /** + * Removes comment markers relevant to the language providing + * the attribuge @p attrib from each line in the selection. + * + * @return whether the operation succeded. + */ + bool removeStartStopCommentFromSelection( KateView *view, int attrib=0 ); + /** + * @see removeStartStopCommentFromSelection. + */ + bool removeStartLineCommentFromSelection( KateView *view, int attrib=0 ); + + public: + QString getWord( const KateTextCursor& cursor ); + + public: + void tagAll(); + + void newBracketMark( const KateTextCursor& start, KateBracketRange& bm, int maxLines = -1 ); + bool findMatchingBracket( KateTextCursor& start, KateTextCursor& end, int maxLines = -1 ); + + private: + void guiActivateEvent( KParts::GUIActivateEvent *ev ); + + public: + + QString docName () {return m_docName;}; + + void setDocName (QString docName); + + void lineInfo (KateLineInfo *info, unsigned int line); + + KateCodeFoldingTree *foldingTree (); + + public: + /** + * @return wheather the document is modified on disc since last saved. + * + * @since 3.3 + */ + bool isModifiedOnDisc() { return m_modOnHd; }; + + /** @deprecated */ + void isModOnHD( bool =false ) {}; + + void setModifiedOnDisk( int reason ); + + public slots: + /** + * Ask the user what to do, if the file has been modified on disc. + * Reimplemented from Kate::Document. + * + * @since 3.3 + */ + void slotModifiedOnDisk( Kate::View *v=0 ); + + /** + * Reloads the current document from disc if possible + */ + void reloadFile(); + + private: + int m_isasking; // don't reenter slotModifiedOnDisk when this is true + // -1: ignore once, 0: false, 1: true + + public slots: + void setEncoding (const QString &e); + QString encoding() const; + + public slots: + void setWordWrap (bool on); + bool wordWrap (); + + void setWordWrapAt (uint col); + uint wordWrapAt (); + + public slots: + void setPageUpDownMovesCursor(bool on); + bool pageUpDownMovesCursor(); + + signals: + void modStateChanged (Kate::Document *doc); + void nameChanged (Kate::Document *doc); + + public slots: + // clear buffer/filename - update the views + void flush (); + + signals: + /** + * The file has been saved (perhaps the name has changed). The main window + * can use this to change its caption + */ + void fileNameChanged (); + + public slots: + void applyWordWrap (); + + // code folding + public: + inline uint getRealLine(unsigned int virtualLine) + { + return m_buffer->lineNumber (virtualLine); + } + + inline uint getVirtualLine(unsigned int realLine) + { + return m_buffer->lineVisibleNumber (realLine); + } + + inline uint visibleLines () + { + return m_buffer->countVisible (); + } + + inline KateTextLine::Ptr kateTextLine(uint i) + { + return m_buffer->line (i); + } + + inline KateTextLine::Ptr plainKateTextLine(uint i) + { + return m_buffer->plainLine (i); + } + + signals: + void codeFoldingUpdated(); + void aboutToRemoveText(const KateTextRange&); + void textRemoved(); + + private slots: + void slotModOnHdDirty (const QString &path); + void slotModOnHdCreated (const QString &path); + void slotModOnHdDeleted (const QString &path); + + private: + /** + * create a MD5 digest of the file, if it is a local file, + * and fill it into the string @p result. + * This is using KMD5::hexDigest(). + * + * @return wheather the operation was attempted and succeded. + * + * @since 3.3 + */ + bool createDigest ( QCString &result ); + + /** + * create a string for the modonhd warnings, giving the reason. + * + * @since 3.3 + */ + QString reasonedMOHString() const; + + /** + * Removes all trailing whitespace form @p line, if + * the cfRemoveTrailingDyn confg flag is set, + * and the active view cursor is not on line and behind + * the last nonspace character. + * + * @since 3.3 + */ + void removeTrailingSpace( uint line ); + + public: + void updateFileType (int newType, bool user = false); + + int fileType () const { return m_fileType; }; + + // + // REALLY internal data ;) + // + private: + // text buffer + KateBuffer *m_buffer; + + KateArbitraryHighlight* m_arbitraryHL; + + KateAutoIndent *m_indenter; + + bool hlSetByUser; + + bool m_modOnHd; + unsigned char m_modOnHdReason; + QCString m_digest; // MD5 digest, updated on load/save + + QString m_docName; + int m_docNameNumber; + + // file type !!! + int m_fileType; + bool m_fileTypeSetByUser; + + /** + * document is still reloading a file + */ + bool m_reloading; + bool m_loading; ///< true in openFile() untill the data is read. + bool m_encodingSticky; ///< true when requests to set encoding should be ignored. + + public slots: + void slotQueryClose_save(bool *handled, bool* abortClosing); + + public: + void makeAttribs (bool needInvalidate = true); + + static bool checkOverwrite( KURL u ); + + static void setDefaultEncoding (const QString &encoding); + + void setEncodingSticky( bool e ) { m_encodingSticky = e; } + + /** + * Configuration + */ + public: + inline KateDocumentConfig *config () { return m_config; }; + + void updateConfig (); + + private: + KateDocumentConfig *m_config; + + /** + * Variable Reader + * TODO add register functionality/ktexteditor interface + */ + private: + /** + * read dir config file + */ + void readDirConfig (); + + /** + Reads all the variables in the document. + Called when opening/saving a document + */ + void readVariables(bool onlyViewAndRenderer = false); + + /** + Reads and applies the variables in a single line + TODO registered variables gets saved in a [map] + */ + void readVariableLine( QString t, bool onlyViewAndRenderer = false ); + /** + Sets a view variable in all the views. + */ + void setViewVariable( QString var, QString val ); + /** + @return weather a string value could be converted + to a bool value as supported. + The value is put in *result. + */ + static bool checkBoolValue( QString value, bool *result ); + /** + @return weather a string value could be converted + to a integer value. + The value is put in *result. + */ + static bool checkIntValue( QString value, int *result ); + /** + Feeds value into @p col using QColor::setNamedColor() and returns + wheather the color is valid + */ + static bool checkColorValue( QString value, QColor &col ); + + /** + * helper regex to capture the document variables + */ + static QRegExp kvLine; + static QRegExp kvLineWildcard; + static QRegExp kvLineMime; + static QRegExp kvVar; + + KIO::TransferJob *m_job; + KTempFile *m_tempFile; + + // TemplateInterface + public: + bool setTabInterceptor(KateKeyInterceptorFunctor *interceptor); /* perhaps make it moregeneral like an eventfilter*/ + bool removeTabInterceptor(KateKeyInterceptorFunctor *interceptor); + bool invokeTabInterceptor(KKey); + + protected: + virtual bool insertTemplateTextImplementation ( uint line, uint column, const QString &templateString, const QMap<QString,QString> &initialValues, QWidget *parentWindow=0 ); + KateKeyInterceptorFunctor *m_tabInterceptor; + + protected slots: + void testTemplateCode(); + void dumpRegionTree(); + + //BEGIN DEPRECATED + // + // KTextEditor::SelectionInterface stuff + // DEPRECATED, this will be removed for KDE 4.x !!!!!!!!!!!!!!!!!!!! + // + public slots: + bool setSelection ( uint startLine, uint startCol, uint endLine, uint endCol ); + bool clearSelection (); + bool hasSelection () const; + QString selection () const; + bool removeSelectedText (); + bool selectAll(); + + // + // KTextEditor::SelectionInterfaceExt + // + int selStartLine(); + int selStartCol(); + int selEndLine(); + int selEndCol(); + + + // hack, only there to still support the deprecated stuff, will be removed for KDE 4.x + #undef signals + #define signals public + signals: + #undef signals + #define signals protected + void selectionChanged (); + + // + // KTextEditor::BlockSelectionInterface stuff + // DEPRECATED, this will be removed for KDE 4.x !!!!!!!!!!!!!!!!!!!! + // + public slots: + bool blockSelectionMode (); + bool setBlockSelectionMode (bool on); + bool toggleBlockSelectionMode (); + + private: +//END DEPRECATED + + k_dcop: + uint documentNumber () const; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; + diff --git a/kate/part/katedocumenthelpers.cpp b/kate/part/katedocumenthelpers.cpp new file mode 100644 index 000000000..9055e7ec3 --- /dev/null +++ b/kate/part/katedocumenthelpers.cpp @@ -0,0 +1,56 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "katedocumenthelpers.h" +#include "katedocumenthelpers.moc" + +#include "katedocument.h" +#include "kateview.h" + +#include <kpopupmenu.h> +#include <klocale.h> + +KateBrowserExtension::KateBrowserExtension( KateDocument* doc ) +: KParts::BrowserExtension( doc, "katepartbrowserextension" ), + m_doc (doc) +{ + connect( doc, SIGNAL( selectionChanged() ), + this, SLOT( slotSelectionChanged() ) ); + emit enableAction( "print", true ); +} + +void KateBrowserExtension::copy() +{ + if (m_doc->activeView()) + m_doc->activeView()->copy(); +} + +void KateBrowserExtension::print() +{ + m_doc->printDialog(); +} + +void KateBrowserExtension::slotSelectionChanged() +{ + if (m_doc->activeView()) + emit enableAction( "copy", m_doc->activeView()->hasSelection() ); +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katedocumenthelpers.h b/kate/part/katedocumenthelpers.h new file mode 100644 index 000000000..8346ffa18 --- /dev/null +++ b/kate/part/katedocumenthelpers.h @@ -0,0 +1,71 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __KATE_DOCUMENT_HELPERS__ +#define __KATE_DOCUMENT_HELPERS__ + +#include "../interfaces/document.h" + +#include <kparts/browserextension.h> + +#include <qstringlist.h> +#include <qguardedptr.h> + +class KateDocument; + +/** + * Interface for embedding KateDocument into a browser + */ +class KateBrowserExtension : public KParts::BrowserExtension +{ + Q_OBJECT + + public: + /** + * Constructor + * @param doc parent document + */ + KateBrowserExtension( KateDocument* doc ); + + public slots: + /** + * copy text to clipboard + */ + void copy(); + + /** + * selection has changed + */ + void slotSelectionChanged(); + + /** + * print the current file + */ + void print(); + + private: + /** + * parent document + */ + KateDocument* m_doc; +}; + +#endif + diff --git a/kate/part/katefactory.cpp b/kate/part/katefactory.cpp new file mode 100644 index 000000000..a02d00fe7 --- /dev/null +++ b/kate/part/katefactory.cpp @@ -0,0 +1,276 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001-2004 Christoph Cullmann <cullmann@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "config.h" +#include "katefactory.h" + +#include "katedocument.h" +#include "kateview.h" +#include "katerenderer.h" +#include "katecmds.h" +#include "katefiletype.h" +#include "kateschema.h" +#include "katesearch.h" +#include "kateconfig.h" +#ifndef Q_WS_WIN //todo +#include "katejscript.h" +#endif +#include "kateluaindentscript.h" +#include "../interfaces/katecmd.h" + +#include <kvmallocator.h> +#include <klocale.h> +#include <kdirwatch.h> +#include <kstaticdeleter.h> + +#include <qapplication.h> + +/** + * dummy wrapper factory to be sure nobody external deletes our katefactory + */ +class KateFactoryPublic : public KParts::Factory +{ + public: + /** + * reimplemented create object method + * @param parentWidget parent widget + * @param widgetName widget name + * @param parent QObject parent + * @param name object name + * @param classname class name + * @param args additional arguments + * @return constructed part object + */ + KParts::Part *createPartObject ( QWidget *parentWidget, const char *widgetName, QObject *parent, const char *name, const char *classname, const QStringList &args ) + { + return KateFactory::self()->createPartObject (parentWidget, widgetName, parent, name, classname, args); + } +}; + +K_EXPORT_COMPONENT_FACTORY( libkatepart, KateFactoryPublic ) + +KateFactory *KateFactory::s_self = 0; + +KateFactory::KateFactory () + : m_aboutData ("katepart", I18N_NOOP("Kate Part"), KATEPART_VERSION, + I18N_NOOP( "Embeddable editor component" ), KAboutData::License_LGPL_V2, + I18N_NOOP( "(c) 2000-2004 The Kate Authors" ), 0, "http://kate.kde.org") + , m_instance (&m_aboutData) + , m_plugins (KTrader::self()->query("KTextEditor/Plugin")) + , m_jscript (0) +{ + // set s_self + s_self = this; + + // + // fill about data + // + m_aboutData.addAuthor ("Christoph Cullmann", I18N_NOOP("Maintainer"), "cullmann@kde.org", "http://www.babylon2k.de"); + m_aboutData.addAuthor ("Anders Lund", I18N_NOOP("Core Developer"), "anders@alweb.dk", "http://www.alweb.dk"); + m_aboutData.addAuthor ("Joseph Wenninger", I18N_NOOP("Core Developer"), "jowenn@kde.org","http://stud3.tuwien.ac.at/~e9925371"); + m_aboutData.addAuthor ("Hamish Rodda",I18N_NOOP("Core Developer"), "rodda@kde.org"); + m_aboutData.addAuthor ("Waldo Bastian", I18N_NOOP( "The cool buffersystem" ), "bastian@kde.org" ); + m_aboutData.addAuthor ("Charles Samuels", I18N_NOOP("The Editing Commands"), "charles@kde.org"); + m_aboutData.addAuthor ("Matt Newell", I18N_NOOP("Testing, ..."), "newellm@proaxis.com"); + m_aboutData.addAuthor ("Michael Bartl", I18N_NOOP("Former Core Developer"), "michael.bartl1@chello.at"); + m_aboutData.addAuthor ("Michael McCallum", I18N_NOOP("Core Developer"), "gholam@xtra.co.nz"); + m_aboutData.addAuthor ("Jochen Wilhemly", I18N_NOOP( "KWrite Author" ), "digisnap@cs.tu-berlin.de" ); + m_aboutData.addAuthor ("Michael Koch",I18N_NOOP("KWrite port to KParts"), "koch@kde.org"); + m_aboutData.addAuthor ("Christian Gebauer", 0, "gebauer@kde.org" ); + m_aboutData.addAuthor ("Simon Hausmann", 0, "hausmann@kde.org" ); + m_aboutData.addAuthor ("Glen Parker",I18N_NOOP("KWrite Undo History, Kspell integration"), "glenebob@nwlink.com"); + m_aboutData.addAuthor ("Scott Manson",I18N_NOOP("KWrite XML Syntax highlighting support"), "sdmanson@alltel.net"); + m_aboutData.addAuthor ("John Firebaugh",I18N_NOOP("Patches and more"), "jfirebaugh@kde.org"); + m_aboutData.addAuthor ("Dominik Haumann", I18N_NOOP("Developer & Highlight wizard"), "dhdev@gmx.de"); + + m_aboutData.addCredit ("Matteo Merli",I18N_NOOP("Highlighting for RPM Spec-Files, Perl, Diff and more"), "merlim@libero.it"); + m_aboutData.addCredit ("Rocky Scaletta",I18N_NOOP("Highlighting for VHDL"), "rocky@purdue.edu"); + m_aboutData.addCredit ("Yury Lebedev",I18N_NOOP("Highlighting for SQL"),""); + m_aboutData.addCredit ("Chris Ross",I18N_NOOP("Highlighting for Ferite"),""); + m_aboutData.addCredit ("Nick Roux",I18N_NOOP("Highlighting for ILERPG"),""); + m_aboutData.addCredit ("Carsten Niehaus", I18N_NOOP("Highlighting for LaTeX"),""); + m_aboutData.addCredit ("Per Wigren", I18N_NOOP("Highlighting for Makefiles, Python"),""); + m_aboutData.addCredit ("Jan Fritz", I18N_NOOP("Highlighting for Python"),""); + m_aboutData.addCredit ("Daniel Naber","",""); + m_aboutData.addCredit ("Roland Pabel",I18N_NOOP("Highlighting for Scheme"),""); + m_aboutData.addCredit ("Cristi Dumitrescu",I18N_NOOP("PHP Keyword/Datatype list"),""); + m_aboutData.addCredit ("Carsten Pfeiffer", I18N_NOOP("Very nice help"), ""); + m_aboutData.addCredit (I18N_NOOP("All people who have contributed and I have forgotten to mention"),"",""); + + m_aboutData.setTranslator(I18N_NOOP("_: NAME OF TRANSLATORS\nYour names"), I18N_NOOP("_: EMAIL OF TRANSLATORS\nYour emails")); + + // + // dir watch + // + m_dirWatch = new KDirWatch (); + + // + // filetype man + // + m_fileTypeManager = new KateFileTypeManager (); + + // + // schema man + // + m_schemaManager = new KateSchemaManager (); + + // config objects + m_documentConfig = new KateDocumentConfig (); + m_viewConfig = new KateViewConfig (); + m_rendererConfig = new KateRendererConfig (); + + // vm allocator + m_vm = new KVMAllocator (); + +#ifndef Q_WS_WIN //todo + // create script man (search scripts) + register commands + m_jscriptManager = new KateJScriptManager (); + KateCmd::self()->registerCommand (m_jscriptManager); + m_indentScriptManagers.append(new KateIndentJScriptManager()); +#else + m_jscriptManager = 0; +#endif +#ifdef HAVE_LUA + m_indentScriptManagers.append(new KateLUAIndentScriptManager()); +#endif + // + // init the cmds + // + m_cmds.push_back (new KateCommands::CoreCommands()); + m_cmds.push_back (new KateCommands::SedReplace ()); + m_cmds.push_back (new KateCommands::Character ()); + m_cmds.push_back (new KateCommands::Date ()); + m_cmds.push_back (new SearchCommand()); + + for ( QValueList<Kate::Command *>::iterator it = m_cmds.begin(); it != m_cmds.end(); ++it ) + KateCmd::self()->registerCommand (*it); +} + +KateFactory::~KateFactory() +{ + /* ?hack? If MainApplication-Interface::quit is called by dcop the factory gets destroyed before all documents are destroyed eg in kwrite. + This could happen in other apps too. Since the documents try to unregister a new factory is created (in the ::self call) and registered with a + KStaticDeleter which causes a crash. That's why I ensure here that all documents are destroyed before the factory goes down (JOWENN)*/ + while (KateDocument *doc=m_documents.first()) { + s_self=this; /* this is needed because the KStaticDeleter sets the global reference to 0, before it deletes the object it handles. + To prevent a crash again restore the factory pointer temporarily. (jowenn)*/ + delete doc; + s_self=0; + } + /*another solution would be to set a flag in the documents, and inhibit calling of the deregistering methods, but I don't see a problem + if all created objects are deleted before their factory. If somebody sees a problem, let me know*/ + + delete m_documentConfig; + delete m_viewConfig; + delete m_rendererConfig; + + delete m_fileTypeManager; + delete m_schemaManager; + + delete m_dirWatch; + + delete m_vm; + + for ( QValueList<Kate::Command *>::iterator it = m_cmds.begin(); it != m_cmds.end(); ++it ) + delete *it; + + // cu manager + delete m_jscriptManager; + m_indentScriptManagers.setAutoDelete(true); + // cu jscript + delete m_jscript; +} + +static KStaticDeleter<KateFactory> sdFactory; + +KateFactory *KateFactory::self () +{ + if (!s_self) { + sdFactory.setObject(s_self, new KateFactory ()); + } + return s_self; +} + +KParts::Part *KateFactory::createPartObject ( QWidget *parentWidget, const char *widgetName, QObject *parent, const char *name, const char *_classname, const QStringList & ) +{ + QCString classname( _classname ); + bool bWantSingleView = ( classname != "KTextEditor::Document" && classname != "Kate::Document" ); + bool bWantBrowserView = ( classname == "Browser/View" ); + bool bWantReadOnly = (bWantBrowserView || ( classname == "KParts::ReadOnlyPart" )); + + KParts::ReadWritePart *part = new KateDocument (bWantSingleView, bWantBrowserView, bWantReadOnly, parentWidget, widgetName, parent, name); + part->setReadWrite( !bWantReadOnly ); + + return part; +} + +void KateFactory::registerDocument ( KateDocument *doc ) +{ + m_documents.append( doc ); +} + +void KateFactory::deregisterDocument ( KateDocument *doc ) +{ + m_documents.removeRef( doc ); +} + +void KateFactory::registerView ( KateView *view ) +{ + m_views.append( view ); +} + +void KateFactory::deregisterView ( KateView *view ) +{ + m_views.removeRef( view ); +} + +void KateFactory::registerRenderer ( KateRenderer *renderer ) +{ + m_renderers.append( renderer ); +} + +void KateFactory::deregisterRenderer ( KateRenderer *renderer ) +{ + m_renderers.removeRef( renderer ); +} + +KateJScript *KateFactory::jscript () +{ +#ifndef Q_WS_WIN //todo + if (m_jscript) + return m_jscript; + + return m_jscript = new KateJScript (); +#else + return 0; +#endif +} + + +KateIndentScript KateFactory::indentScript (const QString &scriptname) +{ + KateIndentScript result; + for(uint i=0;i<m_indentScriptManagers.count();i++) + { + result=m_indentScriptManagers.at(i)->script(scriptname); + if (!result.isNull()) return result; + } + return result; +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katefactory.h b/kate/part/katefactory.h new file mode 100644 index 000000000..5e3986161 --- /dev/null +++ b/kate/part/katefactory.h @@ -0,0 +1,312 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001-2004 Christoph Cullmann <cullmann@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __KATE_FACTORY_H__ +#define __KATE_FACTORY_H__ + +#include "katejscript.h" +#include <kparts/factory.h> + +#include <ktrader.h> +#include <kinstance.h> +#include <kaboutdata.h> + +// katepart version must be a string in double quotes, format: "x.x" +#define KATEPART_VERSION "2.5" + +class KateCmd; +class KateFileTypeManager; +class KateSchemaManager; +class KateDocumentConfig; +class KateViewConfig; +class KateRendererConfig; +class KateDocument; +class KateRenderer; +class KateView; +class KateJScript; +class KateJScriptManager; +class KateIndentScriptManagerAbstract; +class KDirWatch; +class KVMAllocator; + +namespace Kate { + class Command; +} + + +class KateFactory +{ + private: + /** + * Default constructor, private, as singleton + */ + KateFactory (); + + public: + /** + * Destructor + */ + ~KateFactory (); + + /** + * singleton accessor + * @return instance of the factory + */ + static KateFactory *self (); + + /** + * reimplemented create object method + * @param parentWidget parent widget + * @param widgetName widget name + * @param parent QObject parent + * @param name object name + * @param classname class parent + * @param args additional arguments + * @return constructed part object + */ + KParts::Part *createPartObject ( QWidget *parentWidget, const char *widgetName, + QObject *parent, const char *name, const char *classname, + const QStringList &args ); + + /** + * public accessor to the instance + * @return instance + */ + inline KInstance *instance () { return &m_instance; }; + + /** + * register document at the factory + * this allows us to loop over all docs for example on config changes + * @param doc document to register + */ + void registerDocument ( KateDocument *doc ); + + /** + * unregister document at the factory + * @param doc document to register + */ + void deregisterDocument ( KateDocument *doc ); + + /** + * register view at the factory + * this allows us to loop over all views for example on config changes + * @param view view to register + */ + void registerView ( KateView *view ); + + /** + * unregister view at the factory + * @param view view to unregister + */ + void deregisterView ( KateView *view ); + + /** + * register renderer at the factory + * this allows us to loop over all views for example on config changes + * @param renderer renderer to register + */ + void registerRenderer ( KateRenderer *renderer ); + + /** + * unregister renderer at the factory + * @param renderer renderer to unregister + */ + void deregisterRenderer ( KateRenderer *renderer ); + + /** + * return a list of all registered docs + * @return all known documents + */ + inline QPtrList<KateDocument> *documents () { return &m_documents; }; + + /** + * return a list of all registered views + * @return all known views + */ + inline QPtrList<KateView> *views () { return &m_views; }; + + /** + * return a list of all registered renderers + * @return all known renderers + */ + inline QPtrList<KateRenderer> *renderers () { return &m_renderers; }; + + /** + * on start detected plugins + * @return list of all at launch detected ktexteditor::plugins + */ + inline const KTrader::OfferList &plugins () { return m_plugins; }; + + /** + * global dirwatch + * @return dirwatch instance + */ + inline KDirWatch *dirWatch () { return m_dirWatch; }; + + /** + * global filetype manager + * used to manage the file types centrally + * @return filetype manager + */ + inline KateFileTypeManager *fileTypeManager () { return m_fileTypeManager; }; + + /** + * manager for the katepart schemas + * @return schema manager + */ + inline KateSchemaManager *schemaManager () { return m_schemaManager; }; + + /** + * fallback document config + * @return default config for all documents + */ + inline KateDocumentConfig *documentConfig () { return m_documentConfig; } + + /** + * fallback view config + * @return default config for all views + */ + inline KateViewConfig *viewConfig () { return m_viewConfig; } + + /** + * fallback renderer config + * @return default config for all renderers + */ + inline KateRendererConfig *rendererConfig () { return m_rendererConfig; } + + /** + * Global allocator for swapping + * @return allocator + */ + inline KVMAllocator *vm () { return m_vm; } + + /** + * global interpreter, for nice js stuff + */ + KateJScript *jscript (); + + /** + * Global javascript collection + */ + KateJScriptManager *jscriptManager () { return m_jscriptManager; } + + + /** + * looks up a script given by name. If there are more than + * one matching, the first found will be taken + */ + KateIndentScript indentScript (const QString &scriptname); + + private: + /** + * instance of this factory + */ + static KateFactory *s_self; + + /** + * about data (authors and more) + */ + KAboutData m_aboutData; + + /** + * our kinstance + */ + KInstance m_instance; + + /** + * registered docs + */ + QPtrList<KateDocument> m_documents; + + /** + * registered views + */ + QPtrList<KateView> m_views; + + /** + * registered renderers + */ + QPtrList<KateRenderer> m_renderers; + + /** + * global dirwatch object + */ + KDirWatch *m_dirWatch; + + /** + * filetype manager + */ + KateFileTypeManager *m_fileTypeManager; + + /** + * schema manager + */ + KateSchemaManager *m_schemaManager; + + /** + * at start found plugins + */ + KTrader::OfferList m_plugins; + + /** + * fallback document config + */ + KateDocumentConfig *m_documentConfig; + + /** + * fallback view config + */ + KateViewConfig *m_viewConfig; + + /** + * fallback renderer config + */ + KateRendererConfig *m_rendererConfig; + + /** + * vm allocator + */ + KVMAllocator *m_vm; + + /** + * internal commands + */ + QValueList<Kate::Command *> m_cmds; + + /** + * js interpreter + */ + KateJScript *m_jscript; + + + /** + * js script manager + */ + KateJScriptManager *m_jscriptManager; + + + /** + * manager for js based indenters + */ + QPtrList<KateIndentScriptManagerAbstract> m_indentScriptManagers; + +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katefiletype.cpp b/kate/part/katefiletype.cpp new file mode 100644 index 000000000..ea3a487cb --- /dev/null +++ b/kate/part/katefiletype.cpp @@ -0,0 +1,596 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001-2003 Christoph Cullmann <cullmann@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +//BEGIN Includes +#include "katefiletype.h" +#include "katefiletype.moc" + +#include "katedocument.h" +#include "kateconfig.h" +#include "kateview.h" +#include "katefactory.h" + +#include <kconfig.h> +#include <kmimemagic.h> +#include <kmimetype.h> +#include <kmimetypechooser.h> +#include <kdebug.h> +#include <kiconloader.h> +#include <knuminput.h> +#include <klocale.h> +#include <kpopupmenu.h> + +#include <qregexp.h> +#include <qcheckbox.h> +#include <qcombobox.h> +#include <qgroupbox.h> +#include <qhbox.h> +#include <qheader.h> +#include <qhgroupbox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qlineedit.h> +#include <qpushbutton.h> +#include <qtoolbutton.h> +#include <qvbox.h> +#include <qvgroupbox.h> +#include <qwhatsthis.h> +#include <qwidgetstack.h> + +#define KATE_FT_HOWMANY 1024 +//END Includes + +//BEGIN KateFileTypeManager +KateFileTypeManager::KateFileTypeManager () +{ + m_types.setAutoDelete (true); + + update (); +} + +KateFileTypeManager::~KateFileTypeManager () +{ +} + +// +// read the types from config file and update the internal list +// +void KateFileTypeManager::update () +{ + KConfig config ("katefiletyperc", false, false); + + QStringList g (config.groupList()); + g.sort (); + + m_types.clear (); + for (uint z=0; z < g.count(); z++) + { + config.setGroup (g[z]); + + KateFileType *type = new KateFileType (); + + type->number = z; + type->name = g[z]; + type->section = config.readEntry ("Section"); + type->wildcards = config.readListEntry ("Wildcards", ';'); + type->mimetypes = config.readListEntry ("Mimetypes", ';'); + type->priority = config.readNumEntry ("Priority"); + type->varLine = config.readEntry ("Variables"); + + m_types.append (type); + } +} + +// +// save the given list to config file + update +// +void KateFileTypeManager::save (QPtrList<KateFileType> *v) +{ + KConfig config ("katefiletyperc", false, false); + + QStringList newg; + for (uint z=0; z < v->count(); z++) + { + config.setGroup (v->at(z)->name); + + config.writeEntry ("Section", v->at(z)->section); + config.writeEntry ("Wildcards", v->at(z)->wildcards, ';'); + config.writeEntry ("Mimetypes", v->at(z)->mimetypes, ';'); + config.writeEntry ("Priority", v->at(z)->priority); + + QString varLine = v->at(z)->varLine; + if (QRegExp("kate:(.*)").search(varLine) < 0) + varLine.prepend ("kate: "); + + config.writeEntry ("Variables", varLine); + + newg << v->at(z)->name; + } + + QStringList g (config.groupList()); + + for (uint z=0; z < g.count(); z++) + { + if (newg.findIndex (g[z]) == -1) + config.deleteGroup (g[z]); + } + + config.sync (); + + update (); +} + +int KateFileTypeManager::fileType (KateDocument *doc) +{ + kdDebug(13020)<<k_funcinfo<<endl; + if (!doc) + return -1; + + if (m_types.isEmpty()) + return -1; + + QString fileName = doc->url().prettyURL(); + int length = doc->url().prettyURL().length(); + + int result; + + // Try wildcards + if ( ! fileName.isEmpty() ) + { + static QStringList commonSuffixes = QStringList::split (";", ".orig;.new;~;.bak;.BAK"); + + if ((result = wildcardsFind(fileName)) != -1) + return result; + + QString backupSuffix = KateDocumentConfig::global()->backupSuffix(); + if (fileName.endsWith(backupSuffix)) { + if ((result = wildcardsFind(fileName.left(length - backupSuffix.length()))) != -1) + return result; + } + + for (QStringList::Iterator it = commonSuffixes.begin(); it != commonSuffixes.end(); ++it) { + if (*it != backupSuffix && fileName.endsWith(*it)) { + if ((result = wildcardsFind(fileName.left(length - (*it).length()))) != -1) + return result; + } + } + } + + // Even try the document name, if the URL is empty + // This is usefull if the document name is set for example by a plugin which + // created the document + else if ( (result = wildcardsFind(doc->docName())) != -1) + { + kdDebug(13020)<<"KateFiletype::filetype(): got type "<<result<<" using docName '"<<doc->docName()<<"'"<<endl; + return result; + } + + // Try content-based mimetype + KMimeType::Ptr mt = doc->mimeTypeForContent(); + + QPtrList<KateFileType> types; + + for (uint z=0; z < m_types.count(); z++) + { + if (m_types.at(z)->mimetypes.findIndex (mt->name()) > -1) + types.append (m_types.at(z)); + } + + if ( !types.isEmpty() ) + { + int pri = -1; + int hl = -1; + + for (KateFileType *type = types.first(); type != 0L; type = types.next()) + { + if (type->priority > pri) + { + pri = type->priority; + hl = type->number; + } + } + + return hl; + } + + + return -1; +} + +int KateFileTypeManager::wildcardsFind (const QString &fileName) +{ + QPtrList<KateFileType> types; + + for (uint z=0; z < m_types.count(); z++) + { + for( QStringList::Iterator it = m_types.at(z)->wildcards.begin(); it != m_types.at(z)->wildcards.end(); ++it ) + { + // anders: we need to be sure to match the end of string, as eg a css file + // would otherwise end up with the c hl + QRegExp re(*it, true, true); + if ( ( re.search( fileName ) > -1 ) && ( re.matchedLength() == (int)fileName.length() ) ) + types.append (m_types.at(z)); + } + } + + if ( !types.isEmpty() ) + { + int pri = -1; + int hl = -1; + + for (KateFileType *type = types.first(); type != 0L; type = types.next()) + { + if (type->priority > pri) + { + pri = type->priority; + hl = type->number; + } + } + + return hl; + } + + return -1; +} + +const KateFileType *KateFileTypeManager::fileType (uint number) +{ + if (number < m_types.count()) + return m_types.at(number); + + return 0; +} +//END KateFileTypeManager + +//BEGIN KateFileTypeConfigTab +KateFileTypeConfigTab::KateFileTypeConfigTab( QWidget *parent ) + : KateConfigPage( parent ) +{ + m_types.setAutoDelete (true); + m_lastType = 0; + + QVBoxLayout *layout = new QVBoxLayout(this, 0, KDialog::spacingHint() ); + + // hl chooser + QHBox *hbHl = new QHBox( this ); + layout->add (hbHl); + hbHl->setSpacing( KDialog::spacingHint() ); + QLabel *lHl = new QLabel( i18n("&Filetype:"), hbHl ); + typeCombo = new QComboBox( false, hbHl ); + lHl->setBuddy( typeCombo ); + connect( typeCombo, SIGNAL(activated(int)), + this, SLOT(typeChanged(int)) ); + + QPushButton *btnnew = new QPushButton( i18n("&New"), hbHl ); + connect( btnnew, SIGNAL(clicked()), this, SLOT(newType()) ); + + btndel = new QPushButton( i18n("&Delete"), hbHl ); + connect( btndel, SIGNAL(clicked()), this, SLOT(deleteType()) ); + + gbProps = new QGroupBox( 2, Qt::Horizontal, i18n("Properties"), this ); + layout->add (gbProps); + + // file & mime types + QLabel *lname = new QLabel( i18n("N&ame:"), gbProps ); + name = new QLineEdit( gbProps ); + lname->setBuddy( name ); + + // file & mime types + QLabel *lsec = new QLabel( i18n("&Section:"), gbProps ); + section = new QLineEdit( gbProps ); + lsec->setBuddy( section ); + + // file & mime types + QLabel *lvar = new QLabel( i18n("&Variables:"), gbProps ); + varLine = new QLineEdit( gbProps ); + lvar->setBuddy( varLine ); + + // file & mime types + QLabel *lFileExts = new QLabel( i18n("File e&xtensions:"), gbProps ); + wildcards = new QLineEdit( gbProps ); + lFileExts->setBuddy( wildcards ); + + QLabel *lMimeTypes = new QLabel( i18n("MIME &types:"), gbProps); + QHBox *hbMT = new QHBox (gbProps); + mimetypes = new QLineEdit( hbMT ); + lMimeTypes->setBuddy( mimetypes ); + + QToolButton *btnMTW = new QToolButton(hbMT); + btnMTW->setIconSet(QIconSet(SmallIcon("wizard"))); + connect(btnMTW, SIGNAL(clicked()), this, SLOT(showMTDlg())); + + QLabel *lprio = new QLabel( i18n("Prio&rity:"), gbProps); + priority = new KIntNumInput( gbProps ); + lprio->setBuddy( priority ); + + layout->addStretch(); + + reload(); + + connect( name, SIGNAL( textChanged ( const QString & ) ), this, SLOT( slotChanged() ) ); + connect( section, SIGNAL( textChanged ( const QString & ) ), this, SLOT( slotChanged() ) ); + connect( varLine, SIGNAL( textChanged ( const QString & ) ), this, SLOT( slotChanged() ) ); + connect( wildcards, SIGNAL( textChanged ( const QString & ) ), this, SLOT( slotChanged() ) ); + connect( mimetypes, SIGNAL( textChanged ( const QString & ) ), this, SLOT( slotChanged() ) ); + connect( priority, SIGNAL( valueChanged ( int ) ), this, SLOT( slotChanged() ) ); + + QWhatsThis::add( btnnew, i18n("Create a new file type.") ); + QWhatsThis::add( btndel, i18n("Delete the current file type.") ); + QWhatsThis::add( name, i18n( + "The name of the filetype will be the text of the corresponding menu item.") ); + QWhatsThis::add( section, i18n( + "The section name is used to organize the file types in menus.") ); + QWhatsThis::add( varLine, i18n( + "<p>This string allows you to configure Kate's settings for the files " + "selected by this mimetype using Kate variables. You can set almost any " + "configuration option, such as highlight, indent-mode, encoding, etc.</p>" + "<p>For a full list of known variables, see the manual.</p>") ); + QWhatsThis::add( wildcards, i18n( + "The wildcards mask allows you to select files by filename. A typical " + "mask uses an asterisk and the file extension, for example " + "<code>*.txt; *.text</code>. The string is a semicolon-separated list " + "of masks.") ); + QWhatsThis::add( mimetypes, i18n( + "The mime type mask allows you to select files by mimetype. The string is " + "a semicolon-separated list of mimetypes, for example " + "<code>text/plain; text/english</code>.") ); + QWhatsThis::add( btnMTW, i18n( + "Displays a wizard that helps you easily select mimetypes.") ); + QWhatsThis::add( priority, i18n( + "Sets a priority for this file type. If more than one file type selects the same " + "file, the one with the highest priority will be used." ) ); +} + +void KateFileTypeConfigTab::apply() +{ + if (!changed()) + return; + + save (); + + KateFactory::self()->fileTypeManager()->save(&m_types); +} + +void KateFileTypeConfigTab::reload() +{ + m_types.clear(); + for (uint z=0; z < KateFactory::self()->fileTypeManager()->list()->count(); z++) + { + KateFileType *type = new KateFileType (); + + *type = *KateFactory::self()->fileTypeManager()->list()->at(z); + + m_types.append (type); + } + + update (); +} + +void KateFileTypeConfigTab::reset() +{ + reload (); +} + +void KateFileTypeConfigTab::defaults() +{ + reload (); +} + +void KateFileTypeConfigTab::update () +{ + m_lastType = 0; + + typeCombo->clear (); + + for( uint i = 0; i < m_types.count(); i++) { + if (m_types.at(i)->section.length() > 0) + typeCombo->insertItem(m_types.at(i)->section + QString ("/") + m_types.at(i)->name); + else + typeCombo->insertItem(m_types.at(i)->name); + } + + typeCombo->setCurrentItem (0); + + typeChanged (0); + + typeCombo->setEnabled (typeCombo->count() > 0); +} + +void KateFileTypeConfigTab::deleteType () +{ + int type = typeCombo->currentItem (); + + if ((type > -1) && ((uint)type < m_types.count())) + { + m_types.remove (type); + update (); + } +} + +void KateFileTypeConfigTab::newType () +{ + QString newN = i18n("New Filetype"); + + for( uint i = 0; i < m_types.count(); i++) { + if (m_types.at(i)->name == newN) + { + typeCombo->setCurrentItem (i); + typeChanged (i); + return; + } + } + + KateFileType *newT = new KateFileType (); + newT->priority = 0; + newT->name = newN; + + m_types.prepend (newT); + + update (); +} + +void KateFileTypeConfigTab::save () +{ + if (m_lastType) + { + m_lastType->name = name->text (); + m_lastType->section = section->text (); + m_lastType->varLine = varLine->text (); + m_lastType->wildcards = QStringList::split (";", wildcards->text ()); + m_lastType->mimetypes = QStringList::split (";", mimetypes->text ()); + m_lastType->priority = priority->value(); + } +} + +void KateFileTypeConfigTab::typeChanged (int type) +{ + save (); + + KateFileType *t = 0; + + if ((type > -1) && ((uint)type < m_types.count())) + t = m_types.at(type); + + if (t) + { + gbProps->setTitle (i18n("Properties of %1").arg (typeCombo->currentText())); + + gbProps->setEnabled (true); + btndel->setEnabled (true); + + name->setText(t->name); + section->setText(t->section); + varLine->setText(t->varLine); + wildcards->setText(t->wildcards.join (";")); + mimetypes->setText(t->mimetypes.join (";")); + priority->setValue(t->priority); + } + else + { + gbProps->setTitle (i18n("Properties")); + + gbProps->setEnabled (false); + btndel->setEnabled (false); + + name->clear(); + section->clear(); + varLine->clear(); + wildcards->clear(); + mimetypes->clear(); + priority->setValue(0); + } + + m_lastType = t; +} + +void KateFileTypeConfigTab::showMTDlg() +{ + + QString text = i18n("Select the MimeTypes you want for this file type.\nPlease note that this will automatically edit the associated file extensions as well."); + QStringList list = QStringList::split( QRegExp("\\s*;\\s*"), mimetypes->text() ); + KMimeTypeChooserDialog d( i18n("Select Mime Types"), text, list, "text", this ); + if ( d.exec() == KDialogBase::Accepted ) { + // do some checking, warn user if mime types or patterns are removed. + // if the lists are empty, and the fields not, warn. + wildcards->setText( d.chooser()->patterns().join(";") ); + mimetypes->setText( d.chooser()->mimeTypes().join(";") ); + } +} +//END KateFileTypeConfigTab + +//BEGIN KateViewFileTypeAction +void KateViewFileTypeAction::init() +{ + m_doc = 0; + subMenus.setAutoDelete( true ); + + popupMenu()->insertItem ( i18n("None"), this, SLOT(setType(int)), 0, 0); + + connect(popupMenu(),SIGNAL(aboutToShow()),this,SLOT(slotAboutToShow())); +} + +void KateViewFileTypeAction::updateMenu (Kate::Document *doc) +{ + m_doc = (KateDocument *)doc; +} + +void KateViewFileTypeAction::slotAboutToShow() +{ + KateDocument *doc=m_doc; + int count = KateFactory::self()->fileTypeManager()->list()->count(); + + for (int z=0; z<count; z++) + { + QString hlName = KateFactory::self()->fileTypeManager()->list()->at(z)->name; + QString hlSection = KateFactory::self()->fileTypeManager()->list()->at(z)->section; + + if ( !hlSection.isEmpty() && (names.contains(hlName) < 1) ) + { + if (subMenusName.contains(hlSection) < 1) + { + subMenusName << hlSection; + QPopupMenu *menu = new QPopupMenu (); + subMenus.append(menu); + popupMenu()->insertItem (hlSection, menu); + } + + int m = subMenusName.findIndex (hlSection); + names << hlName; + subMenus.at(m)->insertItem ( hlName, this, SLOT(setType(int)), 0, z+1); + } + else if (names.contains(hlName) < 1) + { + names << hlName; + popupMenu()->insertItem ( hlName, this, SLOT(setType(int)), 0, z+1); + } + } + + if (!doc) return; + + for (uint i=0;i<subMenus.count();i++) + { + for (uint i2=0;i2<subMenus.at(i)->count();i2++) + subMenus.at(i)->setItemChecked(subMenus.at(i)->idAt(i2),false); + } + popupMenu()->setItemChecked (0, false); + + if (doc->fileType() == -1) + popupMenu()->setItemChecked (0, true); + else + { + const KateFileType *t = 0; + if ((t = KateFactory::self()->fileTypeManager()->fileType (doc->fileType()))) + { + int i = subMenusName.findIndex (t->section); + if (i >= 0 && subMenus.at(i)) + subMenus.at(i)->setItemChecked (doc->fileType()+1, true); + else + popupMenu()->setItemChecked (0, true); + } + } +} + +void KateViewFileTypeAction::setType (int mode) +{ + KateDocument *doc=m_doc; + + if (doc) + doc->updateFileType(mode-1, true); +} +//END KateViewFileTypeAction +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katefiletype.h b/kate/part/katefiletype.h new file mode 100644 index 000000000..cd343019c --- /dev/null +++ b/kate/part/katefiletype.h @@ -0,0 +1,142 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001-2003 Christoph Cullmann <cullmann@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __kate_filetype_h__ +#define __kate_filetype_h__ + +#include <qstringlist.h> +#include <qptrlist.h> +#include <qpopupmenu.h> // for QPtrList<QPopupMenu>, compile with gcc 3.4 +#include <qguardedptr.h> + +#include "katedialogs.h" + +class KateDocument; + +class KateFileType +{ + public: + int number; + QString name; + QString section; + QStringList wildcards; + QStringList mimetypes; + int priority; + QString varLine; +}; + +class KateFileTypeManager +{ + public: + KateFileTypeManager (); + ~KateFileTypeManager (); + + /** + * File Type Config changed, update all docs (which will take care of views/renderers) + */ + void update (); + + void save (QPtrList<KateFileType> *v); + + /** + * get the right fileType for the given document + * -1 if none found ! + */ + int fileType (KateDocument *doc); + + /** + * Don't store the pointer somewhere longer times, won't be valid after the next update() + */ + const KateFileType *fileType (uint number); + + /** + * Don't modify + */ + QPtrList<KateFileType> *list () { return &m_types; } + + private: + int wildcardsFind (const QString &fileName); + + private: + QPtrList<KateFileType> m_types; +}; + +class KateFileTypeConfigTab : public KateConfigPage +{ + Q_OBJECT + + public: + KateFileTypeConfigTab( QWidget *parent ); + + public slots: + void apply(); + void reload(); + void reset(); + void defaults(); + + private slots: + void update (); + void deleteType (); + void newType (); + void typeChanged (int type); + void showMTDlg(); + void save (); + + private: + class QGroupBox *gbProps; + class QPushButton *btndel; + class QComboBox *typeCombo; + class QLineEdit *wildcards; + class QLineEdit *mimetypes; + class KIntNumInput *priority; + class QLineEdit *name; + class QLineEdit *section; + class QLineEdit *varLine; + + QPtrList<KateFileType> m_types; + KateFileType *m_lastType; +}; + +class KateViewFileTypeAction : public Kate::ActionMenu +{ + Q_OBJECT + + public: + KateViewFileTypeAction(const QString& text, QObject* parent = 0, const char* name = 0) + : Kate::ActionMenu(text, parent, name) { init(); }; + + ~KateViewFileTypeAction(){;}; + + void updateMenu (Kate::Document *doc); + + private: + void init(); + + QGuardedPtr<KateDocument> m_doc; + QStringList subMenusName; + QStringList names; + QPtrList<QPopupMenu> subMenus; + + public slots: + void slotAboutToShow(); + + private slots: + void setType (int mode); +}; + +#endif diff --git a/kate/part/katefont.cpp b/kate/part/katefont.cpp new file mode 100644 index 000000000..efab0268c --- /dev/null +++ b/kate/part/katefont.cpp @@ -0,0 +1,127 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002 Christian Couder <christian@kdevelop.org> + Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "katefont.h" + +#include <kglobalsettings.h> + +#include <qfontinfo.h> + +// +// KateFontMetrics implementation +// + +KateFontMetrics::KateFontMetrics(const QFont& f) : QFontMetrics(f) +{ + for (int i=0; i<256; i++) warray[i]=0; +} + +KateFontMetrics::~KateFontMetrics() +{ + for (int i=0; i<256; i++) + delete[] warray[i]; +} + +short * KateFontMetrics::createRow (short *wa, uchar row) +{ + wa=warray[row]=new short[256]; + + for (int i=0; i<256; i++) wa[i]=-1; + + return wa; +} + +int KateFontMetrics::width(QChar c) +{ + uchar cell=c.cell(); + uchar row=c.row(); + short *wa=warray[row]; + + if (!wa) + wa = createRow (wa, row); + + if (wa[cell]<0) wa[cell]=(short) QFontMetrics::width(c); + + return (int)wa[cell]; +} + +// +// KateFontStruct implementation +// + +KateFontStruct::KateFontStruct() +: myFont(KGlobalSettings::fixedFont()), + myFontBold(KGlobalSettings::fixedFont()), + myFontItalic(KGlobalSettings::fixedFont()), + myFontBI(KGlobalSettings::fixedFont()), + myFontMetrics(myFont), + myFontMetricsBold(myFontBold), + myFontMetricsItalic(myFontItalic), + myFontMetricsBI(myFontBI), + m_fixedPitch (false) +{ + updateFontData (); +} + +KateFontStruct::~KateFontStruct() +{ +} + +void KateFontStruct::updateFontData () +{ + int maxAscent = myFontMetrics.ascent(); + int maxDescent = myFontMetrics.descent(); + + fontHeight = maxAscent + maxDescent + 1; + fontAscent = maxAscent; + + m_fixedPitch = QFontInfo( myFont ).fixedPitch(); +} + +void KateFontStruct::setFont (const QFont & font) +{ + QFontMetrics testFM (font); + + // no valid font tried + if ((testFM.ascent() + testFM.descent() + 1) < 1) + return; + + myFont = font; + + myFontBold = QFont (font); + myFontBold.setBold (true); + + myFontItalic = QFont (font); + myFontItalic.setItalic (true); + + myFontBI = QFont (font); + myFontBI.setBold (true); + myFontBI.setItalic (true); + + myFontMetrics = KateFontMetrics (myFont); + myFontMetricsBold = KateFontMetrics (myFontBold); + myFontMetricsItalic = KateFontMetrics (myFontItalic); + myFontMetricsBI = KateFontMetrics (myFontBI); + + updateFontData (); +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katefont.h b/kate/part/katefont.h new file mode 100644 index 000000000..02c2106c4 --- /dev/null +++ b/kate/part/katefont.h @@ -0,0 +1,114 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002 Christian Couder <christian@kdevelop.org> + Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __kate_font_h__ +#define __kate_font_h__ + +#include <qfont.h> +#include <qfontmetrics.h> + +// +// KateFontMetrics implementation +// + +class KateFontMetrics : public QFontMetrics +{ + public: + KateFontMetrics(const QFont& f); + ~KateFontMetrics(); + + int width(QChar c); + + int width(QString s) { return QFontMetrics::width(s); } + + private: + short *createRow (short *wa, uchar row); + + private: + short *warray[256]; +}; + +// +// KateFontStruct definition +// + +class KateFontStruct +{ + public: + KateFontStruct(); + ~KateFontStruct(); + + void setFont(const QFont & font); + + private: + void updateFontData (); + + public: + inline int width (const QString& text, int col, bool bold, bool italic, int tabWidth) + { + if (text[col] == QChar('\t')) + return tabWidth * myFontMetrics.width(' '); + + return (bold) ? + ( (italic) ? + myFontMetricsBI.charWidth(text, col) : + myFontMetricsBold.charWidth(text, col) ) : + ( (italic) ? + myFontMetricsItalic.charWidth(text, col) : + myFontMetrics.charWidth(text, col) ); + } + + inline int width (const QChar& c, bool bold, bool italic, int tabWidth) + { + if (c == QChar('\t')) + return tabWidth * myFontMetrics.width(' '); + + return (bold) ? + ( (italic) ? + myFontMetricsBI.width(c) : + myFontMetricsBold.width(c) ) : + ( (italic) ? + myFontMetricsItalic.width(c) : + myFontMetrics.width(c) ); + } + + inline const QFont& font(bool bold, bool italic) const + { + return (bold) ? + ( (italic) ? myFontBI : myFontBold ) : + ( (italic) ? myFontItalic : myFont ); + } + + inline bool fixedPitch() const { return m_fixedPitch; } + + public: + QFont myFont, myFontBold, myFontItalic, myFontBI; + + KateFontMetrics myFontMetrics, myFontMetricsBold, myFontMetricsItalic, myFontMetricsBI; + + int fontHeight; + int fontAscent; + + private: + bool m_fixedPitch; +}; + +#endif diff --git a/kate/part/katehighlight.cpp b/kate/part/katehighlight.cpp new file mode 100644 index 000000000..84bfc67fd --- /dev/null +++ b/kate/part/katehighlight.cpp @@ -0,0 +1,3473 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003, 2004 Anders Lund <anders@alweb.dk> + Copyright (C) 2003 Hamish Rodda <rodda@kde.org> + Copyright (C) 2001,2002 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +//BEGIN INCLUDES +#include "katehighlight.h" +#include "katehighlight.moc" + +#include "katetextline.h" +#include "katedocument.h" +#include "katesyntaxdocument.h" +#include "katerenderer.h" +#include "katefactory.h" +#include "kateschema.h" +#include "kateconfig.h" + +#include <kconfig.h> +#include <kglobal.h> +#include <kinstance.h> +#include <kmimetype.h> +#include <klocale.h> +#include <kregexp.h> +#include <kpopupmenu.h> +#include <kglobalsettings.h> +#include <kdebug.h> +#include <kstandarddirs.h> +#include <kmessagebox.h> +#include <kstaticdeleter.h> +#include <kapplication.h> + +#include <qstringlist.h> +#include <qtextstream.h> +//END + +//BEGIN defines +// same as in kmimemagic, no need to feed more data +#define KATE_HL_HOWMANY 1024 + +// min. x seconds between two dynamic contexts reset +static const int KATE_DYNAMIC_CONTEXTS_RESET_DELAY = 30 * 1000; + +// x is a QString. if x is "true" or "1" this expression returns "true" +#define IS_TRUE(x) x.lower() == QString("true") || x.toInt() == 1 +//END defines + +//BEGIN Prviate HL classes + +inline bool kateInsideString (const QString &str, QChar ch) +{ + const QChar *unicode = str.unicode(); + const uint len = str.length(); + for (uint i=0; i < len; i++) + if (unicode[i] == ch) + return true; + + return false; +} + +class KateHlItem +{ + public: + KateHlItem(int attribute, int context,signed char regionId, signed char regionId2); + virtual ~KateHlItem(); + + public: + // caller must keep in mind: LEN > 0 is a must !!!!!!!!!!!!!!!!!!!!!1 + // Now, the function returns the offset detected, or 0 if no match is found. + // bool linestart isn't needed, this is equivalent to offset == 0. + virtual int checkHgl(const QString& text, int offset, int len) = 0; + + virtual bool lineContinue(){return false;} + + virtual QStringList *capturedTexts() {return 0;} + virtual KateHlItem *clone(const QStringList *) {return this;} + + static void dynamicSubstitute(QString& str, const QStringList *args); + + QMemArray<KateHlItem*> subItems; + int attr; + int ctx; + signed char region; + signed char region2; + + bool lookAhead; + + bool dynamic; + bool dynamicChild; + bool firstNonSpace; + bool onlyConsume; + int column; + + // start enable flags, nicer than the virtual methodes + // saves function calls + bool alwaysStartEnable; + bool customStartEnable; +}; + +class KateHlContext +{ + public: + KateHlContext(const QString &_hlId, int attribute, int lineEndContext,int _lineBeginContext, + bool _fallthrough, int _fallthroughContext, bool _dynamic,bool _noIndentationBasedFolding); + virtual ~KateHlContext(); + KateHlContext *clone(const QStringList *args); + + QValueVector<KateHlItem*> items; + QString hlId; ///< A unique highlight identifier. Used to look up correct properties. + int attr; + int ctx; + int lineBeginContext; + /** @internal anders: possible escape if no rules matches. + false unless 'fallthrough="1|true"' (insensitive) + if true, go to ftcxt w/o eating of string. + ftctx is "fallthroughContext" in xml files, valid values are int or #pop[..] + see in KateHighlighting::doHighlight */ + bool fallthrough; + int ftctx; // where to go after no rules matched + + bool dynamic; + bool dynamicChild; + bool noIndentationBasedFolding; +}; + +class KateEmbeddedHlInfo +{ + public: + KateEmbeddedHlInfo() {loaded=false;context0=-1;} + KateEmbeddedHlInfo(bool l, int ctx0) {loaded=l;context0=ctx0;} + + public: + bool loaded; + int context0; +}; + +class KateHlIncludeRule +{ + public: + KateHlIncludeRule(int ctx_=0, uint pos_=0, const QString &incCtxN_="", bool incAttrib=false) + : ctx(ctx_) + , pos( pos_) + , incCtxN( incCtxN_ ) + , includeAttrib( incAttrib ) + { + incCtx=-1; + } + //KateHlIncludeRule(int ctx_, uint pos_, bool incAttrib) {ctx=ctx_;pos=pos_;incCtx=-1;incCtxN="";includeAttrib=incAttrib} + + public: + int ctx; + uint pos; + int incCtx; + QString incCtxN; + bool includeAttrib; +}; + +class KateHlCharDetect : public KateHlItem +{ + public: + KateHlCharDetect(int attribute, int context,signed char regionId,signed char regionId2, QChar); + + virtual int checkHgl(const QString& text, int offset, int len); + virtual KateHlItem *clone(const QStringList *args); + + private: + QChar sChar; +}; + +class KateHl2CharDetect : public KateHlItem +{ + public: + KateHl2CharDetect(int attribute, int context, signed char regionId,signed char regionId2, QChar ch1, QChar ch2); + KateHl2CharDetect(int attribute, int context,signed char regionId,signed char regionId2, const QChar *ch); + + virtual int checkHgl(const QString& text, int offset, int len); + virtual KateHlItem *clone(const QStringList *args); + + private: + QChar sChar1; + QChar sChar2; +}; + +class KateHlStringDetect : public KateHlItem +{ + public: + KateHlStringDetect(int attribute, int context, signed char regionId,signed char regionId2, const QString &, bool inSensitive=false); + + virtual int checkHgl(const QString& text, int offset, int len); + virtual KateHlItem *clone(const QStringList *args); + + private: + const QString str; + const int strLen; + const bool _inSensitive; +}; + +class KateHlRangeDetect : public KateHlItem +{ + public: + KateHlRangeDetect(int attribute, int context, signed char regionId,signed char regionId2, QChar ch1, QChar ch2); + + virtual int checkHgl(const QString& text, int offset, int len); + + private: + QChar sChar1; + QChar sChar2; +}; + +class KateHlKeyword : public KateHlItem +{ + public: + KateHlKeyword(int attribute, int context,signed char regionId,signed char regionId2, bool insensitive, const QString& delims); + virtual ~KateHlKeyword (); + + void addList(const QStringList &); + virtual int checkHgl(const QString& text, int offset, int len); + + private: + QMemArray< QDict<bool>* > dict; + bool _insensitive; + const QString& deliminators; + int minLen; + int maxLen; +}; + +class KateHlInt : public KateHlItem +{ + public: + KateHlInt(int attribute, int context, signed char regionId,signed char regionId2); + + virtual int checkHgl(const QString& text, int offset, int len); +}; + +class KateHlFloat : public KateHlItem +{ + public: + KateHlFloat(int attribute, int context, signed char regionId,signed char regionId2); + virtual ~KateHlFloat () {} + + virtual int checkHgl(const QString& text, int offset, int len); +}; + +class KateHlCFloat : public KateHlFloat +{ + public: + KateHlCFloat(int attribute, int context, signed char regionId,signed char regionId2); + + virtual int checkHgl(const QString& text, int offset, int len); + int checkIntHgl(const QString& text, int offset, int len); +}; + +class KateHlCOct : public KateHlItem +{ + public: + KateHlCOct(int attribute, int context, signed char regionId,signed char regionId2); + + virtual int checkHgl(const QString& text, int offset, int len); +}; + +class KateHlCHex : public KateHlItem +{ + public: + KateHlCHex(int attribute, int context, signed char regionId,signed char regionId2); + + virtual int checkHgl(const QString& text, int offset, int len); +}; + +class KateHlLineContinue : public KateHlItem +{ + public: + KateHlLineContinue(int attribute, int context, signed char regionId,signed char regionId2); + + virtual bool endEnable(QChar c) {return c == '\0';} + virtual int checkHgl(const QString& text, int offset, int len); + virtual bool lineContinue(){return true;} +}; + +class KateHlCStringChar : public KateHlItem +{ + public: + KateHlCStringChar(int attribute, int context, signed char regionId,signed char regionId2); + + virtual int checkHgl(const QString& text, int offset, int len); +}; + +class KateHlCChar : public KateHlItem +{ + public: + KateHlCChar(int attribute, int context,signed char regionId,signed char regionId2); + + virtual int checkHgl(const QString& text, int offset, int len); +}; + +class KateHlAnyChar : public KateHlItem +{ + public: + KateHlAnyChar(int attribute, int context, signed char regionId,signed char regionId2, const QString& charList); + + virtual int checkHgl(const QString& text, int offset, int len); + + private: + const QString _charList; +}; + +class KateHlRegExpr : public KateHlItem +{ + public: + KateHlRegExpr(int attribute, int context,signed char regionId,signed char regionId2 ,QString expr, bool insensitive, bool minimal); + ~KateHlRegExpr() { delete Expr; }; + + virtual int checkHgl(const QString& text, int offset, int len); + virtual QStringList *capturedTexts(); + virtual KateHlItem *clone(const QStringList *args); + + private: + QRegExp *Expr; + bool handlesLinestart; + QString _regexp; + bool _insensitive; + bool _minimal; +}; + +class KateHlDetectSpaces : public KateHlItem +{ + public: + KateHlDetectSpaces (int attribute, int context,signed char regionId,signed char regionId2) + : KateHlItem(attribute,context,regionId,regionId2) {} + + virtual int checkHgl(const QString& text, int offset, int len) + { + int len2 = offset + len; + while ((offset < len2) && text[offset].isSpace()) offset++; + return offset; + } +}; + +class KateHlDetectIdentifier : public KateHlItem +{ + public: + KateHlDetectIdentifier (int attribute, int context,signed char regionId,signed char regionId2) + : KateHlItem(attribute,context,regionId,regionId2) { alwaysStartEnable = false; } + + virtual int checkHgl(const QString& text, int offset, int len) + { + // first char should be a letter or underscore + if ( text[offset].isLetter() || text[offset] == QChar ('_') ) + { + // memorize length + int len2 = offset+len; + + // one char seen + offset++; + + // now loop for all other thingies + while ( + (offset < len2) + && (text[offset].isLetterOrNumber() || (text[offset] == QChar ('_'))) + ) + offset++; + + return offset; + } + + return 0; + } +}; + +//END + +//BEGIN STATICS +KateHlManager *KateHlManager::s_self = 0; + +static const bool trueBool = true; +static const QString stdDeliminator = QString (" \t.():!+,-<=>%&*/;?[]^{|}~\\"); +//END + +//BEGIN NON MEMBER FUNCTIONS +static KateHlItemData::ItemStyles getDefStyleNum(QString name) +{ + if (name=="dsNormal") return KateHlItemData::dsNormal; + else if (name=="dsKeyword") return KateHlItemData::dsKeyword; + else if (name=="dsDataType") return KateHlItemData::dsDataType; + else if (name=="dsDecVal") return KateHlItemData::dsDecVal; + else if (name=="dsBaseN") return KateHlItemData::dsBaseN; + else if (name=="dsFloat") return KateHlItemData::dsFloat; + else if (name=="dsChar") return KateHlItemData::dsChar; + else if (name=="dsString") return KateHlItemData::dsString; + else if (name=="dsComment") return KateHlItemData::dsComment; + else if (name=="dsOthers") return KateHlItemData::dsOthers; + else if (name=="dsAlert") return KateHlItemData::dsAlert; + else if (name=="dsFunction") return KateHlItemData::dsFunction; + else if (name=="dsRegionMarker") return KateHlItemData::dsRegionMarker; + else if (name=="dsError") return KateHlItemData::dsError; + + return KateHlItemData::dsNormal; +} +//END + +//BEGIN KateHlItem +KateHlItem::KateHlItem(int attribute, int context,signed char regionId,signed char regionId2) + : attr(attribute), + ctx(context), + region(regionId), + region2(regionId2), + lookAhead(false), + dynamic(false), + dynamicChild(false), + firstNonSpace(false), + onlyConsume(false), + column (-1), + alwaysStartEnable (true), + customStartEnable (false) +{ +} + +KateHlItem::~KateHlItem() +{ + //kdDebug(13010)<<"In hlItem::~KateHlItem()"<<endl; + for (uint i=0; i < subItems.size(); i++) + delete subItems[i]; +} + +void KateHlItem::dynamicSubstitute(QString &str, const QStringList *args) +{ + for (uint i = 0; i < str.length() - 1; ++i) + { + if (str[i] == '%') + { + char c = str[i + 1].latin1(); + if (c == '%') + str.replace(i, 1, ""); + else if (c >= '0' && c <= '9') + { + if ((uint)(c - '0') < args->size()) + { + str.replace(i, 2, (*args)[c - '0']); + i += ((*args)[c - '0']).length() - 1; + } + else + { + str.replace(i, 2, ""); + --i; + } + } + } + } +} +//END + +//BEGIN KateHlCharDetect +KateHlCharDetect::KateHlCharDetect(int attribute, int context, signed char regionId,signed char regionId2, QChar c) + : KateHlItem(attribute,context,regionId,regionId2) + , sChar(c) +{ +} + +int KateHlCharDetect::checkHgl(const QString& text, int offset, int /*len*/) +{ + if (text[offset] == sChar) + return offset + 1; + + return 0; +} + +KateHlItem *KateHlCharDetect::clone(const QStringList *args) +{ + char c = sChar.latin1(); + + if (c < '0' || c > '9' || (unsigned)(c - '0') >= args->size()) + return this; + + KateHlCharDetect *ret = new KateHlCharDetect(attr, ctx, region, region2, (*args)[c - '0'][0]); + ret->dynamicChild = true; + return ret; +} +//END + +//BEGIN KateHl2CharDetect +KateHl2CharDetect::KateHl2CharDetect(int attribute, int context, signed char regionId,signed char regionId2, QChar ch1, QChar ch2) + : KateHlItem(attribute,context,regionId,regionId2) + , sChar1 (ch1) + , sChar2 (ch2) +{ +} + +int KateHl2CharDetect::checkHgl(const QString& text, int offset, int len) +{ + if ((len >= 2) && text[offset++] == sChar1 && text[offset++] == sChar2) + return offset; + + return 0; +} + +KateHlItem *KateHl2CharDetect::clone(const QStringList *args) +{ + char c1 = sChar1.latin1(); + char c2 = sChar2.latin1(); + + if (c1 < '0' || c1 > '9' || (unsigned)(c1 - '0') >= args->size()) + return this; + + if (c2 < '0' || c2 > '9' || (unsigned)(c2 - '0') >= args->size()) + return this; + + KateHl2CharDetect *ret = new KateHl2CharDetect(attr, ctx, region, region2, (*args)[c1 - '0'][0], (*args)[c2 - '0'][0]); + ret->dynamicChild = true; + return ret; +} +//END + +//BEGIN KateHlStringDetect +KateHlStringDetect::KateHlStringDetect(int attribute, int context, signed char regionId,signed char regionId2,const QString &s, bool inSensitive) + : KateHlItem(attribute, context,regionId,regionId2) + , str(inSensitive ? s.upper() : s) + , strLen (str.length()) + , _inSensitive(inSensitive) +{ +} + +int KateHlStringDetect::checkHgl(const QString& text, int offset, int len) +{ + if (len < strLen) + return 0; + + if (_inSensitive) + { + for (int i=0; i < strLen; i++) + if (text[offset++].upper() != str[i]) + return 0; + + return offset; + } + else + { + for (int i=0; i < strLen; i++) + if (text[offset++] != str[i]) + return 0; + + return offset; + } + + return 0; +} + +KateHlItem *KateHlStringDetect::clone(const QStringList *args) +{ + QString newstr = str; + + dynamicSubstitute(newstr, args); + + if (newstr == str) + return this; + + KateHlStringDetect *ret = new KateHlStringDetect(attr, ctx, region, region2, newstr, _inSensitive); + ret->dynamicChild = true; + return ret; +} +//END + +//BEGIN KateHlRangeDetect +KateHlRangeDetect::KateHlRangeDetect(int attribute, int context, signed char regionId,signed char regionId2, QChar ch1, QChar ch2) + : KateHlItem(attribute,context,regionId,regionId2) + , sChar1 (ch1) + , sChar2 (ch2) +{ +} + +int KateHlRangeDetect::checkHgl(const QString& text, int offset, int len) +{ + if (text[offset] == sChar1) + { + do + { + offset++; + len--; + if (len < 1) return 0; + } + while (text[offset] != sChar2); + + return offset + 1; + } + return 0; +} +//END + +//BEGIN KateHlKeyword +KateHlKeyword::KateHlKeyword (int attribute, int context, signed char regionId,signed char regionId2, bool insensitive, const QString& delims) + : KateHlItem(attribute,context,regionId,regionId2) + , _insensitive(insensitive) + , deliminators(delims) + , minLen (0xFFFFFF) + , maxLen (0) +{ + alwaysStartEnable = false; + customStartEnable = true; +} + +KateHlKeyword::~KateHlKeyword () +{ + for (uint i=0; i < dict.size(); ++i) + delete dict[i]; +} + +void KateHlKeyword::addList(const QStringList& list) +{ + for(uint i=0; i < list.count(); ++i) + { + int len = list[i].length(); + + if (minLen > len) + minLen = len; + + if (maxLen < len) + maxLen = len; + + if ((uint)len >= dict.size()) + { + uint oldSize = dict.size(); + dict.resize (len+1); + + for (uint m=oldSize; m < dict.size(); ++m) + dict[m] = 0; + } + + if (!dict[len]) + dict[len] = new QDict<bool> (17, !_insensitive); + + dict[len]->insert(list[i], &trueBool); + } +} + +int KateHlKeyword::checkHgl(const QString& text, int offset, int len) +{ + int offset2 = offset; + int wordLen = 0; + + while ((len > wordLen) && !kateInsideString (deliminators, text[offset2])) + { + offset2++; + wordLen++; + + if (wordLen > maxLen) return 0; + } + + if (wordLen < minLen) return 0; + + if ( dict[wordLen] && dict[wordLen]->find(QConstString(text.unicode() + offset, wordLen).string()) ) + return offset2; + + return 0; +} +//END + +//BEGIN KateHlInt +KateHlInt::KateHlInt(int attribute, int context, signed char regionId,signed char regionId2) + : KateHlItem(attribute,context,regionId,regionId2) +{ + alwaysStartEnable = false; +} + +int KateHlInt::checkHgl(const QString& text, int offset, int len) +{ + int offset2 = offset; + + while ((len > 0) && text[offset2].isDigit()) + { + offset2++; + len--; + } + + if (offset2 > offset) + { + if (len > 0) + { + for (uint i=0; i < subItems.size(); i++) + { + if ( (offset = subItems[i]->checkHgl(text, offset2, len)) ) + return offset; + } + } + + return offset2; + } + + return 0; +} +//END + +//BEGIN KateHlFloat +KateHlFloat::KateHlFloat(int attribute, int context, signed char regionId,signed char regionId2) + : KateHlItem(attribute,context, regionId,regionId2) +{ + alwaysStartEnable = false; +} + +int KateHlFloat::checkHgl(const QString& text, int offset, int len) +{ + bool b = false; + bool p = false; + + while ((len > 0) && text[offset].isDigit()) + { + offset++; + len--; + b = true; + } + + if ((len > 0) && (p = (text[offset] == '.'))) + { + offset++; + len--; + + while ((len > 0) && text[offset].isDigit()) + { + offset++; + len--; + b = true; + } + } + + if (!b) + return 0; + + if ((len > 0) && ((text[offset] & 0xdf) == 'E')) + { + offset++; + len--; + } + else + { + if (!p) + return 0; + else + { + if (len > 0) + { + for (uint i=0; i < subItems.size(); i++) + { + int offset2 = subItems[i]->checkHgl(text, offset, len); + + if (offset2) + return offset2; + } + } + + return offset; + } + } + + if ((len > 0) && (text[offset] == '-' || text[offset] =='+')) + { + offset++; + len--; + } + + b = false; + + while ((len > 0) && text[offset].isDigit()) + { + offset++; + len--; + b = true; + } + + if (b) + { + if (len > 0) + { + for (uint i=0; i < subItems.size(); i++) + { + int offset2 = subItems[i]->checkHgl(text, offset, len); + + if (offset2) + return offset2; + } + } + + return offset; + } + + return 0; +} +//END + +//BEGIN KateHlCOct +KateHlCOct::KateHlCOct(int attribute, int context, signed char regionId,signed char regionId2) + : KateHlItem(attribute,context,regionId,regionId2) +{ + alwaysStartEnable = false; +} + +int KateHlCOct::checkHgl(const QString& text, int offset, int len) +{ + if (text[offset] == '0') + { + offset++; + len--; + + int offset2 = offset; + + while ((len > 0) && (text[offset2] >= '0' && text[offset2] <= '7')) + { + offset2++; + len--; + } + + if (offset2 > offset) + { + if ((len > 0) && ((text[offset2] & 0xdf) == 'L' || (text[offset] & 0xdf) == 'U' )) + offset2++; + + return offset2; + } + } + + return 0; +} +//END + +//BEGIN KateHlCHex +KateHlCHex::KateHlCHex(int attribute, int context,signed char regionId,signed char regionId2) + : KateHlItem(attribute,context,regionId,regionId2) +{ + alwaysStartEnable = false; +} + +int KateHlCHex::checkHgl(const QString& text, int offset, int len) +{ + if ((len > 1) && (text[offset++] == '0') && ((text[offset++] & 0xdf) == 'X' )) + { + len -= 2; + + int offset2 = offset; + + while ((len > 0) && (text[offset2].isDigit() || ((text[offset2] & 0xdf) >= 'A' && (text[offset2] & 0xdf) <= 'F'))) + { + offset2++; + len--; + } + + if (offset2 > offset) + { + if ((len > 0) && ((text[offset2] & 0xdf) == 'L' || (text[offset2] & 0xdf) == 'U' )) + offset2++; + + return offset2; + } + } + + return 0; +} +//END + +//BEGIN KateHlCFloat +KateHlCFloat::KateHlCFloat(int attribute, int context, signed char regionId,signed char regionId2) + : KateHlFloat(attribute,context,regionId,regionId2) +{ + alwaysStartEnable = false; +} + +int KateHlCFloat::checkIntHgl(const QString& text, int offset, int len) +{ + int offset2 = offset; + + while ((len > 0) && text[offset].isDigit()) { + offset2++; + len--; + } + + if (offset2 > offset) + return offset2; + + return 0; +} + +int KateHlCFloat::checkHgl(const QString& text, int offset, int len) +{ + int offset2 = KateHlFloat::checkHgl(text, offset, len); + + if (offset2) + { + if ((text[offset2] & 0xdf) == 'F' ) + offset2++; + + return offset2; + } + else + { + offset2 = checkIntHgl(text, offset, len); + + if (offset2 && ((text[offset2] & 0xdf) == 'F' )) + return ++offset2; + else + return 0; + } +} +//END + +//BEGIN KateHlAnyChar +KateHlAnyChar::KateHlAnyChar(int attribute, int context, signed char regionId,signed char regionId2, const QString& charList) + : KateHlItem(attribute, context,regionId,regionId2) + , _charList(charList) +{ +} + +int KateHlAnyChar::checkHgl(const QString& text, int offset, int) +{ + if (kateInsideString (_charList, text[offset])) + return ++offset; + + return 0; +} +//END + +//BEGIN KateHlRegExpr +KateHlRegExpr::KateHlRegExpr( int attribute, int context, signed char regionId,signed char regionId2, QString regexp, bool insensitive, bool minimal) + : KateHlItem(attribute, context, regionId,regionId2) + , handlesLinestart (regexp.startsWith("^")) + , _regexp(regexp) + , _insensitive(insensitive) + , _minimal(minimal) +{ + if (!handlesLinestart) + regexp.prepend("^"); + + Expr = new QRegExp(regexp, !_insensitive); + Expr->setMinimal(_minimal); +} + +int KateHlRegExpr::checkHgl(const QString& text, int offset, int /*len*/) +{ + if (offset && handlesLinestart) + return 0; + + int offset2 = Expr->search( text, offset, QRegExp::CaretAtOffset ); + + if (offset2 == -1) return 0; + + return (offset + Expr->matchedLength()); +} + +QStringList *KateHlRegExpr::capturedTexts() +{ + return new QStringList(Expr->capturedTexts()); +} + +KateHlItem *KateHlRegExpr::clone(const QStringList *args) +{ + QString regexp = _regexp; + QStringList escArgs = *args; + + for (QStringList::Iterator it = escArgs.begin(); it != escArgs.end(); ++it) + { + (*it).replace(QRegExp("(\\W)"), "\\\\1"); + } + + dynamicSubstitute(regexp, &escArgs); + + if (regexp == _regexp) + return this; + + // kdDebug (13010) << "clone regexp: " << regexp << endl; + + KateHlRegExpr *ret = new KateHlRegExpr(attr, ctx, region, region2, regexp, _insensitive, _minimal); + ret->dynamicChild = true; + return ret; +} +//END + +//BEGIN KateHlLineContinue +KateHlLineContinue::KateHlLineContinue(int attribute, int context, signed char regionId,signed char regionId2) + : KateHlItem(attribute,context,regionId,regionId2) { +} + +int KateHlLineContinue::checkHgl(const QString& text, int offset, int len) +{ + if ((len == 1) && (text[offset] == '\\')) + return ++offset; + + return 0; +} +//END + +//BEGIN KateHlCStringChar +KateHlCStringChar::KateHlCStringChar(int attribute, int context,signed char regionId,signed char regionId2) + : KateHlItem(attribute,context,regionId,regionId2) { +} + +// checks for C escaped chars \n and escaped hex/octal chars +static int checkEscapedChar(const QString& text, int offset, int& len) +{ + int i; + if (text[offset] == '\\' && len > 1) + { + offset++; + len--; + + switch(text[offset]) + { + case 'a': // checks for control chars + case 'b': // we want to fall through + case 'e': + case 'f': + + case 'n': + case 'r': + case 't': + case 'v': + case '\'': + case '\"': + case '?' : // added ? ANSI C classifies this as an escaped char + case '\\': + offset++; + len--; + break; + + case 'x': // if it's like \xff + offset++; // eat the x + len--; + // these for loops can probably be + // replaced with something else but + // for right now they work + // check for hexdigits + for (i = 0; (len > 0) && (i < 2) && (text[offset] >= '0' && text[offset] <= '9' || (text[offset] & 0xdf) >= 'A' && (text[offset] & 0xdf) <= 'F'); i++) + { + offset++; + len--; + } + + if (i == 0) + return 0; // takes care of case '\x' + + break; + + case '0': case '1': case '2': case '3' : + case '4': case '5': case '6': case '7' : + for (i = 0; (len > 0) && (i < 3) && (text[offset] >='0'&& text[offset] <='7'); i++) + { + offset++; + len--; + } + break; + + default: + return 0; + } + + return offset; + } + + return 0; +} + +int KateHlCStringChar::checkHgl(const QString& text, int offset, int len) +{ + return checkEscapedChar(text, offset, len); +} +//END + +//BEGIN KateHlCChar +KateHlCChar::KateHlCChar(int attribute, int context,signed char regionId,signed char regionId2) + : KateHlItem(attribute,context,regionId,regionId2) { +} + +int KateHlCChar::checkHgl(const QString& text, int offset, int len) +{ + if ((len > 1) && (text[offset] == '\'') && (text[offset+1] != '\'')) + { + int oldl; + oldl = len; + + len--; + + int offset2 = checkEscapedChar(text, offset + 1, len); + + if (!offset2) + { + if (oldl > 2) + { + offset2 = offset + 2; + len = oldl - 2; + } + else + { + return 0; + } + } + + if ((len > 0) && (text[offset2] == '\'')) + return ++offset2; + } + + return 0; +} +//END + +//BEGIN KateHl2CharDetect +KateHl2CharDetect::KateHl2CharDetect(int attribute, int context, signed char regionId,signed char regionId2, const QChar *s) + : KateHlItem(attribute,context,regionId,regionId2) { + sChar1 = s[0]; + sChar2 = s[1]; + } +//END KateHl2CharDetect + +KateHlItemData::KateHlItemData(const QString name, int defStyleNum) + : name(name), defStyleNum(defStyleNum) { +} + +KateHlData::KateHlData(const QString &wildcards, const QString &mimetypes, const QString &identifier, int priority) + : wildcards(wildcards), mimetypes(mimetypes), identifier(identifier), priority(priority) +{ +} + +//BEGIN KateHlContext +KateHlContext::KateHlContext (const QString &_hlId, int attribute, int lineEndContext, int _lineBeginContext, bool _fallthrough, + int _fallthroughContext, bool _dynamic, bool _noIndentationBasedFolding) +{ + hlId = _hlId; + attr = attribute; + ctx = lineEndContext; + lineBeginContext = _lineBeginContext; + fallthrough = _fallthrough; + ftctx = _fallthroughContext; + dynamic = _dynamic; + dynamicChild = false; + noIndentationBasedFolding=_noIndentationBasedFolding; + if (_noIndentationBasedFolding) kdDebug(13010)<<QString("**********************_noIndentationBasedFolding is TRUE*****************")<<endl; + +} + +KateHlContext *KateHlContext::clone(const QStringList *args) +{ + KateHlContext *ret = new KateHlContext(hlId, attr, ctx, lineBeginContext, fallthrough, ftctx, false,noIndentationBasedFolding); + + for (uint n=0; n < items.size(); ++n) + { + KateHlItem *item = items[n]; + KateHlItem *i = (item->dynamic ? item->clone(args) : item); + ret->items.append(i); + } + + ret->dynamicChild = true; + + return ret; +} + +KateHlContext::~KateHlContext() +{ + if (dynamicChild) + { + for (uint n=0; n < items.size(); ++n) + { + if (items[n]->dynamicChild) + delete items[n]; + } + } +} +//END + +//BEGIN KateHighlighting +KateHighlighting::KateHighlighting(const KateSyntaxModeListItem *def) : refCount(0) +{ + m_attributeArrays.setAutoDelete (true); + + errorsAndWarnings = ""; + building=false; + noHl = false; + m_foldingIndentationSensitive = false; + folding=false; + internalIDList.setAutoDelete(true); + + if (def == 0) + { + noHl = true; + iName = "None"; // not translated internal name (for config and more) + iNameTranslated = i18n("None"); // user visible name + iSection = ""; + m_priority = 0; + iHidden = false; + m_additionalData.insert( "none", new HighlightPropertyBag ); + m_additionalData["none"]->deliminator = stdDeliminator; + m_additionalData["none"]->wordWrapDeliminator = stdDeliminator; + m_hlIndex[0] = "none"; + } + else + { + iName = def->name; + iNameTranslated = def->nameTranslated; + iSection = def->section; + iHidden = def->hidden; + iWildcards = def->extension; + iMimetypes = def->mimetype; + identifier = def->identifier; + iVersion=def->version; + iAuthor=def->author; + iLicense=def->license; + m_priority=def->priority.toInt(); + } + + deliminator = stdDeliminator; +} + +KateHighlighting::~KateHighlighting() +{ + // cu contexts + for (uint i=0; i < m_contexts.size(); ++i) + delete m_contexts[i]; + m_contexts.clear (); +} + +void KateHighlighting::generateContextStack(int *ctxNum, int ctx, QMemArray<short>* ctxs, int *prevLine) +{ + //kdDebug(13010)<<QString("Entering generateContextStack with %1").arg(ctx)<<endl; + while (true) + { + if (ctx >= 0) + { + (*ctxNum) = ctx; + + ctxs->resize (ctxs->size()+1, QGArray::SpeedOptim); + (*ctxs)[ctxs->size()-1]=(*ctxNum); + + return; + } + else + { + if (ctx == -1) + { + (*ctxNum)=( (ctxs->isEmpty() ) ? 0 : (*ctxs)[ctxs->size()-1]); + } + else + { + int size = ctxs->size() + ctx + 1; + + if (size > 0) + { + ctxs->resize (size, QGArray::SpeedOptim); + (*ctxNum)=(*ctxs)[size-1]; + } + else + { + ctxs->resize (0, QGArray::SpeedOptim); + (*ctxNum)=0; + } + + ctx = 0; + + if ((*prevLine) >= (int)(ctxs->size()-1)) + { + *prevLine=ctxs->size()-1; + + if ( ctxs->isEmpty() ) + return; + + KateHlContext *c = contextNum((*ctxs)[ctxs->size()-1]); + if (c && (c->ctx != -1)) + { + //kdDebug(13010)<<"PrevLine > size()-1 and ctx!=-1)"<<endl; + ctx = c->ctx; + + continue; + } + } + } + + return; + } + } +} + +/** + * Creates a new dynamic context or reuse an old one if it has already been created. + */ +int KateHighlighting::makeDynamicContext(KateHlContext *model, const QStringList *args) +{ + QPair<KateHlContext *, QString> key(model, args->front()); + short value; + + if (dynamicCtxs.contains(key)) + value = dynamicCtxs[key]; + else + { + kdDebug(13010) << "new stuff: " << startctx << endl; + + KateHlContext *newctx = model->clone(args); + + m_contexts.push_back (newctx); + + value = startctx++; + dynamicCtxs[key] = value; + KateHlManager::self()->incDynamicCtxs(); + } + + // kdDebug(13010) << "Dynamic context: using context #" << value << " (for model " << model << " with args " << *args << ")" << endl; + + return value; +} + +/** + * Drop all dynamic contexts. Shall be called with extreme care, and shall be immediatly + * followed by a full HL invalidation. + */ +void KateHighlighting::dropDynamicContexts() +{ + for (uint i=base_startctx; i < m_contexts.size(); ++i) + delete m_contexts[i]; + + m_contexts.resize (base_startctx); + + dynamicCtxs.clear(); + startctx = base_startctx; +} + +/** + * Parse the text and fill in the context array and folding list array + * + * @param prevLine The previous line, the context array is picked up from that if present. + * @param textLine The text line to parse + * @param foldingList will be filled + * @param ctxChanged will be set to reflect if the context changed + */ +void KateHighlighting::doHighlight ( KateTextLine *prevLine, + KateTextLine *textLine, + QMemArray<uint>* foldingList, + bool *ctxChanged ) +{ + if (!textLine) + return; + + if (noHl) + { + if (textLine->length() > 0) + memset (textLine->attributes(), 0, textLine->length()); + + return; + } + + // duplicate the ctx stack, only once ! + QMemArray<short> ctx; + ctx.duplicate (prevLine->ctxArray()); + + int ctxNum = 0; + int previousLine = -1; + KateHlContext *context; + + if (ctx.isEmpty()) + { + // If the stack is empty, we assume to be in Context 0 (Normal) + context = contextNum(ctxNum); + } + else + { + // There does an old context stack exist -> find the context at the line start + ctxNum = ctx[ctx.size()-1]; //context ID of the last character in the previous line + + //kdDebug(13010) << "\t\tctxNum = " << ctxNum << " contextList[ctxNum] = " << contextList[ctxNum] << endl; // ellis + + //if (lineContinue) kdDebug(13010)<<QString("The old context should be %1").arg((int)ctxNum)<<endl; + + if (!(context = contextNum(ctxNum))) + context = contextNum(0); + + //kdDebug(13010)<<"test1-2-1-text2"<<endl; + + previousLine=ctx.size()-1; //position of the last context ID of th previous line within the stack + + // hl continue set or not ??? + if (prevLine->hlLineContinue()) + { + prevLine--; + } + else + { + generateContextStack(&ctxNum, context->ctx, &ctx, &previousLine); //get stack ID to use + + if (!(context = contextNum(ctxNum))) + context = contextNum(0); + } + + //kdDebug(13010)<<"test1-2-1-text4"<<endl; + + //if (lineContinue) kdDebug(13010)<<QString("The new context is %1").arg((int)ctxNum)<<endl; + } + + // text, for programming convenience :) + QChar lastChar = ' '; + const QString& text = textLine->string(); + const int len = textLine->length(); + + // calc at which char the first char occurs, set it to lenght of line if never + const int firstChar = textLine->firstChar(); + const int startNonSpace = (firstChar == -1) ? len : firstChar; + + // last found item + KateHlItem *item = 0; + + // loop over the line, offset gives current offset + int offset = 0; + while (offset < len) + { + bool anItemMatched = false; + bool standardStartEnableDetermined = false; + bool customStartEnableDetermined = false; + + uint index = 0; + for (item = context->items.empty() ? 0 : context->items[0]; item; item = (++index < context->items.size()) ? context->items[index] : 0 ) + { + // does we only match if we are firstNonSpace? + if (item->firstNonSpace && (offset > startNonSpace)) + continue; + + // have we a column specified? if yes, only match at this column + if ((item->column != -1) && (item->column != offset)) + continue; + + if (!item->alwaysStartEnable) + { + if (item->customStartEnable) + { + if (customStartEnableDetermined || kateInsideString (m_additionalData[context->hlId]->deliminator, lastChar)) + customStartEnableDetermined = true; + else + continue; + } + else + { + if (standardStartEnableDetermined || kateInsideString (stdDeliminator, lastChar)) + standardStartEnableDetermined = true; + else + continue; + } + } + + int offset2 = item->checkHgl(text, offset, len-offset); + + if (offset2 <= offset) + continue; + // BUG 144599: Ignore a context change that would push the same context + // without eating anything... this would be an infinite loop! + if ( item->lookAhead && item->ctx == ctxNum ) + continue; + + if (item->region2) + { + // kdDebug(13010)<<QString("Region mark 2 detected: %1").arg(item->region2)<<endl; + if ( !foldingList->isEmpty() && ((item->region2 < 0) && (*foldingList)[foldingList->size()-2] == -item->region2 ) ) + { + foldingList->resize (foldingList->size()-2, QGArray::SpeedOptim); + } + else + { + foldingList->resize (foldingList->size()+2, QGArray::SpeedOptim); + (*foldingList)[foldingList->size()-2] = (uint)item->region2; + if (item->region2<0) //check not really needed yet + (*foldingList)[foldingList->size()-1] = offset2; + else + (*foldingList)[foldingList->size()-1] = offset; + } + + } + + if (item->region) + { + // kdDebug(13010)<<QString("Region mark detected: %1").arg(item->region)<<endl; + + /* if ( !foldingList->isEmpty() && ((item->region < 0) && (*foldingList)[foldingList->size()-1] == -item->region ) ) + { + foldingList->resize (foldingList->size()-1, QGArray::SpeedOptim); + } + else*/ + { + foldingList->resize (foldingList->size()+2, QGArray::SpeedOptim); + (*foldingList)[foldingList->size()-2] = item->region; + if (item->region<0) //check not really needed yet + (*foldingList)[foldingList->size()-1] = offset2; + else + (*foldingList)[foldingList->size()-1] = offset; + } + + } + + // regenerate context stack if needed + if (item->ctx != -1) + { + generateContextStack (&ctxNum, item->ctx, &ctx, &previousLine); + context = contextNum(ctxNum); + } + + // dynamic context: substitute the model with an 'instance' + if (context->dynamic) + { + QStringList *lst = item->capturedTexts(); + if (lst != 0) + { + // Replace the top of the stack and the current context + int newctx = makeDynamicContext(context, lst); + if (ctx.size() > 0) + ctx[ctx.size() - 1] = newctx; + ctxNum = newctx; + context = contextNum(ctxNum); + } + delete lst; + } + + // dominik: look ahead w/o changing offset? + if (!item->lookAhead) + { + if (offset2 > len) + offset2 = len; + + // even set attributes ;) + memset ( textLine->attributes()+offset + , item->onlyConsume ? context->attr : item->attr + , offset2-offset); + + offset = offset2; + lastChar = text[offset-1]; + } + + anItemMatched = true; + break; + } + + // something matched, continue loop + if (anItemMatched) + continue; + + // nothing found: set attribute of one char + // anders: unless this context does not want that! + if ( context->fallthrough ) + { + // set context to context->ftctx. + generateContextStack(&ctxNum, context->ftctx, &ctx, &previousLine); //regenerate context stack + context=contextNum(ctxNum); + //kdDebug(13010)<<"context num after fallthrough at col "<<z<<": "<<ctxNum<<endl; + // the next is nessecary, as otherwise keyword (or anything using the std delimitor check) + // immediately after fallthrough fails. Is it bad? + // jowenn, can you come up with a nicer way to do this? + /* if (offset) + lastChar = text[offset - 1]; + else + lastChar = '\\';*/ + continue; + } + else + { + *(textLine->attributes() + offset) = context->attr; + lastChar = text[offset]; + offset++; + } + } + + // has the context stack changed ? + if (ctx == textLine->ctxArray()) + { + if (ctxChanged) + (*ctxChanged) = false; + } + else + { + if (ctxChanged) + (*ctxChanged) = true; + + // assign ctx stack ! + textLine->setContext(ctx); + } + + // write hl continue flag + textLine->setHlLineContinue (item && item->lineContinue()); + + if (m_foldingIndentationSensitive) { + bool noindent=false; + for(int i=ctx.size()-1; i>=0; --i) { + if (contextNum(ctx[i])->noIndentationBasedFolding) { + noindent=true; + break; + } + } + textLine->setNoIndentBasedFolding(noindent); + } +} + +void KateHighlighting::loadWildcards() +{ + KConfig *config = KateHlManager::self()->getKConfig(); + config->setGroup("Highlighting " + iName); + + QString extensionString = config->readEntry("Wildcards", iWildcards); + + if (extensionSource != extensionString) { + regexpExtensions.clear(); + plainExtensions.clear(); + + extensionSource = extensionString; + + static QRegExp sep("\\s*;\\s*"); + + QStringList l = QStringList::split( sep, extensionSource ); + + static QRegExp boringExpression("\\*\\.[\\d\\w]+"); + + for( QStringList::Iterator it = l.begin(); it != l.end(); ++it ) + if (boringExpression.exactMatch(*it)) + plainExtensions.append((*it).mid(1)); + else + regexpExtensions.append(QRegExp((*it), true, true)); + } +} + +QValueList<QRegExp>& KateHighlighting::getRegexpExtensions() +{ + return regexpExtensions; +} + +QStringList& KateHighlighting::getPlainExtensions() +{ + return plainExtensions; +} + +QString KateHighlighting::getMimetypes() +{ + KConfig *config = KateHlManager::self()->getKConfig(); + config->setGroup("Highlighting " + iName); + + return config->readEntry("Mimetypes", iMimetypes); +} + +int KateHighlighting::priority() +{ + KConfig *config = KateHlManager::self()->getKConfig(); + config->setGroup("Highlighting " + iName); + + return config->readNumEntry("Priority", m_priority); +} + +KateHlData *KateHighlighting::getData() +{ + KConfig *config = KateHlManager::self()->getKConfig(); + config->setGroup("Highlighting " + iName); + + KateHlData *hlData = new KateHlData( + config->readEntry("Wildcards", iWildcards), + config->readEntry("Mimetypes", iMimetypes), + config->readEntry("Identifier", identifier), + config->readNumEntry("Priority", m_priority)); + + return hlData; +} + +void KateHighlighting::setData(KateHlData *hlData) +{ + KConfig *config = KateHlManager::self()->getKConfig(); + config->setGroup("Highlighting " + iName); + + config->writeEntry("Wildcards",hlData->wildcards); + config->writeEntry("Mimetypes",hlData->mimetypes); + config->writeEntry("Priority",hlData->priority); +} + +void KateHighlighting::getKateHlItemDataList (uint schema, KateHlItemDataList &list) +{ + KConfig *config = KateHlManager::self()->getKConfig(); + config->setGroup("Highlighting " + iName + " - Schema " + KateFactory::self()->schemaManager()->name(schema)); + + list.clear(); + createKateHlItemData(list); + + for (KateHlItemData *p = list.first(); p != 0L; p = list.next()) + { + QStringList s = config->readListEntry(p->name); + +// kdDebug(13010)<<p->name<<s.count()<<endl; + if (s.count()>0) + { + + while(s.count()<9) s<<""; + p->clear(); + + QString tmp=s[0]; if (!tmp.isEmpty()) p->defStyleNum=tmp.toInt(); + + QRgb col; + + tmp=s[1]; if (!tmp.isEmpty()) { + col=tmp.toUInt(0,16); p->setTextColor(col); } + + tmp=s[2]; if (!tmp.isEmpty()) { + col=tmp.toUInt(0,16); p->setSelectedTextColor(col); } + + tmp=s[3]; if (!tmp.isEmpty()) p->setBold(tmp!="0"); + + tmp=s[4]; if (!tmp.isEmpty()) p->setItalic(tmp!="0"); + + tmp=s[5]; if (!tmp.isEmpty()) p->setStrikeOut(tmp!="0"); + + tmp=s[6]; if (!tmp.isEmpty()) p->setUnderline(tmp!="0"); + + tmp=s[7]; if (!tmp.isEmpty()) { + col=tmp.toUInt(0,16); p->setBGColor(col); } + + tmp=s[8]; if (!tmp.isEmpty()) { + col=tmp.toUInt(0,16); p->setSelectedBGColor(col); } + + } + } +} + +/** + * Saves the KateHlData attribute definitions to the config file. + * + * @param schema The id of the schema group to save + * @param list KateHlItemDataList containing the data to be used + */ +void KateHighlighting::setKateHlItemDataList(uint schema, KateHlItemDataList &list) +{ + KConfig *config = KateHlManager::self()->getKConfig(); + config->setGroup("Highlighting " + iName + " - Schema " + + KateFactory::self()->schemaManager()->name(schema)); + + QStringList settings; + + for (KateHlItemData *p = list.first(); p != 0L; p = list.next()) + { + settings.clear(); + settings<<QString::number(p->defStyleNum,10); + settings<<(p->itemSet(KateAttribute::TextColor)?QString::number(p->textColor().rgb(),16):""); + settings<<(p->itemSet(KateAttribute::SelectedTextColor)?QString::number(p->selectedTextColor().rgb(),16):""); + settings<<(p->itemSet(KateAttribute::Weight)?(p->bold()?"1":"0"):""); + settings<<(p->itemSet(KateAttribute::Italic)?(p->italic()?"1":"0"):""); + settings<<(p->itemSet(KateAttribute::StrikeOut)?(p->strikeOut()?"1":"0"):""); + settings<<(p->itemSet(KateAttribute::Underline)?(p->underline()?"1":"0"):""); + settings<<(p->itemSet(KateAttribute::BGColor)?QString::number(p->bgColor().rgb(),16):""); + settings<<(p->itemSet(KateAttribute::SelectedBGColor)?QString::number(p->selectedBGColor().rgb(),16):""); + settings<<"---"; + config->writeEntry(p->name,settings); + } +} + +/** + * Increase the usage count, and trigger initialization if needed. + */ +void KateHighlighting::use() +{ + if (refCount == 0) + init(); + + refCount++; +} + +/** + * Decrease the usage count, and trigger cleanup if needed. + */ +void KateHighlighting::release() +{ + refCount--; + + if (refCount == 0) + done(); +} + +/** + * Initialize a context for the first time. + */ + +void KateHighlighting::init() +{ + if (noHl) + return; + + // cu contexts + for (uint i=0; i < m_contexts.size(); ++i) + delete m_contexts[i]; + m_contexts.clear (); + + makeContextList(); +} + + +/** + * If the there is no document using the highlighting style free the complete + * context structure. + */ +void KateHighlighting::done() +{ + if (noHl) + return; + + // cu contexts + for (uint i=0; i < m_contexts.size(); ++i) + delete m_contexts[i]; + m_contexts.clear (); + + internalIDList.clear(); +} + +/** + * KateHighlighting - createKateHlItemData + * This function reads the itemData entries from the config file, which specifies the + * default attribute styles for matched items/contexts. + * + * @param list A reference to the internal list containing the parsed default config + */ +void KateHighlighting::createKateHlItemData(KateHlItemDataList &list) +{ + // If no highlighting is selected we need only one default. + if (noHl) + { + list.append(new KateHlItemData(i18n("Normal Text"), KateHlItemData::dsNormal)); + return; + } + + // If the internal list isn't already available read the config file + if (internalIDList.isEmpty()) + makeContextList(); + + list=internalIDList; +} + +/** + * Adds the styles of the currently parsed highlight to the itemdata list + */ +void KateHighlighting::addToKateHlItemDataList() +{ + //Tell the syntax document class which file we want to parse and which data group + KateHlManager::self()->syntax->setIdentifier(buildIdentifier); + KateSyntaxContextData *data = KateHlManager::self()->syntax->getGroupInfo("highlighting","itemData"); + + //begin with the real parsing + while (KateHlManager::self()->syntax->nextGroup(data)) + { + // read all attributes + QString color = KateHlManager::self()->syntax->groupData(data,QString("color")); + QString selColor = KateHlManager::self()->syntax->groupData(data,QString("selColor")); + QString bold = KateHlManager::self()->syntax->groupData(data,QString("bold")); + QString italic = KateHlManager::self()->syntax->groupData(data,QString("italic")); + QString underline = KateHlManager::self()->syntax->groupData(data,QString("underline")); + QString strikeOut = KateHlManager::self()->syntax->groupData(data,QString("strikeOut")); + QString bgColor = KateHlManager::self()->syntax->groupData(data,QString("backgroundColor")); + QString selBgColor = KateHlManager::self()->syntax->groupData(data,QString("selBackgroundColor")); + + KateHlItemData* newData = new KateHlItemData( + buildPrefix+KateHlManager::self()->syntax->groupData(data,QString("name")).simplifyWhiteSpace(), + getDefStyleNum(KateHlManager::self()->syntax->groupData(data,QString("defStyleNum")))); + + /* here the custom style overrides are specified, if needed */ + if (!color.isEmpty()) newData->setTextColor(QColor(color)); + if (!selColor.isEmpty()) newData->setSelectedTextColor(QColor(selColor)); + if (!bold.isEmpty()) newData->setBold( IS_TRUE(bold) ); + if (!italic.isEmpty()) newData->setItalic( IS_TRUE(italic) ); + // new attributes for the new rendering view + if (!underline.isEmpty()) newData->setUnderline( IS_TRUE(underline) ); + if (!strikeOut.isEmpty()) newData->setStrikeOut( IS_TRUE(strikeOut) ); + if (!bgColor.isEmpty()) newData->setBGColor(QColor(bgColor)); + if (!selBgColor.isEmpty()) newData->setSelectedBGColor(QColor(selBgColor)); + + internalIDList.append(newData); + } + + //clean up + if (data) + KateHlManager::self()->syntax->freeGroupInfo(data); +} + +/** + * KateHighlighting - lookupAttrName + * This function is a helper for makeContextList and createKateHlItem. It looks the given + * attribute name in the itemData list up and returns it's index + * + * @param name the attribute name to lookup + * @param iDl the list containing all available attributes + * + * @return The index of the attribute, or 0 if the attribute isn't found + */ +int KateHighlighting::lookupAttrName(const QString& name, KateHlItemDataList &iDl) +{ + for (uint i = 0; i < iDl.count(); i++) + if (iDl.at(i)->name == buildPrefix+name) + return i; + + kdDebug(13010)<<"Couldn't resolve itemDataName:"<<name<<endl; + return 0; +} + +/** + * KateHighlighting - createKateHlItem + * This function is a helper for makeContextList. It parses the xml file for + * information. + * + * @param data Data about the item read from the xml file + * @param iDl List of all available itemData entries. + * Needed for attribute name->index translation + * @param RegionList list of code folding region names + * @param ContextNameList list of context names + * + * @return A pointer to the newly created item object + */ +KateHlItem *KateHighlighting::createKateHlItem(KateSyntaxContextData *data, + KateHlItemDataList &iDl, + QStringList *RegionList, + QStringList *ContextNameList) +{ + // No highlighting -> exit + if (noHl) + return 0; + + // get the (tagname) itemd type + QString dataname=KateHlManager::self()->syntax->groupItemData(data,QString("")); + + // code folding region handling: + QString beginRegionStr=KateHlManager::self()->syntax->groupItemData(data,QString("beginRegion")); + QString endRegionStr=KateHlManager::self()->syntax->groupItemData(data,QString("endRegion")); + + signed char regionId=0; + signed char regionId2=0; + + if (!beginRegionStr.isEmpty()) + { + regionId = RegionList->findIndex(beginRegionStr); + + if (regionId==-1) // if the region name doesn't already exist, add it to the list + { + (*RegionList)<<beginRegionStr; + regionId = RegionList->findIndex(beginRegionStr); + } + + regionId++; + + kdDebug(13010) << "########### BEG REG: " << beginRegionStr << " NUM: " << regionId << endl; + } + + if (!endRegionStr.isEmpty()) + { + regionId2 = RegionList->findIndex(endRegionStr); + + if (regionId2==-1) // if the region name doesn't already exist, add it to the list + { + (*RegionList)<<endRegionStr; + regionId2 = RegionList->findIndex(endRegionStr); + } + + regionId2 = -regionId2 - 1; + + kdDebug(13010) << "########### END REG: " << endRegionStr << " NUM: " << regionId2 << endl; + } + + int attr = 0; + QString tmpAttr=KateHlManager::self()->syntax->groupItemData(data,QString("attribute")).simplifyWhiteSpace(); + bool onlyConsume = tmpAttr.isEmpty(); + + // only relevant for non consumer + if (!onlyConsume) + { + if (QString("%1").arg(tmpAttr.toInt())==tmpAttr) + { + errorsAndWarnings+=i18n( + "<B>%1</B>: Deprecated syntax. Attribute (%2) not addressed by symbolic name<BR>"). + arg(buildIdentifier).arg(tmpAttr); + attr=tmpAttr.toInt(); + } + else + attr=lookupAttrName(tmpAttr,iDl); + } + + // Info about context switch + int context = -1; + QString unresolvedContext; + QString tmpcontext=KateHlManager::self()->syntax->groupItemData(data,QString("context")); + if (!tmpcontext.isEmpty()) + context=getIdFromString(ContextNameList, tmpcontext,unresolvedContext); + + // Get the char parameter (eg DetectChar) + char chr; + if (! KateHlManager::self()->syntax->groupItemData(data,QString("char")).isEmpty()) + chr= (KateHlManager::self()->syntax->groupItemData(data,QString("char")).latin1())[0]; + else + chr=0; + + // Get the String parameter (eg. StringDetect) + QString stringdata=KateHlManager::self()->syntax->groupItemData(data,QString("String")); + + // Get a second char parameter (char1) (eg Detect2Chars) + char chr1; + if (! KateHlManager::self()->syntax->groupItemData(data,QString("char1")).isEmpty()) + chr1= (KateHlManager::self()->syntax->groupItemData(data,QString("char1")).latin1())[0]; + else + chr1=0; + + // Will be removed eventually. Atm used for StringDetect, keyword and RegExp + const QString & insensitive_str = KateHlManager::self()->syntax->groupItemData(data,QString("insensitive")); + bool insensitive = IS_TRUE( insensitive_str ); + + // for regexp only + bool minimal = IS_TRUE( KateHlManager::self()->syntax->groupItemData(data,QString("minimal")) ); + + // dominik: look ahead and do not change offset. so we can change contexts w/o changing offset1. + bool lookAhead = IS_TRUE( KateHlManager::self()->syntax->groupItemData(data,QString("lookAhead")) ); + + bool dynamic= IS_TRUE(KateHlManager::self()->syntax->groupItemData(data,QString("dynamic")) ); + + bool firstNonSpace = IS_TRUE(KateHlManager::self()->syntax->groupItemData(data,QString("firstNonSpace")) ); + + int column = -1; + QString colStr = KateHlManager::self()->syntax->groupItemData(data,QString("column")); + if (!colStr.isEmpty()) + column = colStr.toInt(); + + //Create the item corresponding to it's type and set it's parameters + KateHlItem *tmpItem; + + if (dataname=="keyword") + { + bool keywordInsensitive = insensitive_str.isEmpty() ? !casesensitive : insensitive; + KateHlKeyword *keyword=new KateHlKeyword(attr,context,regionId,regionId2,keywordInsensitive, + m_additionalData[ buildIdentifier ]->deliminator); + + //Get the entries for the keyword lookup list + keyword->addList(KateHlManager::self()->syntax->finddata("highlighting",stringdata)); + tmpItem=keyword; + } + else if (dataname=="Float") tmpItem= (new KateHlFloat(attr,context,regionId,regionId2)); + else if (dataname=="Int") tmpItem=(new KateHlInt(attr,context,regionId,regionId2)); + else if (dataname=="DetectChar") tmpItem=(new KateHlCharDetect(attr,context,regionId,regionId2,chr)); + else if (dataname=="Detect2Chars") tmpItem=(new KateHl2CharDetect(attr,context,regionId,regionId2,chr,chr1)); + else if (dataname=="RangeDetect") tmpItem=(new KateHlRangeDetect(attr,context,regionId,regionId2, chr, chr1)); + else if (dataname=="LineContinue") tmpItem=(new KateHlLineContinue(attr,context,regionId,regionId2)); + else if (dataname=="StringDetect") tmpItem=(new KateHlStringDetect(attr,context,regionId,regionId2,stringdata,insensitive)); + else if (dataname=="AnyChar") tmpItem=(new KateHlAnyChar(attr,context,regionId,regionId2,stringdata)); + else if (dataname=="RegExpr") tmpItem=(new KateHlRegExpr(attr,context,regionId,regionId2,stringdata, insensitive, minimal)); + else if (dataname=="HlCChar") tmpItem= ( new KateHlCChar(attr,context,regionId,regionId2)); + else if (dataname=="HlCHex") tmpItem= (new KateHlCHex(attr,context,regionId,regionId2)); + else if (dataname=="HlCOct") tmpItem= (new KateHlCOct(attr,context,regionId,regionId2)); + else if (dataname=="HlCFloat") tmpItem= (new KateHlCFloat(attr,context,regionId,regionId2)); + else if (dataname=="HlCStringChar") tmpItem= (new KateHlCStringChar(attr,context,regionId,regionId2)); + else if (dataname=="DetectSpaces") tmpItem= (new KateHlDetectSpaces(attr,context,regionId,regionId2)); + else if (dataname=="DetectIdentifier") tmpItem= (new KateHlDetectIdentifier(attr,context,regionId,regionId2)); + else + { + // oops, unknown type. Perhaps a spelling error in the xml file + return 0; + } + + // set lookAhead & dynamic properties + tmpItem->lookAhead = lookAhead; + tmpItem->dynamic = dynamic; + tmpItem->firstNonSpace = firstNonSpace; + tmpItem->column = column; + tmpItem->onlyConsume = onlyConsume; + + if (!unresolvedContext.isEmpty()) + { + unresolvedContextReferences.insert(&(tmpItem->ctx),unresolvedContext); + } + + return tmpItem; +} + +QString KateHighlighting::hlKeyForAttrib( int i ) const +{ + // find entry. This is faster than QMap::find. m_hlIndex always has an entry + // for key '0' (it is "none"), so the result is always valid. + int k = 0; + QMap<int,QString>::const_iterator it = m_hlIndex.constEnd(); + while ( it != m_hlIndex.constBegin() ) + { + --it; + k = it.key(); + if ( i >= k ) + break; + } + return it.data(); +} + +bool KateHighlighting::isInWord( QChar c, int attrib ) const +{ + return m_additionalData[ hlKeyForAttrib( attrib ) ]->deliminator.find(c) < 0 + && !c.isSpace() && c != '"' && c != '\''; +} + +bool KateHighlighting::canBreakAt( QChar c, int attrib ) const +{ + static const QString& sq = KGlobal::staticQString("\"'"); + return (m_additionalData[ hlKeyForAttrib( attrib ) ]->wordWrapDeliminator.find(c) != -1) && (sq.find(c) == -1); +} + +signed char KateHighlighting::commentRegion(int attr) const { + QString commentRegion=m_additionalData[ hlKeyForAttrib( attr ) ]->multiLineRegion; + return (commentRegion.isEmpty()?0:(commentRegion.toShort())); +} + +bool KateHighlighting::canComment( int startAttrib, int endAttrib ) const +{ + QString k = hlKeyForAttrib( startAttrib ); + return ( k == hlKeyForAttrib( endAttrib ) && + ( ( !m_additionalData[k]->multiLineCommentStart.isEmpty() && !m_additionalData[k]->multiLineCommentEnd.isEmpty() ) || + ! m_additionalData[k]->singleLineCommentMarker.isEmpty() ) ); +} + +QString KateHighlighting::getCommentStart( int attrib ) const +{ + return m_additionalData[ hlKeyForAttrib( attrib) ]->multiLineCommentStart; +} + +QString KateHighlighting::getCommentEnd( int attrib ) const +{ + return m_additionalData[ hlKeyForAttrib( attrib ) ]->multiLineCommentEnd; +} + +QString KateHighlighting::getCommentSingleLineStart( int attrib ) const +{ + return m_additionalData[ hlKeyForAttrib( attrib) ]->singleLineCommentMarker; +} + +KateHighlighting::CSLPos KateHighlighting::getCommentSingleLinePosition( int attrib ) const +{ + return m_additionalData[ hlKeyForAttrib( attrib) ]->singleLineCommentPosition; +} + + +/** + * Helper for makeContextList. It parses the xml file for + * information, how single or multi line comments are marked + */ +void KateHighlighting::readCommentConfig() +{ + KateHlManager::self()->syntax->setIdentifier(buildIdentifier); + KateSyntaxContextData *data=KateHlManager::self()->syntax->getGroupInfo("general","comment"); + + QString cmlStart="", cmlEnd="", cmlRegion="", cslStart=""; + CSLPos cslPosition=CSLPosColumn0; + + if (data) + { + while (KateHlManager::self()->syntax->nextGroup(data)) + { + if (KateHlManager::self()->syntax->groupData(data,"name")=="singleLine") + { + cslStart=KateHlManager::self()->syntax->groupData(data,"start"); + QString cslpos=KateHlManager::self()->syntax->groupData(data,"position"); + if (cslpos=="afterwhitespace") + cslPosition=CSLPosAfterWhitespace; + else + cslPosition=CSLPosColumn0; + } + else if (KateHlManager::self()->syntax->groupData(data,"name")=="multiLine") + { + cmlStart=KateHlManager::self()->syntax->groupData(data,"start"); + cmlEnd=KateHlManager::self()->syntax->groupData(data,"end"); + cmlRegion=KateHlManager::self()->syntax->groupData(data,"region"); + } + } + + KateHlManager::self()->syntax->freeGroupInfo(data); + } + + m_additionalData[buildIdentifier]->singleLineCommentMarker = cslStart; + m_additionalData[buildIdentifier]->singleLineCommentPosition = cslPosition; + m_additionalData[buildIdentifier]->multiLineCommentStart = cmlStart; + m_additionalData[buildIdentifier]->multiLineCommentEnd = cmlEnd; + m_additionalData[buildIdentifier]->multiLineRegion = cmlRegion; +} + +/** + * Helper for makeContextList. It parses the xml file for information, + * if keywords should be treated case(in)sensitive and creates the keyword + * delimiter list. Which is the default list, without any given weak deliminiators + */ +void KateHighlighting::readGlobalKeywordConfig() +{ + deliminator = stdDeliminator; + // Tell the syntax document class which file we want to parse + kdDebug(13010)<<"readGlobalKeywordConfig:BEGIN"<<endl; + + KateHlManager::self()->syntax->setIdentifier(buildIdentifier); + KateSyntaxContextData *data = KateHlManager::self()->syntax->getConfig("general","keywords"); + + if (data) + { + kdDebug(13010)<<"Found global keyword config"<<endl; + + if ( IS_TRUE( KateHlManager::self()->syntax->groupItemData(data,QString("casesensitive")) ) ) + casesensitive=true; + else + casesensitive=false; + + //get the weak deliminators + weakDeliminator=(KateHlManager::self()->syntax->groupItemData(data,QString("weakDeliminator"))); + + kdDebug(13010)<<"weak delimiters are: "<<weakDeliminator<<endl; + + // remove any weakDelimitars (if any) from the default list and store this list. + for (uint s=0; s < weakDeliminator.length(); s++) + { + int f = deliminator.find (weakDeliminator[s]); + + if (f > -1) + deliminator.remove (f, 1); + } + + QString addDelim = (KateHlManager::self()->syntax->groupItemData(data,QString("additionalDeliminator"))); + + if (!addDelim.isEmpty()) + deliminator=deliminator+addDelim; + + KateHlManager::self()->syntax->freeGroupInfo(data); + } + else + { + //Default values + casesensitive=true; + weakDeliminator=QString(""); + } + + kdDebug(13010)<<"readGlobalKeywordConfig:END"<<endl; + + kdDebug(13010)<<"delimiterCharacters are: "<<deliminator<<endl; + + m_additionalData[buildIdentifier]->deliminator = deliminator; +} + +/** + * Helper for makeContextList. It parses the xml file for any wordwrap + * deliminators, characters * at which line can be broken. In case no keyword + * tag is found in the xml file, the wordwrap deliminators list defaults to the + * standard denominators. In case a keyword tag is defined, but no + * wordWrapDeliminator attribute is specified, the deliminator list as computed + * in readGlobalKeywordConfig is used. + * + * @return the computed delimiter string. + */ +void KateHighlighting::readWordWrapConfig() +{ + // Tell the syntax document class which file we want to parse + kdDebug(13010)<<"readWordWrapConfig:BEGIN"<<endl; + + KateHlManager::self()->syntax->setIdentifier(buildIdentifier); + KateSyntaxContextData *data = KateHlManager::self()->syntax->getConfig("general","keywords"); + + QString wordWrapDeliminator = stdDeliminator; + if (data) + { + kdDebug(13010)<<"Found global keyword config"<<endl; + + wordWrapDeliminator = (KateHlManager::self()->syntax->groupItemData(data,QString("wordWrapDeliminator"))); + //when no wordWrapDeliminator is defined use the deliminator list + if ( wordWrapDeliminator.length() == 0 ) wordWrapDeliminator = deliminator; + + kdDebug(13010) << "word wrap deliminators are " << wordWrapDeliminator << endl; + + KateHlManager::self()->syntax->freeGroupInfo(data); + } + + kdDebug(13010)<<"readWordWrapConfig:END"<<endl; + + m_additionalData[buildIdentifier]->wordWrapDeliminator = wordWrapDeliminator; +} + +void KateHighlighting::readIndentationConfig() +{ + m_indentation = ""; + + KateHlManager::self()->syntax->setIdentifier(buildIdentifier); + KateSyntaxContextData *data = KateHlManager::self()->syntax->getConfig("general","indentation"); + + if (data) + { + m_indentation = (KateHlManager::self()->syntax->groupItemData(data,QString("mode"))); + + KateHlManager::self()->syntax->freeGroupInfo(data); + } +} + +void KateHighlighting::readFoldingConfig() +{ + // Tell the syntax document class which file we want to parse + kdDebug(13010)<<"readfoldignConfig:BEGIN"<<endl; + + KateHlManager::self()->syntax->setIdentifier(buildIdentifier); + KateSyntaxContextData *data = KateHlManager::self()->syntax->getConfig("general","folding"); + + if (data) + { + kdDebug(13010)<<"Found global keyword config"<<endl; + + if ( IS_TRUE( KateHlManager::self()->syntax->groupItemData(data,QString("indentationsensitive")) ) ) + m_foldingIndentationSensitive=true; + else + m_foldingIndentationSensitive=false; + + KateHlManager::self()->syntax->freeGroupInfo(data); + } + else + { + //Default values + m_foldingIndentationSensitive = false; + } + + kdDebug(13010)<<"readfoldingConfig:END"<<endl; + + kdDebug(13010)<<"############################ use indent for fold are: "<<m_foldingIndentationSensitive<<endl; +} + +void KateHighlighting::createContextNameList(QStringList *ContextNameList,int ctx0) +{ + kdDebug(13010)<<"creatingContextNameList:BEGIN"<<endl; + + if (ctx0 == 0) + ContextNameList->clear(); + + KateHlManager::self()->syntax->setIdentifier(buildIdentifier); + + KateSyntaxContextData *data=KateHlManager::self()->syntax->getGroupInfo("highlighting","context"); + + int id=ctx0; + + if (data) + { + while (KateHlManager::self()->syntax->nextGroup(data)) + { + QString tmpAttr=KateHlManager::self()->syntax->groupData(data,QString("name")).simplifyWhiteSpace(); + if (tmpAttr.isEmpty()) + { + tmpAttr=QString("!KATE_INTERNAL_DUMMY! %1").arg(id); + errorsAndWarnings +=i18n("<B>%1</B>: Deprecated syntax. Context %2 has no symbolic name<BR>").arg(buildIdentifier).arg(id-ctx0); + } + else tmpAttr=buildPrefix+tmpAttr; + (*ContextNameList)<<tmpAttr; + id++; + } + KateHlManager::self()->syntax->freeGroupInfo(data); + } + kdDebug(13010)<<"creatingContextNameList:END"<<endl; + +} + +int KateHighlighting::getIdFromString(QStringList *ContextNameList, QString tmpLineEndContext, /*NO CONST*/ QString &unres) +{ + unres=""; + int context; + if ((tmpLineEndContext=="#stay") || (tmpLineEndContext.simplifyWhiteSpace().isEmpty())) + context=-1; + + else if (tmpLineEndContext.startsWith("#pop")) + { + context=-1; + for(;tmpLineEndContext.startsWith("#pop");context--) + { + tmpLineEndContext.remove(0,4); + kdDebug(13010)<<"#pop found"<<endl; + } + } + + else if ( tmpLineEndContext.contains("##")) + { + int o = tmpLineEndContext.find("##"); + // FIXME at least with 'foo##bar'-style contexts the rules are picked up + // but the default attribute is not + QString tmp=tmpLineEndContext.mid(o+2); + if (!embeddedHls.contains(tmp)) embeddedHls.insert(tmp,KateEmbeddedHlInfo()); + unres=tmp+':'+tmpLineEndContext.left(o); + context=0; + } + + else + { + context=ContextNameList->findIndex(buildPrefix+tmpLineEndContext); + if (context==-1) + { + context=tmpLineEndContext.toInt(); + errorsAndWarnings+=i18n( + "<B>%1</B>:Deprecated syntax. Context %2 not addressed by a symbolic name" + ).arg(buildIdentifier).arg(tmpLineEndContext); + } +//#warning restructure this the name list storage. +// context=context+buildContext0Offset; + } + return context; +} + +/** + * The most important initialization function for each highlighting. It's called + * each time a document gets a highlighting style assigned. parses the xml file + * and creates a corresponding internal structure + */ +void KateHighlighting::makeContextList() +{ + if (noHl) // if this a highlighting for "normal texts" only, tere is no need for a context list creation + return; + + embeddedHls.clear(); + unresolvedContextReferences.clear(); + RegionList.clear(); + ContextNameList.clear(); + + // prepare list creation. To reuse as much code as possible handle this + // highlighting the same way as embedded onces + embeddedHls.insert(iName,KateEmbeddedHlInfo()); + + bool something_changed; + // the context "0" id is 0 for this hl, all embedded context "0"s have offsets + startctx=base_startctx=0; + // inform everybody that we are building the highlighting contexts and itemlists + building=true; + + do + { + kdDebug(13010)<<"**************** Outer loop in make ContextList"<<endl; + kdDebug(13010)<<"**************** Hl List count:"<<embeddedHls.count()<<endl; + something_changed=false; //assume all "embedded" hls have already been loaded + for (KateEmbeddedHlInfos::const_iterator it=embeddedHls.begin(); it!=embeddedHls.end();++it) + { + if (!it.data().loaded) // we found one, we still have to load + { + kdDebug(13010)<<"**************** Inner loop in make ContextList"<<endl; + QString identifierToUse; + kdDebug(13010)<<"Trying to open highlighting definition file: "<< it.key()<<endl; + if (iName==it.key()) // the own identifier is known + identifierToUse=identifier; + else // all others have to be looked up + identifierToUse=KateHlManager::self()->identifierForName(it.key()); + + kdDebug(13010)<<"Location is:"<< identifierToUse<<endl; + + buildPrefix=it.key()+':'; // attribute names get prefixed by the names + // of the highlighting definitions they belong to + + if (identifierToUse.isEmpty() ) + kdDebug(13010)<<"OHOH, unknown highlighting description referenced"<<endl; + + kdDebug(13010)<<"setting ("<<it.key()<<") to loaded"<<endl; + + //mark hl as loaded + it=embeddedHls.insert(it.key(),KateEmbeddedHlInfo(true,startctx)); + //set class member for context 0 offset, so we don't need to pass it around + buildContext0Offset=startctx; + //parse one hl definition file + startctx=addToContextList(identifierToUse,startctx); + + if (noHl) return; // an error occurred + + base_startctx = startctx; + something_changed=true; // something has been loaded + } + } + } while (something_changed); // as long as there has been another file parsed + // repeat everything, there could be newly added embedded hls. + + + // at this point all needed highlighing (sub)definitions are loaded. It's time + // to resolve cross file references (if there are any) + kdDebug(13010)<<"Unresolved contexts, which need attention: "<<unresolvedContextReferences.count()<<endl; + + //optimize this a littlebit + for (KateHlUnresolvedCtxRefs::iterator unresIt=unresolvedContextReferences.begin(); + unresIt!=unresolvedContextReferences.end();++unresIt) + { + QString incCtx = unresIt.data(); + kdDebug(13010)<<"Context "<<incCtx<<" is unresolved"<<endl; + // only resolve '##Name' contexts here; handleKateHlIncludeRules() can figure + // out 'Name##Name'-style inclusions, but we screw it up + if (incCtx.endsWith(":")) { + kdDebug(13010)<<"Looking up context0 for ruleset "<<incCtx<<endl; + incCtx = incCtx.left(incCtx.length()-1); + //try to find the context0 id for a given unresolvedReference + KateEmbeddedHlInfos::const_iterator hlIt=embeddedHls.find(incCtx); + if (hlIt!=embeddedHls.end()) + *(unresIt.key())=hlIt.data().context0; + } + } + + // eventually handle KateHlIncludeRules items, if they exist. + // This has to be done after the cross file references, because it is allowed + // to include the context0 from a different definition, than the one the rule + // belongs to + handleKateHlIncludeRules(); + + embeddedHls.clear(); //save some memory. + unresolvedContextReferences.clear(); //save some memory + RegionList.clear(); // I think you get the idea ;) + ContextNameList.clear(); + + + // if there have been errors show them + if (!errorsAndWarnings.isEmpty()) + KMessageBox::detailedSorry(0L,i18n( + "There were warning(s) and/or error(s) while parsing the syntax " + "highlighting configuration."), + errorsAndWarnings, i18n("Kate Syntax Highlighting Parser")); + + // we have finished + building=false; +} + +void KateHighlighting::handleKateHlIncludeRules() +{ + // if there are noe include rules to take care of, just return + kdDebug(13010)<<"KateHlIncludeRules, which need attention: " <<includeRules.count()<<endl; + if (includeRules.isEmpty()) return; + + buildPrefix=""; + QString dummy; + + // By now the context0 references are resolved, now more or less only inner + // file references are resolved. If we decide that arbitrary inclusion is + // needed, this doesn't need to be changed, only the addToContextList + // method. + + //resolove context names + for (KateHlIncludeRules::iterator it=includeRules.begin();it!=includeRules.end();) + { + if ((*it)->incCtx==-1) // context unresolved ? + { + + if ((*it)->incCtxN.isEmpty()) + { + // no context name given, and no valid context id set, so this item is + // going to be removed + KateHlIncludeRules::iterator it1=it; + ++it1; + delete (*it); + includeRules.remove(it); + it=it1; + } + else + { + // resolve name to id + (*it)->incCtx=getIdFromString(&ContextNameList,(*it)->incCtxN,dummy); + kdDebug(13010)<<"Resolved "<<(*it)->incCtxN<< " to "<<(*it)->incCtx<<" for include rule"<<endl; + // It would be good to look here somehow, if the result is valid + } + } + else ++it; //nothing to do, already resolved (by the cross defintion reference resolver) + } + + // now that all KateHlIncludeRule items should be valid and completely resolved, + // do the real inclusion of the rules. + // recursiveness is needed, because context 0 could include context 1, which + // itself includes context 2 and so on. + // In that case we have to handle context 2 first, then 1, 0 + //TODO: catch circular references: eg 0->1->2->3->1 + while (!includeRules.isEmpty()) + handleKateHlIncludeRulesRecursive(includeRules.begin(),&includeRules); +} + +void KateHighlighting::handleKateHlIncludeRulesRecursive(KateHlIncludeRules::iterator it, KateHlIncludeRules *list) +{ + if (it==list->end()) return; //invalid iterator, shouldn't happen, but better have a rule prepared ;) + + KateHlIncludeRules::iterator it1=it; + int ctx=(*it1)->ctx; + + // find the last entry for the given context in the KateHlIncludeRules list + // this is need if one context includes more than one. This saves us from + // updating all insert positions: + // eg: context 0: + // pos 3 - include context 2 + // pos 5 - include context 3 + // During the building of the includeRules list the items are inserted in + // ascending order, now we need it descending to make our life easier. + while ((it!=list->end()) && ((*it)->ctx==ctx)) + { + it1=it; + ++it; + } + + // iterate over each include rule for the context the function has been called for. + while ((it1!=list->end()) && ((*it1)->ctx==ctx)) + { + int ctx1=(*it1)->incCtx; + + //let's see, if the the included context includes other contexts + for (KateHlIncludeRules::iterator it2=list->begin();it2!=list->end();++it2) + { + if ((*it2)->ctx==ctx1) + { + //yes it does, so first handle that include rules, since we want to + // include those subincludes too + handleKateHlIncludeRulesRecursive(it2,list); + break; + } + } + + // if the context we want to include had sub includes, they are already inserted there. + KateHlContext *dest=m_contexts[ctx]; + KateHlContext *src=m_contexts[ctx1]; +// kdDebug(3010)<<"linking included rules from "<<ctx<<" to "<<ctx1<<endl; + + // If so desired, change the dest attribute to the one of the src. + // Required to make commenting work, if text matched by the included context + // is a different highlight than the host context. + if ( (*it1)->includeAttrib ) + dest->attr = src->attr; + + // insert the included context's rules starting at position p + int p=(*it1)->pos; + + // remember some stuff + int oldLen = dest->items.size(); + uint itemsToInsert = src->items.size(); + + // resize target + dest->items.resize (oldLen + itemsToInsert); + + // move old elements + for (int i=oldLen-1; i >= p; --i) + dest->items[i+itemsToInsert] = dest->items[i]; + + // insert new stuff + for (uint i=0; i < itemsToInsert; ++i ) + dest->items[p+i] = src->items[i]; + + it=it1; //backup the iterator + --it1; //move to the next entry, which has to be take care of + delete (*it); //free the already handled data structure + list->remove(it); // remove it from the list + } +} + +/** + * Add one highlight to the contextlist. + * + * @return the number of contexts after this is added. + */ +int KateHighlighting::addToContextList(const QString &ident, int ctx0) +{ + kdDebug(13010)<<"=== Adding hl with ident '"<<ident<<"'"<<endl; + + buildIdentifier=ident; + KateSyntaxContextData *data, *datasub; + KateHlItem *c; + + QString dummy; + + // Let the syntax document class know, which file we'd like to parse + if (!KateHlManager::self()->syntax->setIdentifier(ident)) + { + noHl=true; + KMessageBox::information(0L,i18n( + "Since there has been an error parsing the highlighting description, " + "this highlighting will be disabled")); + return 0; + } + + // only read for the own stuff + if (identifier == ident) + { + readIndentationConfig (); + } + + RegionList<<"!KateInternal_TopLevel!"; + + m_hlIndex[internalIDList.count()] = ident; + m_additionalData.insert( ident, new HighlightPropertyBag ); + + // fill out the propertybag + readCommentConfig(); + readGlobalKeywordConfig(); + readWordWrapConfig(); + + readFoldingConfig (); + + QString ctxName; + + // This list is needed for the translation of the attribute parameter, + // if the itemData name is given instead of the index + addToKateHlItemDataList(); + KateHlItemDataList iDl = internalIDList; + + createContextNameList(&ContextNameList,ctx0); + + + kdDebug(13010)<<"Parsing Context structure"<<endl; + //start the real work + data=KateHlManager::self()->syntax->getGroupInfo("highlighting","context"); + uint i=buildContext0Offset; + if (data) + { + while (KateHlManager::self()->syntax->nextGroup(data)) + { + kdDebug(13010)<<"Found a context in file, building structure now"<<endl; + //BEGIN - Translation of the attribute parameter + QString tmpAttr=KateHlManager::self()->syntax->groupData(data,QString("attribute")).simplifyWhiteSpace(); + int attr; + if (QString("%1").arg(tmpAttr.toInt())==tmpAttr) + attr=tmpAttr.toInt(); + else + attr=lookupAttrName(tmpAttr,iDl); + //END - Translation of the attribute parameter + + ctxName=buildPrefix+KateHlManager::self()->syntax->groupData(data,QString("lineEndContext")).simplifyWhiteSpace(); + + QString tmpLineEndContext=KateHlManager::self()->syntax->groupData(data,QString("lineEndContext")).simplifyWhiteSpace(); + int context; + + context=getIdFromString(&ContextNameList, tmpLineEndContext,dummy); + + QString tmpNIBF = KateHlManager::self()->syntax->groupData(data, QString("noIndentationBasedFolding") ); + bool noIndentationBasedFolding=IS_TRUE(tmpNIBF); + + //BEGIN get fallthrough props + bool ft = false; + int ftc = 0; // fallthrough context + if ( i > 0 ) // fallthrough is not smart in context 0 + { + QString tmpFt = KateHlManager::self()->syntax->groupData(data, QString("fallthrough") ); + if ( IS_TRUE(tmpFt) ) + ft = true; + if ( ft ) + { + QString tmpFtc = KateHlManager::self()->syntax->groupData( data, QString("fallthroughContext") ); + + ftc=getIdFromString(&ContextNameList, tmpFtc,dummy); + if (ftc == -1) ftc =0; + + kdDebug(13010)<<"Setting fall through context (context "<<i<<"): "<<ftc<<endl; + } + } + //END falltrhough props + + bool dynamic = false; + QString tmpDynamic = KateHlManager::self()->syntax->groupData(data, QString("dynamic") ); + if ( tmpDynamic.lower() == "true" || tmpDynamic.toInt() == 1 ) + dynamic = true; + + KateHlContext *ctxNew = new KateHlContext ( + ident, + attr, + context, + (KateHlManager::self()->syntax->groupData(data,QString("lineBeginContext"))).isEmpty()?-1: + (KateHlManager::self()->syntax->groupData(data,QString("lineBeginContext"))).toInt(), + ft, ftc, dynamic,noIndentationBasedFolding); + + m_contexts.push_back (ctxNew); + + kdDebug(13010) << "INDEX: " << i << " LENGTH " << m_contexts.size()-1 << endl; + + //Let's create all items for the context + while (KateHlManager::self()->syntax->nextItem(data)) + { +// kdDebug(13010)<< "In make Contextlist: Item:"<<endl; + + // KateHlIncludeRules : add a pointer to each item in that context + // TODO add a attrib includeAttrib + QString tag = KateHlManager::self()->syntax->groupItemData(data,QString("")); + if ( tag == "IncludeRules" ) //if the new item is an Include rule, we have to take special care + { + QString incCtx = KateHlManager::self()->syntax->groupItemData( data, QString("context")); + QString incAttrib = KateHlManager::self()->syntax->groupItemData( data, QString("includeAttrib")); + bool includeAttrib = IS_TRUE( incAttrib ); + // only context refernces of type Name, ##Name, and Subname##Name are allowed + if (incCtx.startsWith("##") || (!incCtx.startsWith("#"))) + { + int incCtxi = incCtx.find("##"); + //#stay, #pop is not interesting here + if (incCtxi >= 0) + { + QString incSet = incCtx.mid(incCtxi + 2); + QString incCtxN = incSet + ":" + incCtx.left(incCtxi); + + //a cross highlighting reference + kdDebug(13010)<<"Cross highlight reference <IncludeRules>, context "<<incCtxN<<endl; + KateHlIncludeRule *ir=new KateHlIncludeRule(i,m_contexts[i]->items.count(),incCtxN,includeAttrib); + + //use the same way to determine cross hl file references as other items do + if (!embeddedHls.contains(incSet)) + embeddedHls.insert(incSet,KateEmbeddedHlInfo()); + else + kdDebug(13010)<<"Skipping embeddedHls.insert for "<<incCtxN<<endl; + + unresolvedContextReferences.insert(&(ir->incCtx), incCtxN); + + includeRules.append(ir); + } + else + { + // a local reference -> just initialize the include rule structure + incCtx=buildPrefix+incCtx.simplifyWhiteSpace(); + includeRules.append(new KateHlIncludeRule(i,m_contexts[i]->items.count(),incCtx, includeAttrib)); + } + } + + continue; + } + // TODO -- can we remove the block below?? +#if 0 + QString tag = KateHlManager::self()->syntax->groupKateHlItemData(data,QString("")); + if ( tag == "IncludeRules" ) { + // attrib context: the index (jowenn, i think using names here + // would be a cool feat, goes for mentioning the context in + // any item. a map or dict?) + int ctxId = getIdFromString(&ContextNameList, + KateHlManager::self()->syntax->groupKateHlItemData( data, QString("context")),dummy); // the index is *required* + if ( ctxId > -1) { // we can even reuse rules of 0 if we want to:) + kdDebug(13010)<<"makeContextList["<<i<<"]: including all items of context "<<ctxId<<endl; + if ( ctxId < (int) i ) { // must be defined + for ( c = m_contexts[ctxId]->items.first(); c; c = m_contexts[ctxId]->items.next() ) + m_contexts[i]->items.append(c); + } + else + kdDebug(13010)<<"Context "<<ctxId<<"not defined. You can not include the rules of an undefined context"<<endl; + } + continue; // while nextItem + } +#endif + c=createKateHlItem(data,iDl,&RegionList,&ContextNameList); + if (c) + { + m_contexts[i]->items.append(c); + + // Not supported completely atm and only one level. Subitems.(all have + // to be matched to at once) + datasub=KateHlManager::self()->syntax->getSubItems(data); + bool tmpbool; + if (tmpbool=KateHlManager::self()->syntax->nextItem(datasub)) + { + for (;tmpbool;tmpbool=KateHlManager::self()->syntax->nextItem(datasub)) + { + c->subItems.resize (c->subItems.size()+1); + c->subItems[c->subItems.size()-1] = createKateHlItem(datasub,iDl,&RegionList,&ContextNameList); + } } + KateHlManager::self()->syntax->freeGroupInfo(datasub); + // end of sublevel + } + } + i++; + } + } + + KateHlManager::self()->syntax->freeGroupInfo(data); + + if (RegionList.count()!=1) + folding=true; + + folding = folding || m_foldingIndentationSensitive; + + //BEGIN Resolve multiline region if possible + if (!m_additionalData[ ident ]->multiLineRegion.isEmpty()) { + long commentregionid=RegionList.findIndex( m_additionalData[ ident ]->multiLineRegion ); + if (-1==commentregionid) { + errorsAndWarnings+=i18n( + "<B>%1</B>: Specified multiline comment region (%2) could not be resolved<BR>" + ).arg(buildIdentifier).arg( m_additionalData[ ident ]->multiLineRegion ); + m_additionalData[ ident ]->multiLineRegion = QString(); + kdDebug(13010)<<"ERROR comment region attribute could not be resolved"<<endl; + + } else { + m_additionalData[ ident ]->multiLineRegion=QString::number(commentregionid+1); + kdDebug(13010)<<"comment region resolved to:"<<m_additionalData[ ident ]->multiLineRegion<<endl; + } + } + //END Resolve multiline region if possible + return i; +} + +void KateHighlighting::clearAttributeArrays () +{ + for ( QIntDictIterator< QMemArray<KateAttribute> > it( m_attributeArrays ); it.current(); ++it ) + { + // k, schema correct, let create the data + KateAttributeList defaultStyleList; + defaultStyleList.setAutoDelete(true); + KateHlManager::self()->getDefaults(it.currentKey(), defaultStyleList); + + KateHlItemDataList itemDataList; + getKateHlItemDataList(it.currentKey(), itemDataList); + + uint nAttribs = itemDataList.count(); + QMemArray<KateAttribute> *array = it.current(); + array->resize (nAttribs); + + for (uint z = 0; z < nAttribs; z++) + { + KateHlItemData *itemData = itemDataList.at(z); + KateAttribute n = *defaultStyleList.at(itemData->defStyleNum); + + if (itemData && itemData->isSomethingSet()) + n += *itemData; + + array->at(z) = n; + } + } +} + +QMemArray<KateAttribute> *KateHighlighting::attributes (uint schema) +{ + QMemArray<KateAttribute> *array; + + // found it, allready floating around + if ((array = m_attributeArrays[schema])) + return array; + + // ohh, not found, check if valid schema number + if (!KateFactory::self()->schemaManager()->validSchema(schema)) + { + // uhh, not valid :/, stick with normal default schema, it's always there ! + return attributes (0); + } + + // k, schema correct, let create the data + KateAttributeList defaultStyleList; + defaultStyleList.setAutoDelete(true); + KateHlManager::self()->getDefaults(schema, defaultStyleList); + + KateHlItemDataList itemDataList; + getKateHlItemDataList(schema, itemDataList); + + uint nAttribs = itemDataList.count(); + array = new QMemArray<KateAttribute> (nAttribs); + + for (uint z = 0; z < nAttribs; z++) + { + KateHlItemData *itemData = itemDataList.at(z); + KateAttribute n = *defaultStyleList.at(itemData->defStyleNum); + + if (itemData && itemData->isSomethingSet()) + n += *itemData; + + array->at(z) = n; + } + + m_attributeArrays.insert(schema, array); + + return array; +} + +void KateHighlighting::getKateHlItemDataListCopy (uint schema, KateHlItemDataList &outlist) +{ + KateHlItemDataList itemDataList; + getKateHlItemDataList(schema, itemDataList); + + outlist.clear (); + outlist.setAutoDelete (true); + for (uint z=0; z < itemDataList.count(); z++) + outlist.append (new KateHlItemData (*itemDataList.at(z))); +} + +//END + +//BEGIN KateHlManager +KateHlManager::KateHlManager() + : QObject() + , m_config ("katesyntaxhighlightingrc", false, false) + , commonSuffixes (QStringList::split(";", ".orig;.new;~;.bak;.BAK")) + , syntax (new KateSyntaxDocument()) + , dynamicCtxsCount(0) + , forceNoDCReset(false) +{ + hlList.setAutoDelete(true); + hlDict.setAutoDelete(false); + + KateSyntaxModeList modeList = syntax->modeList(); + for (uint i=0; i < modeList.count(); i++) + { + KateHighlighting *hl = new KateHighlighting(modeList[i]); + + uint insert = 0; + for (; insert <= hlList.count(); insert++) + { + if (insert == hlList.count()) + break; + + if ( QString(hlList.at(insert)->section() + hlList.at(insert)->nameTranslated()).lower() + > QString(hl->section() + hl->nameTranslated()).lower() ) + break; + } + + hlList.insert (insert, hl); + hlDict.insert (hl->name(), hl); + } + + // Normal HL + KateHighlighting *hl = new KateHighlighting(0); + hlList.prepend (hl); + hlDict.insert (hl->name(), hl); + + lastCtxsReset.start(); +} + +KateHlManager::~KateHlManager() +{ + delete syntax; +} + +static KStaticDeleter<KateHlManager> sdHlMan; + +KateHlManager *KateHlManager::self() +{ + if ( !s_self ) + sdHlMan.setObject(s_self, new KateHlManager ()); + + return s_self; +} + +KateHighlighting *KateHlManager::getHl(int n) +{ + if (n < 0 || n >= (int) hlList.count()) + n = 0; + + return hlList.at(n); +} + +int KateHlManager::nameFind(const QString &name) +{ + int z (hlList.count() - 1); + for (; z > 0; z--) + if (hlList.at(z)->name() == name) + return z; + + return z; +} + +int KateHlManager::detectHighlighting (KateDocument *doc) +{ + int hl = wildcardFind( doc->url().filename() ); + if ( hl < 0 ) + hl = mimeFind ( doc ); + + return hl; +} + +int KateHlManager::wildcardFind(const QString &fileName) +{ + int result = -1; + if ((result = realWildcardFind(fileName)) != -1) + return result; + + int length = fileName.length(); + QString backupSuffix = KateDocumentConfig::global()->backupSuffix(); + if (fileName.endsWith(backupSuffix)) { + if ((result = realWildcardFind(fileName.left(length - backupSuffix.length()))) != -1) + return result; + } + + for (QStringList::Iterator it = commonSuffixes.begin(); it != commonSuffixes.end(); ++it) { + if (*it != backupSuffix && fileName.endsWith(*it)) { + if ((result = realWildcardFind(fileName.left(length - (*it).length()))) != -1) + return result; + } + } + + return -1; +} + +int KateHlManager::realWildcardFind(const QString &fileName) +{ + static QRegExp sep("\\s*;\\s*"); + + QPtrList<KateHighlighting> highlights; + + for (KateHighlighting *highlight = hlList.first(); highlight != 0L; highlight = hlList.next()) { + highlight->loadWildcards(); + + for (QStringList::Iterator it = highlight->getPlainExtensions().begin(); it != highlight->getPlainExtensions().end(); ++it) + if (fileName.endsWith((*it))) + highlights.append(highlight); + + for (int i = 0; i < (int)highlight->getRegexpExtensions().count(); i++) { + QRegExp re = highlight->getRegexpExtensions()[i]; + if (re.exactMatch(fileName)) + highlights.append(highlight); + } + } + + if ( !highlights.isEmpty() ) + { + int pri = -1; + int hl = -1; + + for (KateHighlighting *highlight = highlights.first(); highlight != 0L; highlight = highlights.next()) + { + if (highlight->priority() > pri) + { + pri = highlight->priority(); + hl = hlList.findRef (highlight); + } + } + return hl; + } + + return -1; +} + +int KateHlManager::mimeFind( KateDocument *doc ) +{ + static QRegExp sep("\\s*;\\s*"); + + KMimeType::Ptr mt = doc->mimeTypeForContent(); + + QPtrList<KateHighlighting> highlights; + + for (KateHighlighting *highlight = hlList.first(); highlight != 0L; highlight = hlList.next()) + { + QStringList l = QStringList::split( sep, highlight->getMimetypes() ); + + for( QStringList::Iterator it = l.begin(); it != l.end(); ++it ) + { + if ( *it == mt->name() ) // faster than a regexp i guess? + highlights.append (highlight); + } + } + + if ( !highlights.isEmpty() ) + { + int pri = -1; + int hl = -1; + + for (KateHighlighting *highlight = highlights.first(); highlight != 0L; highlight = highlights.next()) + { + if (highlight->priority() > pri) + { + pri = highlight->priority(); + hl = hlList.findRef (highlight); + } + } + + return hl; + } + + return -1; +} + +uint KateHlManager::defaultStyles() +{ + return 14; +} + +QString KateHlManager::defaultStyleName(int n, bool translateNames) +{ + static QStringList names; + static QStringList translatedNames; + + if (names.isEmpty()) + { + names << "Normal"; + names << "Keyword"; + names << "Data Type"; + names << "Decimal/Value"; + names << "Base-N Integer"; + names << "Floating Point"; + names << "Character"; + names << "String"; + names << "Comment"; + names << "Others"; + names << "Alert"; + names << "Function"; + // this next one is for denoting the beginning/end of a user defined folding region + names << "Region Marker"; + // this one is for marking invalid input + names << "Error"; + + translatedNames << i18n("Normal"); + translatedNames << i18n("Keyword"); + translatedNames << i18n("Data Type"); + translatedNames << i18n("Decimal/Value"); + translatedNames << i18n("Base-N Integer"); + translatedNames << i18n("Floating Point"); + translatedNames << i18n("Character"); + translatedNames << i18n("String"); + translatedNames << i18n("Comment"); + translatedNames << i18n("Others"); + translatedNames << i18n("Alert"); + translatedNames << i18n("Function"); + // this next one is for denoting the beginning/end of a user defined folding region + translatedNames << i18n("Region Marker"); + // this one is for marking invalid input + translatedNames << i18n("Error"); + } + + return translateNames ? translatedNames[n] : names[n]; +} + +void KateHlManager::getDefaults(uint schema, KateAttributeList &list) +{ + list.setAutoDelete(true); + + KateAttribute* normal = new KateAttribute(); + normal->setTextColor(Qt::black); + normal->setSelectedTextColor(Qt::white); + list.append(normal); + + KateAttribute* keyword = new KateAttribute(); + keyword->setTextColor(Qt::black); + keyword->setSelectedTextColor(Qt::white); + keyword->setBold(true); + list.append(keyword); + + KateAttribute* dataType = new KateAttribute(); + dataType->setTextColor(Qt::darkRed); + dataType->setSelectedTextColor(Qt::white); + list.append(dataType); + + KateAttribute* decimal = new KateAttribute(); + decimal->setTextColor(Qt::blue); + decimal->setSelectedTextColor(Qt::cyan); + list.append(decimal); + + KateAttribute* basen = new KateAttribute(); + basen->setTextColor(Qt::darkCyan); + basen->setSelectedTextColor(Qt::cyan); + list.append(basen); + + KateAttribute* floatAttribute = new KateAttribute(); + floatAttribute->setTextColor(Qt::darkMagenta); + floatAttribute->setSelectedTextColor(Qt::cyan); + list.append(floatAttribute); + + KateAttribute* charAttribute = new KateAttribute(); + charAttribute->setTextColor(Qt::magenta); + charAttribute->setSelectedTextColor(Qt::magenta); + list.append(charAttribute); + + KateAttribute* string = new KateAttribute(); + string->setTextColor(QColor::QColor("#D00")); + string->setSelectedTextColor(Qt::red); + list.append(string); + + KateAttribute* comment = new KateAttribute(); + comment->setTextColor(Qt::darkGray); + comment->setSelectedTextColor(Qt::gray); + comment->setItalic(true); + list.append(comment); + + KateAttribute* others = new KateAttribute(); + others->setTextColor(Qt::darkGreen); + others->setSelectedTextColor(Qt::green); + list.append(others); + + KateAttribute* alert = new KateAttribute(); + alert->setTextColor(Qt::black); + alert->setSelectedTextColor( QColor::QColor("#FCC") ); + alert->setBold(true); + alert->setBGColor( QColor::QColor("#FCC") ); + list.append(alert); + + KateAttribute* functionAttribute = new KateAttribute(); + functionAttribute->setTextColor(Qt::darkBlue); + functionAttribute->setSelectedTextColor(Qt::white); + list.append(functionAttribute); + + KateAttribute* regionmarker = new KateAttribute(); + regionmarker->setTextColor(Qt::white); + regionmarker->setBGColor(Qt::gray); + regionmarker->setSelectedTextColor(Qt::gray); + list.append(regionmarker); + + KateAttribute* error = new KateAttribute(); + error->setTextColor(Qt::red); + error->setUnderline(true); + error->setSelectedTextColor(Qt::red); + list.append(error); + + KConfig *config = KateHlManager::self()->self()->getKConfig(); + config->setGroup("Default Item Styles - Schema " + KateFactory::self()->schemaManager()->name(schema)); + + for (uint z = 0; z < defaultStyles(); z++) + { + KateAttribute *i = list.at(z); + QStringList s = config->readListEntry(defaultStyleName(z)); + if (!s.isEmpty()) + { + while( s.count()<8) + s << ""; + + QString tmp; + QRgb col; + + tmp=s[0]; if (!tmp.isEmpty()) { + col=tmp.toUInt(0,16); i->setTextColor(col); } + + tmp=s[1]; if (!tmp.isEmpty()) { + col=tmp.toUInt(0,16); i->setSelectedTextColor(col); } + + tmp=s[2]; if (!tmp.isEmpty()) i->setBold(tmp!="0"); + + tmp=s[3]; if (!tmp.isEmpty()) i->setItalic(tmp!="0"); + + tmp=s[4]; if (!tmp.isEmpty()) i->setStrikeOut(tmp!="0"); + + tmp=s[5]; if (!tmp.isEmpty()) i->setUnderline(tmp!="0"); + + tmp=s[6]; if (!tmp.isEmpty()) { + if ( tmp != "-" ) + { + col=tmp.toUInt(0,16); + i->setBGColor(col); + } + else + i->clearAttribute(KateAttribute::BGColor); + } + tmp=s[7]; if (!tmp.isEmpty()) { + if ( tmp != "-" ) + { + col=tmp.toUInt(0,16); + i->setSelectedBGColor(col); + } + else + i->clearAttribute(KateAttribute::SelectedBGColor); + } + } + } +} + +void KateHlManager::setDefaults(uint schema, KateAttributeList &list) +{ + KConfig *config = KateHlManager::self()->self()->getKConfig(); + config->setGroup("Default Item Styles - Schema " + KateFactory::self()->schemaManager()->name(schema)); + + for (uint z = 0; z < defaultStyles(); z++) + { + QStringList settings; + KateAttribute *i = list.at(z); + + settings<<(i->itemSet(KateAttribute::TextColor)?QString::number(i->textColor().rgb(),16):""); + settings<<(i->itemSet(KateAttribute::SelectedTextColor)?QString::number(i->selectedTextColor().rgb(),16):""); + settings<<(i->itemSet(KateAttribute::Weight)?(i->bold()?"1":"0"):""); + settings<<(i->itemSet(KateAttribute::Italic)?(i->italic()?"1":"0"):""); + settings<<(i->itemSet(KateAttribute::StrikeOut)?(i->strikeOut()?"1":"0"):""); + settings<<(i->itemSet(KateAttribute::Underline)?(i->underline()?"1":"0"):""); + settings<<(i->itemSet(KateAttribute::BGColor)?QString::number(i->bgColor().rgb(),16):"-"); + settings<<(i->itemSet(KateAttribute::SelectedBGColor)?QString::number(i->selectedBGColor().rgb(),16):"-"); + settings<<"---"; + + config->writeEntry(defaultStyleName(z),settings); + } + + emit changed(); +} + +int KateHlManager::highlights() +{ + return (int) hlList.count(); +} + +QString KateHlManager::hlName(int n) +{ + return hlList.at(n)->name(); +} + +QString KateHlManager::hlNameTranslated(int n) +{ + return hlList.at(n)->nameTranslated(); +} + +QString KateHlManager::hlSection(int n) +{ + return hlList.at(n)->section(); +} + +bool KateHlManager::hlHidden(int n) +{ + return hlList.at(n)->hidden(); +} + +QString KateHlManager::identifierForName(const QString& name) +{ + KateHighlighting *hl = 0; + + if ((hl = hlDict[name])) + return hl->getIdentifier (); + + return QString(); +} + +bool KateHlManager::resetDynamicCtxs() +{ + if (forceNoDCReset) + return false; + + if (lastCtxsReset.elapsed() < KATE_DYNAMIC_CONTEXTS_RESET_DELAY) + return false; + + KateHighlighting *hl; + for (hl = hlList.first(); hl; hl = hlList.next()) + hl->dropDynamicContexts(); + + dynamicCtxsCount = 0; + lastCtxsReset.start(); + + return true; +} +//END + +//BEGIN KateHighlightAction +void KateViewHighlightAction::init() +{ + m_doc = 0; + subMenus.setAutoDelete( true ); + + connect(popupMenu(),SIGNAL(aboutToShow()),this,SLOT(slotAboutToShow())); +} + +void KateViewHighlightAction::updateMenu (Kate::Document *doc) +{ + m_doc = doc; +} + +void KateViewHighlightAction::slotAboutToShow() +{ + Kate::Document *doc=m_doc; + int count = KateHlManager::self()->highlights(); + + for (int z=0; z<count; z++) + { + QString hlName = KateHlManager::self()->hlNameTranslated (z); + QString hlSection = KateHlManager::self()->hlSection (z); + + if (!KateHlManager::self()->hlHidden(z)) + { + if ( !hlSection.isEmpty() && (names.contains(hlName) < 1) ) + { + if (subMenusName.contains(hlSection) < 1) + { + subMenusName << hlSection; + QPopupMenu *menu = new QPopupMenu (); + subMenus.append(menu); + popupMenu()->insertItem ( '&' + hlSection, menu); + } + + int m = subMenusName.findIndex (hlSection); + names << hlName; + subMenus.at(m)->insertItem ( '&' + hlName, this, SLOT(setHl(int)), 0, z); + } + else if (names.contains(hlName) < 1) + { + names << hlName; + popupMenu()->insertItem ( '&' + hlName, this, SLOT(setHl(int)), 0, z); + } + } + } + + if (!doc) return; + + for (uint i=0;i<subMenus.count();i++) + { + for (uint i2=0;i2<subMenus.at(i)->count();i2++) + { + subMenus.at(i)->setItemChecked(subMenus.at(i)->idAt(i2),false); + } + } + popupMenu()->setItemChecked (0, false); + + int i = subMenusName.findIndex (KateHlManager::self()->hlSection(doc->hlMode())); + if (i >= 0 && subMenus.at(i)) + subMenus.at(i)->setItemChecked (doc->hlMode(), true); + else + popupMenu()->setItemChecked (0, true); +} + +void KateViewHighlightAction::setHl (int mode) +{ + Kate::Document *doc=m_doc; + + if (doc) + doc->setHlMode((uint)mode); +} +//END KateViewHighlightAction + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katehighlight.h b/kate/part/katehighlight.h new file mode 100644 index 000000000..70b7016a3 --- /dev/null +++ b/kate/part/katehighlight.h @@ -0,0 +1,438 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001,2002 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __KATE_HIGHLIGHT_H__ +#define __KATE_HIGHLIGHT_H__ + +#include "katetextline.h" +#include "kateattribute.h" + +#include "../interfaces/document.h" + +#include <kconfig.h> + +#include <qptrlist.h> +#include <qvaluelist.h> +#include <qvaluevector.h> +#include <qregexp.h> +#include <qdict.h> +#include <qintdict.h> +#include <qmap.h> +#include <qobject.h> +#include <qstringlist.h> +#include <qguardedptr.h> +#include <qdatetime.h> +#include <qpopupmenu.h> + +class KateHlContext; +class KateHlItem; +class KateHlItemData; +class KateHlData; +class KateEmbeddedHlInfo; +class KateHlIncludeRule; +class KateSyntaxDocument; +class KateTextLine; +class KateSyntaxModeListItem; +class KateSyntaxContextData; + +// some typedefs +typedef QPtrList<KateAttribute> KateAttributeList; +typedef QValueList<KateHlIncludeRule*> KateHlIncludeRules; +typedef QPtrList<KateHlItemData> KateHlItemDataList; +typedef QPtrList<KateHlData> KateHlDataList; +typedef QMap<QString,KateEmbeddedHlInfo> KateEmbeddedHlInfos; +typedef QMap<int*,QString> KateHlUnresolvedCtxRefs; +typedef QValueList<int> IntList; + +//Item Properties: name, Item Style, Item Font +class KateHlItemData : public KateAttribute +{ + public: + KateHlItemData(const QString name, int defStyleNum); + + enum ItemStyles { + dsNormal, + dsKeyword, + dsDataType, + dsDecVal, + dsBaseN, + dsFloat, + dsChar, + dsString, + dsComment, + dsOthers, + dsAlert, + dsFunction, + dsRegionMarker, + dsError }; + + public: + const QString name; + int defStyleNum; +}; + +class KateHlData +{ + public: + KateHlData(const QString &wildcards, const QString &mimetypes,const QString &identifier, int priority); + + public: + QString wildcards; + QString mimetypes; + QString identifier; + int priority; +}; + +class KateHighlighting +{ + public: + KateHighlighting(const KateSyntaxModeListItem *def); + ~KateHighlighting(); + + public: + void doHighlight ( KateTextLine *prevLine, + KateTextLine *textLine, + QMemArray<uint> *foldingList, + bool *ctxChanged ); + + void loadWildcards(); + QValueList<QRegExp>& getRegexpExtensions(); + QStringList& getPlainExtensions(); + + QString getMimetypes(); + + // this pointer needs to be deleted !!!!!!!!!! + KateHlData *getData(); + void setData(KateHlData *); + + void setKateHlItemDataList(uint schema, KateHlItemDataList &); + + // both methodes return hard copies of the internal lists + // the lists are cleared first + autodelete is set ! + // keep track that you delete them, or mem will be lost + void getKateHlItemDataListCopy (uint schema, KateHlItemDataList &); + + const QString &name() const {return iName;} + const QString &nameTranslated() const {return iNameTranslated;} + const QString §ion() const {return iSection;} + bool hidden() const {return iHidden;} + const QString &version() const {return iVersion;} + const QString &author () const { return iAuthor; } + const QString &license () const { return iLicense; } + int priority(); + const QString &getIdentifier() const {return identifier;} + void use(); + void release(); + + /** + * @return true if the character @p c is not a deliminator character + * for the corresponding highlight. + */ + bool isInWord( QChar c, int attrib=0 ) const; + + /** + * @return true if the character @p c is a wordwrap deliminator as specified + * in the general keyword section of the xml file. + */ + bool canBreakAt( QChar c, int attrib=0 ) const; + + /** + * @return true if @p beginAttr and @p endAttr are members of the same + * highlight, and there are comment markers of either type in that. + */ + bool canComment( int startAttr, int endAttr ) const; + + /** + * @return 0 if highlighting which attr is a member of does not + * define a comment region, otherwise the region is returned + */ + signed char commentRegion(int attr) const; + + /** + * @return the mulitiline comment start marker for the highlight + * corresponding to @p attrib. + */ + QString getCommentStart( int attrib=0 ) const; + + /** + * @return the muiltiline comment end marker for the highlight corresponding + * to @p attrib. + */ + QString getCommentEnd( int attrib=0 ) const; + + /** + * @return the single comment marker for the highlight corresponding + * to @p attrib. + */ + QString getCommentSingleLineStart( int attrib=0 ) const; + + + /** + * This enum is used for storing the information where a single line comment marker should be inserted + */ + enum CSLPos { CSLPosColumn0=0,CSLPosAfterWhitespace=1}; + + /** + * @return the single comment marker position for the highlight corresponding + * to @p attrib. + */ + CSLPos getCommentSingleLinePosition( int attrib=0 ) const; + + /** + * @return the attribute for @p context. + */ + int attribute( int context ) const; + + /** + * map attribute to its highlighting file. + * the returned string is used as key for m_additionalData. + */ + QString hlKeyForAttrib( int attrib ) const; + + + void clearAttributeArrays (); + + QMemArray<KateAttribute> *attributes (uint schema); + + inline bool noHighlighting () const { return noHl; }; + + // be carefull: all documents hl should be invalidated after calling this method! + void dropDynamicContexts(); + + QString indentation () { return m_indentation; } + + private: + // make this private, nobody should play with the internal data pointers + void getKateHlItemDataList(uint schema, KateHlItemDataList &); + + void init(); + void done(); + void makeContextList (); + int makeDynamicContext(KateHlContext *model, const QStringList *args); + void handleKateHlIncludeRules (); + void handleKateHlIncludeRulesRecursive(KateHlIncludeRules::iterator it, KateHlIncludeRules *list); + int addToContextList(const QString &ident, int ctx0); + void addToKateHlItemDataList(); + void createKateHlItemData (KateHlItemDataList &list); + void readGlobalKeywordConfig(); + void readWordWrapConfig(); + void readCommentConfig(); + void readIndentationConfig (); + void readFoldingConfig (); + + // manipulates the ctxs array directly ;) + void generateContextStack(int *ctxNum, int ctx, QMemArray<short> *ctxs, int *posPrevLine); + + KateHlItem *createKateHlItem(KateSyntaxContextData *data, KateHlItemDataList &iDl, QStringList *RegionList, QStringList *ContextList); + int lookupAttrName(const QString& name, KateHlItemDataList &iDl); + + void createContextNameList(QStringList *ContextNameList, int ctx0); + int getIdFromString(QStringList *ContextNameList, QString tmpLineEndContext,/*NO CONST*/ QString &unres); + + KateHlItemDataList internalIDList; + + QValueVector<KateHlContext*> m_contexts; + inline KateHlContext *contextNum (uint n) { if (n < m_contexts.size()) return m_contexts[n]; return 0; } + + QMap< QPair<KateHlContext *, QString>, short> dynamicCtxs; + + // make them pointers perhaps + KateEmbeddedHlInfos embeddedHls; + KateHlUnresolvedCtxRefs unresolvedContextReferences; + QStringList RegionList; + QStringList ContextNameList; + + bool noHl; + bool folding; + bool casesensitive; + QString weakDeliminator; + QString deliminator; + + QString iName; + QString iNameTranslated; + QString iSection; + bool iHidden; + QString iWildcards; + QString iMimetypes; + QString identifier; + QString iVersion; + QString iAuthor; + QString iLicense; + QString m_indentation; + int m_priority; + int refCount; + int startctx, base_startctx; + + QString errorsAndWarnings; + QString buildIdentifier; + QString buildPrefix; + bool building; + uint itemData0; + uint buildContext0Offset; + KateHlIncludeRules includeRules; + bool m_foldingIndentationSensitive; + + QIntDict< QMemArray<KateAttribute> > m_attributeArrays; + + + /** + * This class holds the additional properties for one highlight + * definition, such as comment strings, deliminators etc. + * + * When a highlight is added, a instance of this class is appended to + * m_additionalData, and the current position in the attrib and context + * arrays are stored in the indexes for look up. You can then use + * hlKeyForAttrib or hlKeyForContext to find the relevant instance of this + * class from m_additionalData. + * + * If you need to add a property to a highlight, add it here. + */ + class HighlightPropertyBag { + public: + QString singleLineCommentMarker; + QString multiLineCommentStart; + QString multiLineCommentEnd; + QString multiLineRegion; + CSLPos singleLineCommentPosition; + QString deliminator; + QString wordWrapDeliminator; + }; + + /** + * Highlight properties for each included highlight definition. + * The key is the identifier + */ + QDict<HighlightPropertyBag> m_additionalData; + + /** + * Fast lookup of hl properties, based on attribute index + * The key is the starting index in the attribute array for each file. + * @see hlKeyForAttrib + */ + QMap<int, QString> m_hlIndex; + + + QString extensionSource; + QValueList<QRegExp> regexpExtensions; + QStringList plainExtensions; + + public: + inline bool foldingIndentationSensitive () { return m_foldingIndentationSensitive; } + inline bool allowsFolding(){return folding;} +}; + +class KateHlManager : public QObject +{ + Q_OBJECT + + private: + KateHlManager(); + + public: + ~KateHlManager(); + + static KateHlManager *self(); + + inline KConfig *getKConfig() { return &m_config; }; + + KateHighlighting *getHl(int n); + int nameFind(const QString &name); + + int detectHighlighting (class KateDocument *doc); + + int findHl(KateHighlighting *h) {return hlList.find(h);} + QString identifierForName(const QString&); + + // methodes to get the default style count + names + static uint defaultStyles(); + static QString defaultStyleName(int n, bool translateNames = false); + + void getDefaults(uint schema, KateAttributeList &); + void setDefaults(uint schema, KateAttributeList &); + + int highlights(); + QString hlName(int n); + QString hlNameTranslated (int n); + QString hlSection(int n); + bool hlHidden(int n); + + void incDynamicCtxs() { ++dynamicCtxsCount; }; + uint countDynamicCtxs() { return dynamicCtxsCount; }; + void setForceNoDCReset(bool b) { forceNoDCReset = b; }; + + // be carefull: all documents hl should be invalidated after having successfully called this method! + bool resetDynamicCtxs(); + + signals: + void changed(); + + private: + int wildcardFind(const QString &fileName); + int mimeFind(KateDocument *); + int realWildcardFind(const QString &fileName); + + private: + friend class KateHighlighting; + + QPtrList<KateHighlighting> hlList; + QDict<KateHighlighting> hlDict; + + static KateHlManager *s_self; + + KConfig m_config; + QStringList commonSuffixes; + + KateSyntaxDocument *syntax; + + uint dynamicCtxsCount; + QTime lastCtxsReset; + bool forceNoDCReset; +}; + +class KateViewHighlightAction: public Kate::ActionMenu +{ + Q_OBJECT + + public: + KateViewHighlightAction(const QString& text, QObject* parent = 0, const char* name = 0) + : Kate::ActionMenu(text, parent, name) { init(); }; + + ~KateViewHighlightAction(){;}; + + void updateMenu (Kate::Document *doc); + + private: + void init(); + + QGuardedPtr<Kate::Document> m_doc; + QStringList subMenusName; + QStringList names; + QPtrList<QPopupMenu> subMenus; + + public slots: + void slotAboutToShow(); + + private slots: + void setHl (int mode); +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateindentscriptabstracts.cpp b/kate/part/kateindentscriptabstracts.cpp new file mode 100644 index 000000000..f30dd9ebd --- /dev/null +++ b/kate/part/kateindentscriptabstracts.cpp @@ -0,0 +1,49 @@ +/* This file is part of the KDE libraries + Copyright (C) 2005 Joseph Wenninger <jowenn@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kateindentscriptabstracts.h" + +#include <kdebug.h> +#include <qstring.h> + +//BEGIN KateIndentScriptImplAbstractImpl + +KateIndentScriptImplAbstract::KateIndentScriptImplAbstract(const QString& internalName, + const QString &filePath, const QString &niceName, + const QString ©right, double version):m_refcount(0),m_filePath(filePath),m_niceName(niceName), + m_copyright(copyright),m_version(version) +{ +} + +KateIndentScriptImplAbstract::~KateIndentScriptImplAbstract() +{ +} + +void KateIndentScriptImplAbstract::incRef() +{ + kdDebug(13050)<<"KateIndentScriptImplAbstract::incRef()"<<endl; + m_refcount++; +} + +void KateIndentScriptImplAbstract::decRef() +{ + kdDebug(13050)<<"KateIndentScriptImplAbstract::decRef()"<<endl; + m_refcount--; +} + +//END diff --git a/kate/part/kateindentscriptabstracts.h b/kate/part/kateindentscriptabstracts.h new file mode 100644 index 000000000..a47a8431a --- /dev/null +++ b/kate/part/kateindentscriptabstracts.h @@ -0,0 +1,99 @@ +/* This file is part of the KDE libraries + Copyright (C) 2005 Joseph Wenninger <jowenn@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef _KATEINDENTSCRIPTABSTRACTS_H_ +#define _KATEINDENTSCRIPTABSTRACTS_H_ + +#include <qstring.h> +#include <kdebug.h> + +namespace Kate { + class View; +} + +class KateDocCursor; + + +class KateIndentScriptImplAbstract { + public: + friend class KateIndentScript; + KateIndentScriptImplAbstract(const QString& internalName, + const QString &filePath, const QString &niceName, + const QString ©right, double version); + virtual ~KateIndentScriptImplAbstract(); + + virtual bool processChar( class Kate::View *view, QChar c, QString &errorMsg )=0; + virtual bool processLine( class Kate::View *view, const KateDocCursor &line, QString &errorMsg )=0; + virtual bool processNewline( class Kate::View *view, const KateDocCursor &begin, bool needcontinue, QString &errorMsg )=0; + protected: + virtual void decRef(); + long refCount() {return m_refcount;} + QString filePath() const {return m_filePath;} + private: + void incRef(); + long m_refcount; + QString m_internalName; + QString m_filePath; + QString m_niceName; + QString m_copyright; + double m_version; +}; + + +class KateIndentScript { + public: + KateIndentScript(KateIndentScriptImplAbstract *scr):m_scr(scr) { if (scr) scr->incRef(); } + ~KateIndentScript() {if (m_scr) m_scr->decRef();} + KateIndentScript():m_scr(0) {} + KateIndentScript(const KateIndentScript &p):m_scr(p.m_scr){if (m_scr) m_scr->incRef();} + KateIndentScript &operator=(const KateIndentScript &p) { + if (m_scr==p.m_scr) return *this; + if (m_scr) m_scr->decRef(); + m_scr=p.m_scr; + if (m_scr) m_scr->incRef(); + return *this; + } + /*operator KateIndentJScript*() const { return m_scr; }*/ + bool processChar( class Kate::View *view, QChar c, QString &errorMsg ) { + kdDebug(13050)<<"KateIndentScript::processChar: m_scr:"<<m_scr<<endl; + if (m_scr) return m_scr->processChar(view,c,errorMsg); else return true; + } + bool processLine( class Kate::View *view, const KateDocCursor& line, QString &errorMsg ) { + kdDebug(13050)<<"KateIndentScript::processLine: m_scr:"<<m_scr<<endl; + if (m_scr) return m_scr->processLine(view,line,errorMsg); else return true; + } + bool processNewline( class Kate::View *view, const KateDocCursor& begin, bool needcontinue, QString &errorMsg ) { + kdDebug(13050)<<"KateIndentScript::processNewLine: m_scr:"<<m_scr<<endl; + if (m_scr) return m_scr->processNewline(view,begin,needcontinue,errorMsg); else return true; + } + + bool isNull () const {return (m_scr==0);} + private: + KateIndentScriptImplAbstract *m_scr; +}; + +class KateIndentScriptManagerAbstract +{ + + public: + KateIndentScriptManagerAbstract () {}; + virtual ~KateIndentScriptManagerAbstract () {}; + virtual KateIndentScript script(const QString &scriptname)=0; +}; + +#endif diff --git a/kate/part/katejscript.cpp b/kate/part/katejscript.cpp new file mode 100644 index 000000000..024f36500 --- /dev/null +++ b/kate/part/katejscript.cpp @@ -0,0 +1,1169 @@ +/* This file is part of the KDE libraries + Copyright (C) 2005 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2005 Joseph Wenninger <jowenn@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "katejscript.h" + +#include "katedocument.h" +#include "kateview.h" +#include "katefactory.h" +#include "kateconfig.h" +#include "kateautoindent.h" +#include "katehighlight.h" +#include "katetextline.h" + +#include "kateindentscriptabstracts.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <kdebug.h> +#include <kstandarddirs.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kconfig.h> + +#include <kjs/function_object.h> +#include <kjs/interpreter.h> +#include <kjs/lookup.h> + +#include <qfile.h> +#include <qfileinfo.h> +#include <qpopupmenu.h> +#include <qregexp.h> +#include <qtextstream.h> + + +namespace KJS { + +// taken from khtml +// therefor thx to: +// Copyright (C) 1999-2003 Harri Porten (porten@kde.org) +// Copyright (C) 2001-2003 David Faure (faure@kde.org) +// Copyright (C) 2003 Apple Computer, Inc. + +UString::UString(const QString &d) +{ + unsigned int len = d.length(); + UChar *dat = new UChar[len]; + memcpy(dat, d.unicode(), len * sizeof(UChar)); + rep = UString::Rep::create(dat, len); +} + +QString UString::qstring() const +{ + return QString((QChar*) data(), size()); +} + +QConstString UString::qconststring() const +{ + return QConstString((QChar*) data(), size()); +} + +//BEGIN global methods +class KateJSGlobalFunctions : public ObjectImp +{ + public: + KateJSGlobalFunctions(int i, int length); + virtual bool implementsCall() const { return true; } + virtual Value call(ExecState *exec, Object &thisObj, const List &args); + + enum { + Debug + }; + + private: + int id; +}; +KateJSGlobalFunctions::KateJSGlobalFunctions(int i, int length) : ObjectImp(), id(i) +{ + putDirect(lengthPropertyName,length,DontDelete|ReadOnly|DontEnum); +} +Value KateJSGlobalFunctions::call(ExecState *exec, Object &/*thisObj*/, const List &args) +{ + switch (id) { + case Debug: + qDebug("Kate (KJS Scripting): %s", args[0].toString(exec).ascii()); + return Undefined(); + default: + break; + } + + return Undefined(); +} +//END global methods + +} // namespace KJS + +//BEGIN JS API STUFF + +class KateJSGlobal : public KJS::ObjectImp { +public: + virtual KJS::UString className() const { return "global"; } +}; + +class KateJSDocument : public KJS::ObjectImp +{ + public: + KateJSDocument (KJS::ExecState *exec, KateDocument *_doc); + + KJS::Value get( KJS::ExecState *exec, const KJS::Identifier &propertyName) const; + + KJS::Value getValueProperty(KJS::ExecState *exec, int token) const; + + void put(KJS::ExecState *exec, const KJS::Identifier &propertyName, const KJS::Value& value, int attr = KJS::None); + + void putValueProperty(KJS::ExecState *exec, int token, const KJS::Value& value, int attr); + + const KJS::ClassInfo* classInfo() const { return &info; } + + enum { FullText, + Text, + TextLine, + Lines, + Length, + LineLength, + SetText, + Clear, + InsertText, + RemoveText, + InsertLine, + RemoveLine, + EditBegin, + EditEnd, + IndentWidth, + IndentMode, + SpaceIndent, + MixedIndent, + HighlightMode, + IsInWord, + CanBreakAt, + CanComment, + CommentMarker, + CommentStart, + CommentEnd, + Attribute + }; + + public: + KateDocument *doc; + + static const KJS::ClassInfo info; +}; + +class KateJSView : public KJS::ObjectImp +{ + public: + KateJSView (KJS::ExecState *exec, KateView *_view); + + KJS::Value get( KJS::ExecState *exec, const KJS::Identifier &propertyName) const; + + KJS::Value getValueProperty(KJS::ExecState *exec, int token) const; + + void put(KJS::ExecState *exec, const KJS::Identifier &propertyName, const KJS::Value& value, int attr = KJS::None); + + void putValueProperty(KJS::ExecState *exec, int token, const KJS::Value& value, int attr); + + const KJS::ClassInfo* classInfo() const { return &info; } + + enum { CursorLine, + CursorColumn, + CursorColumnReal, + SetCursorPosition, + SetCursorPositionReal, + Selection, + HasSelection, + SetSelection, + RemoveSelectedText, + SelectAll, + ClearSelection, + SelStartLine, + SelStartCol, + SelEndLine, + SelEndCol + }; + + public: + KateView *view; + + static const KJS::ClassInfo info; +}; + +class KateJSIndenter : public KJS::ObjectImp +{ + public: + KateJSIndenter (KJS::ExecState *exec); + /* + KJS::Value get( KJS::ExecState *exec, const KJS::Identifier &propertyName) const; + + KJS::Value getValueProperty(KJS::ExecState *exec, int token) const; + + void put(KJS::ExecState *exec, const KJS::Identifier &propertyName, const KJS::Value& value, int attr = KJS::None); + + void putValueProperty(KJS::ExecState *exec, int token, const KJS::Value& value, int attr); + */ + const KJS::ClassInfo* classInfo() const { return &info; } + + enum { OnChar, + OnLine, + OnNewline, + Dummy + }; + + public: + + static const KJS::ClassInfo info; +}; + +#include "katejscript.lut.h" + +//END + +KateJScript::KateJScript () + : m_global (new KJS::Object (new KateJSGlobal ())) + , m_interpreter (new KJS::Interpreter (*m_global)) + , m_document (new KJS::Object(wrapDocument(m_interpreter->globalExec(), 0))) + , m_view (new KJS::Object (wrapView(m_interpreter->globalExec(), 0))) +{ + // put some stuff into env., this should stay for all executions, as we keep external + // references to the inserted KJS::Objects, this should avoid any garbage collection + m_interpreter->globalObject().put(m_interpreter->globalExec(), "document", *m_document); + m_interpreter->globalObject().put(m_interpreter->globalExec(), "view", *m_view); + m_interpreter->globalObject().put(m_interpreter->globalExec(), "debug", + KJS::Object(new KateJSGlobalFunctions(KateJSGlobalFunctions::Debug,1))); +} + +KateJScript::~KateJScript () +{ + delete m_view; + delete m_document; + delete m_interpreter; + delete m_global; +} + +KJS::ObjectImp *KateJScript::wrapDocument (KJS::ExecState *exec, KateDocument *doc) +{ + return new KateJSDocument(exec, doc); +} + +KJS::ObjectImp *KateJScript::wrapView (KJS::ExecState *exec, KateView *view) +{ + return new KateJSView(exec, view); +} + +bool KateJScript::execute (KateView *view, const QString &script, QString &errorMsg) +{ + // no view, no fun + if (!view) + { + errorMsg = i18n("Could not access view"); + return false; + } + + // init doc & view with new pointers! + static_cast<KateJSDocument *>( m_document->imp() )->doc = view->doc(); + static_cast<KateJSView *>( m_view->imp() )->view = view; + + // run the script for real + KJS::Completion comp (m_interpreter->evaluate(script)); + + if (comp.complType() == KJS::Throw) + { + KJS::ExecState *exec = m_interpreter->globalExec(); + + KJS::Value exVal = comp.value(); + + char *msg = exVal.toString(exec).ascii(); + + int lineno = -1; + + if (exVal.type() == KJS::ObjectType) + { + KJS::Value lineVal = KJS::Object::dynamicCast(exVal).get(exec,"line"); + + if (lineVal.type() == KJS::NumberType) + lineno = int(lineVal.toNumber(exec)); + } + + errorMsg = i18n("Exception, line %1: %2").arg(lineno).arg(msg); + return false; + } + + return true; +} + +//BEGIN KateJSDocument + +// ------------------------------------------------------------------------- +/* Source for KateJSDocumentProtoTable. +@begin KateJSDocumentProtoTable 21 +# +# edit interface stuff + editBegin/End, this is nice start +# + textFull KateJSDocument::FullText DontDelete|Function 0 + textRange KateJSDocument::Text DontDelete|Function 4 + textLine KateJSDocument::TextLine DontDelete|Function 1 + lines KateJSDocument::Lines DontDelete|Function 0 + length KateJSDocument::Length DontDelete|Function 0 + lineLength KateJSDocument::LineLength DontDelete|Function 1 + setText KateJSDocument::SetText DontDelete|Function 1 + clear KateJSDocument::Clear DontDelete|Function 0 + insertText KateJSDocument::InsertText DontDelete|Function 3 + removeText KateJSDocument::RemoveText DontDelete|Function 4 + insertLine KateJSDocument::InsertLine DontDelete|Function 2 + removeLine KateJSDocument::RemoveLine DontDelete|Function 1 + editBegin KateJSDocument::EditBegin DontDelete|Function 0 + editEnd KateJSDocument::EditEnd DontDelete|Function 0 +# +# methods from highlight (and around) +# + isInWord KateJSDocument::IsInWord DontDelete|Function 2 + canBreakAt KateJSDocument::CanBreakAt DontDelete|Function 2 + canComment KateJSDocument::CanComment DontDelete|Function 2 + commentMarker KateJSDocument::CommentMarker DontDelete|Function 1 + commentStart KateJSDocument::CommentStart DontDelete|Function 1 + commentEnd KateJSDocument::CommentEnd DontDelete|Function 1 + attribute KateJSDocument::Attribute DontDelete|Function 2 +@end + +@begin KateJSDocumentTable 6 +# +# Configuration properties +# + indentWidth KateJSDocument::IndentWidth DontDelete|ReadOnly + indentMode KateJSDocument::IndentMode DontDelete|ReadOnly + spaceIndent KateJSDocument::SpaceIndent DontDelete|ReadOnly + mixedIndent KateJSDocument::MixedIndent DontDelete|ReadOnly + highlightMode KateJSDocument::HighlightMode DontDelete|ReadOnly +@end +*/ + +DEFINE_PROTOTYPE("KateJSDocument",KateJSDocumentProto) +IMPLEMENT_PROTOFUNC(KateJSDocumentProtoFunc) +IMPLEMENT_PROTOTYPE(KateJSDocumentProto,KateJSDocumentProtoFunc) + +const KJS::ClassInfo KateJSDocument::info = { "KateJSDocument", 0, 0, 0 }; + +KJS::Value KJS::KateJSDocumentProtoFunc::call(KJS::ExecState *exec, KJS::Object &thisObj, const KJS::List &args) +{ + KJS_CHECK_THIS( KateJSDocument, thisObj ); + + KateDocument *doc = static_cast<KateJSDocument *>( thisObj.imp() )->doc; + + if (!doc) + return KJS::Undefined(); + + switch (id) + { + case KateJSDocument::FullText: + return KJS::String (doc->text()); + + case KateJSDocument::Text: + return KJS::String (doc->text(args[0].toUInt32(exec), args[1].toUInt32(exec), args[2].toUInt32(exec), args[3].toUInt32(exec))); + + case KateJSDocument::TextLine: + return KJS::String (doc->textLine (args[0].toUInt32(exec))); + + case KateJSDocument::Lines: + return KJS::Number (doc->numLines()); + + case KateJSDocument::Length: + return KJS::Number (doc->length()); + + case KateJSDocument::LineLength: + return KJS::Number (doc->lineLength(args[0].toUInt32(exec))); + + case KateJSDocument::SetText: + return KJS::Boolean (doc->setText(args[0].toString(exec).qstring())); + + case KateJSDocument::Clear: + return KJS::Boolean (doc->clear()); + + case KateJSDocument::InsertText: + return KJS::Boolean (doc->insertText (args[0].toUInt32(exec), args[1].toUInt32(exec), args[2].toString(exec).qstring())); + + case KateJSDocument::RemoveText: + return KJS::Boolean (doc->removeText(args[0].toUInt32(exec), args[1].toUInt32(exec), args[2].toUInt32(exec), args[3].toUInt32(exec))); + + case KateJSDocument::InsertLine: + return KJS::Boolean (doc->insertLine (args[0].toUInt32(exec), args[1].toString(exec).qstring())); + + case KateJSDocument::RemoveLine: + return KJS::Boolean (doc->removeLine (args[0].toUInt32(exec))); + + case KateJSDocument::EditBegin: + doc->editBegin(); + return KJS::Null (); + + case KateJSDocument::EditEnd: + doc->editEnd (); + return KJS::Null (); + + case KateJSDocument::IsInWord: + return KJS::Boolean( doc->highlight()->isInWord( args[0].toString(exec).qstring().at(0), args[1].toUInt32(exec) ) ); + + case KateJSDocument::CanBreakAt: + return KJS::Boolean( doc->highlight()->canBreakAt( args[0].toString(exec).qstring().at(0), args[1].toUInt32(exec) ) ); + + case KateJSDocument::CanComment: + return KJS::Boolean( doc->highlight()->canComment( args[0].toUInt32(exec), args[1].toUInt32(exec) ) ); + + case KateJSDocument::CommentMarker: + return KJS::String( doc->highlight()->getCommentSingleLineStart( args[0].toUInt32(exec) ) ); + + case KateJSDocument::CommentStart: + return KJS::String( doc->highlight()->getCommentStart( args[0].toUInt32(exec) ) ); + + case KateJSDocument::CommentEnd: + return KJS::String( doc->highlight()->getCommentEnd( args[0].toUInt32(exec) ) ); + + case KateJSDocument::Attribute: + return KJS::Number( doc->kateTextLine(args[0].toUInt32(exec))->attribute(args[1].toUInt32(exec)) ); + } + + return KJS::Undefined(); +} + +KJS::Value KateJSDocument::get( KJS::ExecState *exec, const KJS::Identifier &propertyName) const +{ + return KJS::lookupGetValue<KateJSDocument,KJS::ObjectImp>(exec, propertyName, &KateJSDocumentTable, this ); +} + +KJS::Value KateJSDocument::getValueProperty(KJS::ExecState *exec, int token) const +{ + if (!doc) + return KJS::Undefined (); + + switch (token) { + case KateJSDocument::IndentWidth: + return KJS::Number( doc->config()->indentationWidth() ); + + case KateJSDocument::IndentMode: + return KJS::String( KateAutoIndent::modeName( doc->config()->indentationMode() ) ); + + case KateJSDocument::SpaceIndent: + return KJS::Boolean( doc->config()->configFlags() & KateDocumentConfig::cfSpaceIndent ); + + case KateJSDocument::MixedIndent: + return KJS::Boolean( doc->config()->configFlags() & KateDocumentConfig::cfMixedIndent ); + + case KateJSDocument::HighlightMode: + return KJS::String( doc->hlModeName( doc->hlMode() ) ); + } + + return KJS::Undefined (); +} + +void KateJSDocument::put(KJS::ExecState *exec, const KJS::Identifier &propertyName, const KJS::Value& value, int attr) +{ + KJS::lookupPut<KateJSDocument,KJS::ObjectImp>(exec, propertyName, value, attr, &KateJSDocumentTable, this ); +} + +void KateJSDocument::putValueProperty(KJS::ExecState *exec, int token, const KJS::Value& value, int attr) +{ + if (!doc) + return; +} + +KateJSDocument::KateJSDocument (KJS::ExecState *exec, KateDocument *_doc) + : KJS::ObjectImp (KateJSDocumentProto::self(exec)) + , doc (_doc) +{ +} + +//END + +//BEGIN KateJSView + +// ------------------------------------------------------------------------- +/* Source for KateJSViewProtoTable. +@begin KateJSViewProtoTable 14 + cursorLine KateJSView::CursorLine DontDelete|Function 0 + cursorColumn KateJSView::CursorColumn DontDelete|Function 0 + cursorColumnReal KateJSView::CursorColumnReal DontDelete|Function 0 + setCursorPosition KateJSView::SetCursorPosition DontDelete|Function 2 + setCursorPositionReal KateJSView::SetCursorPositionReal DontDelete|Function 2 + selection KateJSView::Selection DontDelete|Function 0 + hasSelection KateJSView::HasSelection DontDelete|Function 0 + setSelection KateJSView::SetSelection DontDelete|Function 4 + removeSelectedText KateJSView::RemoveSelectedText DontDelete|Function 0 + selectAll KateJSView::SelectAll DontDelete|Function 0 + clearSelection KateJSView::ClearSelection DontDelete|Function 0 +@end +*/ + +/* Source for KateJSViewTable. +@begin KateJSViewTable 5 + selectionStartLine KateJSView::SelStartLine DontDelete|ReadOnly + selectionStartColumn KateJSView::SelStartCol DontDelete|ReadOnly + selectionEndLine KateJSView::SelEndLine DontDelete|ReadOnly + selectionEndColumn KateJSView::SelEndCol DontDelete|ReadOnly +@end +*/ + +DEFINE_PROTOTYPE("KateJSView",KateJSViewProto) +IMPLEMENT_PROTOFUNC(KateJSViewProtoFunc) +IMPLEMENT_PROTOTYPE(KateJSViewProto,KateJSViewProtoFunc) + +const KJS::ClassInfo KateJSView::info = { "KateJSView", 0, &KateJSViewTable, 0 }; + +KJS::Value KJS::KateJSViewProtoFunc::call(KJS::ExecState *exec, KJS::Object &thisObj, const KJS::List &args) +{ + KJS_CHECK_THIS( KateJSView, thisObj ); + + KateView *view = static_cast<KateJSView *>( thisObj.imp() )->view; + + if (!view) + return KJS::Undefined(); + + switch (id) + { + case KateJSView::CursorLine: + return KJS::Number (view->cursorLine()); + + case KateJSView::CursorColumn: + return KJS::Number (view->cursorColumn()); + + case KateJSView::CursorColumnReal: + return KJS::Number (view->cursorColumnReal()); + + case KateJSView::SetCursorPosition: + return KJS::Boolean( view->setCursorPosition( args[0].toUInt32(exec), args[1].toUInt32(exec) ) ); + + case KateJSView::SetCursorPositionReal: + return KJS::Boolean( view->setCursorPositionReal( args[0].toUInt32(exec), args[1].toUInt32(exec) ) ); + + // SelectionInterface goes in the view, in anticipation of the future + case KateJSView::Selection: + return KJS::String( view->selection() ); + + case KateJSView::HasSelection: + return KJS::Boolean( view->hasSelection() ); + + case KateJSView::SetSelection: + return KJS::Boolean( view->setSelection(args[0].toUInt32(exec), + args[1].toUInt32(exec), + args[2].toUInt32(exec), + args[3].toUInt32(exec)) ); + + case KateJSView::RemoveSelectedText: + return KJS::Boolean( view->removeSelectedText() ); + + case KateJSView::SelectAll: + return KJS::Boolean( view->selectAll() ); + + case KateJSView::ClearSelection: + return KJS::Boolean( view->clearSelection() ); + } + + return KJS::Undefined(); +} + +KateJSView::KateJSView (KJS::ExecState *exec, KateView *_view) + : KJS::ObjectImp (KateJSViewProto::self(exec)) + , view (_view) +{ +} + +KJS::Value KateJSView::get( KJS::ExecState *exec, const KJS::Identifier &propertyName) const +{ + return KJS::lookupGetValue<KateJSView,KJS::ObjectImp>(exec, propertyName, &KateJSViewTable, this ); +} + +KJS::Value KateJSView::getValueProperty(KJS::ExecState *exec, int token) const +{ + if (!view) + return KJS::Undefined (); + + switch (token) { + case KateJSView::SelStartLine: + return KJS::Number( view->selStartLine() ); + + case KateJSView::SelStartCol: + return KJS::Number( view->selStartCol() ); + + case KateJSView::SelEndLine: + return KJS::Number( view->selEndLine() ); + + case KateJSView::SelEndCol: + return KJS::Number( view->selEndCol() ); + } + + return KJS::Undefined (); +} + +void KateJSView::put(KJS::ExecState *exec, const KJS::Identifier &propertyName, const KJS::Value& value, int attr) +{ + KJS::lookupPut<KateJSView,KJS::ObjectImp>(exec, propertyName, value, attr, &KateJSViewTable, this ); +} + +void KateJSView::putValueProperty(KJS::ExecState *exec, int token, const KJS::Value& value, int attr) +{ + if (!view) + return; + + +} + +//END + +//BEGIN KateJScriptManager + +KateJScriptManager::KateJScriptManager () +{ + m_scripts.setAutoDelete (true); + collectScripts (); +} + +KateJScriptManager::~KateJScriptManager () +{ +} + +void KateJScriptManager::collectScripts (bool force) +{ +// If there's something in myModeList the Mode List was already built so, don't do it again + if (!m_scripts.isEmpty()) + return; + + // We'll store the scripts list in this config + KConfig config("katepartjscriptrc", false, false); + + // figure out if the kate install is too new + config.setGroup ("General"); + if (config.readNumEntry ("Version") > config.readNumEntry ("CachedVersion")) + { + config.writeEntry ("CachedVersion", config.readNumEntry ("Version")); + force = true; + } + + // Let's get a list of all the .js files + QStringList list = KGlobal::dirs()->findAllResources("data","katepart/scripts/*.js",false,true); + + // Let's iterate through the list and build the Mode List + for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it ) + { + // Each file has a group called: + QString Group="Cache "+ *it; + + // Let's go to this group + config.setGroup(Group); + + // stat the file + struct stat sbuf; + memset (&sbuf, 0, sizeof(sbuf)); + stat(QFile::encodeName(*it), &sbuf); + + // If the group exist and we're not forced to read the .js file, let's build myModeList for katepartjscriptrc + if (!force && config.hasGroup(Group) && (sbuf.st_mtime == config.readNumEntry("lastModified"))) + { + } + else + { + kdDebug (13050) << "add script: " << *it << endl; + + QString desktopFile = (*it).left((*it).length()-2).append ("desktop"); + + kdDebug (13050) << "add script (desktop file): " << desktopFile << endl; + + QFileInfo dfi (desktopFile); + + if (dfi.exists()) + { + KConfig df (desktopFile, true, false); + df.setDesktopGroup (); + + // get cmdname, fallback to baseName, if it is empty, therefor not use the kconfig fallback + QString cmdname = df.readEntry ("X-Kate-Command"); + if (cmdname.isEmpty()) + { + QFileInfo fi (*it); + cmdname = fi.baseName(); + } + + if (m_scripts[cmdname]) + continue; + + KateJScriptManager::Script *s = new KateJScriptManager::Script (); + + s->name = cmdname; + s->filename = *it; + s->desktopFileExists = true; + + m_scripts.insert (s->name, s); + } + else // no desktop file around, fall back to scriptfilename == commandname + { + kdDebug (13050) << "add script: fallback, no desktop file around!" << endl; + + QFileInfo fi (*it); + + if (m_scripts[fi.baseName()]) + continue; + + KateJScriptManager::Script *s = new KateJScriptManager::Script (); + + s->name = fi.baseName(); + s->filename = *it; + s->desktopFileExists = false; + + m_scripts.insert (s->name, s); + } + } + } + + // Syncronize with the file katepartjscriptrc + config.sync(); +} + +bool KateJScriptManager::exec( Kate::View *view, const QString &_cmd, QString &errorMsg ) +{ + // cast it hardcore, we know that it is really a kateview :) + KateView *v = (KateView*) view; + + if ( !v ) + { + errorMsg = i18n("Could not access view"); + return false; + } + + //create a list of args + QStringList args( QStringList::split( QRegExp("\\s+"), _cmd ) ); + QString cmd ( args.first() ); + args.remove( args.first() ); + + kdDebug(13050) << "try to exec: " << cmd << endl; + + if (!m_scripts[cmd]) + { + errorMsg = i18n("Command not found"); + return false; + } + + QFile file (m_scripts[cmd]->filename); + + if ( !file.open( IO_ReadOnly ) ) + { + errorMsg = i18n("JavaScript file not found"); + return false; + } + + QTextStream stream( &file ); + stream.setEncoding (QTextStream::UnicodeUTF8); + + QString source = stream.read (); + + file.close(); + + return KateFactory::self()->jscript()->execute(v, source, errorMsg); +} + +bool KateJScriptManager::help( Kate::View *, const QString &cmd, QString &msg ) +{ + if (!m_scripts[cmd] || !m_scripts[cmd]->desktopFileExists) + return false; + + KConfig df (m_scripts[cmd]->desktopFilename(), true, false); + df.setDesktopGroup (); + + msg = df.readEntry ("X-Kate-Help"); + + if (msg.isEmpty()) + return false; + + return true; +} + +QStringList KateJScriptManager::cmds() +{ + QStringList l; + + QDictIterator<KateJScriptManager::Script> it( m_scripts ); + for( ; it.current(); ++it ) + l << it.current()->name; + + return l; +} + +//END + + + + +//BEGIN KateJSIndenter + +// ------------------------------------------------------------------------- +/* Source for KateJSIndenterProtoTable. +@begin KateJSIndenterProtoTable 1 + Dummy KateJSIndenter::Dummy DontDelete +@end +*/ + +/* Source for KateJSIndenterTable. +@begin KateJSIndenterTable 3 + onchar KateJSIndenter::OnChar DontDelete + onnewline KateJSIndenter::OnNewline DontDelete + online KateJSIndenter::OnLine DontDelete + +@end +*/ + +KateJSIndenter::KateJSIndenter (KJS::ExecState *exec) + : KJS::ObjectImp (KateJSViewProto::self(exec)) +{ +} + +DEFINE_PROTOTYPE("KateJSIndenter",KateJSIndenterProto) +IMPLEMENT_PROTOFUNC(KateJSIndenterProtoFunc) +IMPLEMENT_PROTOTYPE(KateJSIndenterProto,KateJSIndenterProtoFunc) + +const KJS::ClassInfo KateJSIndenter::info = { "KateJSIndenter", 0, &KateJSIndenterTable, 0 }; + +KJS::Value KJS::KateJSIndenterProtoFunc::call(KJS::ExecState *exec, KJS::Object &thisObj, const KJS::List &args) +{ + KJS_CHECK_THIS( KateJSIndenter, thisObj ); + + return KJS::Undefined(); +} + +//END + +//BEGIN KateIndentJScriptImpl +KateIndentJScriptImpl::KateIndentJScriptImpl(const QString& internalName, + const QString &filePath, const QString &niceName, + const QString ©right, double version): + KateIndentScriptImplAbstract(internalName,filePath,niceName,copyright,version),m_interpreter(0),m_indenter(0) +{ +} + + +KateIndentJScriptImpl::~KateIndentJScriptImpl() +{ + deleteInterpreter(); +} + +void KateIndentJScriptImpl::decRef() +{ + KateIndentScriptImplAbstract::decRef(); + if (refCount()==0) + { + deleteInterpreter(); + } +} + +void KateIndentJScriptImpl::deleteInterpreter() +{ + m_docWrapper=0; + m_viewWrapper=0; + delete m_indenter; + m_indenter=0; + delete m_interpreter; + m_interpreter=0; +} + +bool KateIndentJScriptImpl::setupInterpreter(QString &errorMsg) +{ + if (!m_interpreter) + { + kdDebug(13050)<<"Setting up interpreter"<<endl; + m_interpreter=new KJS::Interpreter(KJS::Object(new KateJSGlobal())); + m_docWrapper=new KateJSDocument(m_interpreter->globalExec(),0); + m_viewWrapper=new KateJSView(m_interpreter->globalExec(),0); + m_indenter=new KJS::Object(new KateJSIndenter(m_interpreter->globalExec())); + m_interpreter->globalObject().put(m_interpreter->globalExec(),"document",KJS::Object(m_docWrapper),KJS::DontDelete | KJS::ReadOnly); + m_interpreter->globalObject().put(m_interpreter->globalExec(),"view",KJS::Object(m_viewWrapper),KJS::DontDelete | KJS::ReadOnly); + m_interpreter->globalObject().put(m_interpreter->globalExec(),"debug", KJS::Object(new + KateJSGlobalFunctions(KateJSGlobalFunctions::Debug,1))); + m_interpreter->globalObject().put(m_interpreter->globalExec(),"indenter",*m_indenter,KJS::DontDelete | KJS::ReadOnly); + QFile file (filePath()); + + if ( !file.open( IO_ReadOnly ) ) + { + errorMsg = i18n("JavaScript file not found"); + deleteInterpreter(); + return false; + } + + QTextStream stream( &file ); + stream.setEncoding (QTextStream::UnicodeUTF8); + + QString source = stream.read (); + + file.close(); + + KJS::Completion comp (m_interpreter->evaluate(source)); + if (comp.complType() == KJS::Throw) + { + KJS::ExecState *exec = m_interpreter->globalExec(); + + KJS::Value exVal = comp.value(); + + char *msg = exVal.toString(exec).ascii(); + + int lineno = -1; + + if (exVal.type() == KJS::ObjectType) + { + KJS::Value lineVal = KJS::Object::dynamicCast(exVal).get(exec,"line"); + + if (lineVal.type() == KJS::NumberType) + lineno = int(lineVal.toNumber(exec)); + } + + errorMsg = i18n("Exception, line %1: %2").arg(lineno).arg(msg); + deleteInterpreter(); + return false; + } else { + return true; + } + } else return true; +} + + +inline static bool KateIndentJScriptCall(Kate::View *view, QString &errorMsg, KateJSDocument *docWrapper, KateJSView *viewWrapper, + KJS::Interpreter *interpreter, KJS::Object lookupobj,const KJS::Identifier& func,KJS::List params) +{ + // no view, no fun + if (!view) + { + errorMsg = i18n("Could not access view"); + return false; + } + + KateView *v=(KateView*)view; + + KJS::Object o=lookupobj.get(interpreter->globalExec(),func).toObject(interpreter->globalExec()); + if (interpreter->globalExec()->hadException()) + { + errorMsg=interpreter->globalExec()->exception().toString(interpreter->globalExec()).qstring(); + kdDebug(13050)<<"Exception(1):"<<errorMsg<<endl; + interpreter->globalExec()->clearException(); + return false; + } + + // init doc & view with new pointers! + docWrapper->doc = v->doc(); + viewWrapper->view = v; + + /*kdDebug(13050)<<"Call Object:"<<o.toString(interpreter->globalExec()).ascii()<<endl;*/ + o.call(interpreter->globalExec(),interpreter->globalObject(),params); + if (interpreter->globalExec()->hadException()) + { + errorMsg=interpreter->globalExec()->exception().toString(interpreter->globalExec()).ascii(); + kdDebug(13050)<<"Exception(2):"<<errorMsg<<endl; + interpreter->globalExec()->clearException(); + return false; + } + return true; +} + +bool KateIndentJScriptImpl::processChar(Kate::View *view, QChar c, QString &errorMsg ) +{ + + kdDebug(13050)<<"KateIndentJScriptImpl::processChar"<<endl; + if (!setupInterpreter(errorMsg)) return false; + KJS::List params; + params.append(KJS::String(QString(c))); + return KateIndentJScriptCall(view,errorMsg,m_docWrapper,m_viewWrapper,m_interpreter,*m_indenter,KJS::Identifier("onchar"),params); +} + +bool KateIndentJScriptImpl::processLine(Kate::View *view, const KateDocCursor &line, QString &errorMsg ) +{ + kdDebug(13050)<<"KateIndentJScriptImpl::processLine"<<endl; + if (!setupInterpreter(errorMsg)) return false; + return KateIndentJScriptCall(view,errorMsg,m_docWrapper,m_viewWrapper,m_interpreter,*m_indenter,KJS::Identifier("online"),KJS::List()); +} + +bool KateIndentJScriptImpl::processNewline( class Kate::View *view, const KateDocCursor &begin, bool needcontinue, QString &errorMsg ) +{ + kdDebug(13050)<<"KateIndentJScriptImpl::processNewline"<<endl; + if (!setupInterpreter(errorMsg)) return false; + return KateIndentJScriptCall(view,errorMsg,m_docWrapper,m_viewWrapper,m_interpreter,*m_indenter,KJS::Identifier("onnewline"),KJS::List()); +} +//END + +//BEGIN KateIndentJScriptManager +KateIndentJScriptManager::KateIndentJScriptManager():KateIndentScriptManagerAbstract() +{ + m_scripts.setAutoDelete (true); + collectScripts (); +} + +KateIndentJScriptManager::~KateIndentJScriptManager () +{ +} + +void KateIndentJScriptManager::collectScripts (bool force) +{ +// If there's something in myModeList the Mode List was already built so, don't do it again + if (!m_scripts.isEmpty()) + return; + + + // We'll store the scripts list in this config + KConfig config("katepartindentjscriptrc", false, false); +#if 0 + // figure out if the kate install is too new + config.setGroup ("General"); + if (config.readNumEntry ("Version") > config.readNumEntry ("CachedVersion")) + { + config.writeEntry ("CachedVersion", config.readNumEntry ("Version")); + force = true; + } +#endif + + // Let's get a list of all the .js files + QStringList list = KGlobal::dirs()->findAllResources("data","katepart/scripts/indent/*.js",false,true); + + // Let's iterate through the list and build the Mode List + for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it ) + { + // Each file has a group ed: + QString Group="Cache "+ *it; + + // Let's go to this group + config.setGroup(Group); + + // stat the file + struct stat sbuf; + memset (&sbuf, 0, sizeof(sbuf)); + stat(QFile::encodeName(*it), &sbuf); + + // If the group exist and we're not forced to read the .js file, let's build myModeList for katepartjscriptrc + bool readnew=false; + if (!force && config.hasGroup(Group) && (sbuf.st_mtime == config.readNumEntry("lastModified"))) + { + config.setGroup(Group); + QString filePath=*it; + QString internalName=config.readEntry("internlName","KATE-ERROR"); + if (internalName=="KATE-ERROR") readnew=true; + else + { + QString niceName=config.readEntry("niceName",internalName); + QString copyright=config.readEntry("copyright",i18n("(Unknown)")); + double version=config.readDoubleNumEntry("version",0.0); + KateIndentJScriptImpl *s=new KateIndentJScriptImpl( + internalName,filePath,niceName,copyright,version); + m_scripts.insert (internalName, s); + } + } + else readnew=true; + if (readnew) + { + QFileInfo fi (*it); + + if (m_scripts[fi.baseName()]) + continue; + + QString internalName=fi.baseName(); + QString filePath=*it; + QString niceName=internalName; + QString copyright=i18n("(Unknown)"); + double version=0.0; + parseScriptHeader(filePath,&niceName,©right,&version); + /*save the information for retrieval*/ + config.setGroup(Group); + config.writeEntry("lastModified",sbuf.st_mtime); + config.writeEntry("internalName",internalName); + config.writeEntry("niceName",niceName); + config.writeEntry("copyright",copyright); + config.writeEntry("version",version); + KateIndentJScriptImpl *s=new KateIndentJScriptImpl( + internalName,filePath,niceName,copyright,version); + m_scripts.insert (internalName, s); + } + } + + // Syncronize with the file katepartjscriptrc + config.sync(); +} + +KateIndentScript KateIndentJScriptManager::script(const QString &scriptname) { + KateIndentJScriptImpl *s=m_scripts[scriptname]; + kdDebug(13050)<<scriptname<<"=="<<s<<endl; + return KateIndentScript(s); +} + +void KateIndentJScriptManager::parseScriptHeader(const QString &filePath, + QString *niceName,QString *copyright,double *version) +{ + QFile f(QFile::encodeName(filePath)); + if (!f.open(IO_ReadOnly) ) { + kdDebug(13050)<<"Header could not be parsed, because file could not be opened"<<endl; + return; + } + QTextStream st(&f); + st.setEncoding (QTextStream::UnicodeUTF8); + if (!st.readLine().upper().startsWith("/**KATE")) { + kdDebug(13050)<<"No header found"<<endl; + f.close(); + return; + } + // here the real parsing begins + kdDebug(13050)<<"Parsing indent script header"<<endl; + enum {NOTHING=0,COPYRIGHT=1} currentState=NOTHING; + QString line; + QString tmpblockdata=""; + QRegExp endExpr("[\\s\\t]*\\*\\*\\/[\\s\\t]*$"); + QRegExp keyValue("[\\s\\t]*\\*\\s*(.+):(.*)$"); + QRegExp blockContent("[\\s\\t]*\\*(.*)$"); + while ((line=st.readLine())!=QString::null) { + if (endExpr.exactMatch(line)) { + kdDebug(13050)<<"end of config block"<<endl; + if (currentState==NOTHING) break; + if (currentState==COPYRIGHT) { + *copyright=tmpblockdata; + break; + } + Q_ASSERT(0); + } + if (currentState==NOTHING) + { + if (keyValue.exactMatch(line)) { + QStringList sl=keyValue.capturedTexts(); + kdDebug(13050)<<"key:"<<sl[1]<<endl<<"value:"<<sl[2]<<endl; + kdDebug(13050)<<"key-length:"<<sl[1].length()<<endl<<"value-length:"<<sl[2].length()<<endl; + QString key=sl[1]; + QString value=sl[2]; + if (key=="NAME") (*niceName)=value.stripWhiteSpace(); + else if (key=="VERSION") (*version)=value.stripWhiteSpace().toDouble(0); + else if (key=="COPYRIGHT") + { + tmpblockdata=""; + if (value.stripWhiteSpace().length()>0) tmpblockdata=value; + currentState=COPYRIGHT; + } else kdDebug(13050)<<"ignoring key"<<endl; + } + } else { + if (blockContent.exactMatch(line)) + { + QString bl=blockContent.capturedTexts()[1]; + //kdDebug(13050)<<"block content line:"<<bl<<endl<<bl.length()<<" "<<bl.isEmpty()<<endl; + if (bl.isEmpty()) + { + (*copyright)=tmpblockdata; + kdDebug(13050)<<"Copyright block:"<<endl<<(*copyright)<<endl; + currentState=NOTHING; + } else tmpblockdata=tmpblockdata+"\n"+bl; + } + } + } + f.close(); +} +//END +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katejscript.h b/kate/part/katejscript.h new file mode 100644 index 000000000..ac9e20246 --- /dev/null +++ b/kate/part/katejscript.h @@ -0,0 +1,233 @@ +/* This file is part of the KDE libraries + Copyright (C) 2005 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2005 Joseph Wenninger <jowenn@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __kate_jscript_h__ +#define __kate_jscript_h__ + +#include "../interfaces/document.h" +#include "kateindentscriptabstracts.h" +#include <qdict.h> +#include <kdebug.h> +/** + * Some common stuff + */ +class KateDocument; +class KateView; +class QString; +class KateJSDocument; +class KateJSView; +class KateJSIndenter; +class KateDocCursor; + +/** + * Cool, this is all we need here + */ +namespace KJS { + class Object; + class ObjectImp; + class Interpreter; + class ExecState; +} + +/** + * Whole Kate Part scripting in one classs + * Allow subclassing to allow specialized scripting engine for indenters + */ +class KateJScript +{ + public: + /** + * generate new global interpreter for part scripting + */ + KateJScript (); + + /** + * be destructive + */ + virtual ~KateJScript (); + + /** + * creates a JS wrapper object for given KateDocument + * @param exec execution state, to find out interpreter to use + * @param doc document object to wrap + * @return new js wrapper object + */ + KJS::ObjectImp *wrapDocument (KJS::ExecState *exec, KateDocument *doc); + + /** + * creates a JS wrapper object for given KateView + * @param exec execution state, to find out interpreter to use + * @param view view object to wrap + * @return new js wrapper object + */ + KJS::ObjectImp *wrapView (KJS::ExecState *exec, KateView *view); + + /** + * execute given script + * the script will get the doc and view exposed via document and view object + * in global scope + * @param view view to expose + * @param script source code of script to execute + * @param errorMsg error to return if no success + * @return success or not? + */ + bool execute (KateView *view, const QString &script, QString &errorMsg); + + protected: + /** + * global object of interpreter + */ + KJS::Object *m_global; + + /** + * js interpreter + */ + KJS::Interpreter *m_interpreter; + + /** + * object for document + */ + KJS::Object *m_document; + + /** + * object for view + */ + KJS::Object *m_view; +}; + +class KateJScriptManager : public Kate::Command +{ + private: + /** + * Internal used Script Representation + */ + class Script + { + public: + /** + * get desktop filename + * @return desktop filename + */ + inline QString desktopFilename () { return filename.left(filename.length()-2).append ("desktop"); } + + public: + /** + * command name, as used for command line and more + */ + QString name; + + /** + * filename of the script + */ + QString filename; + + /** + * has it a desktop file? + */ + bool desktopFileExists; + }; + + public: + KateJScriptManager (); + ~KateJScriptManager (); + + private: + /** + * go, search our scripts + * @param force force cache updating? + */ + void collectScripts (bool force = false); + + // + // Here we deal with the Kate::Command stuff + // + public: + /** + * execute command + * @param view view to use for execution + * @param cmd cmd string + * @param errorMsg error to return if no success + * @return success + */ + bool exec( class Kate::View *view, const QString &cmd, QString &errorMsg ); + + /** + * get help for a command + * @param view view to use + * @param cmd cmd name + * @param msg help message + * @return help available or not + */ + bool help( class Kate::View *view, const QString &cmd, QString &msg ); + + /** + * supported commands as prefixes + * @return prefix list + */ + QStringList cmds(); + + private: + /** + * we need to know somewhere which scripts are around + */ + QDict<KateJScriptManager::Script> m_scripts; +}; + +class KateIndentJScriptImpl: public KateIndentScriptImplAbstract { + public: + KateIndentJScriptImpl(const QString& internalName, + const QString &filePath, const QString &niceName, + const QString ©right, double version); + ~KateIndentJScriptImpl(); + + virtual bool processChar( class Kate::View *view, QChar c, QString &errorMsg ); + virtual bool processLine( class Kate::View *view, const KateDocCursor &line, QString &errorMsg ); + virtual bool processNewline( class Kate::View *view, const KateDocCursor &begin, bool needcontinue, QString &errorMsg ); + protected: + virtual void decRef(); + private: + KateJSView *m_viewWrapper; + KateJSDocument *m_docWrapper; + KJS::Object *m_indenter; + KJS::Interpreter *m_interpreter; + bool setupInterpreter(QString &errorMsg); + void deleteInterpreter(); +}; + +class KateIndentJScriptManager: public KateIndentScriptManagerAbstract +{ + + public: + KateIndentJScriptManager (); + virtual ~KateIndentJScriptManager (); + virtual KateIndentScript script(const QString &scriptname); + private: + /** + * go, search our scripts + * @param force force cache updating? + */ + void collectScripts (bool force = false); + void parseScriptHeader(const QString &filePath, + QString *niceName,QString *copyright,double *version); + QDict<KateIndentJScriptImpl> m_scripts; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katekeyinterceptorfunctor.h b/kate/part/katekeyinterceptorfunctor.h new file mode 100644 index 000000000..0126ff814 --- /dev/null +++ b/kate/part/katekeyinterceptorfunctor.h @@ -0,0 +1,25 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004 Joseph Wenninger <jowenn@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef _KATE_KEY_INTERCEPTOR_FUNCTOR_H +#define _KATE_KEY_INTERCEPTOR_FUNCTOR_H +#include <kshortcut.h> +class KateKeyInterceptorFunctor { + public: + virtual bool operator()(KKey key)=0; +}; +#endif diff --git a/kate/part/katelinerange.cpp b/kate/part/katelinerange.cpp new file mode 100644 index 000000000..7500b4e8a --- /dev/null +++ b/kate/part/katelinerange.cpp @@ -0,0 +1,75 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002,2003 Hamish Rodda <rodda@kde.org> + Copyright (C) 2003 Anakim Border <aborder@sources.sourceforge.net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "katelinerange.h" + +KateLineRange::KateLineRange() + : line(-1) + , virtualLine(-1) + , startCol(-1) + , endCol(-1) + , startX(-1) + , endX(-1) + , dirty(false) + , viewLine(-1) + , wrap(false) + , startsInvisibleBlock(false) + , shiftX(0) +{ +} + +KateLineRange::~KateLineRange () +{ +} + +void KateLineRange::clear() +{ + line = -1; + virtualLine = -1; + startCol = -1; + endCol = -1; + startX = -1; + shiftX = 0; + endX = -1; + viewLine = -1; + wrap = false; + startsInvisibleBlock = false; +} + +bool operator> (const KateLineRange& r, const KateTextCursor& c) +{ + return r.line > c.line() || r.endCol > c.col(); +} + +bool operator>= (const KateLineRange& r, const KateTextCursor& c) +{ + return r.line > c.line() || r.endCol >= c.col(); +} + +bool operator< (const KateLineRange& r, const KateTextCursor& c) +{ + return r.line < c.line() || r.startCol < c.col(); +} + +bool operator<= (const KateLineRange& r, const KateTextCursor& c) +{ + return r.line < c.line() || r.startCol <= c.col(); +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katelinerange.h b/kate/part/katelinerange.h new file mode 100644 index 000000000..0557d565d --- /dev/null +++ b/kate/part/katelinerange.h @@ -0,0 +1,70 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002,2003 Hamish Rodda <rodda@kde.org> + Copyright (C) 2003 Anakim Border <aborder@sources.sourceforge.net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef _KATE_LINERANGE_H_ +#define _KATE_LINERANGE_H_ + +#include "katecursor.h" + +class KateLineRange +{ + public: + KateLineRange(); + virtual ~KateLineRange (); + + void clear(); + + inline bool includesCursor (const KateTextCursor& realCursor) const + { + return realCursor.line() == line && realCursor.col() >= startCol && (!wrap || realCursor.col() < endCol); + } + + inline int xOffset () const + { + return startX ? shiftX : 0; + } + + friend bool operator> (const KateLineRange& r, const KateTextCursor& c); + friend bool operator>= (const KateLineRange& r, const KateTextCursor& c); + friend bool operator< (const KateLineRange& r, const KateTextCursor& c); + friend bool operator<= (const KateLineRange& r, const KateTextCursor& c); + + int line; + int virtualLine; + int startCol; + int endCol; + int startX; + int endX; + + bool dirty; + int viewLine; + bool wrap; + bool startsInvisibleBlock; + + // This variable is used as follows: + // non-dynamic-wrapping mode: unused + // dynamic wrapping mode: + // first viewLine of a line: the X position of the first non-whitespace char + // subsequent viewLines: the X offset from the left of the display. + // + // this is used to provide a dynamic-wrapping-retains-indent feature. + int shiftX; +}; + +#endif diff --git a/kate/part/kateluaindentscript.cpp b/kate/part/kateluaindentscript.cpp new file mode 100644 index 000000000..a8872c0e8 --- /dev/null +++ b/kate/part/kateluaindentscript.cpp @@ -0,0 +1,528 @@ +/* This file is part of the KDE libraries + Copyright (C) 2005 Joseph Wenninger <jowenn@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "config.h" +#ifdef HAVE_LUA + +#include "kateluaindentscript.h" +#include "katedocument.h" +#include "kateview.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <qfile.h> +#include <qfileinfo.h> +#include <kstandarddirs.h> + +#include <kconfig.h> +#include <kglobal.h> +#include <klocale.h> + +extern "C" { +#include <lua.h> +#include <lualib.h> +} + +#define ONCHAR 1 +#define ONNEWLINE 2 +#define ONCHARSTR "kateonchar" +#define ONNEWLINESTR "kateonnewline" + +#define katelua_registerFunc(n,f,t) \ + (lua_pushstring(m_interpreter, n), \ + lua_pushcfunction(m_interpreter, f), \ + lua_settable(m_interpreter, t)) + +#define katelua_registerNumConst(n,v,t) \ + (lua_pushstring(m_interpreter, n), \ + lua_pushnumber(m_interpreter, v), \ + lua_settable(m_interpreter, t)) + +//BEGIN temporary, try to use registry later +static KateDocument *katelua_doc; +static Kate::View *katelua_view; +//END + + + +//BEGIN STATIC BINDING FUNCTIONS +typedef struct KATELUA_FUNCTIONS { + char *name; + lua_CFunction func; +} KATELUA_FUNCTIONS; + +static int katelua_katedebug(lua_State *L) { + int n=lua_gettop(L); + for (int i=1;i<=n;i++) { + if (lua_isnil(L,i)) kdDebug()<<"NIL VALUE"<<endl; + else if (lua_isstring(L,i)) kdDebug()<<lua_tostring(L,i)<<endl; + else if (lua_isboolean(L,i)) kdDebug()<<(bool)lua_toboolean(L,i)<<endl; + else if (lua_isnumber(L,i)) kdDebug()<<lua_tonumber(L,i)<<endl; + else kdDebug()<<"Invalid type for katedebug:"<<lua_type(L,i)<<endl; + } + return 0; +} + +static int katelua_indenter_register(lua_State *L) { + int n=lua_gettop(L); + if (n!=2) { + lua_pushstring(L,i18n("indenter.register requires 2 parameters (event id, function to call)").utf8().data()); + lua_error(L); + } + if ( (!lua_isfunction(L,2)) || (!lua_isnumber(L,1))) + { + /*if (lua_isnumber(L,1)) kdDebug()<<"A"<<endl; + if (lua_isfunction(L,2)) kdDebug()<<"B"<<endl; + kdDebug()<<lua_type(L,2)<<endl;*/ + lua_pushstring(L,i18n("indenter.register requires 2 parameters (event id (number), function to call (function))").utf8().data()); + lua_error(L); + } + switch ((int)lua_tonumber(L,1)) + { + case ONCHAR: + lua_pushstring(L,ONCHARSTR); + lua_pushstring(L,ONCHARSTR); + break; + case ONNEWLINE: + lua_pushstring(L,ONNEWLINESTR); + lua_pushstring(L,ONNEWLINESTR); + break; + default: + lua_pushstring(L,i18n("indenter.register:invalid event id").utf8().data()); + lua_error(L); + } + lua_gettable(L,LUA_REGISTRYINDEX); + if (!lua_isnil(L,lua_gettop(L))) { + lua_pushstring(L,i18n("indenter.register:there is already a function set for given").utf8().data()); + lua_error(L); + } + lua_pop(L,1); + lua_pushvalue(L,2); + lua_settable(L,LUA_REGISTRYINDEX); + kdDebug()<<"katelua_indenter_register: Success"<<endl; + return 0; +} + + +static int katelua_document_textline(lua_State *L) { + if (lua_gettop(L)!=1) { + lua_pushstring(L,i18n("document.textLine:One parameter (line number) required").utf8().data()); + lua_error(L); + } + if (!lua_isnumber(L,1)) { + lua_pushstring(L,i18n("document.textLine:One parameter (line number) required (number)").utf8().data()); + lua_error(L); + } + lua_pushstring(L,katelua_doc->textLine((uint)lua_tonumber(L,1)).utf8().data()); + return 1; +} + +static int katelua_document_removeText(lua_State *L) { + if (lua_gettop(L)!=4) { + lua_pushstring(L,i18n("document.removeText:Four parameters needed (start line, start col,end line, end col)").utf8().data()); + lua_error(L); + } + if ((!lua_isnumber(L,1)) || (!lua_isnumber(L,2)) ||(!lua_isnumber(L,3)) || (!lua_isnumber(L,4))) { + lua_pushstring(L,i18n("document.removeText:Four parameters needed (start line, start col,end line, end col) (4x number)").utf8().data()); + lua_error(L); + } + lua_pushboolean(L,katelua_doc->removeText((uint)lua_tonumber(L,1),(uint)lua_tonumber(L,2),(uint)lua_tonumber(L,3),(uint)lua_tonumber(L,4))); + return 1; +} + +static int katelua_document_insertText(lua_State *L) { + if (lua_gettop(L)!=3) { + lua_pushstring(L,i18n("document.insertText:Three parameters needed (line,col,text)").utf8().data()); + lua_error(L); + } + if ((!lua_isnumber(L,1)) || (!lua_isnumber(L,2)) ||(!lua_isstring(L,3)) ) { + lua_pushstring(L,i18n("document.removeText:Three parameters needed (line,col,text) (number,number,string)").utf8().data()); + lua_error(L); + } + lua_pushboolean(L,katelua_doc->insertText((uint)lua_tonumber(L,1),(uint)lua_tonumber(L,2),QString::fromUtf8(lua_tostring(L,3)))); + return 1; +} + +static int katelua_view_cursorline(lua_State *L) { + lua_pushnumber(L,katelua_view->cursorLine()); + return 1; +} +static int katelua_view_cursorcolumn(lua_State *L) { + lua_pushnumber(L,katelua_view->cursorColumn()); + return 1; +} +static int katelua_view_cursorposition(lua_State *L) { + lua_pushnumber(L,katelua_view->cursorLine()); + lua_pushnumber(L,katelua_view->cursorColumn()); + return 2; + +} +static int katelua_view_setcursorpositionreal(lua_State *L) { + return 0; +} + +static const struct KATELUA_FUNCTIONS katelua_documenttable[4]= { +{"textLine",katelua_document_textline}, +{"removeText",katelua_document_removeText}, +{"insertText",katelua_document_insertText}, +{0,0} +}; + +static const struct KATELUA_FUNCTIONS katelua_viewtable[5]= { +{"cursorLine",katelua_view_cursorline}, +{"cursorColumn",katelua_view_cursorcolumn}, +{"cursorPosition",katelua_view_cursorposition}, +{"setCursorPositionReal",katelua_view_setcursorpositionreal}, +{0,0} +}; + +static void kateregistertable(lua_State* m_interpreter,const KATELUA_FUNCTIONS funcs[],char * tablename) { + lua_newtable(m_interpreter); + int table=lua_gettop(m_interpreter); + for (uint i=0;funcs[i].name!=0;i++) + { + katelua_registerFunc(funcs[i].name,funcs[i].func,table); + } + + lua_pushstring(m_interpreter,tablename); + lua_pushvalue(m_interpreter,table); + lua_settable(m_interpreter,LUA_GLOBALSINDEX); + lua_pop(m_interpreter,1); + +} + +//END STATIC BINDING FUNCTIONS + + +//BEGIN KateLUAIndentScriptImpl +KateLUAIndentScriptImpl::KateLUAIndentScriptImpl(const QString& internalName, + const QString &filePath, const QString &niceName, + const QString ©right, double version): + KateIndentScriptImplAbstract(internalName,filePath,niceName,copyright,version),m_interpreter(0)/*,m_indenter(0)*/ +{ +} + + +KateLUAIndentScriptImpl::~KateLUAIndentScriptImpl() +{ + deleteInterpreter(); +} + +void KateLUAIndentScriptImpl::decRef() +{ + KateIndentScriptImplAbstract::decRef(); + if (refCount()==0) + { + deleteInterpreter(); + } +} + +void KateLUAIndentScriptImpl::deleteInterpreter() +{ + if (m_interpreter) + { + lua_close(m_interpreter); + m_interpreter=0; + } +} + +bool KateLUAIndentScriptImpl::setupInterpreter(QString &errorMsg) +{ + if (m_interpreter) return true; + m_interpreter=lua_open(); + + if (!m_interpreter) + { + errorMsg=i18n("LUA interpreter could not be initialized"); + return false; + } + luaopen_base(m_interpreter); + luaopen_string( m_interpreter ); + luaopen_table( m_interpreter ); + luaopen_math( m_interpreter ); + luaopen_io( m_interpreter ); + luaopen_debug( m_interpreter ); + + + /*indenter callback setup table*/ + lua_newtable(m_interpreter); + int indentertable=lua_gettop(m_interpreter); + katelua_registerFunc("register",katelua_indenter_register,indentertable); + katelua_registerNumConst("OnChar",ONCHAR,indentertable); + katelua_registerNumConst("OnNewline",ONNEWLINE,indentertable); + lua_pushstring(m_interpreter,"indenter"); + lua_pushvalue(m_interpreter,indentertable); + lua_settable(m_interpreter,LUA_GLOBALSINDEX); + lua_pop(m_interpreter,1); + + /*debug*/ + katelua_registerFunc("katedebug",katelua_katedebug,LUA_GLOBALSINDEX); + + /*document interface*/ + kateregistertable(m_interpreter,katelua_documenttable,"document"); + /*view interface*/ + kateregistertable(m_interpreter,katelua_viewtable,"view"); + + /*open script*/ + lua_pushstring(m_interpreter,"dofile"); + lua_gettable(m_interpreter,LUA_GLOBALSINDEX); + QCString fn=QFile::encodeName(filePath()); + lua_pushstring(m_interpreter,fn.data()); + int execresult=lua_pcall(m_interpreter,1,1,0); + if (execresult==0) { + kdDebug()<<"Lua script has been loaded successfully. Lua interpreter version:"<<lua_version()<<endl; + return true; + } else { + errorMsg=i18n("Lua indenting script had errors: %1").arg(lua_tostring(m_interpreter,lua_gettop(m_interpreter))); + kdDebug()<<errorMsg<<endl; + deleteInterpreter(); + + return false; + } +} + + +bool KateLUAIndentScriptImpl::processChar(Kate::View *view, QChar c, QString &errorMsg ) +{ + if (!setupInterpreter(errorMsg)) return false; + katelua_doc=((KateView*)view)->doc(); + katelua_view=view; + int oldtop=lua_gettop(m_interpreter); + lua_pushstring(m_interpreter,ONCHARSTR); + lua_gettable(m_interpreter,LUA_REGISTRYINDEX); + bool result=true; + if (!lua_isnil(m_interpreter,lua_gettop(m_interpreter))) + { + lua_pushstring(m_interpreter,QString(c).utf8().data()); + if (lua_pcall(m_interpreter,1,0,0)!=0) + { + errorMsg=i18n("Lua indenting script had errors: %1").arg(lua_tostring(m_interpreter,lua_gettop(m_interpreter))); + kdDebug()<<errorMsg<<endl; + result=false; + } + } + lua_settop(m_interpreter,oldtop); + return result; +} + +bool KateLUAIndentScriptImpl::processLine(Kate::View *view, const KateDocCursor &line, QString &errorMsg ) +{ + if (!setupInterpreter(errorMsg)) return false; + return true; +} + +bool KateLUAIndentScriptImpl::processNewline( class Kate::View *view, const KateDocCursor &begin, bool needcontinue, QString &errorMsg ) +{ + if (!setupInterpreter(errorMsg)) return false; + katelua_doc=((KateView*)view)->doc(); + katelua_view=view; + int oldtop=lua_gettop(m_interpreter); + lua_pushstring(m_interpreter,ONNEWLINESTR); + lua_gettable(m_interpreter,LUA_REGISTRYINDEX); + bool result=true; + if (!lua_isnil(m_interpreter,lua_gettop(m_interpreter))) + { + if (lua_pcall(m_interpreter,0,0,0)!=0) + { + errorMsg=i18n("Lua indenting script had errors: %1").arg(lua_tostring(m_interpreter,lua_gettop(m_interpreter))); + kdDebug()<<errorMsg<<endl; + result=false; + } + } + lua_settop(m_interpreter,oldtop); + return result; +} +//END + +//BEGIN KateLUAIndentScriptManager +KateLUAIndentScriptManager::KateLUAIndentScriptManager():KateIndentScriptManagerAbstract() +{ + collectScripts(); +} + +KateLUAIndentScriptManager::~KateLUAIndentScriptManager () +{ +} + +void KateLUAIndentScriptManager::collectScripts (bool force) +{ +// If there's something in myModeList the Mode List was already built so, don't do it again + if (!m_scripts.isEmpty()) + return; + + kdDebug()<<"================================================="<<endl<<"Trying to find Lua scripts"<<endl + <<"================================================="<<endl; + + // We'll store the scripts list in this config + KConfig config("katepartluaindentscriptrc", false, false); +#if 0 + // figure out if the kate install is too new + config.setGroup ("General"); + if (config.readNumEntry ("Version") > config.readNumEntry ("CachedVersion")) + { + config.writeEntry ("CachedVersion", config.readNumEntry ("Version")); + force = true; + } +#endif + + // Let's get a list of all the .js files + QStringList list = KGlobal::dirs()->findAllResources("data","katepart/scripts/indent/*.lua",false,true); + + // Let's iterate through the list and build the Mode List + for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it ) + { + // Each file has a group ed: + QString Group="Cache "+ *it; + + // Let's go to this group + config.setGroup(Group); + + // stat the file + struct stat sbuf; + memset (&sbuf, 0, sizeof(sbuf)); + stat(QFile::encodeName(*it), &sbuf); + kdDebug()<<"Lua script file:"<<(*it)<<endl; + // If the group exist and we're not forced to read the .js file, let's build myModeList for katepartjscriptrc + bool readnew=false; + if (!force && config.hasGroup(Group) && (sbuf.st_mtime == config.readNumEntry("lastModified"))) + { + config.setGroup(Group); + QString filePath=*it; + QString internalName=config.readEntry("internlName","KATE-ERROR"); + if (internalName=="KATE-ERROR") readnew=true; + else + { + QString niceName=config.readEntry("niceName",internalName); + QString copyright=config.readEntry("copyright",i18n("(Unknown)")); + double version=config.readDoubleNumEntry("version",0.0); + KateLUAIndentScriptImpl *s=new KateLUAIndentScriptImpl( + internalName,filePath,niceName,copyright,version); + m_scripts.insert (internalName, s); + } + } + else readnew=true; + if (readnew) + { + QFileInfo fi (*it); + + if (m_scripts[fi.baseName()]) + continue; + + QString internalName=fi.baseName(); + QString filePath=*it; + QString niceName=internalName; + QString copyright=i18n("(Unknown)"); + double version=0.0; + parseScriptHeader(filePath,&niceName,©right,&version); + /*save the information for retrieval*/ + config.setGroup(Group); + config.writeEntry("lastModified",sbuf.st_mtime); + config.writeEntry("internalName",internalName); + config.writeEntry("niceName",niceName); + config.writeEntry("copyright",copyright); + config.writeEntry("version",version); + KateLUAIndentScriptImpl *s=new KateLUAIndentScriptImpl( + internalName,filePath,niceName,copyright,version); + m_scripts.insert (internalName, s); + } + } + + // Syncronize with the file katepartjscriptrc + config.sync(); +} + +KateIndentScript KateLUAIndentScriptManager::script(const QString &scriptname) { + KateLUAIndentScriptImpl *s=m_scripts[scriptname]; + kdDebug(13050)<<scriptname<<"=="<<s<<endl; + return KateIndentScript(s); +} + +void KateLUAIndentScriptManager::parseScriptHeader(const QString &filePath, + QString *niceName,QString *copyright,double *version) +{ +#if 0 + QFile f(QFile::encodeName(filePath)); + if (!f.open(IO_ReadOnly) ) { + kdDebug(13050)<<"Header could not be parsed, because file could not be opened"<<endl; + return; + } + QTextStream st(&f); + st.setEncoding (QTextStream::UnicodeUTF8); + if (!st.readLine().upper().startsWith("/**KATE")) { + kdDebug(13050)<<"No header found"<<endl; + f.close(); + return; + } + // here the real parsing begins + kdDebug(13050)<<"Parsing indent script header"<<endl; + enum {NOTHING=0,COPYRIGHT=1} currentState=NOTHING; + QString line; + QString tmpblockdata=""; + QRegExp endExpr("[\\s\\t]*\\*\\*\\/[\\s\\t]*$"); + QRegExp keyValue("[\\s\\t]*\\*\\s*(.+):(.*)$"); + QRegExp blockContent("[\\s\\t]*\\*(.*)$"); + while ((line=st.readLine())!=QString::null) { + if (endExpr.exactMatch(line)) { + kdDebug(13050)<<"end of config block"<<endl; + if (currentState==NOTHING) break; + if (currentState==COPYRIGHT) { + *copyright=tmpblockdata; + break; + } + Q_ASSERT(0); + } + if (currentState==NOTHING) + { + if (keyValue.exactMatch(line)) { + QStringList sl=keyValue.capturedTexts(); + kdDebug(13050)<<"key:"<<sl[1]<<endl<<"value:"<<sl[2]<<endl; + kdDebug(13050)<<"key-length:"<<sl[1].length()<<endl<<"value-length:"<<sl[2].length()<<endl; + QString key=sl[1]; + QString value=sl[2]; + if (key=="NAME") (*niceName)=value.stripWhiteSpace(); + else if (key=="VERSION") (*version)=value.stripWhiteSpace().toDouble(0); + else if (key=="COPYRIGHT") + { + tmpblockdata=""; + if (value.stripWhiteSpace().length()>0) tmpblockdata=value; + currentState=COPYRIGHT; + } else kdDebug(13050)<<"ignoring key"<<endl; + } + } else { + if (blockContent.exactMatch(line)) + { + QString bl=blockContent.capturedTexts()[1]; + //kdDebug(13050)<<"block content line:"<<bl<<endl<<bl.length()<<" "<<bl.isEmpty()<<endl; + if (bl.isEmpty()) + { + (*copyright)=tmpblockdata; + kdDebug(13050)<<"Copyright block:"<<endl<<(*copyright)<<endl; + currentState=NOTHING; + } else tmpblockdata=tmpblockdata+"\n"+bl; + } + } + } + f.close(); +#endif +} +//END + +#endif +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateluaindentscript.h b/kate/part/kateluaindentscript.h new file mode 100644 index 000000000..4cccf85ba --- /dev/null +++ b/kate/part/kateluaindentscript.h @@ -0,0 +1,69 @@ +/* This file is part of the KDE libraries + Copyright (C) 2005 Joseph Wenninger <jowenn@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "config.h" +#ifdef HAVE_LUA + +#ifndef _KATELUAINDENTSCRIPT_H_ +#define _KATELUAINDENTSCRIPT_H_ + +#include "kateindentscriptabstracts.h" +#include <qdict.h> + +struct lua_State; + +class KateLUAIndentScriptImpl: public KateIndentScriptImplAbstract { + public: + KateLUAIndentScriptImpl(const QString& internalName, + const QString &filePath, const QString &niceName, + const QString ©right, double version); + ~KateLUAIndentScriptImpl(); + + virtual bool processChar( class Kate::View *view, QChar c, QString &errorMsg ); + virtual bool processLine( class Kate::View *view, const KateDocCursor &line, QString &errorMsg ); + virtual bool processNewline( class Kate::View *view, const KateDocCursor &begin, bool needcontinue, QString &errorMsg ); + protected: + virtual void decRef(); + private: + bool setupInterpreter(QString &errorMsg); + void deleteInterpreter(); + struct lua_State *m_interpreter; +}; + +class KateLUAIndentScriptManager: public KateIndentScriptManagerAbstract +{ + + public: + KateLUAIndentScriptManager (); + virtual ~KateLUAIndentScriptManager (); + virtual KateIndentScript script(const QString &scriptname); + private: + /** + * go, search our scripts + * @param force force cache updating? + */ + void collectScripts (bool force = false); + void parseScriptHeader(const QString &filePath, + QString *niceName,QString *copyright,double *version); + QDict<KateLUAIndentScriptImpl> m_scripts; +}; + +#endif + +#endif +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateprinter.cpp b/kate/part/kateprinter.cpp new file mode 100644 index 000000000..380c50216 --- /dev/null +++ b/kate/part/kateprinter.cpp @@ -0,0 +1,1005 @@ +/* + * This file is part of the KDE libraries + * Copyright (c) 2001-2002 Michael Goffioul <kdeprint@swing.be> + * Complete rewrite on Sat Jun 15 2002 (c) Anders Lund <anders@alweb.dk> + * Copyright (c) 2002, 2003 Anders Lund <anders@alweb.dk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + **/ + +#include "kateprinter.h" + +#include <kateconfig.h> +#include <katedocument.h> +#include <katefactory.h> +#include <katehighlight.h> +#include <katelinerange.h> +#include <katerenderer.h> +#include <kateschema.h> +#include <katetextline.h> + +#include <kapplication.h> +#include <kcolorbutton.h> +#include <kdebug.h> +#include <kdialog.h> // for spacingHint() +#include <kfontdialog.h> +#include <klocale.h> +#include <kprinter.h> +#include <kurl.h> +#include <kuser.h> // for loginName + +#include <qpainter.h> +#include <qpopupmenu.h> +#include <qpaintdevicemetrics.h> +#include <qcheckbox.h> +#include <qcombobox.h> +#include <qgroupbox.h> +#include <qhbox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qlineedit.h> +#include <qspinbox.h> +#include <qstringlist.h> +#include <qwhatsthis.h> + +//BEGIN KatePrinter +bool KatePrinter::print (KateDocument *doc) +{ +#ifndef Q_WS_WIN //TODO: reenable + KPrinter printer; + + // docname is now always there, including the right Untitled name + printer.setDocName(doc->docName()); + + KatePrintTextSettings *kpts = new KatePrintTextSettings(&printer, NULL); + kpts->enableSelection( doc->hasSelection() ); + printer.addDialogPage( kpts ); + printer.addDialogPage( new KatePrintHeaderFooter(&printer, NULL) ); + printer.addDialogPage( new KatePrintLayout(&printer, NULL) ); + + if ( printer.setup( kapp->mainWidget(), i18n("Print %1").arg(printer.docName()) ) ) + { + KateRenderer renderer(doc); + //renderer.config()->setSchema (1); + renderer.setPrinterFriendly(true); + + QPainter paint( &printer ); + QPaintDeviceMetrics pdm( &printer ); + /* + We work in tree cycles: + 1) initialize variables and retrieve print settings + 2) prepare data according to those settings + 3) draw to the printer + */ + uint pdmWidth = pdm.width(); + uint y = 0; + uint xstart = 0; // beginning point for painting lines + uint lineCount = 0; + uint maxWidth = pdmWidth; + uint headerWidth = pdmWidth; + int startCol = 0; + int endCol = 0; + bool needWrap = true; + bool pageStarted = true; + + // Text Settings Page + bool selectionOnly = ( doc->hasSelection() && + ( printer.option("app-kate-printselection") == "true" ) ); + int selStartCol = 0; + int selEndCol = 0; + + bool useGuide = ( printer.option("app-kate-printguide") == "true" ); + int guideHeight = 0; + int guideCols = 0; + + bool printLineNumbers = ( printer.option("app-kate-printlinenumbers") == "true" ); + uint lineNumberWidth( 0 ); + + // Header/Footer Page + QFont headerFont; // used for header/footer + QString f = printer.option("app-kate-hffont"); + if (!f.isEmpty()) + headerFont.fromString( f ); + + bool useHeader = (printer.option("app-kate-useheader") == "true"); + QColor headerBgColor(printer.option("app-kate-headerbg")); + QColor headerFgColor(printer.option("app-kate-headerfg")); + uint headerHeight( 0 ); // further init only if needed + QStringList headerTagList; // do + bool headerDrawBg = false; // do + + bool useFooter = (printer.option("app-kate-usefooter") == "true"); + QColor footerBgColor(printer.option("app-kate-footerbg")); + QColor footerFgColor(printer.option("app-kate-footerfg")); + uint footerHeight( 0 ); // further init only if needed + QStringList footerTagList = 0; // do + bool footerDrawBg = 0; // do + + // Layout Page + renderer.config()->setSchema( KateFactory::self()->schemaManager()->number( + printer.option("app-kate-colorscheme") ) ); + bool useBackground = ( printer.option("app-kate-usebackground") == "true" ); + bool useBox = (printer.option("app-kate-usebox") == "true"); + int boxWidth(printer.option("app-kate-boxwidth").toInt()); + QColor boxColor(printer.option("app-kate-boxcolor")); + int innerMargin = useBox ? printer.option("app-kate-boxmargin").toInt() : 6; + + // Post initialization + uint maxHeight = (useBox ? pdm.height()-innerMargin : pdm.height()); + uint currentPage( 1 ); + uint lastline = doc->lastLine(); // nessecary to print selection only + uint firstline( 0 ); + + KateHlItemDataList ilist; + + if (useGuide) + doc->highlight()->getKateHlItemDataListCopy (renderer.config()->schema(), ilist); + + /* + Now on for preparations... + during preparations, variable names starting with a "_" means + those variables are local to the enclosing block. + */ + { + if ( selectionOnly ) + { + // set a line range from the first selected line to the last + firstline = doc->selStartLine(); + selStartCol = doc->selStartCol(); + lastline = doc->selEndLine(); + selEndCol = doc->selEndCol(); + + lineCount = firstline; + } + + if ( printLineNumbers ) + { + // figure out the horiizontal space required + QString s( QString("%1 ").arg( doc->numLines() ) ); + s.fill('5', -1); // some non-fixed fonts haven't equally wide numbers + // FIXME calculate which is actually the widest... + lineNumberWidth = renderer.currentFontMetrics()->width( s ); + // a small space between the line numbers and the text + int _adj = renderer.currentFontMetrics()->width( "5" ); + // adjust available width and set horizontal start point for data + maxWidth -= (lineNumberWidth + _adj); + xstart += lineNumberWidth + _adj; + } + + if ( useHeader || useFooter ) + { + // Set up a tag map + // This retrieves all tags, ued or not, but + // none of theese operations should be expensive, + // and searcing each tag in the format strings is avoided. + QDateTime dt = QDateTime::currentDateTime(); + QMap<QString,QString> tags; + + KUser u (KUser::UseRealUserID); + tags["u"] = u.loginName(); + + tags["d"] = KGlobal::locale()->formatDateTime(dt, true, false); + tags["D"] = KGlobal::locale()->formatDateTime(dt, false, false); + tags["h"] = KGlobal::locale()->formatTime(dt.time(), false); + tags["y"] = KGlobal::locale()->formatDate(dt.date(), true); + tags["Y"] = KGlobal::locale()->formatDate(dt.date(), false); + tags["f"] = doc->url().fileName(); + tags["U"] = doc->url().prettyURL(); + if ( selectionOnly ) + { + QString s( i18n("(Selection of) ") ); + tags["f"].prepend( s ); + tags["U"].prepend( s ); + } + + QRegExp reTags( "%([dDfUhuyY])" ); // TODO tjeck for "%%<TAG>" + + if (useHeader) + { + headerDrawBg = ( printer.option("app-kate-headerusebg") == "true" ); + headerHeight = QFontMetrics( headerFont ).height(); + if ( useBox || headerDrawBg ) + headerHeight += innerMargin * 2; + else + headerHeight += 1 + QFontMetrics( headerFont ).leading(); + + QString headerTags = printer.option("app-kate-headerformat"); + int pos = reTags.search( headerTags ); + QString rep; + while ( pos > -1 ) + { + rep = tags[reTags.cap( 1 )]; + headerTags.replace( (uint)pos, 2, rep ); + pos += rep.length(); + pos = reTags.search( headerTags, pos ); + } + headerTagList = QStringList::split('|', headerTags, true); + + if (!headerBgColor.isValid()) + headerBgColor = Qt::lightGray; + if (!headerFgColor.isValid()) + headerFgColor = Qt::black; + } + + if (useFooter) + { + footerDrawBg = ( printer.option("app-kate-footerusebg") == "true" ); + footerHeight = QFontMetrics( headerFont ).height(); + if ( useBox || footerDrawBg ) + footerHeight += 2*innerMargin; + else + footerHeight += 1; // line only + + QString footerTags = printer.option("app-kate-footerformat"); + int pos = reTags.search( footerTags ); + QString rep; + while ( pos > -1 ) + { + rep = tags[reTags.cap( 1 )]; + footerTags.replace( (uint)pos, 2, rep ); + pos += rep.length(); + pos = reTags.search( footerTags, pos ); + } + + footerTagList = QStringList::split('|', footerTags, true); + if (!footerBgColor.isValid()) + footerBgColor = Qt::lightGray; + if (!footerFgColor.isValid()) + footerFgColor = Qt::black; + // adjust maxheight, so we can know when/where to print footer + maxHeight -= footerHeight; + } + } // if ( useHeader || useFooter ) + + if ( useBackground ) + { + if ( ! useBox ) + { + xstart += innerMargin; + maxWidth -= innerMargin * 2; + } + } + + if ( useBox ) + { + if (!boxColor.isValid()) + boxColor = Qt::black; + if (boxWidth < 1) // shouldn't be pssible no more! + boxWidth = 1; + // set maxwidth to something sensible + maxWidth -= ( ( boxWidth + innerMargin ) * 2 ); + xstart += boxWidth + innerMargin; + // maxheight too.. + maxHeight -= boxWidth; + } + else + boxWidth = 0; + + if ( useGuide ) + { + // calculate the height required + // the number of columns is a side effect, saved for drawing time + // first width is needed + int _w = pdmWidth - innerMargin * 2; + if ( useBox ) + _w -= boxWidth * 2; + else + { + if ( useBackground ) + _w -= ( innerMargin * 2 ); + _w -= 2; // 1 px line on each side + } + + // base of height: margins top/bottom, above and below tetle sep line + guideHeight = ( innerMargin * 4 ) + 1; + + // get a title and add the height required to draw it + QString _title = i18n("Typographical Conventions for %1").arg(doc->highlight()->name()); + guideHeight += paint.boundingRect( 0, 0, _w, 1000, Qt::AlignTop|Qt::AlignHCenter, _title ).height(); + + // see how many columns we can fit in + int _widest( 0 ); + + QPtrListIterator<KateHlItemData> it( ilist ); + KateHlItemData *_d; + + int _items ( 0 ); + while ( ( _d = it.current()) != 0 ) + { + _widest = kMax( _widest, ((QFontMetrics)( + _d->bold() ? + _d->italic() ? + renderer.config()->fontStruct()->myFontMetricsBI : + renderer.config()->fontStruct()->myFontMetricsBold : + _d->italic() ? + renderer.config()->fontStruct()->myFontMetricsItalic : + renderer.config()->fontStruct()->myFontMetrics + ) ).width( _d->name ) ); + _items++; + ++it; + } + guideCols = _w/( _widest + innerMargin ); + // add height for required number of lines needed given columns + guideHeight += renderer.fontHeight() * ( _items/guideCols ); + if ( _items%guideCols ) + guideHeight += renderer.fontHeight(); + } + + // now that we know the vertical amount of space needed, + // it is possible to calculate the total number of pages + // if needed, that is if any header/footer tag contains "%P". + if ( headerTagList.grep("%P").count() || footerTagList.grep("%P").count() ) + { + kdDebug(13020)<<"'%P' found! calculating number of pages..."<<endl; + uint _pages = 0; + uint _ph = maxHeight; + if ( useHeader ) + _ph -= ( headerHeight + innerMargin ); + if ( useFooter ) + _ph -= innerMargin; + int _lpp = _ph / renderer.fontHeight(); + uint _lt = 0, _c=0; + + // add space for guide if required + if ( useGuide ) + _lt += (guideHeight + (renderer.fontHeight() /2)) / renderer.fontHeight(); + long _lw; + for ( uint i = firstline; i < lastline; i++ ) + { + _lw = renderer.textWidth( doc->kateTextLine( i ), -1 ); + while ( _lw >= 0 ) + { + _c++; + _lt++; + if ( (int)_lt == _lpp ) + { + _pages++; + _lt = 0; + } + _lw -= maxWidth; + if ( ! _lw ) _lw--; // skip lines matching exactly! + } + } + if ( _lt ) _pages++; // last page + + // substitute both tag lists + QString re("%P"); + QStringList::Iterator it; + for ( it=headerTagList.begin(); it!=headerTagList.end(); ++it ) + (*it).replace( re, QString( "%1" ).arg( _pages ) ); + for ( it=footerTagList.begin(); it!=footerTagList.end(); ++it ) + (*it).replace( re, QString( "%1" ).arg( _pages ) ); + } + } // end prepare block + + /* + On to draw something :-) + */ + uint _count = 0; + while ( lineCount <= lastline ) + { + startCol = 0; + endCol = 0; + needWrap = true; + + while (needWrap) + { + if ( y + renderer.fontHeight() >= (uint)(maxHeight) ) + { + kdDebug(13020)<<"Starting new page, "<<_count<<" lines up to now."<<endl; + printer.newPage(); + currentPage++; + pageStarted = true; + y=0; + } + + if ( pageStarted ) + { + + if ( useHeader ) + { + paint.setPen(headerFgColor); + paint.setFont(headerFont); + if ( headerDrawBg ) + paint.fillRect(0, 0, headerWidth, headerHeight, headerBgColor); + if (headerTagList.count() == 3) + { + int valign = ( (useBox||headerDrawBg||useBackground) ? + Qt::AlignVCenter : Qt::AlignTop ); + int align = valign|Qt::AlignLeft; + int marg = ( useBox || headerDrawBg ) ? innerMargin : 0; + if ( useBox ) marg += boxWidth; + QString s; + for (int i=0; i<3; i++) + { + s = headerTagList[i]; + if (s.find("%p") != -1) s.replace("%p", QString::number(currentPage)); + paint.drawText(marg, 0, headerWidth-(marg*2), headerHeight, align, s); + align = valign|(i == 0 ? Qt::AlignHCenter : Qt::AlignRight); + } + } + if ( ! ( headerDrawBg || useBox || useBackground ) ) // draw a 1 px (!?) line to separate header from contents + { + paint.drawLine( 0, headerHeight-1, headerWidth, headerHeight-1 ); + //y += 1; now included in headerHeight + } + y += headerHeight + innerMargin; + } + + if ( useFooter ) + { + if ( ! ( footerDrawBg || useBox || useBackground ) ) // draw a 1 px (!?) line to separate footer from contents + paint.drawLine( 0, maxHeight + innerMargin - 1, headerWidth, maxHeight + innerMargin - 1 ); + if ( footerDrawBg ) + paint.fillRect(0, maxHeight+innerMargin+boxWidth, headerWidth, footerHeight, footerBgColor); + if (footerTagList.count() == 3) + { + int align = Qt::AlignVCenter|Qt::AlignLeft; + int marg = ( useBox || footerDrawBg ) ? innerMargin : 0; + if ( useBox ) marg += boxWidth; + QString s; + for (int i=0; i<3; i++) + { + s = footerTagList[i]; + if (s.find("%p") != -1) s.replace("%p", QString::number(currentPage)); + paint.drawText(marg, maxHeight+innerMargin, headerWidth-(marg*2), footerHeight, align, s); + align = Qt::AlignVCenter|(i == 0 ? Qt::AlignHCenter : Qt::AlignRight); + } + } + } // done footer + + if ( useBackground ) + { + // If we have a box, or the header/footer has backgrounds, we want to paint + // to the border of those. Otherwise just the contents area. + int _y = y, _h = maxHeight - y; + if ( useBox ) + { + _y -= innerMargin; + _h += 2 * innerMargin; + } + else + { + if ( headerDrawBg ) + { + _y -= innerMargin; + _h += innerMargin; + } + if ( footerDrawBg ) + { + _h += innerMargin; + } + } + paint.fillRect( 0, _y, pdmWidth, _h, renderer.config()->backgroundColor()); + } + + if ( useBox ) + { + paint.setPen(QPen(boxColor, boxWidth)); + paint.drawRect(0, 0, pdmWidth, pdm.height()); + if (useHeader) + paint.drawLine(0, headerHeight, headerWidth, headerHeight); + else + y += innerMargin; + + if ( useFooter ) // drawline is not trustable, grr. + paint.fillRect( 0, maxHeight+innerMargin, headerWidth, boxWidth, boxColor ); + } + + if ( useGuide && currentPage == 1 ) + { // FIXME - this may span more pages... + // draw a box unless we have boxes, in which case we end with a box line + + // use color of dsNormal for the title string and the hline + KateAttributeList _dsList; + KateHlManager::self()->getDefaults ( renderer.config()->schema(), _dsList ); + paint.setPen( _dsList.at(0)->textColor() ); + int _marg = 0; // this could be available globally!?? + if ( useBox ) + { + _marg += (2*boxWidth) + (2*innerMargin); + paint.fillRect( 0, y+guideHeight-innerMargin-boxWidth, headerWidth, boxWidth, boxColor ); + } + else + { + if ( useBackground ) + _marg += 2*innerMargin; + paint.drawRect( _marg, y, pdmWidth-(2*_marg), guideHeight ); + _marg += 1; + y += 1 + innerMargin; + } + // draw a title string + paint.setFont( renderer.config()->fontStruct()->myFontBold ); + QRect _r; + paint.drawText( _marg, y, pdmWidth-(2*_marg), maxHeight - y, + Qt::AlignTop|Qt::AlignHCenter, + i18n("Typographical Conventions for %1").arg(doc->highlight()->name()), -1, &_r ); + int _w = pdmWidth - (_marg*2) - (innerMargin*2); + int _x = _marg + innerMargin; + y += _r.height() + innerMargin; + paint.drawLine( _x, y, _x + _w, y ); + y += 1 + innerMargin; + // draw attrib names using their styles + + QPtrListIterator<KateHlItemData> _it( ilist ); + KateHlItemData *_d; + int _cw = _w/guideCols; + int _i(0); + + while ( ( _d = _it.current() ) != 0 ) + { + paint.setPen( renderer.attribute(_i)->textColor() ); + paint.setFont( renderer.attribute(_i)->font( *renderer.currentFont() ) ); + paint.drawText(( _x + ((_i%guideCols)*_cw)), y, _cw, renderer.fontHeight(), + Qt::AlignVCenter|Qt::AlignLeft, _d->name, -1, &_r ); + _i++; + if ( _i && ! ( _i%guideCols ) ) y += renderer.fontHeight(); + ++_it; + } + if ( _i%guideCols ) y += renderer.fontHeight();// last row not full + y += ( useBox ? boxWidth : 1 ) + (innerMargin*2); + } + + pageStarted = false; + } // pageStarted; move on to contents:) + + if ( printLineNumbers && ! startCol ) // don't repeat! + { + paint.setFont( renderer.config()->fontStruct()->font( false, false ) ); + paint.setPen( renderer.config()->lineNumberColor() ); + paint.drawText( (( useBox || useBackground ) ? innerMargin : 0), y, + lineNumberWidth, renderer.fontHeight(), + Qt::AlignRight, QString("%1").arg( lineCount + 1 ) ); + } + endCol = renderer.textWidth(doc->kateTextLine(lineCount), startCol, maxWidth, &needWrap); + + if ( endCol < startCol ) + { + //kdDebug(13020)<<"--- Skipping garbage, line: "<<lineCount<<" start: "<<startCol<<" end: "<<endCol<<" real EndCol; "<< buffer->line(lineCount)->length()<< " !?"<<endl; + lineCount++; + continue; // strange case... + // Happens if the line fits exactly. + // When it happens, a line of garbage would be printed. + // FIXME Most likely this is an error in textWidth(), + // failing to correctly set needWrap to false in this case? + } + + // if we print only selection: + // print only selected range of chars. + bool skip = false; + if ( selectionOnly ) + { + bool inBlockSelection = ( doc->blockSelectionMode() && lineCount >= firstline && lineCount <= lastline ); + if ( lineCount == firstline || inBlockSelection ) + { + if ( startCol < selStartCol ) + startCol = selStartCol; + } + if ( lineCount == lastline || inBlockSelection ) + { + if ( endCol > selEndCol ) + { + endCol = selEndCol; + skip = true; + } + } + } + + // HA! this is where we print [part of] a line ;]] + // FIXME Convert this function + related functionality to a separate KatePrintView + KateLineRange range; + range.line = lineCount; + range.startCol = startCol; + range.endCol = endCol; + range.wrap = needWrap; + paint.translate(xstart, y); + renderer.paintTextLine(paint, &range, 0, maxWidth); + paint.resetXForm(); + if ( skip ) + { + needWrap = false; + startCol = 0; + } + else + { + startCol = endCol; + } + + y += renderer.fontHeight(); + _count++; + } // done while ( needWrap ) + + lineCount++; + } // done lineCount <= lastline + return true; + } + +#endif //!Q_WS_WIN + return false; +} +//END KatePrinter + +#ifndef Q_WS_WIN //TODO: reenable +//BEGIN KatePrintTextSettings +KatePrintTextSettings::KatePrintTextSettings( KPrinter * /*printer*/, QWidget *parent, const char *name ) + : KPrintDialogPage( parent, name ) +{ + setTitle( i18n("Te&xt Settings") ); + + QVBoxLayout *lo = new QVBoxLayout ( this ); + lo->setSpacing( KDialog::spacingHint() ); + + cbSelection = new QCheckBox( i18n("Print &selected text only"), this ); + lo->addWidget( cbSelection ); + + cbLineNumbers = new QCheckBox( i18n("Print &line numbers"), this ); + lo->addWidget( cbLineNumbers ); + + cbGuide = new QCheckBox( i18n("Print syntax &guide"), this ); + lo->addWidget( cbGuide ); + + lo->addStretch( 1 ); + + // set defaults - nothing to do :-) + + // whatsthis + QWhatsThis::add( cbSelection, i18n( + "<p>This option is only available if some text is selected in the document.</p>" + "<p>If available and enabled, only the selected text is printed.</p>") ); + QWhatsThis::add( cbLineNumbers, i18n( + "<p>If enabled, line numbers will be printed on the left side of the page(s).</p>") ); + QWhatsThis::add( cbGuide, i18n( + "<p>Print a box displaying typographical conventions for the document type, as " + "defined by the syntax highlighting being used.") ); +} + +void KatePrintTextSettings::getOptions( QMap<QString,QString>& opts, bool ) +{ + opts["app-kate-printselection"] = cbSelection->isChecked() ? "true" : "false"; + opts["app-kate-printlinenumbers"] = cbLineNumbers->isChecked() ? "true" : "false"; + opts["app-kate-printguide"] = cbGuide->isChecked() ? "true" : "false" ; +} + +void KatePrintTextSettings::setOptions( const QMap<QString,QString>& opts ) +{ + QString v; + v = opts["app-kate-printselection"]; + if ( ! v.isEmpty() ) + cbSelection->setChecked( v == "true" ); + v = opts["app-kate-printlinenumbers"]; + if ( ! v.isEmpty() ) + cbLineNumbers->setChecked( v == "true" ); + v = opts["app-kate-printguide"]; + if ( ! v.isEmpty() ) + cbGuide->setChecked( v == "true" ); +} + +void KatePrintTextSettings::enableSelection( bool enable ) +{ + cbSelection->setEnabled( enable ); +} + +//END KatePrintTextSettings + +//BEGIN KatePrintHeaderFooter +KatePrintHeaderFooter::KatePrintHeaderFooter( KPrinter * /*printer*/, QWidget *parent, const char *name ) + : KPrintDialogPage( parent, name ) +{ + setTitle( i18n("Hea&der && Footer") ); + + QVBoxLayout *lo = new QVBoxLayout ( this ); + uint sp = KDialog::spacingHint(); + lo->setSpacing( sp ); + + // enable + QHBoxLayout *lo1 = new QHBoxLayout ( lo ); + cbEnableHeader = new QCheckBox( i18n("Pr&int header"), this ); + lo1->addWidget( cbEnableHeader ); + cbEnableFooter = new QCheckBox( i18n("Pri&nt footer"), this ); + lo1->addWidget( cbEnableFooter ); + + // font + QHBoxLayout *lo2 = new QHBoxLayout( lo ); + lo2->addWidget( new QLabel( i18n("Header/footer font:"), this ) ); + lFontPreview = new QLabel( this ); + lFontPreview->setFrameStyle( QFrame::Panel|QFrame::Sunken ); + lo2->addWidget( lFontPreview ); + lo2->setStretchFactor( lFontPreview, 1 ); + QPushButton *btnChooseFont = new QPushButton( i18n("Choo&se Font..."), this ); + lo2->addWidget( btnChooseFont ); + connect( btnChooseFont, SIGNAL(clicked()), this, SLOT(setHFFont()) ); + // header + gbHeader = new QGroupBox( 2, Qt::Horizontal, i18n("Header Properties"), this ); + lo->addWidget( gbHeader ); + + QLabel *lHeaderFormat = new QLabel( i18n("&Format:"), gbHeader ); + QHBox *hbHeaderFormat = new QHBox( gbHeader ); + hbHeaderFormat->setSpacing( sp ); + leHeaderLeft = new QLineEdit( hbHeaderFormat ); + leHeaderCenter = new QLineEdit( hbHeaderFormat ); + leHeaderRight = new QLineEdit( hbHeaderFormat ); + lHeaderFormat->setBuddy( leHeaderLeft ); + new QLabel( i18n("Colors:"), gbHeader ); + QHBox *hbHeaderColors = new QHBox( gbHeader ); + hbHeaderColors->setSpacing( sp ); + QLabel *lHeaderFgCol = new QLabel( i18n("Foreground:"), hbHeaderColors ); + kcbtnHeaderFg = new KColorButton( hbHeaderColors ); + lHeaderFgCol->setBuddy( kcbtnHeaderFg ); + cbHeaderEnableBgColor = new QCheckBox( i18n("Bac&kground"), hbHeaderColors ); + kcbtnHeaderBg = new KColorButton( hbHeaderColors ); + + gbFooter = new QGroupBox( 2, Qt::Horizontal, i18n("Footer Properties"), this ); + lo->addWidget( gbFooter ); + + // footer + QLabel *lFooterFormat = new QLabel( i18n("For&mat:"), gbFooter ); + QHBox *hbFooterFormat = new QHBox( gbFooter ); + hbFooterFormat->setSpacing( sp ); + leFooterLeft = new QLineEdit( hbFooterFormat ); + leFooterCenter = new QLineEdit( hbFooterFormat ); + leFooterRight = new QLineEdit( hbFooterFormat ); + lFooterFormat->setBuddy( leFooterLeft ); + + new QLabel( i18n("Colors:"), gbFooter ); + QHBox *hbFooterColors = new QHBox( gbFooter ); + hbFooterColors->setSpacing( sp ); + QLabel *lFooterBgCol = new QLabel( i18n("Foreground:"), hbFooterColors ); + kcbtnFooterFg = new KColorButton( hbFooterColors ); + lFooterBgCol->setBuddy( kcbtnFooterFg ); + cbFooterEnableBgColor = new QCheckBox( i18n("&Background"), hbFooterColors ); + kcbtnFooterBg = new KColorButton( hbFooterColors ); + + lo->addStretch( 1 ); + + // user friendly + connect( cbEnableHeader, SIGNAL(toggled(bool)), gbHeader, SLOT(setEnabled(bool)) ); + connect( cbEnableFooter, SIGNAL(toggled(bool)), gbFooter, SLOT(setEnabled(bool)) ); + connect( cbHeaderEnableBgColor, SIGNAL(toggled(bool)), kcbtnHeaderBg, SLOT(setEnabled(bool)) ); + connect( cbFooterEnableBgColor, SIGNAL(toggled(bool)), kcbtnFooterBg, SLOT(setEnabled(bool)) ); + + // set defaults + cbEnableHeader->setChecked( true ); + leHeaderLeft->setText( "%y" ); + leHeaderCenter->setText( "%f" ); + leHeaderRight->setText( "%p" ); + kcbtnHeaderFg->setColor( QColor("black") ); + cbHeaderEnableBgColor->setChecked( true ); + kcbtnHeaderBg->setColor( QColor("lightgrey") ); + + cbEnableFooter->setChecked( true ); + leFooterRight->setText( "%U" ); + kcbtnFooterFg->setColor( QColor("black") ); + cbFooterEnableBgColor->setChecked( true ); + kcbtnFooterBg->setColor( QColor("lightgrey") ); + + // whatsthis + QString s = i18n("<p>Format of the page header. The following tags are supported:</p>"); + QString s1 = i18n( + "<ul><li><tt>%u</tt>: current user name</li>" + "<li><tt>%d</tt>: complete date/time in short format</li>" + "<li><tt>%D</tt>: complete date/time in long format</li>" + "<li><tt>%h</tt>: current time</li>" + "<li><tt>%y</tt>: current date in short format</li>" + "<li><tt>%Y</tt>: current date in long format</li>" + "<li><tt>%f</tt>: file name</li>" + "<li><tt>%U</tt>: full URL of the document</li>" + "<li><tt>%p</tt>: page number</li>" + "</ul><br>" + "<u>Note:</u> Do <b>not</b> use the '|' (vertical bar) character."); + QWhatsThis::add(leHeaderRight, s + s1 ); + QWhatsThis::add(leHeaderCenter, s + s1 ); + QWhatsThis::add(leHeaderLeft, s + s1 ); + s = i18n("<p>Format of the page footer. The following tags are supported:</p>"); + QWhatsThis::add(leFooterRight, s + s1 ); + QWhatsThis::add(leFooterCenter, s + s1 ); + QWhatsThis::add(leFooterLeft, s + s1 ); + + +} + +void KatePrintHeaderFooter::getOptions(QMap<QString,QString>& opts, bool ) +{ + opts["app-kate-hffont"] = strFont; + + opts["app-kate-useheader"] = (cbEnableHeader->isChecked() ? "true" : "false"); + opts["app-kate-headerfg"] = kcbtnHeaderFg->color().name(); + opts["app-kate-headerusebg"] = (cbHeaderEnableBgColor->isChecked() ? "true" : "false"); + opts["app-kate-headerbg"] = kcbtnHeaderBg->color().name(); + opts["app-kate-headerformat"] = leHeaderLeft->text() + "|" + leHeaderCenter->text() + "|" + leHeaderRight->text(); + + opts["app-kate-usefooter"] = (cbEnableFooter->isChecked() ? "true" : "false"); + opts["app-kate-footerfg"] = kcbtnFooterFg->color().name(); + opts["app-kate-footerusebg"] = (cbFooterEnableBgColor->isChecked() ? "true" : "false"); + opts["app-kate-footerbg"] = kcbtnFooterBg->color().name(); + opts["app-kate-footerformat"] = leFooterLeft->text() + "|" + leFooterCenter->text() + "|" + leFooterRight->text(); +} + +void KatePrintHeaderFooter::setOptions( const QMap<QString,QString>& opts ) +{ + QString v; + v = opts["app-kate-hffont"]; + strFont = v; + QFont f = font(); + if ( ! v.isEmpty() ) + { + if (!strFont.isEmpty()) + f.fromString( strFont ); + + lFontPreview->setFont( f ); + } + lFontPreview->setText( (f.family() + ", %1pt").arg( f.pointSize() ) ); + + v = opts["app-kate-useheader"]; + if ( ! v.isEmpty() ) + cbEnableHeader->setChecked( v == "true" ); + v = opts["app-kate-headerfg"]; + if ( ! v.isEmpty() ) + kcbtnHeaderFg->setColor( QColor( v ) ); + v = opts["app-kate-headerusebg"]; + if ( ! v.isEmpty() ) + cbHeaderEnableBgColor->setChecked( v == "true" ); + v = opts["app-kate-headerbg"]; + if ( ! v.isEmpty() ) + kcbtnHeaderBg->setColor( QColor( v ) ); + + QStringList tags = QStringList::split('|', opts["app-kate-headerformat"], "true"); + if (tags.count() == 3) + { + leHeaderLeft->setText(tags[0]); + leHeaderCenter->setText(tags[1]); + leHeaderRight->setText(tags[2]); + } + + v = opts["app-kate-usefooter"]; + if ( ! v.isEmpty() ) + cbEnableFooter->setChecked( v == "true" ); + v = opts["app-kate-footerfg"]; + if ( ! v.isEmpty() ) + kcbtnFooterFg->setColor( QColor( v ) ); + v = opts["app-kate-footerusebg"]; + if ( ! v.isEmpty() ) + cbFooterEnableBgColor->setChecked( v == "true" ); + v = opts["app-kate-footerbg"]; + if ( ! v.isEmpty() ) + kcbtnFooterBg->setColor( QColor( v ) ); + + tags = QStringList::split('|', opts["app-kate-footerformat"], "true"); + if (tags.count() == 3) + { + leFooterLeft->setText(tags[0]); + leFooterCenter->setText(tags[1]); + leFooterRight->setText(tags[2]); + } +} + +void KatePrintHeaderFooter::setHFFont() +{ + QFont fnt( lFontPreview->font() ); + // display a font dialog + if ( KFontDialog::getFont( fnt, false, this ) == KFontDialog::Accepted ) + { + // change strFont + strFont = fnt.toString(); + // set preview + lFontPreview->setFont( fnt ); + lFontPreview->setText( (fnt.family() + ", %1pt").arg( fnt.pointSize() ) ); + } +} + +//END KatePrintHeaderFooter + +//BEGIN KatePrintLayout + +KatePrintLayout::KatePrintLayout( KPrinter * /*printer*/, QWidget *parent, const char *name ) + : KPrintDialogPage( parent, name ) +{ + setTitle( i18n("L&ayout") ); + + QVBoxLayout *lo = new QVBoxLayout ( this ); + lo->setSpacing( KDialog::spacingHint() ); + + QHBox *hb = new QHBox( this ); + lo->addWidget( hb ); + QLabel *lSchema = new QLabel( i18n("&Schema:"), hb ); + cmbSchema = new QComboBox( false, hb ); + lSchema->setBuddy( cmbSchema ); + + cbDrawBackground = new QCheckBox( i18n("Draw bac&kground color"), this ); + lo->addWidget( cbDrawBackground ); + + cbEnableBox = new QCheckBox( i18n("Draw &boxes"), this ); + lo->addWidget( cbEnableBox ); + + gbBoxProps = new QGroupBox( 2, Qt::Horizontal, i18n("Box Properties"), this ); + lo->addWidget( gbBoxProps ); + + QLabel *lBoxWidth = new QLabel( i18n("W&idth:"), gbBoxProps ); + sbBoxWidth = new QSpinBox( 1, 100, 1, gbBoxProps ); + lBoxWidth->setBuddy( sbBoxWidth ); + + QLabel *lBoxMargin = new QLabel( i18n("&Margin:"), gbBoxProps ); + sbBoxMargin = new QSpinBox( 0, 100, 1, gbBoxProps ); + lBoxMargin->setBuddy( sbBoxMargin ); + + QLabel *lBoxColor = new QLabel( i18n("Co&lor:"), gbBoxProps ); + kcbtnBoxColor = new KColorButton( gbBoxProps ); + lBoxColor->setBuddy( kcbtnBoxColor ); + + connect( cbEnableBox, SIGNAL(toggled(bool)), gbBoxProps, SLOT(setEnabled(bool)) ); + + lo->addStretch( 1 ); + // set defaults: + sbBoxMargin->setValue( 6 ); + gbBoxProps->setEnabled( false ); + cmbSchema->insertStringList (KateFactory::self()->schemaManager()->list ()); + cmbSchema->setCurrentItem( 1 ); + + // whatsthis + // FIXME uncomment when string freeze is over +// QWhatsThis::add ( cmbSchema, i18n( +// "Select the color scheme to use for the print." ) ); + QWhatsThis::add( cbDrawBackground, i18n( + "<p>If enabled, the background color of the editor will be used.</p>" + "<p>This may be useful if your color scheme is designed for a dark background.</p>") ); + QWhatsThis::add( cbEnableBox, i18n( + "<p>If enabled, a box as defined in the properties below will be drawn " + "around the contents of each page. The Header and Footer will be separated " + "from the contents with a line as well.</p>") ); + QWhatsThis::add( sbBoxWidth, i18n( + "The width of the box outline" ) ); + QWhatsThis::add( sbBoxMargin, i18n( + "The margin inside boxes, in pixels") ); + QWhatsThis::add( kcbtnBoxColor, i18n( + "The line color to use for boxes") ); +} + +void KatePrintLayout::getOptions(QMap<QString,QString>& opts, bool ) +{ + opts["app-kate-colorscheme"] = cmbSchema->currentText(); + opts["app-kate-usebackground"] = cbDrawBackground->isChecked() ? "true" : "false"; + opts["app-kate-usebox"] = cbEnableBox->isChecked() ? "true" : "false"; + opts["app-kate-boxwidth"] = sbBoxWidth->cleanText(); + opts["app-kate-boxmargin"] = sbBoxMargin->cleanText(); + opts["app-kate-boxcolor"] = kcbtnBoxColor->color().name(); +} + +void KatePrintLayout::setOptions( const QMap<QString,QString>& opts ) +{ + QString v; + v = opts["app-kate-colorscheme"]; + if ( ! v.isEmpty() ) + cmbSchema->setCurrentItem( KateFactory::self()->schemaManager()->number( v ) ); + v = opts["app-kate-usebackground"]; + if ( ! v.isEmpty() ) + cbDrawBackground->setChecked( v == "true" ); + v = opts["app-kate-usebox"]; + if ( ! v.isEmpty() ) + cbEnableBox->setChecked( v == "true" ); + v = opts["app-kate-boxwidth"]; + if ( ! v.isEmpty() ) + sbBoxWidth->setValue( v.toInt() ); + v = opts["app-kate-boxmargin"]; + if ( ! v.isEmpty() ) + sbBoxMargin->setValue( v.toInt() ); + v = opts["app-kate-boxcolor"]; + if ( ! v.isEmpty() ) + kcbtnBoxColor->setColor( QColor( v ) ); +} +//END KatePrintLayout + +#include "kateprinter.moc" +#endif //!Q_WS_WIN + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateprinter.h b/kate/part/kateprinter.h new file mode 100644 index 000000000..f2fb0bc87 --- /dev/null +++ b/kate/part/kateprinter.h @@ -0,0 +1,136 @@ +/* + * This file is part of the KDE libraries + * Copyright (c) 2002 Michael Goffioul <kdeprint@swing.be> + * Complete rewrite on Sat Jun 15 2002 (c) Anders Lund <anders@alweb.dk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + **/ + +#ifndef __KATE_PRINTER_H__ +#define __KATE_PRINTER_H__ + +#include <kprintdialogpage.h> + +class KateDocument; + +class KColorButton; +class KPrinter; +class QCheckBox; +class QComboBox; +class QGroupBox; +class QLabel; +class QLineEdit; +class QSpinBox; + +class KatePrinter +{ + public: + static bool print (KateDocument *doc); +}; + +#ifndef Q_WS_WIN //TODO: reenable +//BEGIN Text settings +/* + Text settings page: + - [ ] Print Selection (enabled if there is a selection in the view) + - Print Line Numbers + () Smart () Yes () No +*/ +class KatePrintTextSettings : public KPrintDialogPage +{ + Q_OBJECT + public: + KatePrintTextSettings( KPrinter *printer, QWidget *parent=0, const char *name=0 ); + ~KatePrintTextSettings(){}; + + void getOptions(QMap<QString,QString>& opts, bool incldef = false); + void setOptions(const QMap<QString,QString>& opts); + + /* call if view has a selection, enables the seelction checkbox according to the arg */ + void enableSelection( bool ); + + private: + QCheckBox *cbSelection, *cbLineNumbers, *cbGuide; +}; +//END Text Settings + +//BEGIN Header/Footer +/* + Header & Footer page: + - enable header/footer + - header/footer props + o formats + o colors +*/ + +class KatePrintHeaderFooter : public KPrintDialogPage +{ + Q_OBJECT + public: + KatePrintHeaderFooter( KPrinter *printer, QWidget *parent=0, const char *name=0 ); + ~KatePrintHeaderFooter(){}; + + void getOptions(QMap<QString,QString>& opts, bool incldef = false); + void setOptions(const QMap<QString,QString>& opts); + + public slots: + void setHFFont(); + + private: + QCheckBox *cbEnableHeader, *cbEnableFooter; + QLabel *lFontPreview; + QString strFont; + QGroupBox *gbHeader, *gbFooter; + QLineEdit *leHeaderLeft, *leHeaderCenter, *leHeaderRight; + KColorButton *kcbtnHeaderFg, *kcbtnHeaderBg; + QCheckBox *cbHeaderEnableBgColor; + QLineEdit *leFooterLeft, *leFooterCenter, *leFooterRight; + KColorButton *kcbtnFooterFg, *kcbtnFooterBg; + QCheckBox *cbFooterEnableBgColor; +}; + +//END Header/Footer + +//BEGIN Layout +/* + Layout page: + - Color scheme + - Use Box + - Box properties + o Width + o Margin + o Color +*/ +class KatePrintLayout : public KPrintDialogPage +{ + Q_OBJECT + public: + KatePrintLayout( KPrinter *printer, QWidget *parent=0, const char *name=0 ); + ~KatePrintLayout(){}; + + void getOptions(QMap<QString,QString>& opts, bool incldef = false); + void setOptions(const QMap<QString,QString>& opts); + + private: + QComboBox *cmbSchema; + QCheckBox *cbEnableBox, *cbDrawBackground; + QGroupBox *gbBoxProps; + QSpinBox *sbBoxWidth, *sbBoxMargin; + KColorButton* kcbtnBoxColor; +}; +//END Layout +#endif //!Q_WS_WIN + +#endif diff --git a/kate/part/katerenderer.cpp b/kate/part/katerenderer.cpp new file mode 100644 index 000000000..60851481d --- /dev/null +++ b/kate/part/katerenderer.cpp @@ -0,0 +1,1032 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Hamish Rodda <rodda@kde.org> + Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "katerenderer.h" + +#include "katelinerange.h" +#include "katedocument.h" +#include "katearbitraryhighlight.h" +#include "kateconfig.h" +#include "katehighlight.h" +#include "katefactory.h" +#include "kateview.h" + +#include <kdebug.h> + +#include <qpainter.h> +#include <qpopupmenu.h> + +KateRenderer::KateRenderer(KateDocument* doc, KateView *view) + : m_doc(doc), m_view (view), m_caretStyle(KateRenderer::Insert) + , m_drawCaret(true) + , m_showSelections(true) + , m_showTabs(true) + , m_printerFriendly(false) +{ + KateFactory::self()->registerRenderer ( this ); + m_config = new KateRendererConfig (this); + + m_tabWidth = m_doc->config()->tabWidth(); + m_indentWidth = m_tabWidth; + if (m_doc->config()->configFlags() & KateDocumentConfig::cfSpaceIndent) + { + m_indentWidth = m_doc->config()->indentationWidth(); + } + + updateAttributes (); +} + +KateRenderer::~KateRenderer() +{ + delete m_config; + KateFactory::self()->deregisterRenderer ( this ); +} + +void KateRenderer::updateAttributes () +{ + m_schema = config()->schema (); + m_attributes = m_doc->highlight()->attributes (m_schema); +} + +KateAttribute* KateRenderer::attribute(uint pos) +{ + if (pos < m_attributes->size()) + return &m_attributes->at(pos); + + return &m_attributes->at(0); +} + +void KateRenderer::setDrawCaret(bool drawCaret) +{ + m_drawCaret = drawCaret; +} + +void KateRenderer::setCaretStyle(KateRenderer::caretStyles style) +{ + m_caretStyle = style; +} + +void KateRenderer::setShowTabs(bool showTabs) +{ + m_showTabs = showTabs; +} + +void KateRenderer::setTabWidth(int tabWidth) +{ + m_tabWidth = tabWidth; +} + +bool KateRenderer::showIndentLines() const +{ + return m_config->showIndentationLines(); +} + +void KateRenderer::setShowIndentLines(bool showIndentLines) +{ + m_config->setShowIndentationLines(showIndentLines); +} + +void KateRenderer::setIndentWidth(int indentWidth) +{ + m_indentWidth = m_tabWidth; + if (m_doc->config()->configFlags() & KateDocumentConfig::cfSpaceIndent) + { + m_indentWidth = indentWidth; + } +} + +void KateRenderer::setShowSelections(bool showSelections) +{ + m_showSelections = showSelections; +} + +void KateRenderer::increaseFontSizes() +{ + QFont f ( *config()->font () ); + f.setPointSize (f.pointSize ()+1); + + config()->setFont (f); +} + +void KateRenderer::decreaseFontSizes() +{ + QFont f ( *config()->font () ); + + if ((f.pointSize ()-1) > 0) + f.setPointSize (f.pointSize ()-1); + + config()->setFont (f); +} + +bool KateRenderer::isPrinterFriendly() const +{ + return m_printerFriendly; +} + +void KateRenderer::setPrinterFriendly(bool printerFriendly) +{ + m_printerFriendly = printerFriendly; + setShowTabs(false); + setShowSelections(false); + setDrawCaret(false); +} + +bool KateRenderer::paintTextLineBackground(QPainter& paint, int line, bool isCurrentLine, int xStart, int xEnd) +{ + if (isPrinterFriendly()) + return false; + + // font data + KateFontStruct *fs = config()->fontStruct(); + + // Normal background color + QColor backgroundColor( config()->backgroundColor() ); + + bool selectionPainted = false; + if (showSelections() && m_view->lineSelected(line)) + { + backgroundColor = config()->selectionColor(); + selectionPainted = true; + } + else + { + // paint the current line background if we're on the current line + if (isCurrentLine) + backgroundColor = config()->highlightedLineColor(); + + // Check for mark background + int markRed = 0, markGreen = 0, markBlue = 0, markCount = 0; + + // Retrieve marks for this line + uint mrk = m_doc->mark( line ); + if (mrk) + { + for (uint bit = 0; bit < 32; bit++) + { + KTextEditor::MarkInterface::MarkTypes markType = (KTextEditor::MarkInterface::MarkTypes)(1<<bit); + if (mrk & markType) + { + QColor markColor = config()->lineMarkerColor(markType); + + if (markColor.isValid()) { + markCount++; + markRed += markColor.red(); + markGreen += markColor.green(); + markBlue += markColor.blue(); + } + } + } // for + } // Marks + + if (markCount) { + markRed /= markCount; + markGreen /= markCount; + markBlue /= markCount; + backgroundColor.setRgb( + int((backgroundColor.red() * 0.9) + (markRed * 0.1)), + int((backgroundColor.green() * 0.9) + (markGreen * 0.1)), + int((backgroundColor.blue() * 0.9) + (markBlue * 0.1)) + ); + } + } // background preprocessing + + // Draw line background + paint.fillRect(0, 0, xEnd - xStart, fs->fontHeight, backgroundColor); + + return selectionPainted; +} + +void KateRenderer::paintWhitespaceMarker(QPainter &paint, uint x, uint y) +{ + QPen penBackup( paint.pen() ); + paint.setPen( config()->tabMarkerColor() ); + paint.drawPoint(x, y); + paint.drawPoint(x + 1, y); + paint.drawPoint(x, y - 1); + paint.setPen( penBackup ); +} + + +void KateRenderer::paintIndentMarker(QPainter &paint, uint x, uint row) +{ + QPen penBackup( paint.pen() ); + paint.setPen( config()->tabMarkerColor() ); + + const int top = paint.window().top(); + const int bottom = paint.window().bottom(); + const int h = bottom - top + 1; + + // Dot padding. + int pad = 0; + if(row & 1 && h & 1) pad = 1; + + for(int i = top; i <= bottom; i++) + { + if((i + pad) & 1) + { + paint.drawPoint(x + 2, i); + } + } + + paint.setPen( penBackup ); +} + + +void KateRenderer::paintTextLine(QPainter& paint, const KateLineRange* range, int xStart, int xEnd, const KateTextCursor* cursor, const KateBracketRange* bracketmark) +{ + int line = range->line; + + // textline + KateTextLine::Ptr textLine = m_doc->kateTextLine(line); + if (!textLine) + return; + + bool showCursor = drawCaret() && cursor && range->includesCursor(*cursor); + + KateSuperRangeList& superRanges = m_doc->arbitraryHL()->rangesIncluding(range->line, 0); + + int minIndent = 0; + + // A bit too verbose for my tastes + // Re-write a bracketmark class? put into its own function? add more helper constructors to the range stuff? + // Also, need a light-weight arbitraryhighlightrange class for static stuff + KateArbitraryHighlightRange* bracketStartRange (0L); + KateArbitraryHighlightRange* bracketEndRange (0L); + if (bracketmark && bracketmark->isValid()) { + if (range->includesCursor(bracketmark->start())) { + KateTextCursor startend = bracketmark->start(); + startend.setCol(startend.col()+1); + bracketStartRange = new KateArbitraryHighlightRange(m_doc, bracketmark->start(), startend); + bracketStartRange->setBGColor(config()->highlightedBracketColor()); + bracketStartRange->setBold(true); + superRanges.append(bracketStartRange); + } + + if (range->includesCursor(bracketmark->end())) { + KateTextCursor endend = bracketmark->end(); + endend.setCol(endend.col()+1); + bracketEndRange = new KateArbitraryHighlightRange(m_doc, bracketmark->end(), endend); + bracketEndRange->setBGColor(config()->highlightedBracketColor()); + bracketEndRange->setBold(true); + superRanges.append(bracketEndRange); + } + + Q_ASSERT(bracketmark->start().line() <= bracketmark->end().line()); + if (bracketmark->start().line() < line && bracketmark->end().line() >= line) + { + minIndent = bracketmark->getMinIndent(); + } + } + + + // length, chars + raw attribs + uint len = textLine->length(); + uint oldLen = len; + + // should the cursor be painted (if it is in the current xstart - xend range) + bool cursorVisible = false; + int cursorMaxWidth = 0; + + // font data + KateFontStruct * fs = config()->fontStruct(); + + // Paint selection background as the whole line is selected + // selection startcol/endcol calc + bool hasSel = false; + uint startSel = 0; + uint endSel = 0; + + // was the selection background already completely painted ? + bool selectionPainted = false; + bool isCurrentLine = (cursor && range->includesCursor(*cursor)); + selectionPainted = paintTextLineBackground(paint, line, isCurrentLine, xStart, xEnd); + if (selectionPainted) + { + hasSel = true; + startSel = 0; + endSel = len + 1; + } + + int startcol = range->startCol; + if (startcol > (int)len) + startcol = len; + + if (startcol < 0) + startcol = 0; + + int endcol = range->wrap ? range->endCol : -1; + if (endcol < 0) + len = len - startcol; + else + len = endcol - startcol; + + // text attribs font/style data + KateAttribute* attr = m_doc->highlight()->attributes(m_schema)->data(); + + const QColor *cursorColor = &attr[0].textColor(); + + // Start arbitrary highlighting + KateTextCursor currentPos(line, startcol); + superRanges.firstBoundary(¤tPos); + + if (showSelections() && !selectionPainted) + hasSel = getSelectionBounds(line, oldLen, startSel, endSel); + + // Draws the dashed underline at the start of a folded block of text. + if (range->startsInvisibleBlock) { + paint.setPen(QPen(config()->wordWrapMarkerColor(), 1, Qt::DashLine)); + paint.drawLine(0, fs->fontHeight - 1, xEnd - xStart, fs->fontHeight - 1); + } + + // draw word-wrap-honor-indent filling + if (range->xOffset() && range->xOffset() > xStart) + { + paint.fillRect(0, 0, range->xOffset() - xStart, fs->fontHeight, + QBrush(config()->wordWrapMarkerColor(), QBrush::DiagCrossPattern)); + } + + // painting loop + uint xPos = range->xOffset(); + int cursorXPos = 0; + + // Optimisation to quickly draw an empty line of text + if (len < 1) + { + if (showCursor && (cursor->col() >= int(startcol))) + { + cursorVisible = true; + cursorXPos = xPos + cursor->col() * fs->myFontMetrics.width(QChar(' ')); + } + } + else + { + bool isIMSel = false; + bool isIMEdit = false; + + bool isSel = false; + + KateAttribute customHL; + + const QColor *curColor = 0; + const QColor *oldColor = 0; + + KateAttribute* oldAt = &attr[0]; + + uint oldXPos = xPos; + uint xPosAfter = xPos; + + KateAttribute currentHL; + + uint blockStartCol = startcol; + uint curCol = startcol; + uint nextCol = curCol + 1; + + // text + attrib data from line + const uchar *textAttributes = textLine->attributes (); + bool noAttribs = !textAttributes; + + // adjust to startcol ;) + textAttributes = textAttributes + startcol; + + uint atLen = m_doc->highlight()->attributes(m_schema)->size(); + + // Determine if we have trailing whitespace and store the column + // if lastChar == -1, set to 0, if lastChar exists, increase by one + uint trailingWhitespaceColumn = textLine->lastChar() + 1; + const uint lastIndentColumn = textLine->firstChar(); + + // Could be precomputed. + const uint spaceWidth = fs->width (QChar(' '), false, false, m_tabWidth); + + // Get current x position. + int curPos = textLine->cursorX(curCol, m_tabWidth); + + while (curCol - startcol < len) + { + // make sure curPos is updated correctly. + // ### if uncommented, causes an O(n^2) behaviour + //Q_ASSERT(curPos == textLine->cursorX(curCol, m_tabWidth)); + + QChar curChar = textLine->string()[curCol]; + // Decide if this character is a tab - we treat the spacing differently + // TODO: move tab width calculation elsewhere? + bool isTab = curChar == QChar('\t'); + + // Determine current syntax highlighting attribute + // A bit legacy but doesn't need to change + KateAttribute* curAt = (noAttribs || ((*textAttributes) >= atLen)) ? &attr[0] : &attr[*textAttributes]; + + // X position calculation. Incorrect for fonts with non-zero leftBearing() and rightBearing() results. + // TODO: make internal charWidth() function, use QFontMetrics::charWidth(). + xPosAfter += curAt->width(*fs, curChar, m_tabWidth); + + // Tab special treatment, move to charWidth(). + if (isTab) + xPosAfter -= (xPosAfter % curAt->width(*fs, curChar, m_tabWidth)); + + // Only draw after the starting X value + // Haha, this was always wrong, due to the use of individual char width calculations...?? :( + if ((int)xPosAfter >= xStart) + { + // Determine if we're in a selection and should be drawing it + isSel = (showSelections() && hasSel && (curCol >= startSel) && (curCol < endSel)); + + // input method edit area + isIMEdit = m_view && m_view->isIMEdit( line, curCol ); + + // input method selection + isIMSel = m_view && m_view->isIMSelection( line, curCol ); + + // Determine current color, taking into account selection + curColor = isSel ? &(curAt->selectedTextColor()) : &(curAt->textColor()); + + // Incorporate in arbitrary highlighting + if (curAt != oldAt || curColor != oldColor || (superRanges.count() && superRanges.currentBoundary() && *(superRanges.currentBoundary()) == currentPos)) { + if (superRanges.count() && superRanges.currentBoundary() && *(superRanges.currentBoundary()) == currentPos) + customHL = KateArbitraryHighlightRange::merge(superRanges.rangesIncluding(currentPos)); + + KateAttribute hl = customHL; + + hl += *curAt; + + // use default highlighting color if we haven't defined one above. + if (!hl.itemSet(KateAttribute::TextColor)) + hl.setTextColor(*curColor); + + if (!isSel) + paint.setPen(hl.textColor()); + else + paint.setPen(hl.selectedTextColor()); + + paint.setFont(hl.font(*currentFont())); + + if (superRanges.currentBoundary() && *(superRanges.currentBoundary()) == currentPos) + superRanges.nextBoundary(); + + currentHL = hl; + } + + // Determine whether we can delay painting to draw a block of similarly formatted + // characters or not + // Reasons for NOT delaying the drawing until the next character + // You have to detect the change one character in advance. + // TODO: KateAttribute::canBatchRender() + bool renderNow = false; + if ((isTab) + // formatting has changed OR + || (superRanges.count() && superRanges.currentBoundary() && *(superRanges.currentBoundary()) == KateTextCursor(line, nextCol)) + + // it is the end of the line OR + || (curCol - startcol >= len - 1) + + // the rest of the line is trailing whitespace OR + || (curCol + 1 >= trailingWhitespaceColumn) + + // indentation lines OR + || (showIndentLines() && curCol < lastIndentColumn) + + // the x position is past the end OR + || ((int)xPos > xEnd) + + // it is a different attribute OR + || (!noAttribs && curAt != &attr[*(textAttributes+1)]) + + // the selection boundary was crossed OR + || (isSel != (hasSel && (nextCol >= startSel) && (nextCol < endSel))) + + // the next char is a tab (removed the "and this isn't" because that's dealt with above) + // i.e. we have to draw the current text so the tab can be rendered as above. + || (textLine->string()[nextCol] == QChar('\t')) + + // input method edit area + || ( m_view && (isIMEdit != m_view->isIMEdit( line, nextCol )) ) + + // input method selection + || ( m_view && (isIMSel != m_view->isIMSelection( line, nextCol )) ) + ) + { + renderNow = true; + } + + if (renderNow) + { + if (!isPrinterFriendly()) + { + bool paintBackground = true; + uint width = xPosAfter - oldXPos; + QColor fillColor; + + if (isIMSel && !isTab) + { + // input method selection + fillColor = m_view->colorGroup().color(QColorGroup::Foreground); + } + else if (isIMEdit && !isTab) + { + // XIM support + // input method edit area + const QColorGroup& cg = m_view->colorGroup(); + int h1, s1, v1, h2, s2, v2; + cg.color( QColorGroup::Base ).hsv( &h1, &s1, &v1 ); + cg.color( QColorGroup::Background ).hsv( &h2, &s2, &v2 ); + fillColor.setHsv( h1, s1, ( v1 + v2 ) / 2 ); + } + else if (!selectionPainted && (isSel || currentHL.itemSet(KateAttribute::BGColor))) + { + if (isSel) + { + fillColor = config()->selectionColor(); + + // If this is the last block of text, fill up to the end of the line if the + // selection stretches that far + if ((curCol >= len - 1) && m_view->lineEndSelected (line, endcol)) + width = xEnd - oldXPos; + } + else + { + fillColor = currentHL.bgColor(); + } + } + else + { + paintBackground = false; + } + + if (paintBackground) + paint.fillRect(oldXPos - xStart, 0, width, fs->fontHeight, fillColor); + + if (isIMSel && paintBackground && !isTab) + { + paint.save(); + paint.setPen( m_view->colorGroup().color( QColorGroup::BrightText ) ); + } + + // Draw indentation markers. + if (showIndentLines() && curCol < lastIndentColumn) + { + // Draw multiple guides when tab width greater than indent width. + const int charWidth = isTab ? m_tabWidth - curPos % m_tabWidth : 1; + + // Do not draw indent guides on the first line. + int i = 0; + if (curPos == 0 || curPos % m_indentWidth > 0) + i = m_indentWidth - curPos % m_indentWidth; + + for (; i < charWidth; i += m_indentWidth) + { + // In most cases this is done one or zero times. + paintIndentMarker(paint, xPos - xStart + i * spaceWidth, line); + + // Draw highlighted line. + if (curPos+i == minIndent) + { + paintIndentMarker(paint, xPos - xStart + 1 + i * spaceWidth, line+1); + } + } + } + } + + // or we will see no text ;) + int y = fs->fontAscent; + + // make sure we redraw the right character groups on attrib/selection changes + // Special case... de-special case some of it + if (isTab || (curCol >= trailingWhitespaceColumn)) + { + // Draw spaces too, because it might be eg. underlined + static QString spaces; + if (int(spaces.length()) != m_tabWidth) + spaces.fill(' ', m_tabWidth); + + paint.drawText(oldXPos-xStart, y, isTab ? spaces : QString(" ")); + + if (showTabs()) + { + // trailing spaces and tabs may also have to be different options. + // if( curCol >= lastIndentColumn ) + paintWhitespaceMarker(paint, xPos - xStart, y); + } + + // variable advancement + blockStartCol = nextCol; + oldXPos = xPosAfter; + } + else + { + // Here's where the money is... + paint.drawText(oldXPos-xStart, y, textLine->string(), blockStartCol, nextCol-blockStartCol); + + // Draw preedit's underline + if (isIMEdit) { + QRect r( oldXPos - xStart, 0, xPosAfter - oldXPos, fs->fontHeight ); + paint.drawLine( r.bottomLeft(), r.bottomRight() ); + } + + // Put pen color back + if (isIMSel) paint.restore(); + + // We're done drawing? + if ((int)xPos > xEnd) + break; + + // variable advancement + blockStartCol = nextCol; + oldXPos = xPosAfter; + //oldS = s+1; + } + } // renderNow + + // determine cursor X position + if (showCursor && (cursor->col() == int(curCol))) + { + cursorVisible = true; + cursorXPos = xPos; + cursorMaxWidth = xPosAfter - xPos; + cursorColor = &curAt->textColor(); + } + } // xPosAfter >= xStart + else + { + // variable advancement + blockStartCol = nextCol; + oldXPos = xPosAfter; + } + + // increase xPos + xPos = xPosAfter; + + // increase attribs pos + textAttributes++; + + // to only switch font/color if needed + oldAt = curAt; + oldColor = curColor; + + // col move + curCol++; + nextCol++; + currentPos.setCol(currentPos.col() + 1); + + // Update the current indentation pos. + if (isTab) + { + curPos += m_tabWidth - (curPos % m_tabWidth); + } + else + { + curPos++; + } + } + + // If this line has a partial selection that's the start of a multi-line selection, + // we have to fill areas on the right side of the text with the selection color. + if (showSelections() && hasSel && !selectionPainted && xStart >= (int)xPos && m_view->lineEndSelected(line, -1)) + { + paint.fillRect(0, 0, xEnd-xStart, fs->fontHeight, config()->selectionColor()); + } + + // Determine cursor position (if it is not within the range being drawn) + if (showCursor && (cursor->col() >= int(curCol))) + { + cursorVisible = true; + cursorXPos = xPos + (cursor->col() - int(curCol)) * fs->myFontMetrics.width(QChar(' ')); + cursorMaxWidth = xPosAfter - xPos; + cursorColor = &oldAt->textColor(); + } + } + + // Paint cursor + if (cursorVisible) + { + uint cursorWidth = (caretStyle() == Replace && (cursorMaxWidth > 2)) ? cursorMaxWidth : 2; + paint.fillRect(cursorXPos-xStart, 0, cursorWidth, fs->fontHeight, *cursorColor); + } + + // show word wrap marker if desirable + if (!isPrinterFriendly() && config()->wordWrapMarker() && fs->fixedPitch()) + { + paint.setPen( config()->wordWrapMarkerColor() ); + int _x = m_doc->config()->wordWrapAt() * fs->myFontMetrics.width('x') - xStart; + paint.drawLine( _x,0,_x,fs->fontHeight ); + } + + // cleanup ;) + delete bracketStartRange; + delete bracketEndRange; +} + +uint KateRenderer::textWidth(const KateTextLine::Ptr &textLine, int cursorCol) +{ + if (!textLine) + return 0; + + const int len = textLine->length(); + + if (cursorCol < 0) + cursorCol = len; + + KateFontStruct *fs = config()->fontStruct(); + + const QChar *unicode = textLine->text(); + const QString &textString = textLine->string(); + + int x = 0; + int width; + for (int z = 0; z < cursorCol; z++) { + KateAttribute* a = attribute(textLine->attribute(z)); + + if (z < len) { + width = a->width(*fs, textString, z, m_tabWidth); + } else { + // DF: commented out. It happens all the time. + //Q_ASSERT(!m_doc->wrapCursor()); + width = a->width(*fs, QChar(' '), m_tabWidth); + } + + x += width; + + if (z < len && unicode[z] == QChar('\t')) + x -= x % width; + } + + return x; +} + +uint KateRenderer::textWidth(const KateTextLine::Ptr &textLine, uint startcol, uint maxwidth, bool *needWrap, int *endX) +{ + KateFontStruct *fs = config()->fontStruct(); + uint x = 0; + uint endcol = startcol; + int endX2 = 0; + int lastWhiteSpace = -1; + int lastWhiteSpaceX = -1; + + // used to not wrap a solitary word off the first line, ie. the + // first line should not wrap until some characters have been displayed if possible + bool foundNonWhitespace = startcol != 0; + bool foundWhitespaceAfterNonWhitespace = startcol != 0; + + *needWrap = false; + + const uint len = textLine->length(); + const QChar *unicode = textLine->text(); + const QString &textString = textLine->string(); + + uint z = startcol; + for (; z < len; z++) + { + KateAttribute* a = attribute(textLine->attribute(z)); + int width = a->width(*fs, textString, z, m_tabWidth); + Q_ASSERT(width); + x += width; + + // How should tabs be treated when they word-wrap on a print-out? + // if startcol != 0, this messes up (then again, word wrapping messes up anyway) + if (unicode[z] == QChar('\t')) + x -= x % width; + + if (unicode[z].isSpace()) + { + lastWhiteSpace = z+1; + lastWhiteSpaceX = x; + + if (foundNonWhitespace) + foundWhitespaceAfterNonWhitespace = true; + } + else + { + if (!foundWhitespaceAfterNonWhitespace) { + foundNonWhitespace = true; + + lastWhiteSpace = z+1; + lastWhiteSpaceX = x; + } + } + + if (x <= maxwidth) + { + if (lastWhiteSpace > -1) + { + endcol = lastWhiteSpace; + endX2 = lastWhiteSpaceX; + } + else + { + endcol = z+1; + endX2 = x; + } + } + else if (z == startcol) + { + // require a minimum of 1 character advancement per call, even if it means drawing gets cut off + // (geez gideon causes troubles with starting the views very small) + endcol = z+1; + endX2 = x; + } + + if (x >= maxwidth) + { + *needWrap = true; + break; + } + } + + if (*needWrap) + { + if (endX) + *endX = endX2; + + return endcol; + } + else + { + if (endX) + *endX = x; + + return z+1; + } +} + +uint KateRenderer::textWidth(const KateTextCursor &cursor) +{ + int line = kMin(kMax(0, cursor.line()), (int)m_doc->numLines() - 1); + int col = kMax(0, cursor.col()); + + return textWidth(m_doc->kateTextLine(line), col); +} + +uint KateRenderer::textWidth( KateTextCursor &cursor, int xPos, uint startCol) +{ + bool wrapCursor = m_view->wrapCursor(); + int x, oldX; + + KateFontStruct *fs = config()->fontStruct(); + + if (cursor.line() < 0) cursor.setLine(0); + if (cursor.line() > (int)m_doc->lastLine()) cursor.setLine(m_doc->lastLine()); + KateTextLine::Ptr textLine = m_doc->kateTextLine(cursor.line()); + + if (!textLine) return 0; + + const uint len = textLine->length(); + const QChar *unicode = textLine->text(); + const QString &textString = textLine->string(); + + x = oldX = 0; + uint z = startCol; + while (x < xPos && (!wrapCursor || z < len)) { + oldX = x; + + KateAttribute* a = attribute(textLine->attribute(z)); + + int width = 0; + + if (z < len) + width = a->width(*fs, textString, z, m_tabWidth); + else + width = a->width(*fs, QChar(' '), m_tabWidth); + + x += width; + + if (z < len && unicode[z] == QChar('\t')) + x -= x % width; + + z++; + } + if (xPos - oldX < x - xPos && z > 0) { + z--; + x = oldX; + } + cursor.setCol(z); + return x; +} + +const QFont *KateRenderer::currentFont() +{ + return config()->font(); +} + +const QFontMetrics* KateRenderer::currentFontMetrics() +{ + return config()->fontMetrics(); +} + +uint KateRenderer::textPos(uint line, int xPos, uint startCol, bool nearest) +{ + return textPos(m_doc->kateTextLine(line), xPos, startCol, nearest); +} + +uint KateRenderer::textPos(const KateTextLine::Ptr &textLine, int xPos, uint startCol, bool nearest) +{ + Q_ASSERT(textLine); + if (!textLine) + return 0; + + KateFontStruct *fs = config()->fontStruct(); + + int x, oldX; + x = oldX = 0; + + uint z = startCol; + const uint len = textLine->length(); + const QString &textString = textLine->string(); + + while ( (x < xPos) && (z < len)) { + oldX = x; + + KateAttribute* a = attribute(textLine->attribute(z)); + x += a->width(*fs, textString, z, m_tabWidth); + + z++; + } + if ( ( (! nearest) || xPos - oldX < x - xPos ) && z > 0 ) { + z--; + // newXPos = oldX; + }// else newXPos = x; + return z; +} + +uint KateRenderer::fontHeight() +{ + return config()->fontStruct ()->fontHeight; +} + +uint KateRenderer::documentHeight() +{ + return m_doc->numLines() * fontHeight(); +} + +bool KateRenderer::getSelectionBounds(uint line, uint lineLength, uint &start, uint &end) +{ + bool hasSel = false; + + if (m_view->hasSelection() && !m_view->blockSelectionMode()) + { + if (m_view->lineIsSelection(line)) + { + start = m_view->selStartCol(); + end = m_view->selEndCol(); + hasSel = true; + } + else if ((int)line == m_view->selStartLine()) + { + start = m_view->selStartCol(); + end = lineLength; + hasSel = true; + } + else if ((int)line == m_view->selEndLine()) + { + start = 0; + end = m_view->selEndCol(); + hasSel = true; + } + } + else if (m_view->lineHasSelected(line)) + { + start = m_view->selStartCol(); + end = m_view->selEndCol(); + hasSel = true; + } + + if (start > end) { + int temp = end; + end = start; + start = temp; + } + + return hasSel; +} + +void KateRenderer::updateConfig () +{ + // update the attibute list pointer + updateAttributes (); + + if (m_view) + m_view->updateRendererConfig(); +} + +uint KateRenderer::spaceWidth() +{ + return attribute(0)->width(*config()->fontStruct(), QChar(' '), m_tabWidth); +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katerenderer.h b/kate/part/katerenderer.h new file mode 100644 index 000000000..fc4865ab1 --- /dev/null +++ b/kate/part/katerenderer.h @@ -0,0 +1,272 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Hamish Rodda <rodda@kde.org> + Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __KATE_RENDERER_H__ +#define __KATE_RENDERER_H__ + +#include "katecursor.h" +#include "kateattribute.h" +#include "katetextline.h" + +#include <qfont.h> +#include <qfontmetrics.h> + +class KateDocument; +class KateView; +class KateLineRange; +class KateRendererConfig; + +/** + * Handles all of the work of rendering the text + * (used for the views and printing) + * + **/ +class KateRenderer +{ +public: + /** + * Style of Caret (Insert or Replace mode) + */ + enum caretStyles { + Insert, + Replace + }; + + /** + * Constructor + * @param doc document to render + * @param view view which is output (0 for example for rendering to print) + */ + KateRenderer(KateDocument* doc, KateView *view = 0); + + /** + * Destructor + */ + ~KateRenderer(); + + /** + * update the highlighting attributes + * (for example after an hl change or after hl config changed) + */ + void updateAttributes (); + + /** + * Determine whether the caret (text cursor) will be drawn. + * @return should it be drawn? + */ + inline bool drawCaret() const { return m_drawCaret; } + + /** + * Set whether the caret (text cursor) will be drawn. + * @param drawCaret should caret be drawn? + */ + void setDrawCaret(bool drawCaret); + + /** + * The style of the caret (text cursor) to be painted. + * @return caretStyle + */ + inline KateRenderer::caretStyles caretStyle() const { return m_caretStyle; } + + /** + * Set the style of caret to be painted. + * @param style style to set + */ + void setCaretStyle(KateRenderer::caretStyles style); + + /** + * @returns whether tabs should be shown (ie. a small mark + * drawn to identify a tab) + * @return tabs should be shown + */ + inline bool showTabs() const { return m_showTabs; } + + /** + * Set whether a mark should be painted to help identifying tabs. + * @param showTabs show the tabs? + */ + void setShowTabs(bool showTabs); + + /** + * Sets the width of the tab. Helps performance. + * @param tabWidth new tab width + */ + void setTabWidth(int tabWidth); + + /** + * @returns whether indent lines should be shown + * @return indent lines should be shown + */ + bool showIndentLines() const; + + /** + * Set whether a guide should be painted to help identifying indent lines. + * @param showLines show the indent lines? + */ + void setShowIndentLines(bool showLines); + + /** + * Sets the width of the tab. Helps performance. + * @param indentWidth new indent width + */ + void setIndentWidth(int indentWidth); + + /** + * Show the view's selection? + * @return show sels? + */ + inline bool showSelections() const { return m_showSelections; } + + /** + * Set whether the view's selections should be shown. + * The default is true. + * @param showSelections show the selections? + */ + void setShowSelections(bool showSelections); + + /** + * Change to a different font (soon to be font set?) + */ + void increaseFontSizes(); + void decreaseFontSizes(); + const QFont* currentFont(); + const QFontMetrics* currentFontMetrics(); + + /** + * @return whether the renderer is configured to paint in a + * printer-friendly fashion. + */ + bool isPrinterFriendly() const; + + /** + * Configure this renderer to paint in a printer-friendly fashion. + * + * Sets the other options appropriately if true. + */ + void setPrinterFriendly(bool printerFriendly); + + /** + * Text width & height calculation functions... + */ + + // Width calculators + uint spaceWidth(); + uint textWidth(const KateTextLine::Ptr &, int cursorCol); + uint textWidth(const KateTextLine::Ptr &textLine, uint startcol, uint maxwidth, bool *needWrap, int *endX = 0); + uint textWidth(const KateTextCursor &cursor); + + // Cursor constrainer + uint textWidth(KateTextCursor &cursor, int xPos, uint startCol = 0); + + // Column calculators + /** + * @return the index of the character at the horixontal position @p xpos + * in @p line. + * + * If @p nearest is true, the character starting nearest to + * @p xPos is returned. If @p nearest is false, the index of the character + * containing @p xPos is returned. + **/ + uint textPos(uint line, int xPos, uint startCol = 0, bool nearest=true); + /** + * @overload + */ + uint textPos(const KateTextLine::Ptr &, int xPos, uint startCol = 0, bool nearest=true); + + // Font height + uint fontHeight(); + + // Document height + uint documentHeight(); + + // Selection boundaries + bool getSelectionBounds(uint line, uint lineLength, uint &start, uint &end); + + /** + * This is the ultimate function to perform painting of a text line. + * (supports startcol/endcol, startx/endx) + * + * The text line is painted from the upper limit of (0,0). To move that, + * apply a transform to your painter. + */ + void paintTextLine(QPainter& paint, const KateLineRange* range, int xStart, int xEnd, const KateTextCursor* cursor = 0L, const KateBracketRange* bracketmark = 0L); + + /** + * Paint the background of a line + * + * Split off from the main @ref paintTextLine method to make it smaller. As it's being + * called only once per line it shouldn't noticably affect performance and it + * helps readability a LOT. + * + * @return whether the selection has been painted or not + */ + bool paintTextLineBackground(QPainter& paint, int line, bool isCurrentLine, int xStart, int xEnd); + + /** + * This takes an in index, and returns all the attributes for it. + * For example, if you have a ktextline, and want the KateAttribute + * for a given position, do: + * + * attribute(myktextline->attribute(position)); + */ + KateAttribute* attribute(uint pos); + + private: + /** + * Paint a whitespace marker on position (x, y). + * + * Currently only used by the tabs, but it will also be used for highlighting trailing whitespace + */ + void paintWhitespaceMarker(QPainter &paint, uint x, uint y); + + /** Paint a SciTE-like indent marker. */ + void paintIndentMarker(QPainter &paint, uint x, uint y); + + KateDocument* m_doc; + KateView *m_view; + + // cache of config values + int m_tabWidth; + int m_indentWidth; + uint m_schema; + + // some internal flags + KateRenderer::caretStyles m_caretStyle; + bool m_drawCaret; + bool m_showSelections; + bool m_showTabs; + bool m_printerFriendly; + + QMemArray<KateAttribute> *m_attributes; + + /** + * Configuration + */ + public: + inline KateRendererConfig *config () { return m_config; }; + + void updateConfig (); + + private: + KateRendererConfig *m_config; +}; + +#endif diff --git a/kate/part/kateschema.cpp b/kate/part/kateschema.cpp new file mode 100644 index 000000000..e0a5d5dba --- /dev/null +++ b/kate/part/kateschema.cpp @@ -0,0 +1,1611 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001-2003 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2002, 2003 Anders Lund <anders.lund@lund.tdcadsl.dk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +//BEGIN Includes +#include "kateschema.h" +#include "kateschema.moc" + +#include "kateconfig.h" +#include "katedocument.h" +#include "katefactory.h" +#include "kateview.h" +#include "katerenderer.h" + +#include <klocale.h> +#include <kdialogbase.h> +#include <kcolorbutton.h> +#include <kcombobox.h> +#include <kinputdialog.h> +#include <kfontdialog.h> +#include <kdebug.h> +#include <kiconloader.h> +#include <kmessagebox.h> +#include <kpopupmenu.h> +#include <kcolordialog.h> +#include <kapplication.h> +#include <kaboutdata.h> +#include <ktexteditor/markinterface.h> + +#include <qbuttongroup.h> +#include <qcheckbox.h> +#include <qptrcollection.h> +#include <qdialog.h> +#include <qgrid.h> +#include <qgroupbox.h> +#include <qlabel.h> +#include <qtextcodec.h> +#include <qlayout.h> +#include <qlineedit.h> +#include <qheader.h> +#include <qlistbox.h> +#include <qhbox.h> +#include <qpainter.h> +#include <qobjectlist.h> +#include <qpixmap.h> +#include <qpushbutton.h> +#include <qradiobutton.h> +#include <qspinbox.h> +#include <qstringlist.h> +#include <qtabwidget.h> +#include <qvbox.h> +#include <qvgroupbox.h> +#include <qwhatsthis.h> +//END + +//BEGIN KateStyleListViewItem decl +/* + QListViewItem subclass to display/edit a style, bold/italic is check boxes, + normal and selected colors are boxes, which will display a color chooser when + activated. + The context name for the style will be drawn using the editor default font and + the chosen colors. + This widget id designed to handle the default as well as the individual hl style + lists. + This widget is designed to work with the KateStyleListView class exclusively. + Added by anders, jan 23 2002. +*/ +class KateStyleListItem : public QListViewItem +{ + public: + KateStyleListItem( QListViewItem *parent=0, const QString & stylename=0, + class KateAttribute* defaultstyle=0, class KateHlItemData *data=0 ); + KateStyleListItem( QListView *parent, const QString & stylename=0, + class KateAttribute* defaultstyle=0, class KateHlItemData *data=0 ); + ~KateStyleListItem() { if (st) delete is; }; + + /* mainly for readability */ + enum Property { ContextName, Bold, Italic, Underline, Strikeout, Color, SelColor, BgColor, SelBgColor, UseDefStyle }; + + /* initializes the style from the default and the hldata */ + void initStyle(); + /* updates the hldata's style */ + void updateStyle(); + /* reimp */ + virtual int width ( const QFontMetrics & fm, const QListView * lv, int c ) const; + /* calls changeProperty() if it makes sense considering pos. */ + void activate( int column, const QPoint &localPos ); + /* For bool fields, toggles them, for color fields, display a color chooser */ + void changeProperty( Property p ); + /** unset a color. + * c is 100 (BGColor) or 101 (SelectedBGColor) for now. + */ + void unsetColor( int c ); + /* style context name */ + QString contextName() { return text(0); }; + /* only true for a hl mode item using it's default style */ + bool defStyle(); + /* true for default styles */ + bool isDefault(); + /* whichever style is active (st for hl mode styles not using + the default style, ds otherwise) */ + class KateAttribute* style() { return is; }; + + protected: + /* reimp */ + void paintCell(QPainter *p, const QColorGroup& cg, int col, int width, int align); + + private: + /* private methods to change properties */ + void toggleDefStyle(); + void setColor( int ); + /* helper function to copy the default style into the KateHlItemData, + when a property is changed and we are using default style. */ + + class KateAttribute *is, // the style currently in use + *ds; // default style for hl mode contexts and default styles + class KateHlItemData *st; // itemdata for hl mode contexts +}; +//END + +//BEGIN KateStyleListCaption decl +/* + This is a simple subclass for drawing the language names in a nice treeview + with the styles. It is needed because we do not like to mess with the default + palette of the containing ListView. Only the paintCell method is overwritten + to use our own palette (that is set on the viewport rather than on the listview + itself). +*/ +class KateStyleListCaption : public QListViewItem +{ + public: + KateStyleListCaption( QListView *parent, const QString & name ); + ~KateStyleListCaption() {}; + + protected: + void paintCell(QPainter *p, const QColorGroup& cg, int col, int width, int align); +}; +//END + +//BEGIN KateSchemaManager +QString KateSchemaManager::normalSchema () +{ + return KApplication::kApplication()->aboutData()->appName () + QString (" - Normal"); +} + +QString KateSchemaManager::printingSchema () +{ + return KApplication::kApplication()->aboutData()->appName () + QString (" - Printing"); +} + +KateSchemaManager::KateSchemaManager () + : m_config ("kateschemarc", false, false) +{ + update (); +} + +KateSchemaManager::~KateSchemaManager () +{ +} + +// +// read the types from config file and update the internal list +// +void KateSchemaManager::update (bool readfromfile) +{ + if (readfromfile) + m_config.reparseConfiguration (); + + m_schemas = m_config.groupList(); + m_schemas.sort (); + + m_schemas.remove (printingSchema()); + m_schemas.remove (normalSchema()); + m_schemas.prepend (printingSchema()); + m_schemas.prepend (normalSchema()); +} + +// +// get the right group +// special handling of the default schemas ;) +// +KConfig *KateSchemaManager::schema (uint number) +{ + if ((number>1) && (number < m_schemas.count())) + m_config.setGroup (m_schemas[number]); + else if (number == 1) + m_config.setGroup (printingSchema()); + else + m_config.setGroup (normalSchema()); + + return &m_config; +} + +void KateSchemaManager::addSchema (const QString &t) +{ + m_config.setGroup (t); + m_config.writeEntry("Color Background", KGlobalSettings::baseColor()); + + update (false); +} + +void KateSchemaManager::removeSchema (uint number) +{ + if (number >= m_schemas.count()) + return; + + if (number < 2) + return; + + m_config.deleteGroup (name (number)); + + update (false); +} + +bool KateSchemaManager::validSchema (uint number) +{ + if (number < m_schemas.count()) + return true; + + return false; +} + +uint KateSchemaManager::number (const QString &name) +{ + if (name == normalSchema()) + return 0; + + if (name == printingSchema()) + return 1; + + int i; + if ((i = m_schemas.findIndex(name)) > -1) + return i; + + return 0; +} + +QString KateSchemaManager::name (uint number) +{ + if ((number>1) && (number < m_schemas.count())) + return m_schemas[number]; + else if (number == 1) + return printingSchema(); + + return normalSchema(); +} +//END + +// +// DIALOGS !!! +// + +//BEGIN KateSchemaConfigColorTab -- 'Colors' tab +KateSchemaConfigColorTab::KateSchemaConfigColorTab( QWidget *parent, const char * ) + : QWidget (parent) +{ + m_schema = -1; + + QHBox *b; + QLabel *label; + + QVBoxLayout *blay=new QVBoxLayout(this, 0, KDialog::spacingHint()); + + QVGroupBox *gbTextArea = new QVGroupBox(i18n("Text Area Background"), this); + + b = new QHBox (gbTextArea); + b->setSpacing(KDialog::spacingHint()); + label = new QLabel( i18n("Normal text:"), b); + label->setAlignment( AlignLeft|AlignVCenter); + m_back = new KColorButton(b); + + b = new QHBox (gbTextArea); + b->setSpacing(KDialog::spacingHint()); + label = new QLabel( i18n("Selected text:"), b); + label->setAlignment( AlignLeft|AlignVCenter); + m_selected = new KColorButton(b); + + b = new QHBox (gbTextArea); + b->setSpacing(KDialog::spacingHint()); + label = new QLabel( i18n("Current line:"), b); + label->setAlignment( AlignLeft|AlignVCenter); + m_current = new KColorButton(b); + + // Markers from kdelibs/interfaces/ktextinterface/markinterface.h + b = new QHBox (gbTextArea); + b->setSpacing(KDialog::spacingHint()); + m_combobox = new KComboBox(b, "color_combo_box"); + // add the predefined mark types as defined in markinterface.h + m_combobox->insertItem(i18n("Bookmark")); // markType01 + m_combobox->insertItem(i18n("Active Breakpoint")); // markType02 + m_combobox->insertItem(i18n("Reached Breakpoint")); // markType03 + m_combobox->insertItem(i18n("Disabled Breakpoint")); // markType04 + m_combobox->insertItem(i18n("Execution")); // markType05 + m_combobox->insertItem(i18n("Warning")); // markType06 + m_combobox->insertItem(i18n("Error")); // markType07 + m_combobox->setCurrentItem(0); + m_markers = new KColorButton(b, "marker_color_button"); + connect( m_combobox, SIGNAL( activated( int ) ), SLOT( slotComboBoxChanged( int ) ) ); + + blay->addWidget(gbTextArea); + + QVGroupBox *gbBorder = new QVGroupBox(i18n("Additional Elements"), this); + + b = new QHBox (gbBorder); + b->setSpacing(KDialog::spacingHint()); + label = new QLabel( i18n("Left border background:"), b); + label->setAlignment( AlignLeft|AlignVCenter); + m_iconborder = new KColorButton(b); + + b = new QHBox (gbBorder); + b->setSpacing(KDialog::spacingHint()); + label = new QLabel( i18n("Line numbers:"), b); + label->setAlignment( AlignLeft|AlignVCenter); + m_linenumber = new KColorButton(b); + + b = new QHBox (gbBorder); + b->setSpacing(KDialog::spacingHint()); + label = new QLabel( i18n("Bracket highlight:"), b); + label->setAlignment( AlignLeft|AlignVCenter); + m_bracket = new KColorButton(b); + + b = new QHBox (gbBorder); + b->setSpacing(KDialog::spacingHint()); + label = new QLabel( i18n("Word wrap markers:"), b); + label->setAlignment( AlignLeft|AlignVCenter); + m_wwmarker = new KColorButton(b); + + b = new QHBox (gbBorder); + b->setSpacing(KDialog::spacingHint()); + label = new QLabel( i18n("Tab markers:"), b); + label->setAlignment( AlignLeft|AlignVCenter); + m_tmarker = new KColorButton(b); + + blay->addWidget(gbBorder); + + blay->addStretch(); + + // connect signal changed(); changed is emitted by a ColorButton change! + connect( this, SIGNAL( changed() ), parent->parentWidget(), SLOT( slotChanged() ) ); + + // QWhatsThis help + QWhatsThis::add(m_back, i18n("<p>Sets the background color of the editing area.</p>")); + QWhatsThis::add(m_selected, i18n("<p>Sets the background color of the selection.</p>" + "<p>To set the text color for selected text, use the \"<b>Configure " + "Highlighting</b>\" dialog.</p>")); + QWhatsThis::add(m_markers, i18n("<p>Sets the background color of the selected " + "marker type.</p><p><b>Note</b>: The marker color is displayed lightly because " + "of transparency.</p>")); + QWhatsThis::add(m_combobox, i18n("<p>Select the marker type you want to change.</p>")); + QWhatsThis::add(m_current, i18n("<p>Sets the background color of the currently " + "active line, which means the line where your cursor is positioned.</p>")); + QWhatsThis::add( m_linenumber, i18n( + "<p>This color will be used to draw the line numbers (if enabled) and the " + "lines in the code-folding pane.</p>" ) ); + QWhatsThis::add(m_bracket, i18n("<p>Sets the bracket matching color. This means, " + "if you place the cursor e.g. at a <b>(</b>, the matching <b>)</b> will " + "be highlighted with this color.</p>")); + QWhatsThis::add(m_wwmarker, i18n( + "<p>Sets the color of Word Wrap-related markers:</p>" + "<dl><dt>Static Word Wrap</dt><dd>A vertical line which shows the column where " + "text is going to be wrapped</dd>" + "<dt>Dynamic Word Wrap</dt><dd>An arrow shown to the left of " + "visually-wrapped lines</dd></dl>")); + QWhatsThis::add(m_tmarker, i18n( + "<p>Sets the color of the tabulator marks:</p>")); +} + +KateSchemaConfigColorTab::~KateSchemaConfigColorTab() +{ +} + +void KateSchemaConfigColorTab::schemaChanged ( int newSchema ) +{ + // save curent schema + if ( m_schema > -1 ) + { + m_schemas[ m_schema ].back = m_back->color(); + m_schemas[ m_schema ].selected = m_selected->color(); + m_schemas[ m_schema ].current = m_current->color(); + m_schemas[ m_schema ].bracket = m_bracket->color(); + m_schemas[ m_schema ].wwmarker = m_wwmarker->color(); + m_schemas[ m_schema ].iconborder = m_iconborder->color(); + m_schemas[ m_schema ].tmarker = m_tmarker->color(); + m_schemas[ m_schema ].linenumber = m_linenumber->color(); + } + + if ( newSchema == m_schema ) return; + + // switch + m_schema = newSchema; + + // first disconnect all signals otherwise setColor emits changed + m_back ->disconnect( SIGNAL( changed( const QColor & ) ) ); + m_selected ->disconnect( SIGNAL( changed( const QColor & ) ) ); + m_current ->disconnect( SIGNAL( changed( const QColor & ) ) ); + m_bracket ->disconnect( SIGNAL( changed( const QColor & ) ) ); + m_wwmarker ->disconnect( SIGNAL( changed( const QColor & ) ) ); + m_iconborder->disconnect( SIGNAL( changed( const QColor & ) ) ); + m_tmarker ->disconnect( SIGNAL( changed( const QColor & ) ) ); + m_markers ->disconnect( SIGNAL( changed( const QColor & ) ) ); + m_linenumber->disconnect( SIGNAL( changed( const QColor & ) ) ); + + // If we havent this schema, read in from config file + if ( ! m_schemas.contains( newSchema ) ) + { + // fallback defaults + QColor tmp0 (KGlobalSettings::baseColor()); + QColor tmp1 (KGlobalSettings::highlightColor()); + QColor tmp2 (KGlobalSettings::alternateBackgroundColor()); + QColor tmp3 ( "#FFFF99" ); + QColor tmp4 (tmp2.dark()); + QColor tmp5 ( KGlobalSettings::textColor() ); + QColor tmp6 ( "#EAE9E8" ); + QColor tmp7 ( "#000000" ); + + // same std colors like in KateDocument::markColor + QValueVector <QColor> mark(KTextEditor::MarkInterface::reservedMarkersCount()); + Q_ASSERT(mark.size() > 6); + mark[0] = Qt::blue; + mark[1] = Qt::red; + mark[2] = Qt::yellow; + mark[3] = Qt::magenta; + mark[4] = Qt::gray; + mark[5] = Qt::green; + mark[6] = Qt::red; + + SchemaColors c; + KConfig *config = KateFactory::self()->schemaManager()->schema(newSchema); + + c.back= config->readColorEntry("Color Background", &tmp0); + c.selected = config->readColorEntry("Color Selection", &tmp1); + c.current = config->readColorEntry("Color Highlighted Line", &tmp2); + c.bracket = config->readColorEntry("Color Highlighted Bracket", &tmp3); + c.wwmarker = config->readColorEntry("Color Word Wrap Marker", &tmp4); + c.tmarker = config->readColorEntry("Color Tab Marker", &tmp5); + c.iconborder = config->readColorEntry("Color Icon Bar", &tmp6); + c.linenumber = config->readColorEntry("Color Line Number", &tmp7); + + for (int i = 0; i < KTextEditor::MarkInterface::reservedMarkersCount(); i++) + c.markerColors[i] = config->readColorEntry( QString("Color MarkType%1").arg(i+1), &mark[i] ); + + m_schemas[ newSchema ] = c; + } + + m_back->setColor( m_schemas[ newSchema ].back); + m_selected->setColor( m_schemas [ newSchema ].selected ); + m_current->setColor( m_schemas [ newSchema ].current ); + m_bracket->setColor( m_schemas [ newSchema ].bracket ); + m_wwmarker->setColor( m_schemas [ newSchema ].wwmarker ); + m_tmarker->setColor( m_schemas [ newSchema ].tmarker ); + m_iconborder->setColor( m_schemas [ newSchema ].iconborder ); + m_linenumber->setColor( m_schemas [ newSchema ].linenumber ); + + // map from 0..reservedMarkersCount()-1 - the same index as in markInterface + for (int i = 0; i < KTextEditor::MarkInterface::reservedMarkersCount(); i++) + { + QPixmap pix(16, 16); + pix.fill( m_schemas [ newSchema ].markerColors[i]); + m_combobox->changeItem(pix, m_combobox->text(i), i); + } + m_markers->setColor( m_schemas [ newSchema ].markerColors[ m_combobox->currentItem() ] ); + + connect( m_back , SIGNAL( changed( const QColor& ) ), SIGNAL( changed() ) ); + connect( m_selected , SIGNAL( changed( const QColor& ) ), SIGNAL( changed() ) ); + connect( m_current , SIGNAL( changed( const QColor& ) ), SIGNAL( changed() ) ); + connect( m_bracket , SIGNAL( changed( const QColor& ) ), SIGNAL( changed() ) ); + connect( m_wwmarker , SIGNAL( changed( const QColor& ) ), SIGNAL( changed() ) ); + connect( m_iconborder, SIGNAL( changed( const QColor& ) ), SIGNAL( changed() ) ); + connect( m_tmarker , SIGNAL( changed( const QColor& ) ), SIGNAL( changed() ) ); + connect( m_linenumber, SIGNAL( changed( const QColor& ) ), SIGNAL( changed() ) ); + connect( m_markers , SIGNAL( changed( const QColor& ) ), SLOT( slotMarkerColorChanged( const QColor& ) ) ); +} + +void KateSchemaConfigColorTab::apply () +{ + schemaChanged( m_schema ); + QMap<int,SchemaColors>::Iterator it; + for ( it = m_schemas.begin(); it != m_schemas.end(); ++it ) + { + kdDebug(13030)<<"APPLY scheme = "<<it.key()<<endl; + KConfig *config = KateFactory::self()->schemaManager()->schema( it.key() ); + kdDebug(13030)<<"Using config group "<<config->group()<<endl; + SchemaColors c = it.data(); + + config->writeEntry("Color Background", c.back); + config->writeEntry("Color Selection", c.selected); + config->writeEntry("Color Highlighted Line", c.current); + config->writeEntry("Color Highlighted Bracket", c.bracket); + config->writeEntry("Color Word Wrap Marker", c.wwmarker); + config->writeEntry("Color Tab Marker", c.tmarker); + config->writeEntry("Color Icon Bar", c.iconborder); + config->writeEntry("Color Line Number", c.linenumber); + + for (int i = 0; i < KTextEditor::MarkInterface::reservedMarkersCount(); i++) + { + config->writeEntry(QString("Color MarkType%1").arg(i + 1), c.markerColors[i]); + } + } +} + +void KateSchemaConfigColorTab::slotMarkerColorChanged( const QColor& color) +{ + int index = m_combobox->currentItem(); + m_schemas[ m_schema ].markerColors[ index ] = color; + QPixmap pix(16, 16); + pix.fill(color); + m_combobox->changeItem(pix, m_combobox->text(index), index); + + emit changed(); +} + +void KateSchemaConfigColorTab::slotComboBoxChanged(int index) +{ + // temporarily disconnect the changed-signal because setColor emits changed as well + m_markers->disconnect( SIGNAL( changed( const QColor& ) ) ); + m_markers->setColor( m_schemas[m_schema].markerColors[index] ); + connect( m_markers, SIGNAL( changed( const QColor& ) ), SLOT( slotMarkerColorChanged( const QColor& ) ) ); +} + +//END KateSchemaConfigColorTab + +//BEGIN FontConfig -- 'Fonts' tab +KateSchemaConfigFontTab::KateSchemaConfigFontTab( QWidget *parent, const char * ) + : QWidget (parent) +{ + // sizemanagment + QGridLayout *grid = new QGridLayout( this, 1, 1 ); + + m_fontchooser = new KFontChooser ( this, 0L, false, QStringList(), false ); + m_fontchooser->enableColumn(KFontChooser::StyleList, false); + grid->addWidget( m_fontchooser, 0, 0); + + connect (this, SIGNAL( changed()), parent->parentWidget(), SLOT (slotChanged())); + m_schema = -1; +} + +KateSchemaConfigFontTab::~KateSchemaConfigFontTab() +{ +} + +void KateSchemaConfigFontTab::slotFontSelected( const QFont &font ) +{ + if ( m_schema > -1 ) + { + m_fonts[m_schema] = font; + emit changed(); + } +} + +void KateSchemaConfigFontTab::apply() +{ + FontMap::Iterator it; + for ( it = m_fonts.begin(); it != m_fonts.end(); ++it ) + { + KateFactory::self()->schemaManager()->schema( it.key() )->writeEntry( "Font", it.data() ); + } +} + +void KateSchemaConfigFontTab::schemaChanged( int newSchema ) +{ + if ( m_schema > -1 ) + m_fonts[ m_schema ] = m_fontchooser->font(); + + m_schema = newSchema; + + QFont f (KGlobalSettings::fixedFont()); + + m_fontchooser->disconnect ( this ); + m_fontchooser->setFont ( KateFactory::self()->schemaManager()->schema( newSchema )->readFontEntry("Font", &f) ); + m_fonts[ newSchema ] = m_fontchooser->font(); + connect (m_fontchooser, SIGNAL (fontSelected( const QFont & )), this, SLOT (slotFontSelected( const QFont & ))); +} +//END FontConfig + +//BEGIN FontColorConfig -- 'Normal Text Styles' tab +KateSchemaConfigFontColorTab::KateSchemaConfigFontColorTab( QWidget *parent, const char * ) + : QWidget (parent) +{ + m_defaultStyleLists.setAutoDelete(true); + + // sizemanagment + QGridLayout *grid = new QGridLayout( this, 1, 1 ); + + m_defaultStyles = new KateStyleListView( this, false ); + grid->addWidget( m_defaultStyles, 0, 0); + + connect (m_defaultStyles, SIGNAL (changed()), parent->parentWidget(), SLOT (slotChanged())); + + QWhatsThis::add( m_defaultStyles, i18n( + "This list displays the default styles for the current schema and " + "offers the means to edit them. The style name reflects the current " + "style settings." + "<p>To edit the colors, click the colored squares, or select the color " + "to edit from the popup menu.<p>You can unset the Background and Selected " + "Background colors from the popup menu when appropriate.") ); +} + +KateSchemaConfigFontColorTab::~KateSchemaConfigFontColorTab() +{ +} + +KateAttributeList *KateSchemaConfigFontColorTab::attributeList (uint schema) +{ + if (!m_defaultStyleLists[schema]) + { + KateAttributeList *list = new KateAttributeList (); + KateHlManager::self()->getDefaults(schema, *list); + + m_defaultStyleLists.insert (schema, list); + } + + return m_defaultStyleLists[schema]; +} + +void KateSchemaConfigFontColorTab::schemaChanged (uint schema) +{ + m_defaultStyles->clear (); + + KateAttributeList *l = attributeList (schema); + + // set colors + QPalette p ( m_defaultStyles->palette() ); + QColor _c ( KGlobalSettings::baseColor() ); + p.setColor( QColorGroup::Base, + KateFactory::self()->schemaManager()->schema(schema)-> + readColorEntry( "Color Background", &_c ) ); + _c = KGlobalSettings::highlightColor(); + p.setColor( QColorGroup::Highlight, + KateFactory::self()->schemaManager()->schema(schema)-> + readColorEntry( "Color Selection", &_c ) ); + _c = l->at(0)->textColor(); // not quite as much of an assumption ;) + p.setColor( QColorGroup::Text, _c ); + m_defaultStyles->viewport()->setPalette( p ); + + // insert the default styles backwards to get them in the right order + for ( int i = KateHlManager::self()->defaultStyles() - 1; i >= 0; i-- ) + { + new KateStyleListItem( m_defaultStyles, KateHlManager::self()->defaultStyleName(i, true), l->at( i ) ); + } +} + +void KateSchemaConfigFontColorTab::reload () +{ + m_defaultStyles->clear (); + m_defaultStyleLists.clear (); +} + +void KateSchemaConfigFontColorTab::apply () +{ + for ( QIntDictIterator<KateAttributeList> it( m_defaultStyleLists ); it.current(); ++it ) + KateHlManager::self()->setDefaults(it.currentKey(), *(it.current())); +} + +//END FontColorConfig + +//BEGIN KateSchemaConfigHighlightTab -- 'Highlighting Text Styles' tab +KateSchemaConfigHighlightTab::KateSchemaConfigHighlightTab( QWidget *parent, const char *, KateSchemaConfigFontColorTab *page, uint hl ) + : QWidget (parent) +{ + m_defaults = page; + + m_schema = 0; + m_hl = 0; + + m_hlDict.setAutoDelete (true); + + QVBoxLayout *layout = new QVBoxLayout(this, 0, KDialog::spacingHint() ); + + // hl chooser + QHBox *hbHl = new QHBox( this ); + layout->add (hbHl); + + hbHl->setSpacing( KDialog::spacingHint() ); + QLabel *lHl = new QLabel( i18n("H&ighlight:"), hbHl ); + hlCombo = new QComboBox( false, hbHl ); + lHl->setBuddy( hlCombo ); + connect( hlCombo, SIGNAL(activated(int)), + this, SLOT(hlChanged(int)) ); + + for( int i = 0; i < KateHlManager::self()->highlights(); i++) { + if (KateHlManager::self()->hlSection(i).length() > 0) + hlCombo->insertItem(KateHlManager::self()->hlSection(i) + QString ("/") + KateHlManager::self()->hlNameTranslated(i)); + else + hlCombo->insertItem(KateHlManager::self()->hlNameTranslated(i)); + } + hlCombo->setCurrentItem(0); + + // styles listview + m_styles = new KateStyleListView( this, true ); + layout->addWidget (m_styles, 999); + + hlCombo->setCurrentItem ( hl ); + hlChanged ( hl ); + + QWhatsThis::add( m_styles, i18n( + "This list displays the contexts of the current syntax highlight mode and " + "offers the means to edit them. The context name reflects the current " + "style settings.<p>To edit using the keyboard, press " + "<strong><SPACE></strong> and choose a property from the popup menu." + "<p>To edit the colors, click the colored squares, or select the color " + "to edit from the popup menu.<p>You can unset the Background and Selected " + "Background colors from the context menu when appropriate.") ); + + connect (m_styles, SIGNAL (changed()), parent->parentWidget(), SLOT (slotChanged())); +} + +KateSchemaConfigHighlightTab::~KateSchemaConfigHighlightTab() +{ +} + +void KateSchemaConfigHighlightTab::hlChanged(int z) +{ + m_hl = z; + + schemaChanged (m_schema); +} + +void KateSchemaConfigHighlightTab::schemaChanged (uint schema) +{ + m_schema = schema; + + kdDebug(13030) << "NEW SCHEMA: " << m_schema << " NEW HL: " << m_hl << endl; + + m_styles->clear (); + + if (!m_hlDict[m_schema]) + { + kdDebug(13030) << "NEW SCHEMA, create dict" << endl; + + m_hlDict.insert (schema, new QIntDict<KateHlItemDataList>); + m_hlDict[m_schema]->setAutoDelete (true); + } + + if (!m_hlDict[m_schema]->find(m_hl)) + { + kdDebug(13030) << "NEW HL, create list" << endl; + + KateHlItemDataList *list = new KateHlItemDataList (); + KateHlManager::self()->getHl( m_hl )->getKateHlItemDataListCopy (m_schema, *list); + m_hlDict[m_schema]->insert (m_hl, list); + } + + KateAttributeList *l = m_defaults->attributeList (schema); + + // Set listview colors + // We do that now, because we can now get the "normal text" color. + // TODO this reads of the KConfig object, which should be changed when + // the color tab is fixed. + QPalette p ( m_styles->palette() ); + QColor _c ( KGlobalSettings::baseColor() ); + p.setColor( QColorGroup::Base, + KateFactory::self()->schemaManager()->schema(m_schema)-> + readColorEntry( "Color Background", &_c ) ); + _c = KGlobalSettings::highlightColor(); + p.setColor( QColorGroup::Highlight, + KateFactory::self()->schemaManager()->schema(m_schema)-> + readColorEntry( "Color Selection", &_c ) ); + _c = l->at(0)->textColor(); // not quite as much of an assumption ;) + p.setColor( QColorGroup::Text, _c ); + m_styles->viewport()->setPalette( p ); + + QDict<KateStyleListCaption> prefixes; + for ( KateHlItemData *itemData = m_hlDict[m_schema]->find(m_hl)->last(); + itemData != 0L; + itemData = m_hlDict[m_schema]->find(m_hl)->prev()) + { + kdDebug(13030) << "insert items " << itemData->name << endl; + + // All stylenames have their language mode prefixed, e.g. HTML:Comment + // split them and put them into nice substructures. + int c = itemData->name.find(':'); + if ( c > 0 ) { + QString prefix = itemData->name.left(c); + QString name = itemData->name.mid(c+1); + + KateStyleListCaption *parent = prefixes.find( prefix ); + if ( ! parent ) + { + parent = new KateStyleListCaption( m_styles, prefix ); + parent->setOpen(true); + prefixes.insert( prefix, parent ); + } + new KateStyleListItem( parent, name, l->at(itemData->defStyleNum), itemData ); + } else { + new KateStyleListItem( m_styles, itemData->name, l->at(itemData->defStyleNum), itemData ); + } + } +} + +void KateSchemaConfigHighlightTab::reload () +{ + m_styles->clear (); + m_hlDict.clear (); + + hlChanged (0); +} + +void KateSchemaConfigHighlightTab::apply () +{ + for ( QIntDictIterator< QIntDict<KateHlItemDataList> > it( m_hlDict ); it.current(); ++it ) + for ( QIntDictIterator< KateHlItemDataList > it2( *it.current() ); it2.current(); ++it2 ) + { + KateHlManager::self()->getHl( it2.currentKey() )->setKateHlItemDataList (it.currentKey(), *(it2.current())); + } +} + +//END KateSchemaConfigHighlightTab + +//BEGIN KateSchemaConfigPage -- Main dialog page +KateSchemaConfigPage::KateSchemaConfigPage( QWidget *parent, KateDocument *doc ) + : KateConfigPage( parent ), + m_lastSchema (-1) +{ + QVBoxLayout *layout = new QVBoxLayout(this, 0, KDialog::spacingHint() ); + + QHBox *hbHl = new QHBox( this ); + layout->add (hbHl); + hbHl->setSpacing( KDialog::spacingHint() ); + QLabel *lHl = new QLabel( i18n("&Schema:"), hbHl ); + schemaCombo = new QComboBox( false, hbHl ); + lHl->setBuddy( schemaCombo ); + connect( schemaCombo, SIGNAL(activated(int)), + this, SLOT(schemaChanged(int)) ); + + QPushButton *btnnew = new QPushButton( i18n("&New..."), hbHl ); + connect( btnnew, SIGNAL(clicked()), this, SLOT(newSchema()) ); + + btndel = new QPushButton( i18n("&Delete"), hbHl ); + connect( btndel, SIGNAL(clicked()), this, SLOT(deleteSchema()) ); + + m_tabWidget = new QTabWidget ( this ); + m_tabWidget->setMargin (KDialog::marginHint()); + layout->add (m_tabWidget); + + connect (m_tabWidget, SIGNAL (currentChanged (QWidget *)), this, SLOT (newCurrentPage (QWidget *))); + + m_colorTab = new KateSchemaConfigColorTab (m_tabWidget); + m_tabWidget->addTab (m_colorTab, i18n("Colors")); + + m_fontTab = new KateSchemaConfigFontTab (m_tabWidget); + m_tabWidget->addTab (m_fontTab, i18n("Font")); + + m_fontColorTab = new KateSchemaConfigFontColorTab (m_tabWidget); + m_tabWidget->addTab (m_fontColorTab, i18n("Normal Text Styles")); + + uint hl = doc ? doc->hlMode() : 0; + m_highlightTab = new KateSchemaConfigHighlightTab (m_tabWidget, "", m_fontColorTab, hl ); + m_tabWidget->addTab (m_highlightTab, i18n("Highlighting Text Styles")); + + hbHl = new QHBox( this ); + layout->add (hbHl); + hbHl->setSpacing( KDialog::spacingHint() ); + lHl = new QLabel( i18n("&Default schema for %1:").arg(KApplication::kApplication()->aboutData()->programName ()), hbHl ); + defaultSchemaCombo = new QComboBox( false, hbHl ); + lHl->setBuddy( defaultSchemaCombo ); + + + m_defaultSchema = (doc && doc->activeView()) ? doc->activeView()->renderer()->config()->schema() : KateRendererConfig::global()->schema(); + + reload(); + + connect( defaultSchemaCombo, SIGNAL(activated(int)), + this, SLOT(slotChanged()) ); +} + +KateSchemaConfigPage::~KateSchemaConfigPage () +{ + // just reload config from disc + KateFactory::self()->schemaManager()->update (); +} + +void KateSchemaConfigPage::apply() +{ + m_colorTab->apply(); + m_fontTab->apply(); + m_fontColorTab->apply (); + m_highlightTab->apply (); + + // just sync the config + KateFactory::self()->schemaManager()->schema (0)->sync(); + + KateFactory::self()->schemaManager()->update (); + + // clear all attributes + for (int i = 0; i < KateHlManager::self()->highlights(); ++i) + KateHlManager::self()->getHl (i)->clearAttributeArrays (); + + // than reload the whole stuff + KateRendererConfig::global()->setSchema (defaultSchemaCombo->currentItem()); + KateRendererConfig::global()->reloadSchema(); + + // sync the hl config for real + KateHlManager::self()->getKConfig()->sync (); +} + +void KateSchemaConfigPage::reload() +{ + // just reload the config from disc + KateFactory::self()->schemaManager()->update (); + + // special for the highlighting stuff + m_fontColorTab->reload (); + + update (); + + defaultSchemaCombo->setCurrentItem (KateRendererConfig::global()->schema()); + + // initialize to the schema in the current document, or default schema + schemaCombo->setCurrentItem( m_defaultSchema ); + schemaChanged( m_defaultSchema ); +} + +void KateSchemaConfigPage::reset() +{ + reload (); +} + +void KateSchemaConfigPage::defaults() +{ + reload (); +} + +void KateSchemaConfigPage::update () +{ + // soft update, no load from disk + KateFactory::self()->schemaManager()->update (false); + + schemaCombo->clear (); + schemaCombo->insertStringList (KateFactory::self()->schemaManager()->list ()); + + defaultSchemaCombo->clear (); + defaultSchemaCombo->insertStringList (KateFactory::self()->schemaManager()->list ()); + + schemaCombo->setCurrentItem (0); + schemaChanged (0); + + schemaCombo->setEnabled (schemaCombo->count() > 0); +} + +void KateSchemaConfigPage::deleteSchema () +{ + int t = schemaCombo->currentItem (); + + KateFactory::self()->schemaManager()->removeSchema (t); + + update (); +} + +void KateSchemaConfigPage::newSchema () +{ + QString t = KInputDialog::getText (i18n("Name for New Schema"), i18n ("Name:"), i18n("New Schema"), 0, this); + + KateFactory::self()->schemaManager()->addSchema (t); + + // soft update, no load from disk + KateFactory::self()->schemaManager()->update (false); + int i = KateFactory::self()->schemaManager()->list ().findIndex (t); + + update (); + if (i > -1) + { + schemaCombo->setCurrentItem (i); + schemaChanged (i); + } +} + +void KateSchemaConfigPage::schemaChanged (int schema) +{ + btndel->setEnabled( schema > 1 ); + + m_colorTab->schemaChanged( schema ); + m_fontTab->schemaChanged( schema ); + m_fontColorTab->schemaChanged (schema); + m_highlightTab->schemaChanged (schema); + + m_lastSchema = schema; +} + +void KateSchemaConfigPage::newCurrentPage (QWidget *w) +{ + if (w == m_highlightTab) + m_highlightTab->schemaChanged (m_lastSchema); +} +//END KateSchemaConfigPage + +//BEGIN SCHEMA ACTION -- the 'View->Schema' menu action +void KateViewSchemaAction::init() +{ + m_view = 0; + last = 0; + + connect(popupMenu(),SIGNAL(aboutToShow()),this,SLOT(slotAboutToShow())); +} + +void KateViewSchemaAction::updateMenu (KateView *view) +{ + m_view = view; +} + +void KateViewSchemaAction::slotAboutToShow() +{ + KateView *view=m_view; + int count = KateFactory::self()->schemaManager()->list().count(); + + for (int z=0; z<count; z++) + { + QString hlName = KateFactory::self()->schemaManager()->list().operator[](z); + + if (names.contains(hlName) < 1) + { + names << hlName; + popupMenu()->insertItem ( hlName, this, SLOT(setSchema(int)), 0, z+1); + } + } + + if (!view) return; + + popupMenu()->setItemChecked (last, false); + popupMenu()->setItemChecked (view->renderer()->config()->schema()+1, true); + + last = view->renderer()->config()->schema()+1; +} + +void KateViewSchemaAction::setSchema (int mode) +{ + KateView *view=m_view; + + if (view) + view->renderer()->config()->setSchema (mode-1); +} +//END SCHEMA ACTION + +//BEGIN KateStyleListView +KateStyleListView::KateStyleListView( QWidget *parent, bool showUseDefaults ) + : QListView( parent ) +{ + setSorting( -1 ); // disable sorting, let the styles appear in their defined order + addColumn( i18n("Context") ); + addColumn( SmallIconSet("text_bold"), QString::null ); + addColumn( SmallIconSet("text_italic"), QString::null ); + addColumn( SmallIconSet("text_under"), QString::null ); + addColumn( SmallIconSet("text_strike"), QString::null ); + addColumn( i18n("Normal") ); + addColumn( i18n("Selected") ); + addColumn( i18n("Background") ); + addColumn( i18n("Background Selected") ); + if ( showUseDefaults ) + addColumn( i18n("Use Default Style") ); + connect( this, SIGNAL(mouseButtonPressed(int, QListViewItem*, const QPoint&, int)), + this, SLOT(slotMousePressed(int, QListViewItem*, const QPoint&, int)) ); + connect( this, SIGNAL(contextMenuRequested(QListViewItem*,const QPoint&, int)), + this, SLOT(showPopupMenu(QListViewItem*, const QPoint&)) ); + // grap the bg color, selected color and default font + normalcol = KGlobalSettings::textColor(); + bgcol = KateRendererConfig::global()->backgroundColor(); + selcol = KateRendererConfig::global()->selectionColor(); + docfont = *KateRendererConfig::global()->font(); + + viewport()->setPaletteBackgroundColor( bgcol ); +} + +void KateStyleListView::showPopupMenu( KateStyleListItem *i, const QPoint &globalPos, bool showtitle ) +{ + if ( !dynamic_cast<KateStyleListItem*>(i) ) return; + + KPopupMenu m( this ); + KateAttribute *is = i->style(); + int id; + // the title is used, because the menu obscures the context name when + // displayed on behalf of spacePressed(). + QPixmap cl(16,16); + cl.fill( i->style()->textColor() ); + QPixmap scl(16,16); + scl.fill( i->style()->selectedTextColor() ); + QPixmap bgcl(16,16); + bgcl.fill( i->style()->itemSet(KateAttribute::BGColor) ? i->style()->bgColor() : viewport()->colorGroup().base() ); + QPixmap sbgcl(16,16); + sbgcl.fill( i->style()->itemSet(KateAttribute::SelectedBGColor) ? i->style()->selectedBGColor() : viewport()->colorGroup().base() ); + + if ( showtitle ) + m.insertTitle( i->contextName(), KateStyleListItem::ContextName ); + id = m.insertItem( i18n("&Bold"), this, SLOT(mSlotPopupHandler(int)), 0, KateStyleListItem::Bold ); + m.setItemChecked( id, is->bold() ); + id = m.insertItem( i18n("&Italic"), this, SLOT(mSlotPopupHandler(int)), 0, KateStyleListItem::Italic ); + m.setItemChecked( id, is->italic() ); + id = m.insertItem( i18n("&Underline"), this, SLOT(mSlotPopupHandler(int)), 0, KateStyleListItem::Underline ); + m.setItemChecked( id, is->underline() ); + id = m.insertItem( i18n("S&trikeout"), this, SLOT(mSlotPopupHandler(int)), 0, KateStyleListItem::Strikeout ); + m.setItemChecked( id, is->strikeOut() ); + + m.insertSeparator(); + + m.insertItem( QIconSet(cl), i18n("Normal &Color..."), this, SLOT(mSlotPopupHandler(int)), 0, KateStyleListItem::Color ); + m.insertItem( QIconSet(scl), i18n("&Selected Color..."), this, SLOT(mSlotPopupHandler(int)), 0, KateStyleListItem::SelColor ); + m.insertItem( QIconSet(bgcl), i18n("&Background Color..."), this, SLOT(mSlotPopupHandler(int)), 0, KateStyleListItem::BgColor ); + m.insertItem( QIconSet(sbgcl), i18n("S&elected Background Color..."), this, SLOT(mSlotPopupHandler(int)), 0, KateStyleListItem::SelBgColor ); + + // Unset [some] colors. I could show one only if that button was clicked, but that + // would disable setting this with the keyboard (how many aren't doing just + // that every day? ;) + // ANY ideas for doing this in a nicer way will be warmly wellcomed. + KateAttribute *style = i->style(); + if ( style->itemSet( KateAttribute::BGColor) || style->itemSet( KateAttribute::SelectedBGColor ) ) + { + m.insertSeparator(); + if ( style->itemSet( KateAttribute::BGColor) ) + m.insertItem( i18n("Unset Background Color"), this, SLOT(unsetColor(int)), 0, 100 ); + if ( style->itemSet( KateAttribute::SelectedBGColor ) ) + m.insertItem( i18n("Unset Selected Background Color"), this, SLOT(unsetColor(int)), 0, 101 ); + } + + if ( ! i->isDefault() && ! i->defStyle() ) { + m.insertSeparator(); + id = m.insertItem( i18n("Use &Default Style"), this, SLOT(mSlotPopupHandler(int)), 0, KateStyleListItem::UseDefStyle ); + m.setItemChecked( id, i->defStyle() ); + } + m.exec( globalPos ); +} + +void KateStyleListView::showPopupMenu( QListViewItem *i, const QPoint &pos ) +{ + if ( dynamic_cast<KateStyleListItem*>(i) ) + showPopupMenu( (KateStyleListItem*)i, pos, true ); +} + +void KateStyleListView::mSlotPopupHandler( int z ) +{ + ((KateStyleListItem*)currentItem())->changeProperty( (KateStyleListItem::Property)z ); +} + +void KateStyleListView::unsetColor( int c ) +{ + ((KateStyleListItem*)currentItem())->unsetColor( c ); + emitChanged(); +} + +// Because QListViewItem::activatePos() is going to become deprecated, +// and also because this attempt offers more control, I connect mousePressed to this. +void KateStyleListView::slotMousePressed(int btn, QListViewItem* i, const QPoint& pos, int c) +{ + if ( dynamic_cast<KateStyleListItem*>(i) ) { + if ( btn == Qt::LeftButton && c > 0 ) { + // map pos to item/column and call KateStyleListItem::activate(col, pos) + ((KateStyleListItem*)i)->activate( c, viewport()->mapFromGlobal( pos ) - QPoint( 0, itemRect(i).top() ) ); + } + } +} + +//END + +//BEGIN KateStyleListItem +static const int BoxSize = 16; +static const int ColorBtnWidth = 32; + +KateStyleListItem::KateStyleListItem( QListViewItem *parent, const QString & stylename, + KateAttribute *style, KateHlItemData *data ) + : QListViewItem( parent, stylename ), + ds( style ), + st( data ) +{ + initStyle(); +} + +KateStyleListItem::KateStyleListItem( QListView *parent, const QString & stylename, + KateAttribute *style, KateHlItemData *data ) + : QListViewItem( parent, stylename ), + ds( style ), + st( data ) +{ + initStyle(); +} + +void KateStyleListItem::initStyle() +{ + if (!st) + is = ds; + else + { + is = new KateAttribute (*ds); + + if (st->isSomethingSet()) + *is += *st; + } +} + +void KateStyleListItem::updateStyle() +{ + // nothing there, not update it, will crash + if (!st) + return; + + if ( is->itemSet(KateAttribute::Weight) ) + { + if ( is->weight() != st->weight()) + st->setWeight( is->weight() ); + } + else st->clearAttribute( KateAttribute::Weight ); + + if ( is->itemSet(KateAttribute::Italic) ) + { + if ( is->italic() != st->italic()) + st->setItalic( is->italic() ); + } + else st->clearAttribute( KateAttribute::Italic ); + + if ( is->itemSet(KateAttribute::StrikeOut) ) + { + if ( is->strikeOut() != st->strikeOut()) + + st->setStrikeOut( is->strikeOut() ); + } + else st->clearAttribute( KateAttribute::StrikeOut ); + + if ( is->itemSet(KateAttribute::Underline) ) + { + if ( is->underline() != st->underline()) + st->setUnderline( is->underline() ); + } + else st->clearAttribute( KateAttribute::Underline ); + + if ( is->itemSet(KateAttribute::Outline) ) + { + if ( is->outline() != st->outline()) + st->setOutline( is->outline() ); + } + else st->clearAttribute( KateAttribute::Outline ); + + if ( is->itemSet(KateAttribute::TextColor) ) + { + if ( is->textColor() != st->textColor()) + st->setTextColor( is->textColor() ); + } + else st->clearAttribute( KateAttribute::TextColor ); + + if ( is->itemSet(KateAttribute::SelectedTextColor) ) + { + if ( is->selectedTextColor() != st->selectedTextColor()) + st->setSelectedTextColor( is->selectedTextColor() ); + } + else st->clearAttribute( KateAttribute::SelectedTextColor); + + if ( is->itemSet(KateAttribute::BGColor) ) + { + if ( is->bgColor() != st->bgColor()) + st->setBGColor( is->bgColor() ); + } + else st->clearAttribute( KateAttribute::BGColor ); + + if ( is->itemSet(KateAttribute::SelectedBGColor) ) + { + if ( is->selectedBGColor() != st->selectedBGColor()) + st->setSelectedBGColor( is->selectedBGColor() ); + } + else st->clearAttribute( KateAttribute::SelectedBGColor ); +} + +/* only true for a hl mode item using it's default style */ +bool KateStyleListItem::defStyle() { return st && st->itemsSet() != ds->itemsSet(); } + +/* true for default styles */ +bool KateStyleListItem::isDefault() { return st ? false : true; } + +int KateStyleListItem::width( const QFontMetrics & /*fm*/, const QListView * lv, int col ) const +{ + int m = lv->itemMargin() * 2; + switch ( col ) { + case ContextName: + // FIXME: width for name column should reflect bold/italic + // (relevant for non-fixed fonts only - nessecary?) + return QListViewItem::width( QFontMetrics( ((KateStyleListView*)lv)->docfont), lv, col); + case Bold: + case Italic: + case UseDefStyle: + return BoxSize + m; + case Color: + case SelColor: + case BgColor: + case SelBgColor: + return ColorBtnWidth +m; + default: + return 0; + } +} + +void KateStyleListItem::activate( int column, const QPoint &localPos ) +{ + QListView *lv = listView(); + int x = 0; + for( int c = 0; c < column-1; c++ ) + x += lv->columnWidth( c ); + int w; + switch( column ) { + case Bold: + case Italic: + case Underline: + case Strikeout: + case UseDefStyle: + w = BoxSize; + break; + case Color: + case SelColor: + case BgColor: + case SelBgColor: + w = ColorBtnWidth; + break; + default: + return; + } + if ( !QRect( x, 0, w, BoxSize ).contains( localPos ) ) + changeProperty( (Property)column ); +} + +void KateStyleListItem::changeProperty( Property p ) +{ + if ( p == Bold ) + is->setBold( ! is->bold() ); + else if ( p == Italic ) + is->setItalic( ! is->italic() ); + else if ( p == Underline ) + is->setUnderline( ! is->underline() ); + else if ( p == Strikeout ) + is->setStrikeOut( ! is->strikeOut() ); + else if ( p == UseDefStyle ) + toggleDefStyle(); + else + setColor( p ); + + updateStyle (); + + ((KateStyleListView*)listView())->emitChanged(); +} + +void KateStyleListItem::toggleDefStyle() +{ + if ( *is == *ds ) { + KMessageBox::information( listView(), + i18n("\"Use Default Style\" will be automatically unset when you change any style properties."), + i18n("Kate Styles"), + "Kate hl config use defaults" ); + } + else { + delete is; + is = new KateAttribute( *ds ); + updateStyle(); + repaint(); + } +} + +void KateStyleListItem::setColor( int column ) +{ + QColor c; // use this + QColor d; // default color + if ( column == Color) + { + c = is->textColor(); + d = ds->textColor(); + } + else if ( column == SelColor ) + { + c = is->selectedTextColor(); + d = is->selectedTextColor(); + } + else if ( column == BgColor ) + { + c = is->bgColor(); + d = ds->bgColor(); + } + else if ( column == SelBgColor ) + { + c = is->selectedBGColor(); + d = ds->selectedBGColor(); + } + + if ( KColorDialog::getColor( c, d, listView() ) != QDialog::Accepted) return; + + bool def = ! c.isValid(); + + // if set default, and the attrib is set in the default style use it + // else if set default, unset it + // else set the selected color + switch (column) + { + case Color: + if ( def ) + { + if ( ds->itemSet(KateAttribute::TextColor) ) + is->setTextColor( ds->textColor()); + else + is->clearAttribute(KateAttribute::TextColor); + } + else + is->setTextColor( c ); + break; + case SelColor: + if ( def ) + { + if ( ds->itemSet(KateAttribute::SelectedTextColor) ) + is->setSelectedTextColor( ds->selectedTextColor()); + else + is->clearAttribute(KateAttribute::SelectedTextColor); + } + else + is->setSelectedTextColor( c ); + break; + case BgColor: + if ( def ) + { + if ( ds->itemSet(KateAttribute::BGColor) ) + is->setBGColor( ds->bgColor()); + else + is->clearAttribute(KateAttribute::BGColor); + } + else + is->setBGColor( c ); + break; + case SelBgColor: + if ( def ) + { + if ( ds->itemSet(KateAttribute::SelectedBGColor) ) + is->setSelectedBGColor( ds->selectedBGColor()); + else + is->clearAttribute(KateAttribute::SelectedBGColor); + } + else + is->setSelectedBGColor( c ); + break; + } + + repaint(); +} + +void KateStyleListItem::unsetColor( int c ) +{ + if ( c == 100 && is->itemSet(KateAttribute::BGColor) ) + is->clearAttribute(KateAttribute::BGColor); + else if ( c == 101 && is->itemSet(KateAttribute::SelectedBGColor) ) + is->clearAttribute(KateAttribute::SelectedBGColor); + updateStyle(); +} + +void KateStyleListItem::paintCell( QPainter *p, const QColorGroup& /*cg*/, int col, int width, int align ) +{ + + if ( !p ) + return; + + QListView *lv = listView(); + if ( !lv ) + return; + Q_ASSERT( lv ); //### + + // use a private color group and set the text/highlighted text colors + QColorGroup mcg = lv->viewport()->colorGroup(); + + if ( col ) // col 0 is drawn by the superclass method + p->fillRect( 0, 0, width, height(), QBrush( mcg.base() ) ); + + int marg = lv->itemMargin(); + + QColor c; + + switch ( col ) + { + case ContextName: + { + mcg.setColor(QColorGroup::Text, is->textColor()); + mcg.setColor(QColorGroup::HighlightedText, is->selectedTextColor()); + // text background color + c = is->bgColor(); + if ( c.isValid() && is->itemSet(KateAttribute::BGColor) ) + mcg.setColor( QColorGroup::Base, c ); + if ( isSelected() && is->itemSet(KateAttribute::SelectedBGColor) ) + { + c = is->selectedBGColor(); + if ( c.isValid() ) + mcg.setColor( QColorGroup::Highlight, c ); + } + QFont f ( ((KateStyleListView*)lv)->docfont ); + p->setFont( is->font(f) ); + // FIXME - repainting when text is cropped, and the column is enlarged is buggy. + // Maybe I need painting the string myself :( + // (wilbert) it depends on the font used + QListViewItem::paintCell( p, mcg, col, width, align ); + } + break; + case Bold: + case Italic: + case Underline: + case Strikeout: + case UseDefStyle: + { + // Bold/Italic/use default checkboxes + // code allmost identical to QCheckListItem + int x = 0; + int y = (height() - BoxSize) / 2; + + if ( isEnabled() ) + p->setPen( QPen( mcg.text(), 2 ) ); + else + p->setPen( QPen( lv->palette().color( QPalette::Disabled, QColorGroup::Text ), 2 ) ); + + p->drawRect( x+marg, y+2, BoxSize-4, BoxSize-4 ); + x++; + y++; + if ( (col == Bold && is->bold()) || + (col == Italic && is->italic()) || + (col == Underline && is->underline()) || + (col == Strikeout && is->strikeOut()) || + (col == UseDefStyle && *is == *ds ) ) + { + QPointArray a( 7*2 ); + int i, xx, yy; + xx = x+1+marg; + yy = y+5; + for ( i=0; i<3; i++ ) { + a.setPoint( 2*i, xx, yy ); + a.setPoint( 2*i+1, xx, yy+2 ); + xx++; yy++; + } + yy -= 2; + for ( i=3; i<7; i++ ) { + a.setPoint( 2*i, xx, yy ); + a.setPoint( 2*i+1, xx, yy+2 ); + xx++; yy--; + } + p->drawLineSegments( a ); + } + } + break; + case Color: + case SelColor: + case BgColor: + case SelBgColor: + { + bool set( false ); + if ( col == Color) + { + c = is->textColor(); + set = is->itemSet(KateAttribute::TextColor); + } + else if ( col == SelColor ) + { + c = is->selectedTextColor(); + set = is->itemSet( KateAttribute::SelectedTextColor); + } + else if ( col == BgColor ) + { + set = is->itemSet(KateAttribute::BGColor); + c = set ? is->bgColor() : mcg.base(); + } + else if ( col == SelBgColor ) + { + set = is->itemSet(KateAttribute::SelectedBGColor); + c = set ? is->selectedBGColor(): mcg.base(); + } + + // color "buttons" + int x = 0; + int y = (height() - BoxSize) / 2; + if ( isEnabled() ) + p->setPen( QPen( mcg.text(), 2 ) ); + else + p->setPen( QPen( lv->palette().color( QPalette::Disabled, QColorGroup::Text ), 2 ) ); + + p->drawRect( x+marg, y+2, ColorBtnWidth-4, BoxSize-4 ); + p->fillRect( x+marg+1,y+3,ColorBtnWidth-7,BoxSize-7,QBrush( c ) ); + // if this item is unset, draw a diagonal line over the button + if ( ! set ) + p->drawLine( x+marg-1, BoxSize-3, ColorBtnWidth-4, y+1 ); + } + //case default: // no warning... + } +} +//END + +//BEGIN KateStyleListCaption +KateStyleListCaption::KateStyleListCaption( QListView *parent, const QString & name ) + : QListViewItem( parent, name ) +{ +} + +void KateStyleListCaption::paintCell( QPainter *p, const QColorGroup& /*cg*/, int col, int width, int align ) +{ + QListView *lv = listView(); + if ( !lv ) + return; + Q_ASSERT( lv ); //### + + // use the same colorgroup as the other items in the viewport + QColorGroup mcg = lv->viewport()->colorGroup(); + + QListViewItem::paintCell( p, mcg, col, width, align ); +} +//END + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateschema.h b/kate/part/kateschema.h new file mode 100644 index 000000000..917530fcc --- /dev/null +++ b/kate/part/kateschema.h @@ -0,0 +1,323 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001-2003 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2002, 2003 Anders Lund <anders.lund@lund.tdcadsl.dk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __KATE_SCHEMA_H__ +#define __KATE_SCHEMA_H__ + +#include "katehighlight.h" +#include "katedialogs.h" + +#include <qstringlist.h> +#include <qintdict.h> +#include <qmap.h> +#include <qlistview.h> +#include <qfont.h> + +#include <kconfig.h> +#include <kaction.h> + +class KateView; +class KateStyleListItem; +class KateStyleListCaption; + +class KColorButton; + +class QPopupMenu; +class KComboBox; + +class KateSchemaManager +{ + public: + KateSchemaManager (); + ~KateSchemaManager (); + + /** + * Schema Config changed, update all renderers + */ + void update (bool readfromfile = true); + + /** + * return kconfig with right group set or set to Normal if not there + */ + KConfig *schema (uint number); + + void addSchema (const QString &t); + + void removeSchema (uint number); + + /** + * is this schema valid ? (does it exist ?) + */ + bool validSchema (uint number); + + /** + * if not found, defaults to 0 + */ + uint number (const QString &name); + + /** + * group names in the end, no i18n involved + */ + QString name (uint number); + + /** + * Don't modify, list with the names of the schemas (i18n name for the default ones) + */ + const QStringList &list () { return m_schemas; } + + static QString normalSchema (); + static QString printingSchema (); + + private: + KConfig m_config; + QStringList m_schemas; +}; + + +class KateViewSchemaAction : public KActionMenu +{ + Q_OBJECT + + public: + KateViewSchemaAction(const QString& text, QObject* parent = 0, const char* name = 0) + : KActionMenu(text, parent, name) { init(); }; + + ~KateViewSchemaAction(){;}; + + void updateMenu (KateView *view); + + private: + void init(); + + QGuardedPtr<KateView> m_view; + QStringList names; + int last; + + public slots: + void slotAboutToShow(); + + private slots: + void setSchema (int mode); +}; + +// +// DIALOGS +// + +/* + QListView that automatically adds columns for KateStyleListItems and provides a + popup menu and a slot to edit a style using the keyboard. + Added by anders, jan 23 2002. +*/ +class KateStyleListView : public QListView +{ + Q_OBJECT + + friend class KateStyleListItem; + + public: + KateStyleListView( QWidget *parent=0, bool showUseDefaults=false); + ~KateStyleListView() {}; + /* Display a popupmenu for item i at the specified global position, eventually with a title, + promoting the context name of that item */ + void showPopupMenu( KateStyleListItem *i, const QPoint &globalPos, bool showtitle=false ); + void emitChanged() { emit changed(); }; + + void setBgCol( const QColor &c ) { bgcol = c; } + void setSelCol( const QColor &c ) { selcol = c; } + void setNormalCol( const QColor &c ) { normalcol = c; } + + private slots: + /* Display a popupmenu for item i at item position */ + void showPopupMenu( QListViewItem *i, const QPoint &globalPos ); + /* call item to change a property, or display a menu */ + void slotMousePressed( int, QListViewItem*, const QPoint&, int ); + /* asks item to change the property in q */ + void mSlotPopupHandler( int z ); + void unsetColor( int ); + + signals: + void changed(); + + private: + QColor bgcol, selcol, normalcol; + QFont docfont; +}; + +class KateSchemaConfigColorTab : public QWidget +{ + Q_OBJECT + + public: + KateSchemaConfigColorTab( QWidget *parent = 0, const char *name = 0 ); + ~KateSchemaConfigColorTab(); + + private: + KColorButton *m_back; + KColorButton *m_selected; + KColorButton *m_current; + KColorButton *m_bracket; + KColorButton *m_wwmarker; + KColorButton *m_iconborder; + KColorButton *m_tmarker; + KColorButton *m_linenumber; + + KColorButton *m_markers; // bg color for current selected marker + KComboBox* m_combobox; // switch marker type + + // Class for storing the properties on 1 schema. + class SchemaColors { + public: + QColor back, selected, current, bracket, wwmarker, iconborder, tmarker, linenumber; + QMap<int, QColor> markerColors; // stores all markerColors + }; + + // schemaid=data, created when a schema is entered + QMap<int,SchemaColors> m_schemas; + // current schema + int m_schema; + + public slots: + void apply(); + void schemaChanged( int newSchema ); + + signals: + void changed(); // connected to parentWidget()->parentWidget() SLOT(slotChanged) + + protected slots: + void slotMarkerColorChanged(const QColor&); + void slotComboBoxChanged(int index); +}; + +typedef QMap<int,QFont> FontMap; // ### remove it + +class KateSchemaConfigFontTab : public QWidget +{ + Q_OBJECT + + public: + KateSchemaConfigFontTab( QWidget *parent = 0, const char *name = 0 ); + ~KateSchemaConfigFontTab(); + + public: + void readConfig (KConfig *config); + + public slots: + void apply(); + void schemaChanged( int newSchema ); + + signals: + void changed(); // connected to parentWidget()->parentWidget() SLOT(slotChanged) + + private: + class KFontChooser *m_fontchooser; + FontMap m_fonts; + int m_schema; + + private slots: + void slotFontSelected( const QFont &font ); +}; + +class KateSchemaConfigFontColorTab : public QWidget +{ + Q_OBJECT + + public: + KateSchemaConfigFontColorTab( QWidget *parent = 0, const char *name = 0 ); + ~KateSchemaConfigFontColorTab(); + + public: + void schemaChanged (uint schema); + void reload (); + void apply (); + + KateAttributeList *attributeList (uint schema); + + private: + KateStyleListView *m_defaultStyles; + QIntDict<KateAttributeList> m_defaultStyleLists; +}; + +class KateSchemaConfigHighlightTab : public QWidget +{ + Q_OBJECT + + public: + KateSchemaConfigHighlightTab( QWidget *parent = 0, const char *name = 0, KateSchemaConfigFontColorTab *page = 0, uint hl = 0 ); + ~KateSchemaConfigHighlightTab(); + + public: + void schemaChanged (uint schema); + void reload (); + void apply (); + + protected slots: + void hlChanged(int z); + + private: + KateSchemaConfigFontColorTab *m_defaults; + + QComboBox *hlCombo; + KateStyleListView *m_styles; + + uint m_schema; + int m_hl; + + QIntDict< QIntDict<KateHlItemDataList> > m_hlDict; +}; + +class KateSchemaConfigPage : public KateConfigPage +{ + Q_OBJECT + + public: + KateSchemaConfigPage ( QWidget *parent, class KateDocument *doc=0 ); + ~KateSchemaConfigPage (); + + public slots: + void apply(); + void reload(); + void reset(); + void defaults(); + + private slots: + void update (); + void deleteSchema (); + void newSchema (); + void schemaChanged (int schema); + + void newCurrentPage (QWidget *w); + + private: + int m_lastSchema; + int m_defaultSchema; + + class QTabWidget *m_tabWidget; + class QPushButton *btndel; + class QComboBox *defaultSchemaCombo; + class QComboBox *schemaCombo; + KateSchemaConfigColorTab *m_colorTab; + KateSchemaConfigFontTab *m_fontTab; + KateSchemaConfigFontColorTab *m_fontColorTab; + KateSchemaConfigHighlightTab *m_highlightTab; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katesearch.cpp b/kate/part/katesearch.cpp new file mode 100644 index 000000000..8f4911137 --- /dev/null +++ b/kate/part/katesearch.cpp @@ -0,0 +1,1012 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004-2005 Anders Lund <anders@alweb.dk> + Copyright (C) 2003 Clarence Dang <dang@kde.org> + Copyright (C) 2002 John Firebaugh <jfirebaugh@kde.org> + Copyright (C) 2001-2004 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "katesearch.h" +#include "katesearch.moc" + +#include "kateview.h" +#include "katedocument.h" +#include "katesupercursor.h" +#include "katearbitraryhighlight.h" +#include "kateconfig.h" +#include "katehighlight.h" + +#include <klocale.h> +#include <kstdaction.h> +#include <kmessagebox.h> +#include <kstringhandler.h> +#include <kdebug.h> +#include <kfinddialog.h> +#include <kreplacedialog.h> +#include <kpushbutton.h> + +#include <qlayout.h> +#include <qlabel.h> + +//BEGIN KateSearch +QStringList KateSearch::s_searchList = QStringList(); +QStringList KateSearch::s_replaceList = QStringList(); +QString KateSearch::s_pattern = QString(); +static const bool arbitraryHLExample = false; + +KateSearch::KateSearch( KateView* view ) + : QObject( view, "kate search" ) + , m_view( view ) + , m_doc( view->doc() ) + , replacePrompt( new KateReplacePrompt( view ) ) +{ + m_arbitraryHLList = new KateSuperRangeList(); + if (arbitraryHLExample) m_doc->arbitraryHL()->addHighlightToView(m_arbitraryHLList, m_view); + + connect(replacePrompt,SIGNAL(clicked()),this,SLOT(replaceSlot())); +} + +KateSearch::~KateSearch() +{ + delete m_arbitraryHLList; +} + +void KateSearch::createActions( KActionCollection* ac ) +{ + KStdAction::find( this, SLOT(find()), ac )->setWhatsThis( + i18n("Look up the first occurrence of a piece of text or regular expression.")); + KStdAction::findNext( this, SLOT(slotFindNext()), ac )->setWhatsThis( + i18n("Look up the next occurrence of the search phrase.")); + KStdAction::findPrev( this, SLOT(slotFindPrev()), ac, "edit_find_prev" )->setWhatsThis( + i18n("Look up the previous occurrence of the search phrase.")); + KStdAction::replace( this, SLOT(replace()), ac )->setWhatsThis( + i18n("Look up a piece of text or regular expression and replace the result with some given text.")); +} + +void KateSearch::addToList( QStringList& list, const QString& s ) +{ + if( list.count() > 0 ) { + QStringList::Iterator it = list.find( s ); + if( *it != 0L ) + list.remove( it ); + if( list.count() >= 16 ) + list.remove( list.fromLast() ); + } + list.prepend( s ); +} + +void KateSearch::find() +{ + // if multiline selection around, search in it + long searchf = KateViewConfig::global()->searchFlags(); + if (m_view->hasSelection() && m_view->selStartLine() != m_view->selEndLine()) + searchf |= KFindDialog::SelectedText; + + KFindDialog *findDialog = new KFindDialog ( m_view, "", searchf, + s_searchList, m_view->hasSelection() ); + + findDialog->setPattern (getSearchText()); + + + if( findDialog->exec() == QDialog::Accepted ) { + s_searchList = findDialog->findHistory () ; + // Do *not* remove the QString() wrapping, it fixes a nasty crash + find( QString(s_searchList.first()), findDialog->options(), true, true ); + } + + delete findDialog; + m_view->repaintText (); +} + +void KateSearch::find( const QString &pattern, long flags, bool add, bool shownotfound ) +{ + KateViewConfig::global()->setSearchFlags( flags ); + if( add ) + addToList( s_searchList, pattern ); + + s_pattern = pattern; + + SearchFlags searchFlags; + + searchFlags.caseSensitive = KateViewConfig::global()->searchFlags() & KFindDialog::CaseSensitive; + searchFlags.wholeWords = KateViewConfig::global()->searchFlags() & KFindDialog::WholeWordsOnly; + searchFlags.fromBeginning = !(KateViewConfig::global()->searchFlags() & KFindDialog::FromCursor) + && !(KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText); + searchFlags.backward = KateViewConfig::global()->searchFlags() & KFindDialog::FindBackwards; + searchFlags.selected = KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText; + searchFlags.prompt = false; + searchFlags.replace = false; + searchFlags.finished = false; + searchFlags.regExp = KateViewConfig::global()->searchFlags() & KFindDialog::RegularExpression; + searchFlags.useBackRefs = KateViewConfig::global()->searchFlags() & KReplaceDialog::BackReference; + + if ( searchFlags.selected ) + { + s.selBegin = KateTextCursor( m_view->selStartLine(), m_view->selStartCol() ); + s.selEnd = KateTextCursor( m_view->selEndLine(), m_view->selEndCol() ); + s.cursor = s.flags.backward ? s.selEnd : s.selBegin; + } else { + s.cursor = getCursor( searchFlags ); + } + + s.wrappedEnd = s.cursor; + s.wrapped = false; + s.showNotFound = shownotfound; + + search( searchFlags ); +} + +void KateSearch::replace() +{ + if (!doc()->isReadWrite()) return; + + // if multiline selection around, search in it + long searchf = KateViewConfig::global()->searchFlags(); + if (m_view->hasSelection() && m_view->selStartLine() != m_view->selEndLine()) + searchf |= KFindDialog::SelectedText; + + KReplaceDialog *replaceDialog = new KReplaceDialog ( m_view, "", searchf, + s_searchList, s_replaceList, m_view->hasSelection() ); + + replaceDialog->setPattern (getSearchText()); + + if( replaceDialog->exec() == QDialog::Accepted ) { + long opts = replaceDialog->options(); + m_replacement = replaceDialog->replacement(); + s_searchList = replaceDialog->findHistory () ; + s_replaceList = replaceDialog->replacementHistory () ; + + // Do *not* remove the QString() wrapping, it fixes a nasty crash + replace( QString(s_searchList.first()), m_replacement, opts ); + } + + delete replaceDialog; + m_view->update (); +} + +void KateSearch::replace( const QString& pattern, const QString &replacement, long flags ) +{ + if (!doc()->isReadWrite()) return; + + addToList( s_searchList, pattern ); + s_pattern = pattern; + addToList( s_replaceList, replacement ); + m_replacement = replacement; + KateViewConfig::global()->setSearchFlags( flags ); + + SearchFlags searchFlags; + searchFlags.caseSensitive = KateViewConfig::global()->searchFlags() & KFindDialog::CaseSensitive; + searchFlags.wholeWords = KateViewConfig::global()->searchFlags() & KFindDialog::WholeWordsOnly; + searchFlags.fromBeginning = !(KateViewConfig::global()->searchFlags() & KFindDialog::FromCursor) + && !(KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText); + searchFlags.backward = KateViewConfig::global()->searchFlags() & KFindDialog::FindBackwards; + searchFlags.selected = KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText; + searchFlags.prompt = KateViewConfig::global()->searchFlags() & KReplaceDialog::PromptOnReplace; + searchFlags.replace = true; + searchFlags.finished = false; + searchFlags.regExp = KateViewConfig::global()->searchFlags() & KFindDialog::RegularExpression; + searchFlags.useBackRefs = KateViewConfig::global()->searchFlags() & KReplaceDialog::BackReference; + if ( searchFlags.selected ) + { + s.selBegin = KateTextCursor( m_view->selStartLine(), m_view->selStartCol() ); + s.selEnd = KateTextCursor( m_view->selEndLine(), m_view->selEndCol() ); + s.cursor = s.flags.backward ? s.selEnd : s.selBegin; + } else { + s.cursor = getCursor( searchFlags ); + } + + s.wrappedEnd = s.cursor; + s.wrapped = false; + + search( searchFlags ); +} + +void KateSearch::findAgain( bool reverseDirection ) +{ + SearchFlags searchFlags; + searchFlags.caseSensitive = KateViewConfig::global()->searchFlags() & KFindDialog::CaseSensitive; + searchFlags.wholeWords = KateViewConfig::global()->searchFlags() & KFindDialog::WholeWordsOnly; + searchFlags.fromBeginning = !(KateViewConfig::global()->searchFlags() & KFindDialog::FromCursor) + && !(KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText); + searchFlags.backward = KateViewConfig::global()->searchFlags() & KFindDialog::FindBackwards; + searchFlags.selected = KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText; + searchFlags.prompt = KateViewConfig::global()->searchFlags() & KReplaceDialog::PromptOnReplace; + searchFlags.replace = false; + searchFlags.finished = false; + searchFlags.regExp = KateViewConfig::global()->searchFlags() & KFindDialog::RegularExpression; + searchFlags.useBackRefs = KateViewConfig::global()->searchFlags() & KReplaceDialog::BackReference; + + if (reverseDirection) + searchFlags.backward = !searchFlags.backward; + + searchFlags.fromBeginning = false; + searchFlags.prompt = true; // ### why is the above assignment there? + + s.cursor = getCursor( searchFlags ); + search( searchFlags ); +} + +void KateSearch::search( SearchFlags flags ) +{ + s.flags = flags; + + if( s.flags.fromBeginning ) { + if( !s.flags.backward ) { + s.cursor.setPos(0, 0); + } else { + s.cursor.setLine(doc()->numLines() - 1); + s.cursor.setCol(doc()->lineLength( s.cursor.line() )); + } + } + + if((!s.flags.backward && + s.cursor.col() == 0 && + s.cursor.line() == 0 ) || + ( s.flags.backward && + s.cursor.col() == doc()->lineLength( s.cursor.line() ) && + s.cursor.line() == (((int)doc()->numLines()) - 1) ) ) { + s.flags.finished = true; + } + + if( s.flags.replace ) { + replaces = 0; + if( s.flags.prompt ) + promptReplace(); + else + replaceAll(); + } else { + findAgain(); + } +} + +void KateSearch::wrapSearch() +{ + if( s.flags.selected ) + { + KateTextCursor start (s.selBegin); + KateTextCursor end (s.selEnd); + + // recalc for block sel, to have start with lowest col, end with highest + if (m_view->blockSelectionMode()) + { + start.setCol (kMin(s.selBegin.col(), s.selEnd.col())); + end.setCol (kMax(s.selBegin.col(), s.selEnd.col())); + } + + s.cursor = s.flags.backward ? end : start; + } + else + { + if( !s.flags.backward ) { + s.cursor.setPos(0, 0); + } else { + s.cursor.setLine(doc()->numLines() - 1); + s.cursor.setCol(doc()->lineLength( s.cursor.line() ) ); + } + } + + // oh, we wrapped around one time allready now ! + // only check that on replace + s.wrapped = s.flags.replace; + + replaces = 0; + s.flags.finished = true; +} + +void KateSearch::findAgain() +{ + if( s_pattern.isEmpty() ) { + find(); + return; + } + + if ( doSearch( s_pattern ) ) { + exposeFound( s.cursor, s.matchedLength ); + } else if( !s.flags.finished ) { + if( askContinue() ) { + wrapSearch(); + findAgain(); + } else { + if (arbitraryHLExample) m_arbitraryHLList->clear(); + } + } else { + if (arbitraryHLExample) m_arbitraryHLList->clear(); + if ( s.showNotFound ) + KMessageBox::sorry( view(), + i18n("Search string '%1' not found!") + .arg( KStringHandler::csqueeze( s_pattern ) ), + i18n("Find")); + } +} + +void KateSearch::replaceAll() +{ + doc()->editStart (); + + while( doSearch( s_pattern ) ) + replaceOne(); + + doc()->editEnd (); + + if( !s.flags.finished ) { + if( askContinue() ) { + wrapSearch(); + replaceAll(); + } + } else { + KMessageBox::information( view(), + i18n("%n replacement made.","%n replacements made.",replaces), + i18n("Replace") ); + } +} + +void KateSearch::promptReplace() +{ + if ( doSearch( s_pattern ) ) { + exposeFound( s.cursor, s.matchedLength ); + replacePrompt->show(); + replacePrompt->setFocus (); + } else if( !s.flags.finished && askContinue() ) { + wrapSearch(); + promptReplace(); + } else { + if (arbitraryHLExample) m_arbitraryHLList->clear(); + replacePrompt->hide(); + KMessageBox::information( view(), + i18n("%n replacement made.","%n replacements made.",replaces), + i18n("Replace") ); + } +} + +void KateSearch::replaceOne() +{ + QString replaceWith = m_replacement; + if ( s.flags.regExp && s.flags.useBackRefs ) { + // replace each "(?!\)\d+" with the corresponding capture + QRegExp br("\\\\(\\d+)"); + int pos = br.search( replaceWith ); + int ncaps = m_re.numCaptures(); + while ( pos >= 0 ) { + QString sc; + if ( !pos || replaceWith.at( pos-1) != '\\' ) { + int ccap = br.cap(1).toInt(); + if (ccap <= ncaps ) { + sc = m_re.cap( ccap ); + replaceWith.replace( pos, br.matchedLength(), sc ); + } + else { + kdDebug()<<"KateSearch::replaceOne(): you don't have "<<ccap<<" backreferences in regexp '"<<m_re.pattern()<<"'"<<endl; + } + } + pos = br.search( replaceWith, pos + (int)sc.length() ); + } + } + + doc()->editStart(); + doc()->removeText( s.cursor.line(), s.cursor.col(), + s.cursor.line(), s.cursor.col() + s.matchedLength ); + doc()->insertText( s.cursor.line(), s.cursor.col(), replaceWith ); + doc()->editEnd(), + + replaces++; + + // if we inserted newlines, we better adjust. + uint newlines = replaceWith.contains('\n'); + if ( newlines ) + { + if ( ! s.flags.backward ) + { + s.cursor.setLine( s.cursor.line() + newlines ); + s.cursor.setCol( replaceWith.length() - replaceWith.findRev('\n') ); + } + // selection? + if ( s.flags.selected ) + s.selEnd.setLine( s.selEnd.line() + newlines ); + } + + + // adjust selection endcursor if needed + if( s.flags.selected && s.cursor.line() == s.selEnd.line() ) + { + s.selEnd.setCol(s.selEnd.col() + replaceWith.length() - s.matchedLength ); + } + + // adjust wrap cursor if needed + if( s.cursor.line() == s.wrappedEnd.line() && s.cursor.col() <= s.wrappedEnd.col()) + { + s.wrappedEnd.setCol(s.wrappedEnd.col() + replaceWith.length() - s.matchedLength ); + } + + if( !s.flags.backward ) { + s.cursor.setCol(s.cursor.col() + replaceWith.length()); + } else if( s.cursor.col() > 0 ) { + s.cursor.setCol(s.cursor.col() - 1); + } else { + s.cursor.setLine(s.cursor.line() - 1); + if( s.cursor.line() >= 0 ) { + s.cursor.setCol(doc()->lineLength( s.cursor.line() )); + } + } +} + +void KateSearch::skipOne() +{ + if( !s.flags.backward ) { + s.cursor.setCol(s.cursor.col() + s.matchedLength); + } else if( s.cursor.col() > 0 ) { + s.cursor.setCol(s.cursor.col() - 1); + } else { + s.cursor.setLine(s.cursor.line() - 1); + if( s.cursor.line() >= 0 ) { + s.cursor.setCol(doc()->lineLength(s.cursor.line())); + } + } +} + +void KateSearch::replaceSlot() { + switch( (Dialog_results)replacePrompt->result() ) { + case srCancel: replacePrompt->hide(); break; + case srAll: replacePrompt->hide(); replaceAll(); break; + case srYes: replaceOne(); promptReplace(); break; + case srLast: replacePrompt->hide(), replaceOne(); break; + case srNo: skipOne(); promptReplace(); break; + } +} + +bool KateSearch::askContinue() +{ + QString made = + i18n( "%n replacement made.", + "%n replacements made.", + replaces ); + + QString reached = !s.flags.backward ? + i18n( "End of document reached." ) : + i18n( "Beginning of document reached." ); + + if (KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText) + { + reached = !s.flags.backward ? + i18n( "End of selection reached." ) : + i18n( "Beginning of selection reached." ); + } + + QString question = !s.flags.backward ? + i18n( "Continue from the beginning?" ) : + i18n( "Continue from the end?" ); + + QString text = s.flags.replace ? + made + "\n" + reached + "\n" + question : + reached + "\n" + question; + + return KMessageBox::Yes == KMessageBox::questionYesNo( + view(), text, s.flags.replace ? i18n("Replace") : i18n("Find"), + KStdGuiItem::cont(), i18n("&Stop") ); +} + +QString KateSearch::getSearchText() +{ + // SelectionOnly: use selection + // WordOnly: use word under cursor + // SelectionWord: use selection if available, else use word under cursor + // WordSelection: use word if available, else use selection + QString str; + + int getFrom = view()->config()->textToSearchMode(); + switch (getFrom) + { + case KateViewConfig::SelectionOnly: // (Windows) + //kdDebug() << "getSearchText(): SelectionOnly" << endl; + if( m_view->hasSelection() ) + str = m_view->selection(); + break; + + case KateViewConfig::SelectionWord: // (classic Kate behavior) + //kdDebug() << "getSearchText(): SelectionWord" << endl; + if( m_view->hasSelection() ) + str = m_view->selection(); + else + str = view()->currentWord(); + break; + + case KateViewConfig::WordOnly: // (weird?) + //kdDebug() << "getSearchText(): WordOnly" << endl; + str = view()->currentWord(); + break; + + case KateViewConfig::WordSelection: // (persistent selection lover) + //kdDebug() << "getSearchText(): WordSelection" << endl; + str = view()->currentWord(); + if (str.isEmpty() && m_view->hasSelection() ) + str = m_view->selection(); + break; + + default: // (nowhere) + //kdDebug() << "getSearchText(): Nowhere" << endl; + break; + } + + str.replace( QRegExp("^\\n"), "" ); + str.replace( QRegExp("\\n.*"), "" ); + + return str; +} + +KateTextCursor KateSearch::getCursor( SearchFlags flags ) +{ + if (flags.backward && !flags.selected && view()->hasSelection()) + { + // We're heading backwards (and not within a selection), + // the selection might start before the cursor. + return kMin( KateTextCursor(view()->selStartLine(), view()->selStartCol()), + KateTextCursor(view()->cursorLine(), view()->cursorColumnReal())); + } + return KateTextCursor(view()->cursorLine(), view()->cursorColumnReal()); +} + +bool KateSearch::doSearch( const QString& text ) +{ +/* + rodda: Still Working on this... :) + + bool result = false; + + if (m_searchResults.count()) { + m_resultIndex++; + if (m_resultIndex < (int)m_searchResults.count()) { + s = m_searchResults[m_resultIndex]; + result = true; + } + + } else { + int temp = 0; + do {*/ + +#if 0 + static int oldLine = -1; + static int oldCol = -1; +#endif + + uint line = s.cursor.line(); + uint col = s.cursor.col();// + (result ? s.matchedLength : 0); + bool backward = s.flags.backward; + bool caseSensitive = s.flags.caseSensitive; + bool regExp = s.flags.regExp; + bool wholeWords = s.flags.wholeWords; + uint foundLine, foundCol, matchLen; + bool found = false; + //kdDebug() << "Searching at " << line << ", " << col << endl; +// kdDebug()<<"KateSearch::doSearch: "<<line<<", "<<col<<", "<<backward<<endl; + + if (backward) + { + KateDocCursor docCursor(line, col, doc()); + + // If we're at the top of the document, we're not gonna find anything, so bail. + if (docCursor.line() == 0 && docCursor.col() == 0) + return false; + + // Move one step backward before searching, if this is a "find again", we don't + // want to find the same match. + docCursor.moveBackward(1); + line = docCursor.line(); + col = docCursor.col(); + } + + do { + if( regExp ) { + m_re = QRegExp( text, caseSensitive ); + found = doc()->searchText( line, col, m_re, + &foundLine, &foundCol, + &matchLen, backward ); + } + else if ( wholeWords ) + { + bool maybefound = false; + do + { + maybefound = doc()->searchText( line, col, text, + &foundLine, &foundCol, + &matchLen, caseSensitive, backward ); + if ( maybefound ) + { + found = ( + ( foundCol == 0 || + ! doc()->highlight()->isInWord( doc()->textLine( foundLine ).at( foundCol - 1 ) ) ) && + ( foundCol + matchLen == doc()->lineLength( foundLine ) || + ! doc()->highlight()->isInWord( doc()->textLine( foundLine ).at( foundCol + matchLen ) ) ) + ); + if ( found ) + { + break; + } + else if ( backward && foundCol == 0 ) // we are done on this line and want to avoid endless loops like in #137312 + { + if ( line == 0 ) // we are completely done... + break; + else + line--; + } + else + { + line = foundLine; + col = foundCol + 1; + } + } + } while ( maybefound ); + } + else { + found = doc()->searchText( line, col, text, + &foundLine, &foundCol, + &matchLen, caseSensitive, backward ); + } + + if ( found && s.flags.selected ) + { + KateTextCursor start (s.selBegin); + KateTextCursor end (s.selEnd); + + // recalc for block sel, to have start with lowest col, end with highest + if (m_view->blockSelectionMode()) + { + start.setCol (kMin(s.selBegin.col(), s.selEnd.col())); + end.setCol (kMax(s.selBegin.col(), s.selEnd.col())); + } + + if ( !s.flags.backward && KateTextCursor( foundLine, foundCol ) >= end + || s.flags.backward && KateTextCursor( foundLine, foundCol ) < start ) + { + found = false; + } + else if (m_view->blockSelectionMode()) + { + if ((int)foundCol >= start.col() && (int)foundCol < end.col()) + break; + } + } + + line = foundLine; + col = foundCol+1; + } + while (s.flags.selected && m_view->blockSelectionMode() && found); + // in the case we want to search in selection + blockselection we need to loop + + if( !found ) return false; + + // save the search result + s.cursor.setPos(foundLine, foundCol); + s.matchedLength = matchLen; + + // we allready wrapped around one time + if (s.wrapped) + { + if (s.flags.backward) + { + if ( (s.cursor.line() < s.wrappedEnd.line()) + || ( (s.cursor.line() == s.wrappedEnd.line()) && ((s.cursor.col()+matchLen) <= uint(s.wrappedEnd.col())) ) ) + return false; + } + else + { + if ( (s.cursor.line() > s.wrappedEnd.line()) + || ( (s.cursor.line() == s.wrappedEnd.line()) && (s.cursor.col() > s.wrappedEnd.col()) ) ) + return false; + } + } + +// kdDebug() << "Found at " << s.cursor.line() << ", " << s.cursor.col() << endl; + + + //m_searchResults.append(s); + + if (arbitraryHLExample) { + KateArbitraryHighlightRange* hl = new KateArbitraryHighlightRange(new KateSuperCursor(m_doc, true, s.cursor), new KateSuperCursor(m_doc, true, s.cursor.line(), s.cursor.col() + s.matchedLength), this); + hl->setBold(); + hl->setTextColor(Qt::white); + hl->setBGColor(Qt::black); + // destroy the highlight upon change + connect(hl, SIGNAL(contentsChanged()), hl, SIGNAL(eliminated())); + m_arbitraryHLList->append(hl); + } + + return true; + + /* rodda: more of my search highlighting work + + } while (++temp < 100); + + if (result) { + s = m_searchResults.first(); + m_resultIndex = 0; + } + } + + return result;*/ +} + +void KateSearch::exposeFound( KateTextCursor &cursor, int slen ) +{ + view()->setCursorPositionInternal ( cursor.line(), cursor.col() + slen, 1 ); + view()->setSelection( cursor.line(), cursor.col(), cursor.line(), cursor.col() + slen ); + view()->syncSelectionCache(); +} +//END KateSearch + +//BEGIN KateReplacePrompt +// this dialog is not modal +KateReplacePrompt::KateReplacePrompt ( QWidget *parent ) + : KDialogBase ( parent, 0L, false, i18n( "Replace Confirmation" ), + User3 | User2 | User1 | Close | Ok , Ok, true, + i18n("Replace &All"), i18n("Re&place && Close"), i18n("&Replace") ) +{ + setButtonOK( i18n("&Find Next") ); + QWidget *page = new QWidget(this); + setMainWidget(page); + + QBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() ); + QLabel *label = new QLabel(i18n("Found an occurrence of your search term. What do you want to do?"),page); + topLayout->addWidget(label ); +} + +void KateReplacePrompt::slotOk () +{ // Search Next + done(KateSearch::srNo); + actionButton(Ok)->setFocus(); +} + +void KateReplacePrompt::slotClose () +{ // Close + done(KateSearch::srCancel); + actionButton(Close)->setFocus(); +} + +void KateReplacePrompt::slotUser1 () +{ // Replace All + done(KateSearch::srAll); + actionButton(User1)->setFocus(); +} + +void KateReplacePrompt::slotUser2 () +{ // Replace & Close + done(KateSearch::srLast); + actionButton(User2)->setFocus(); +} + +void KateReplacePrompt::slotUser3 () +{ // Replace + done(KateSearch::srYes); + actionButton(User3)->setFocus(); +} + +void KateReplacePrompt::done (int result) +{ + setResult(result); + + emit clicked(); +} +//END KateReplacePrompt + +//BEGIN SearchCommand +bool SearchCommand::exec(class Kate::View *view, const QString &cmd, QString &msg) +{ + QString flags, pattern, replacement; + if ( cmd.startsWith( "find" ) ) + { + + static QRegExp re_find("find(?::([bcersw]*))?\\s+(.+)"); + if ( re_find.search( cmd ) < 0 ) + { + msg = i18n("Usage: find[:[bcersw]] PATTERN"); + return false; + } + flags = re_find.cap( 1 ); + pattern = re_find.cap( 2 ); + } + + else if ( cmd.startsWith( "ifind" ) ) + { + static QRegExp re_ifind("ifind(?::([bcrs]*))?\\s+(.*)"); + if ( re_ifind.search( cmd ) < 0 ) + { + msg = i18n("Usage: ifind[:[bcrs]] PATTERN"); + return false; + } + ifindClear(); + return true; + } + + else if ( cmd.startsWith( "replace" ) ) + { + // Try if the pattern and replacement is quoted, using a quote character ["'] + static QRegExp re_rep("replace(?::([bceprsw]*))?\\s+([\"'])((?:[^\\\\\\\\2]|\\\\.)*)\\2\\s+\\2((?:[^\\\\\\\\2]|\\\\.)*)\\2\\s*$"); + // Or one quoted argument + QRegExp re_rep1("replace(?::([bceprsw]*))?\\s+([\"'])((?:[^\\\\\\\\2]|\\\\.)*)\\2\\s*$"); + // Else, it's just one or two (space separated) words + QRegExp re_rep2("replace(?::([bceprsw]*))?\\s+(\\S+)(.*)"); +#define unbackslash(s) p=0;\ +while ( (p = pattern.find( '\\' + delim, p )) > -1 )\ +{\ + if ( !p || pattern[p-1] != '\\' )\ + pattern.remove( p, 1 );\ + p++;\ +} + + if ( re_rep.search( cmd ) >= 0 ) + { + flags = re_rep.cap(1); + pattern = re_rep.cap( 3 ); + replacement = re_rep.cap( 4 ); + + int p(0); + // unbackslash backslashed delimiter strings + // in pattern .. + QString delim = re_rep.cap( 2 ); + unbackslash(pattern); + // .. and in replacement + unbackslash(replacement); + } + else if ( re_rep1.search( cmd ) >= 0 ) + { + flags = re_rep1.cap(1); + pattern = re_rep1.cap( 3 ); + + int p(0); + QString delim = re_rep1.cap( 2 ); + unbackslash(pattern); + } + else if ( re_rep2.search( cmd ) >= 0 ) + { + flags = re_rep2.cap( 1 ); + pattern = re_rep2.cap( 2 ); + replacement = re_rep2.cap( 3 ).stripWhiteSpace(); + } + else + { + msg = i18n("Usage: replace[:[bceprsw]] PATTERN [REPLACEMENT]"); + return false; + } + kdDebug()<<"replace '"<<pattern<<"' with '"<<replacement<<"'"<<endl; +#undef unbackslash + } + + long f = 0; + if ( flags.contains( 'b' ) ) f |= KFindDialog::FindBackwards; + if ( flags.contains( 'c' ) ) f |= KFindDialog::FromCursor; + if ( flags.contains( 'e' ) ) f |= KFindDialog::SelectedText; + if ( flags.contains( 'r' ) ) f |= KFindDialog::RegularExpression; + if ( flags.contains( 'p' ) ) f |= KReplaceDialog::PromptOnReplace; + if ( flags.contains( 's' ) ) f |= KFindDialog::CaseSensitive; + if ( flags.contains( 'w' ) ) f |= KFindDialog::WholeWordsOnly; + + if ( cmd.startsWith( "find" ) ) + { + ((KateView*)view)->find( pattern, f ); + return true; + } + else if ( cmd.startsWith( "replace" ) ) + { + f |= KReplaceDialog::BackReference; // mandatory here? + ((KateView*)view)->replace( pattern, replacement, f ); + return true; + } + + return false; +} + +bool SearchCommand::help(class Kate::View *, const QString &cmd, QString &msg) +{ + if ( cmd == "find" ) + msg = i18n("<p>Usage: <code>find[:bcersw] PATTERN</code></p>"); + + else if ( cmd == "ifind" ) + msg = i18n("<p>Usage: <code>ifind:[:bcrs] PATTERN</code>" + "<br>ifind does incremental or 'as-you-type' search</p>"); + + else + msg = i18n("<p>Usage: <code>replace[:bceprsw] PATTERN REPLACEMENT</code></p>"); + + msg += i18n( + "<h4><caption>Options</h4><p>" + "<b>b</b> - Search backward" + "<br><b>c</b> - Search from cursor" + "<br><b>r</b> - Pattern is a regular expression" + "<br><b>s</b> - Case sensitive search" + ); + + if ( cmd == "find" ) + msg += i18n( + "<br><b>e</b> - Search in selected text only" + "<br><b>w</b> - Search whole words only" + ); + + if ( cmd == "replace" ) + msg += i18n( + "<br><b>p</b> - Prompt for replace</p>" + "<p>If REPLACEMENT is not present, an empty string is used.</p>" + "<p>If you want to have whitespace in your PATTERN, you need to " + "quote both PATTERN and REPLACEMENT with either single or double " + "quotes. To have the quote characters in the strings, prepend them " + "with a backslash."); + + msg += "</p>"; + return true; +} + +QStringList SearchCommand::cmds() +{ + QStringList l; + l << "find" << "replace" << "ifind"; + return l; +} + +bool SearchCommand::wantsToProcessText( const QString &cmdname ) +{ + return cmdname == "ifind"; +} + +void SearchCommand::processText( Kate::View *view, const QString &cmd ) +{ + static QRegExp re_ifind("ifind(?::([bcrs]*))?\\s(.*)"); + if ( re_ifind.search( cmd ) > -1 ) + { + QString flags = re_ifind.cap( 1 ); + QString pattern = re_ifind.cap( 2 ); + + + // if there is no setup, or the text length is 0, set up the properties + if ( ! m_ifindFlags || pattern.isEmpty() ) + ifindInit( flags ); + // if there is no fromCursor, add it if this is not the first character + else if ( ! ( m_ifindFlags & KFindDialog::FromCursor ) && ! pattern.isEmpty() ) + m_ifindFlags |= KFindDialog::FromCursor; + + // search.. + if ( ! pattern.isEmpty() ) + { + KateView *v = (KateView*)view; + + // If it *looks like* we are continuing, place the cursor + // at the beginning of the selection, so that the search continues. + // ### check more carefully, like is the cursor currently at the end + // of the selection. + if ( pattern.startsWith( v->selection() ) && + v->selection().length() + 1 == pattern.length() ) + v->setCursorPositionInternal( v->selStartLine(), v->selStartCol() ); + + v->find( pattern, m_ifindFlags, false ); + } + } +} + +void SearchCommand::ifindInit( const QString &flags ) +{ + long f = 0; + if ( flags.contains( 'b' ) ) f |= KFindDialog::FindBackwards; + if ( flags.contains( 'c' ) ) f |= KFindDialog::FromCursor; + if ( flags.contains( 'r' ) ) f |= KFindDialog::RegularExpression; + if ( flags.contains( 's' ) ) f |= KFindDialog::CaseSensitive; + m_ifindFlags = f; +} + +void SearchCommand::ifindClear() +{ + m_ifindFlags = 0; +} +//END SearchCommand + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katesearch.h b/kate/part/katesearch.h new file mode 100644 index 000000000..3f2ce2ec6 --- /dev/null +++ b/kate/part/katesearch.h @@ -0,0 +1,243 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004-2005 Anders Lund <anders@alweb.dk> + Copyright (C) 2002 John Firebaugh <jfirebaugh@kde.org> + Copyright (C) 2001-2004 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __KATE_SEARCH_H__ +#define __KATE_SEARCH_H__ + +#include "katecursor.h" +#include "../interfaces/document.h" + +#include <kdialogbase.h> + +#include <qstring.h> +#include <qregexp.h> +#include <qstringlist.h> +#include <qvaluelist.h> + +class KateView; +class KateDocument; +class KateSuperRangeList; + +class KActionCollection; + +class KateSearch : public QObject +{ + Q_OBJECT + + friend class KateDocument; + + private: + class SearchFlags + { + public: + bool caseSensitive :1; + bool wholeWords :1; + bool fromBeginning :1; + bool backward :1; + bool selected :1; + bool prompt :1; + bool replace :1; + bool finished :1; + bool regExp :1; + bool useBackRefs :1; + }; + + class SConfig + { + public: + SearchFlags flags; + KateTextCursor cursor; + KateTextCursor wrappedEnd; // after wraping around, search/replace until here + bool wrapped; // have we allready wrapped around ? + bool showNotFound; // pop up annoying dialogs? + uint matchedLength; + KateTextCursor selBegin; + KateTextCursor selEnd; + }; + + public: + enum Dialog_results { + srCancel = KDialogBase::Cancel, + srAll = KDialogBase::User1, + srLast = KDialogBase::User2, + srNo = KDialogBase::User3, + srYes = KDialogBase::Ok + }; + + public: + KateSearch( KateView* ); + ~KateSearch(); + + void createActions( KActionCollection* ); + + public slots: + void find(); + /** + * Search for @p pattern given @p flags + * This is for the commandline "find", and is forwarded by + * KateView. + * @param pattern string or regex pattern to search for. + * @param flags a OR'ed combination of KFindDialog::Options + * @param add wether this string should be added to the recent search list + * @param shownotfound wether to pop up "Not round: PATTERN" when that happens. + * That must now be explicitly required -- the find dialog does, but the commandline + * incremental search does not. + */ + void find( const QString &pattern, long flags, bool add=true, bool shownotfound=false ); + void replace(); + /** + * Replace @p pattern with @p replacement given @p flags. + * This is for the commandline "replace" and is forwarded + * by KateView. + * @param pattern string or regular expression to search for + * @param replacement Replacement string. + * @param flags OR'd combination of KFindDialog::Options + */ + void replace( const QString &pattern, const QString &replacement, long flags ); + void findAgain( bool reverseDirection ); + + private slots: + void replaceSlot(); + void slotFindNext() { findAgain( false ); } + void slotFindPrev() { findAgain( true ); } + + private: + static void addToList( QStringList&, const QString& ); + static void addToSearchList( const QString& s ) { addToList( s_searchList, s ); } + static void addToReplaceList( const QString& s ) { addToList( s_replaceList, s ); } + static QStringList s_searchList; ///< recent patterns + static QStringList s_replaceList; ///< recent replacement strings + static QString s_pattern; ///< the string to search for + + void search( SearchFlags flags ); + void wrapSearch(); + bool askContinue(); + + void findAgain(); + void promptReplace(); + void replaceAll(); + void replaceOne(); + void skipOne(); + + QString getSearchText(); + KateTextCursor getCursor( SearchFlags flags ); + bool doSearch( const QString& text ); + void exposeFound( KateTextCursor &cursor, int slen ); + + inline KateView* view() { return m_view; } + inline KateDocument* doc() { return m_doc; } + + KateView* m_view; + KateDocument* m_doc; + + KateSuperRangeList* m_arbitraryHLList; + + SConfig s; + + QValueList<SConfig> m_searchResults; + int m_resultIndex; + + int replaces; + QDialog* replacePrompt; + QString m_replacement; + QRegExp m_re; +}; + +/** + * simple replace prompt dialog + */ +class KateReplacePrompt : public KDialogBase +{ + Q_OBJECT + + public: + /** + * Constructor + * @param parent parent widget for the dialog + */ + KateReplacePrompt(QWidget *parent); + + signals: + /** + * button clicked + */ + void clicked(); + + protected slots: + /** + * ok pressed + */ + void slotOk (); + + /** + * close pressed + */ + void slotClose (); + + /** + * replace all pressed + */ + void slotUser1 (); + + /** + * last pressed + */ + void slotUser2 (); + + /** + * Yes pressed + */ + void slotUser3 (); + + /** + * dialog done + * @param result dialog result + */ + void done (int result); +}; + +class SearchCommand : public Kate::Command, public Kate::CommandExtension +{ + public: + SearchCommand() : m_ifindFlags(0) {;} + bool exec(class Kate::View *view, const QString &cmd, QString &errorMsg); + bool help(class Kate::View *, const QString &, QString &); + QStringList cmds(); + bool wantsToProcessText( const QString &/*cmdname*/ ); + void processText( Kate::View *view, const QString& text ); + + private: + /** + * set up properties for incremental find + */ + void ifindInit( const QString &cmd ); + /** + * clear properties for incremental find + */ + void ifindClear(); + + long m_ifindFlags; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katespell.cpp b/kate/part/katespell.cpp new file mode 100644 index 000000000..1afd8d53f --- /dev/null +++ b/kate/part/katespell.cpp @@ -0,0 +1,221 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004-2005 Anders Lund <anders@alweb.dk> + Copyright (C) 2003 Clarence Dang <dang@kde.org> + Copyright (C) 2002 John Firebaugh <jfirebaugh@kde.org> + Copyright (C) 2001-2004 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "katespell.h" +#include "katespell.moc" + +#include "kateview.h" + +#include <kaction.h> +#include <kstdaction.h> +#include <kspell.h> +#include <ksconfig.h> +#include <kdebug.h> +#include <kmessagebox.h> + +KateSpell::KateSpell( KateView* view ) + : QObject( view ) + , m_view (view) + , m_kspell (0) +{ +} + +KateSpell::~KateSpell() +{ + // kspell stuff + if( m_kspell ) + { + m_kspell->setAutoDelete(true); + m_kspell->cleanUp(); // need a way to wait for this to complete + delete m_kspell; + } +} + +void KateSpell::createActions( KActionCollection* ac ) +{ + KStdAction::spelling( this, SLOT(spellcheck()), ac ); + KAction *a = new KAction( i18n("Spelling (from cursor)..."), "spellcheck", 0, this, SLOT(spellcheckFromCursor()), ac, "tools_spelling_from_cursor" ); + a->setWhatsThis(i18n("Check the document's spelling from the cursor and forward")); + + m_spellcheckSelection = new KAction( i18n("Spellcheck Selection..."), "spellcheck", 0, this, SLOT(spellcheckSelection()), ac, "tools_spelling_selection" ); + m_spellcheckSelection->setWhatsThis(i18n("Check spelling of the selected text")); +} + +void KateSpell::updateActions () +{ + m_spellcheckSelection->setEnabled (m_view->hasSelection ()); +} + +void KateSpell::spellcheckFromCursor() +{ + spellcheck( KateTextCursor(m_view->cursorLine(), m_view->cursorColumnReal()) ); +} + +void KateSpell::spellcheckSelection() +{ + KateTextCursor from( m_view->selStartLine(), m_view->selStartCol() ); + KateTextCursor to( m_view->selEndLine(), m_view->selEndCol() ); + spellcheck( from, to ); +} + +void KateSpell::spellcheck() +{ + spellcheck( KateTextCursor( 0, 0 ) ); +} + +void KateSpell::spellcheck( const KateTextCursor &from, const KateTextCursor &to ) +{ + m_spellStart = from; + m_spellEnd = to; + + if ( to.line() == 0 && to.col() == 0 ) + { + int lln = m_view->doc()->lastLine(); + m_spellEnd.setLine( lln ); + m_spellEnd.setCol( m_view->doc()->lineLength( lln ) ); + } + + m_spellPosCursor = from; + m_spellLastPos = 0; + + QString mt = m_view->doc()->mimeType()/*->name()*/; + + KSpell::SpellerType type = KSpell::Text; + if ( mt == "text/x-tex" || mt == "text/x-latex" ) + type = KSpell::TeX; + else if ( mt == "text/html" || mt == "text/xml" || mt == "text/docbook" || mt == "application/x-php") + type = KSpell::HTML; + + KSpellConfig *ksc = new KSpellConfig; + QStringList ksEncodings; + ksEncodings << "US-ASCII" << "ISO 8859-1" << "ISO 8859-2" << "ISO 8859-3" + << "ISO 8859-4" << "ISO 8859-5" << "ISO 8859-7" << "ISO 8859-8" + << "ISO 8859-9" << "ISO 8859-13" << "ISO 8859-15" << "UTF-8" + << "KOI8-R" << "KOI8-U" << "CP1251" << "CP1255"; + + int enc = ksEncodings.findIndex( m_view->doc()->encoding() ); + if ( enc > -1 ) + { + ksc->setEncoding( enc ); + kdDebug(13020)<<"KateSpell::spellCheck(): using encoding: "<<enc<<" ("<<ksEncodings[enc]<<") and KSpell::Type "<<type<<" (for '"<<mt<<"')"<<endl; + } + else + kdDebug(13020)<<"KateSpell::spellCheck(): using encoding: "<<enc<<" and KSpell::Type "<<type<<" (for '"<<mt<<"')"<<endl; + + m_kspell = new KSpell( m_view, i18n("Spellcheck"), + this, SLOT(ready(KSpell *)), ksc, true, true, type ); + + connect( m_kspell, SIGNAL(death()), + this, SLOT(spellCleanDone()) ); + + connect( m_kspell, SIGNAL(misspelling(const QString&, const QStringList&, unsigned int)), + this, SLOT(misspelling(const QString&, const QStringList&, unsigned int)) ); + connect( m_kspell, SIGNAL(corrected(const QString&, const QString&, unsigned int)), + this, SLOT(corrected(const QString&, const QString&, unsigned int)) ); + connect( m_kspell, SIGNAL(done(const QString&)), + this, SLOT(spellResult(const QString&)) ); +} + +void KateSpell::ready(KSpell *) +{ + m_kspell->setProgressResolution( 1 ); + + m_kspell->check( m_view->doc()->text( m_spellStart.line(), m_spellStart.col(), m_spellEnd.line(), m_spellEnd.col() ) ); + + kdDebug (13020) << "SPELLING READY STATUS: " << m_kspell->status () << endl; +} + +void KateSpell::locatePosition( uint pos, uint& line, uint& col ) +{ + uint remains; + + while ( m_spellLastPos < pos ) + { + remains = pos - m_spellLastPos; + uint l = m_view->doc()->lineLength( m_spellPosCursor.line() ) - m_spellPosCursor.col(); + if ( l > remains ) + { + m_spellPosCursor.setCol( m_spellPosCursor.col() + remains ); + m_spellLastPos = pos; + } + else + { + m_spellPosCursor.setLine( m_spellPosCursor.line() + 1 ); + m_spellPosCursor.setCol(0); + m_spellLastPos += l + 1; + } + } + + line = m_spellPosCursor.line(); + col = m_spellPosCursor.col(); +} + +void KateSpell::misspelling( const QString& origword, const QStringList&, unsigned int pos ) +{ + uint line, col; + + locatePosition( pos, line, col ); + + m_view->setCursorPositionInternal (line, col, 1); + m_view->setSelection( line, col, line, col + origword.length() ); +} + +void KateSpell::corrected( const QString& originalword, const QString& newword, unsigned int pos ) +{ + uint line, col; + + locatePosition( pos, line, col ); + + m_view->doc()->removeText( line, col, line, col + originalword.length() ); + m_view->doc()->insertText( line, col, newword ); +} + +void KateSpell::spellResult( const QString& ) +{ + m_view->clearSelection(); + m_kspell->cleanUp(); +} + +void KateSpell::spellCleanDone() +{ + KSpell::spellStatus status = m_kspell->status(); + + if( status == KSpell::Error ) { + KMessageBox::sorry( 0, + i18n("The spelling program could not be started. " + "Please make sure you have set the correct spelling program " + "and that it is properly configured and in your PATH.")); + } else if( status == KSpell::Crashed ) { + KMessageBox::sorry( 0, + i18n("The spelling program seems to have crashed.")); + } + + delete m_kspell; + m_kspell = 0; + + kdDebug (13020) << "SPELLING END" << endl; +} +//END + + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katespell.h b/kate/part/katespell.h new file mode 100644 index 000000000..db7dfe0ed --- /dev/null +++ b/kate/part/katespell.h @@ -0,0 +1,86 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004-2005 Anders Lund <anders@alweb.dk> + Copyright (C) 2002 John Firebaugh <jfirebaugh@kde.org> + Copyright (C) 2001-2004 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __KATE_SPELL_H__ +#define __KATE_SPELL_H__ + +#include "katecursor.h" + +class KateView; + +class KAction; +class KSpell; + +class KateSpell : public QObject +{ + Q_OBJECT + + public: + KateSpell( KateView* ); + ~KateSpell(); + + void createActions( KActionCollection* ); + + void updateActions (); + + // spellcheck from cursor, selection + private slots: + void spellcheckFromCursor(); + + // defined here in anticipation of pr view selections ;) + void spellcheckSelection(); + + void spellcheck(); + + /** + * Spellcheck a defined portion of the text. + * + * @param from Where to start the check + * @param to Where to end. If this is (0,0), it will be set to the end of the document. + */ + void spellcheck( const KateTextCursor &from, const KateTextCursor &to=KateTextCursor() ); + + void ready(KSpell *); + void misspelling( const QString&, const QStringList&, unsigned int ); + void corrected ( const QString&, const QString&, unsigned int); + void spellResult( const QString& ); + void spellCleanDone(); + + void locatePosition( uint pos, uint& line, uint& col ); + + private: + KateView *m_view; + KAction *m_spellcheckSelection; + + KSpell *m_kspell; + + // define the part of the text to check + KateTextCursor m_spellStart, m_spellEnd; + + // keep track of where we are. + KateTextCursor m_spellPosCursor; + uint m_spellLastPos; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katesupercursor.cpp b/kate/part/katesupercursor.cpp new file mode 100644 index 000000000..774b695db --- /dev/null +++ b/kate/part/katesupercursor.cpp @@ -0,0 +1,746 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Hamish Rodda <rodda@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "katesupercursor.h" +#include "katesupercursor.moc" + +#include "katedocument.h" + +#include <kdebug.h> + +#include <qobjectlist.h> + +KateSuperCursor::KateSuperCursor(KateDocument* doc, bool privateC, const KateTextCursor& cursor, QObject* parent, const char* name) + : QObject(parent, name) + , KateDocCursor(cursor.line(), cursor.col(), doc) + , Kate::Cursor () + , m_doc (doc) +{ + m_moveOnInsert = false; + m_lineRemoved = false; + m_privateCursor = privateC; + + m_doc->addSuperCursor (this, privateC); +} + +KateSuperCursor::KateSuperCursor(KateDocument* doc, bool privateC, int lineNum, int col, QObject* parent, const char* name) + : QObject(parent, name) + , KateDocCursor(lineNum, col, doc) + , Kate::Cursor () + , m_doc (doc) +{ + m_moveOnInsert = false; + m_lineRemoved = false; + m_privateCursor = privateC; + + m_doc->addSuperCursor (this, privateC); +} + +KateSuperCursor::~KateSuperCursor () +{ + m_doc->removeSuperCursor (this, m_privateCursor); +} + +void KateSuperCursor::position(uint *pline, uint *pcol) const +{ + KateDocCursor::position(pline, pcol); +} + +bool KateSuperCursor::setPosition(uint line, uint col) +{ + if (line == uint(-2) && col == uint(-2)) { delete this; return true; } + return KateDocCursor::setPosition(line, col); +} + +bool KateSuperCursor::insertText(const QString& s) +{ + return KateDocCursor::insertText(s); +} + +bool KateSuperCursor::removeText(uint nbChar) +{ + return KateDocCursor::removeText(nbChar); +} + +QChar KateSuperCursor::currentChar() const +{ + return KateDocCursor::currentChar(); +} + +bool KateSuperCursor::atStartOfLine() const +{ + return col() == 0; +} + +bool KateSuperCursor::atEndOfLine() const +{ + return col() >= (int)m_doc->kateTextLine(line())->length(); +} + +bool KateSuperCursor::moveOnInsert() const +{ + return m_moveOnInsert; +} + +void KateSuperCursor::setMoveOnInsert(bool moveOnInsert) +{ + m_moveOnInsert = moveOnInsert; +} + +void KateSuperCursor::setLine(int lineNum) +{ + int tempLine = line(), tempcol = col(); + KateDocCursor::setLine(lineNum); + + if (tempLine != line() || tempcol != col()) + emit positionDirectlyChanged(); +} + +void KateSuperCursor::setCol(int colNum) +{ + KateDocCursor::setCol(colNum); +} + +void KateSuperCursor::setPos(const KateTextCursor& pos) +{ + KateDocCursor::setPos(pos); +} + +void KateSuperCursor::setPos(int lineNum, int colNum) +{ + KateDocCursor::setPos(lineNum, colNum); +} + +void KateSuperCursor::editTextInserted(uint line, uint col, uint len) +{ + if (m_line == int(line)) + { + if ((m_col > int(col)) || (m_moveOnInsert && (m_col == int(col)))) + { + bool insertedAt = m_col == int(col); + + m_col += len; + + if (insertedAt) + emit charInsertedAt(); + + emit positionChanged(); + return; + } + } + + emit positionUnChanged(); +} + +void KateSuperCursor::editTextRemoved(uint line, uint col, uint len) +{ + if (m_line == int(line)) + { + if (m_col > int(col)) + { + if (m_col > int(col + len)) + { + m_col -= len; + } + else + { + bool prevCharDeleted = m_col == int(col + len); + + m_col = col; + + if (prevCharDeleted) + emit charDeletedBefore(); + else + emit positionDeleted(); + } + + emit positionChanged(); + return; + + } + else if (m_col == int(col)) + { + emit charDeletedAfter(); + } + } + + emit positionUnChanged(); +} + +void KateSuperCursor::editLineWrapped(uint line, uint col, bool newLine) +{ + if (newLine) + { + if (m_line > int(line) || (m_line == int(line) && m_col >= int(col))) + { + if(m_line == int(line)) + m_col -= col; + m_line++; + + emit positionChanged(); + return; + } + } + else if ( (m_line == int(line)) && (m_col > int(col)) || (m_moveOnInsert && (m_col == int(col))) ) + { + m_line++; + m_col -= col; + + emit positionChanged(); + return; + } + + emit positionUnChanged(); +} + +void KateSuperCursor::editLineUnWrapped(uint line, uint col, bool removeLine, uint length) +{ + if (removeLine && (m_line > int(line+1))) + { + m_line--; + + emit positionChanged(); + return; + } + else if ( (m_line == int(line+1)) && (removeLine || (m_col < int(length))) ) + { + m_line = line; + m_col += col; + + emit positionChanged(); + return; + } + else if ( (m_line == int(line+1)) && (m_col >= int(length)) ) + { + m_col -= length; + + emit positionChanged(); + return; + } + + emit positionUnChanged(); +} + +void KateSuperCursor::editLineInserted (uint line) +{ + if (m_line >= int(line)) + { + m_line++; + + emit positionChanged(); + return; + } + + emit positionUnChanged(); +} + +void KateSuperCursor::editLineRemoved(uint line) +{ + if (m_line > int(line)) + { + m_line--; + + emit positionChanged(); + return; + } + else if (m_line == int(line)) + { + m_line = (line <= m_doc->lastLine()) ? line : (line - 1); + m_col = 0; + + emit positionDeleted(); + + emit positionChanged(); + return; + } + + emit positionUnChanged(); +} + +KateSuperCursor::operator QString() +{ + return QString("[%1,%1]").arg(line()).arg(col()); +} + +KateSuperRange::KateSuperRange(KateSuperCursor* start, KateSuperCursor* end, QObject* parent, const char* name) + : QObject(parent, name) + , m_start(start) + , m_end(end) + , m_evaluate(false) + , m_startChanged(false) + , m_endChanged(false) + , m_deleteCursors(false) + , m_allowZeroLength(false) +{ + init(); +} + +KateSuperRange::KateSuperRange(KateDocument* doc, const KateRange& range, QObject* parent, const char* name) + : QObject(parent, name) + , m_start(new KateSuperCursor(doc, true, range.start())) + , m_end(new KateSuperCursor(doc, true, range.end())) + , m_evaluate(false) + , m_startChanged(false) + , m_endChanged(false) + , m_deleteCursors(true) + , m_allowZeroLength(false) +{ + init(); +} + +KateSuperRange::KateSuperRange(KateDocument* doc, const KateTextCursor& start, const KateTextCursor& end, QObject* parent, const char* name) + : QObject(parent, name) + , m_start(new KateSuperCursor(doc, true, start)) + , m_end(new KateSuperCursor(doc, true, end)) + , m_evaluate(false) + , m_startChanged(false) + , m_endChanged(false) + , m_deleteCursors(true) + , m_allowZeroLength(false) +{ + init(); +} + +void KateSuperRange::init() +{ + Q_ASSERT(isValid()); + if (!isValid()) + kdDebug(13020) << superStart() << " " << superEnd() << endl; + + insertChild(m_start); + insertChild(m_end); + + setBehaviour(DoNotExpand); + + // Not necessarily the best implementation + connect(m_start, SIGNAL(positionDirectlyChanged()), SIGNAL(contentsChanged())); + connect(m_end, SIGNAL(positionDirectlyChanged()), SIGNAL(contentsChanged())); + + connect(m_start, SIGNAL(positionChanged()), SLOT(slotEvaluateChanged())); + connect(m_end, SIGNAL(positionChanged()), SLOT(slotEvaluateChanged())); + connect(m_start, SIGNAL(positionUnChanged()), SLOT(slotEvaluateUnChanged())); + connect(m_end, SIGNAL(positionUnChanged()), SLOT(slotEvaluateUnChanged())); + connect(m_start, SIGNAL(positionDeleted()), SIGNAL(boundaryDeleted())); + connect(m_end, SIGNAL(positionDeleted()), SIGNAL(boundaryDeleted())); +} + +KateSuperRange::~KateSuperRange() +{ + if (m_deleteCursors) + { + //insertChild(m_start); + //insertChild(m_end); + delete m_start; + delete m_end; + } +} + +KateTextCursor& KateSuperRange::start() +{ + return *m_start; +} + +const KateTextCursor& KateSuperRange::start() const +{ + return *m_start; +} + +KateTextCursor& KateSuperRange::end() +{ + return *m_end; +} + +const KateTextCursor& KateSuperRange::end() const +{ + return *m_end; +} + +KateSuperCursor& KateSuperRange::superStart() +{ + return *m_start; +} + +const KateSuperCursor& KateSuperRange::superStart() const +{ + return *m_start; +} + +KateSuperCursor& KateSuperRange::superEnd() +{ + return *m_end; +} + +const KateSuperCursor& KateSuperRange::superEnd() const +{ + return *m_end; +} + +int KateSuperRange::behaviour() const +{ + return (m_start->moveOnInsert() ? DoNotExpand : ExpandLeft) | (m_end->moveOnInsert() ? ExpandRight : DoNotExpand); +} + +void KateSuperRange::setBehaviour(int behaviour) +{ + m_start->setMoveOnInsert(behaviour & ExpandLeft); + m_end->setMoveOnInsert(!(behaviour & ExpandRight)); +} + +bool KateSuperRange::isValid() const +{ + return superStart() <= superEnd(); +} + +bool KateSuperRange::owns(const KateTextCursor& cursor) const +{ + if (!includes(cursor)) return false; + + if (children()) + for (QObjectListIt it(*children()); *it; ++it) + if ((*it)->inherits("KateSuperRange")) + if (static_cast<KateSuperRange*>(*it)->owns(cursor)) + return false; + + return true; +} + +bool KateSuperRange::includes(const KateTextCursor& cursor) const +{ + return isValid() && cursor >= superStart() && cursor < superEnd(); +} + +bool KateSuperRange::includes(uint lineNum) const +{ + return isValid() && (int)lineNum >= superStart().line() && (int)lineNum <= superEnd().line(); +} + +bool KateSuperRange::includesWholeLine(uint lineNum) const +{ + return isValid() && ((int)lineNum > superStart().line() || ((int)lineNum == superStart().line() && superStart().atStartOfLine())) && ((int)lineNum < superEnd().line() || ((int)lineNum == superEnd().line() && superEnd().atEndOfLine())); +} + +bool KateSuperRange::boundaryAt(const KateTextCursor& cursor) const +{ + return isValid() && (cursor == superStart() || cursor == superEnd()); +} + +bool KateSuperRange::boundaryOn(uint lineNum) const +{ + return isValid() && (superStart().line() == (int)lineNum || superEnd().line() == (int)lineNum); +} + +void KateSuperRange::slotEvaluateChanged() +{ + if (sender() == static_cast<QObject*>(m_start)) { + if (m_evaluate) { + if (!m_endChanged) { + // Only one was changed + evaluateEliminated(); + + } else { + // Both were changed + evaluatePositionChanged(); + m_endChanged = false; + } + + } else { + m_startChanged = true; + } + + } else { + if (m_evaluate) { + if (!m_startChanged) { + // Only one was changed + evaluateEliminated(); + + } else { + // Both were changed + evaluatePositionChanged(); + m_startChanged = false; + } + + } else { + m_endChanged = true; + } + } + + m_evaluate = !m_evaluate; +} + +void KateSuperRange::slotEvaluateUnChanged() +{ + if (sender() == static_cast<QObject*>(m_start)) { + if (m_evaluate) { + if (m_endChanged) { + // Only one changed + evaluateEliminated(); + m_endChanged = false; + + } else { + // Neither changed + emit positionUnChanged(); + } + } + + } else { + if (m_evaluate) { + if (m_startChanged) { + // Only one changed + evaluateEliminated(); + m_startChanged = false; + + } else { + // Neither changed + emit positionUnChanged(); + } + } + } + + m_evaluate = !m_evaluate; +} + +void KateSuperRange::slotTagRange() +{ + emit tagRange(this); +} + +void KateSuperRange::evaluateEliminated() +{ + if (superStart() == superEnd()) { + if (!m_allowZeroLength) emit eliminated(); + } + else + emit contentsChanged(); +} + +void KateSuperRange::evaluatePositionChanged() +{ + if (superStart() == superEnd()) + emit eliminated(); + else + emit positionChanged(); +} + +int KateSuperCursorList::compareItems(QPtrCollection::Item item1, QPtrCollection::Item item2) +{ + if (*(static_cast<KateSuperCursor*>(item1)) == *(static_cast<KateSuperCursor*>(item2))) + return 0; + + return *(static_cast<KateSuperCursor*>(item1)) < *(static_cast<KateSuperCursor*>(item2)) ? -1 : 1; +} + +KateSuperRangeList::KateSuperRangeList(bool autoManage, QObject* parent, const char* name) + : QObject(parent, name) + , m_autoManage(autoManage) + , m_connect(true) + , m_trackingBoundaries(false) +{ + setAutoManage(autoManage); +} + +KateSuperRangeList::KateSuperRangeList(const QPtrList<KateSuperRange>& rangeList, QObject* parent, const char* name) + : QObject(parent, name) + , m_autoManage(false) + , m_connect(false) + , m_trackingBoundaries(false) +{ + appendList(rangeList); +} + +void KateSuperRangeList::appendList(const QPtrList<KateSuperRange>& rangeList) +{ + for (QPtrListIterator<KateSuperRange> it = rangeList; *it; ++it) + append(*it); +} + +void KateSuperRangeList::clear() +{ + for (KateSuperRange* range = first(); range; range = next()) + emit rangeEliminated(range); + + QPtrList<KateSuperRange>::clear(); +} + +void KateSuperRangeList::connectAll() +{ + if (!m_connect) { + m_connect = true; + for (KateSuperRange* range = first(); range; range = next()) { + connect(range, SIGNAL(destroyed(QObject*)), SLOT(slotDeleted(QObject*))); + connect(range, SIGNAL(eliminated()), SLOT(slotEliminated())); + } + } +} + +bool KateSuperRangeList::autoManage() const +{ + return m_autoManage; +} + +void KateSuperRangeList::setAutoManage(bool autoManage) +{ + m_autoManage = autoManage; + setAutoDelete(m_autoManage); +} + +QPtrList<KateSuperRange> KateSuperRangeList::rangesIncluding(const KateTextCursor& cursor) +{ + sort(); + + QPtrList<KateSuperRange> ret; + + for (KateSuperRange* r = first(); r; r = next()) + if (r->includes(cursor)) + ret.append(r); + + return ret; +} + +QPtrList<KateSuperRange> KateSuperRangeList::rangesIncluding(uint line) +{ + sort(); + + QPtrList<KateSuperRange> ret; + + for (KateSuperRange* r = first(); r; r = next()) + if (r->includes(line)) + ret.append(r); + + return ret; +} + +bool KateSuperRangeList::rangesInclude(const KateTextCursor& cursor) +{ + for (KateSuperRange* r = first(); r; r = next()) + if (r->includes(cursor)) + return true; + + return false; +} + +void KateSuperRangeList::slotEliminated() +{ + if (sender()) { + KateSuperRange* range = static_cast<KateSuperRange*>(const_cast<QObject*>(sender())); + emit rangeEliminated(range); + + if (m_trackingBoundaries) { + m_columnBoundaries.removeRef(range->m_start); + m_columnBoundaries.removeRef(range->m_end); + } + + if (m_autoManage) + removeRef(range); + + if (!count()) + emit listEmpty(); + } +} + +void KateSuperRangeList::slotDeleted(QObject* range) +{ + //kdDebug(13020)<<"KateSuperRangeList::slotDeleted"<<endl; + KateSuperRange* r = static_cast<KateSuperRange*>(range); + + if (m_trackingBoundaries) { + m_columnBoundaries.removeRef(r->m_start); + m_columnBoundaries.removeRef(r->m_end); + } + + int index = findRef(r); + if (index != -1) + take(index); + //else kdDebug(13020)<<"Range not found in list"<<endl; + + if (!count()) + emit listEmpty(); +} + +KateSuperCursor* KateSuperRangeList::firstBoundary(const KateTextCursor* start) +{ + if (!m_trackingBoundaries) { + m_trackingBoundaries = true; + + for (KateSuperRange* r = first(); r; r = next()) { + m_columnBoundaries.append(&(r->superStart())); + m_columnBoundaries.append(&(r->superEnd())); + } + } + + m_columnBoundaries.sort(); + + if (start) + // OPTIMISE: QMap with QPtrList for each line? (==> sorting issues :( ) + for (KateSuperCursor* c = m_columnBoundaries.first(); c; c = m_columnBoundaries.next()) + if (*start <= *c) + break; + + return m_columnBoundaries.current(); +} + +KateSuperCursor* KateSuperRangeList::nextBoundary() +{ + KateSuperCursor* current = m_columnBoundaries.current(); + + // make sure the new cursor is after the current cursor; multiple cursors with the same position can be in the list. + if (current) + while (m_columnBoundaries.next()) + if (*(m_columnBoundaries.current()) != *current) + break; + + return m_columnBoundaries.current(); +} + +KateSuperCursor* KateSuperRangeList::currentBoundary() +{ + return m_columnBoundaries.current(); +} + +int KateSuperRangeList::compareItems(QPtrCollection::Item item1, QPtrCollection::Item item2) +{ + if (static_cast<KateSuperRange*>(item1)->superStart() == static_cast<KateSuperRange*>(item2)->superStart()) { + if (static_cast<KateSuperRange*>(item1)->superEnd() == static_cast<KateSuperRange*>(item2)->superEnd()) { + return 0; + } else { + return static_cast<KateSuperRange*>(item1)->superEnd() < static_cast<KateSuperRange*>(item2)->superEnd() ? -1 : 1; + } + } + + return static_cast<KateSuperRange*>(item1)->superStart() < static_cast<KateSuperRange*>(item2)->superStart() ? -1 : 1; +} + +QPtrCollection::Item KateSuperRangeList::newItem(QPtrCollection::Item d) +{ + if (m_connect) { + connect(static_cast<KateSuperRange*>(d), SIGNAL(destroyed(QObject*)), SLOT(slotDeleted(QObject*))); + connect(static_cast<KateSuperRange*>(d), SIGNAL(eliminated()), SLOT(slotEliminated())); + connect(static_cast<KateSuperRange*>(d), SIGNAL(tagRange(KateSuperRange*)), SIGNAL(tagRange(KateSuperRange*))); + + // HACK HACK + static_cast<KateSuperRange*>(d)->slotTagRange(); + } + + if (m_trackingBoundaries) { + m_columnBoundaries.append(&(static_cast<KateSuperRange*>(d)->superStart())); + m_columnBoundaries.append(&(static_cast<KateSuperRange*>(d)->superEnd())); + } + + return QPtrList<KateSuperRange>::newItem(d); +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katesupercursor.h b/kate/part/katesupercursor.h new file mode 100644 index 000000000..e6b16baa8 --- /dev/null +++ b/kate/part/katesupercursor.h @@ -0,0 +1,463 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Hamish Rodda <rodda@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KATESUPERCURSOR_H +#define KATESUPERCURSOR_H + +#include "katecursor.h" + +class KateDocument; +class KateView; + +/** + * Possible additional features: + * - Notification when a cursor enters or exits a view + * - suggest something :) + * + * Unresolved issues: + * - testing, testing, testing + * - ie. everything which hasn't already been tested, you can see that which has inside the #ifdefs + * - api niceness + */ + +/** + * A cursor which updates and gives off various interesting signals. + * + * This aims to be a working version of what KateCursor was originally intended to be. + * + * @author Hamish Rodda + **/ +class KateSuperCursor : public QObject, public KateDocCursor, public Kate::Cursor +{ + Q_OBJECT + +public: + /** + * bool privateC says: if private, than don't show to apps using the cursorinterface in the list, + * all internally only used SuperCursors should be private or people could modify them from the + * outside breaking kate's internals + */ + KateSuperCursor(KateDocument* doc, bool privateC, const KateTextCursor& cursor, QObject* parent = 0L, const char* name = 0L); + KateSuperCursor(KateDocument* doc, bool privateC, int lineNum = 0, int col = 0, QObject* parent = 0L, const char* name = 0L); + + ~KateSuperCursor (); + + public: + // KTextEditor::Cursor interface + void position(uint *line, uint *col) const; + bool setPosition(uint line, uint col); + bool insertText(const QString& text); + bool removeText(uint numberOfCharacters); + QChar currentChar() const; + + /** + * @returns true if the cursor is situated at the start of the line, false if it isn't. + */ + bool atStartOfLine() const; + + /** + * @returns true if the cursor is situated at the end of the line, false if it isn't. + */ + bool atEndOfLine() const; + + /** + * Returns how this cursor behaves when text is inserted at the cursor. + * Defaults to not moving on insert. + */ + bool moveOnInsert() const; + + /** + * Change the behavior of the cursor when text is inserted at the cursor. + * + * If @p moveOnInsert is true, the cursor will end up at the end of the insert. + */ + void setMoveOnInsert(bool moveOnInsert); + + /** + * Debug: output the position. + */ + operator QString(); + + // Reimplementations; + virtual void setLine(int lineNum); + virtual void setCol(int colNum); + virtual void setPos(const KateTextCursor& pos); + virtual void setPos(int lineNum, int colNum); + + signals: + /** + * The cursor's position was directly changed by the program. + */ + void positionDirectlyChanged(); + + /** + * The cursor's position was changed. + */ + void positionChanged(); + + /** + * Athough an edit took place, the cursor's position was unchanged. + */ + void positionUnChanged(); + + /** + * The cursor's surrounding characters were both deleted simultaneously. + * The cursor is automatically placed at the start of the deleted region. + */ + void positionDeleted(); + + /** + * A character was inserted immediately before the cursor. + * + * Whether the char was inserted before or after this cursor depends on + * moveOnInsert(): + * @li true -> the char was inserted before + * @li false -> the char was inserted after + */ + void charInsertedAt(); + + /** + * The character immediately before the cursor was deleted. + */ + void charDeletedBefore(); + + /** + * The character immediately after the cursor was deleted. + */ + void charDeletedAfter(); + + //BEGIN METHODES TO CALL FROM KATE DOCUMENT TO KEEP CURSOR UP TO DATE + public: + void editTextInserted ( uint line, uint col, uint len); + void editTextRemoved ( uint line, uint col, uint len); + + void editLineWrapped ( uint line, uint col, bool newLine = true ); + void editLineUnWrapped ( uint line, uint col, bool removeLine = true, uint length = 0 ); + + void editLineInserted ( uint line ); + void editLineRemoved ( uint line ); + //END + + private: + KateDocument *m_doc; + bool m_moveOnInsert : 1; + bool m_lineRemoved : 1; + bool m_privateCursor : 1; +}; + +/** + * Represents a range of text, from the start() to the end(). + * + * Also tracks its position and emits useful signals. + */ +class KateSuperRange : public QObject, public KateRange +{ + friend class KateSuperRangeList; + + Q_OBJECT + +public: + /// Determine how the range reacts to characters inserted immediately outside the range. + enum InsertBehaviour { + /// Don't expand to encapsulate new characters in either direction. This is the default. + DoNotExpand = 0, + /// Expand to encapsulate new characters to the left of the range. + ExpandLeft = 0x1, + /// Expand to encapsulate new characters to the right of the range. + ExpandRight = 0x2 + }; + + /** + * Constructor. Takes posession of @p start and @p end. + */ + KateSuperRange(KateSuperCursor* start, KateSuperCursor* end, QObject* parent = 0L, const char* name = 0L); + KateSuperRange(KateDocument* doc, const KateRange& range, QObject* parent = 0L, const char* name = 0L); + KateSuperRange(KateDocument* doc, const KateTextCursor& start, const KateTextCursor& end, QObject* parent = 0L, const char* name = 0L); + + virtual ~KateSuperRange(); + + // fulfill KateRange requirements + virtual KateTextCursor& start(); + virtual KateTextCursor& end(); + virtual const KateTextCursor& start() const; + virtual const KateTextCursor& end() const; + + void allowZeroLength(bool yes=true){m_allowZeroLength=yes;} + /** + * Returns the super start cursor. + */ + KateSuperCursor& superStart(); + const KateSuperCursor& superStart() const; + + /** + * Returns the super end cursor. + */ + KateSuperCursor& superEnd(); + const KateSuperCursor& superEnd() const; + + /** + * Returns how this range reacts to characters inserted immediately outside the range. + */ + int behaviour() const; + + /** + * Determine how the range should react to characters inserted immediately outside the range. + * + * TODO does this need a custom function to enable determining of the behavior based on the + * text that is inserted / deleted? + * + * @sa InsertBehaviour + */ + void setBehaviour(int behaviour); + + /** + * Start and end must be valid and start <= end. + */ + virtual bool isValid() const; + + /** + * This is for use where the ranges are used in a heirachy, + * ie. their parents are KateSuperRanges which completely + * encapsulate them. + * + * @todo constrain children when their position changes deliberately; + * eliminate() children when they are equivalent to their parents + * + * @returns true if the range contains the cursor and no children + * also contain it; false otherwise. + */ + bool owns(const KateTextCursor& cursor) const; + + /** + * Returns true if the range includes @p cursor 's character. + * Returns false if @p cursor == end(). + */ + bool includes(const KateTextCursor& cursor) const; + + /** + * Returns true if the range includes @p line + */ + bool includes(uint lineNum) const; + + /** + * Returns true if the range totally encompasses @p line + */ + bool includesWholeLine(uint lineNum) const; + + /** + * Returns whether @p cursor is the site of a boundary of this range. + */ + bool boundaryAt(const KateTextCursor& cursor) const; + + /** + * Returns whether there is a boundary of this range on @p line. + */ + bool boundaryOn(uint lineNum) const; + +signals: + /** + * More interesting signals that aren't worth implementing here: + * firstCharDeleted: start()::charDeleted() + * lastCharDeleted: end()::previousCharDeleted() + */ + + /** + * The range's position changed. + */ + void positionChanged(); + + /** + * The range's position was unchanged. + */ + void positionUnChanged(); + + /** + * The contents of the range changed. + */ + void contentsChanged(); + + /** + * Either cursor's surrounding characters were both deleted. + */ + void boundaryDeleted(); + + /** + * The range now contains no characters (ie. the start and end cursors are the same). + * + * To eliminate this range under different conditions, connect the other signal directly + * to this signal. + */ + void eliminated(); + + /** + * Indicates the region needs re-drawing. + */ + void tagRange(KateSuperRange* range); + +public slots: + void slotTagRange(); + +private slots: + void slotEvaluateChanged(); + void slotEvaluateUnChanged(); + +private: + void init(); + void evaluateEliminated(); + void evaluatePositionChanged(); + + KateSuperCursor* m_start; + KateSuperCursor* m_end; + bool m_evaluate; + bool m_startChanged; + bool m_endChanged; + bool m_deleteCursors; + bool m_allowZeroLength; +}; + +class KateSuperCursorList : public QPtrList<KateSuperCursor> +{ +protected: + virtual int compareItems(QPtrCollection::Item item1, QPtrCollection::Item item2); +}; + +class KateSuperRangeList : public QObject, public QPtrList<KateSuperRange> +{ + Q_OBJECT + +public: + /** + * @sa autoManage() + */ + KateSuperRangeList(bool autoManage = true, QObject* parent = 0L, const char* name = 0L); + + /** + * Semi-copy constructor. + * + * Does not copy auto-manage value, as that would make it too easy to perform + * double-deletions. + * + * Also, does not connect signals and slots to save time, as this is mainly + * used by the document itself while drawing (call connectAll() to re-constitute) + */ + KateSuperRangeList(const QPtrList<KateSuperRange>& rangeList, QObject* parent = 0L, const char* name = 0L); + + virtual ~KateSuperRangeList() {} + /** + * Append another list. + * If this object was created by the semi-copy constructor, it may not connect items + * (unless connectAll() has already been called), call connectAll(). + */ + void appendList(const QPtrList<KateSuperRange>& rangeList); + + /** + * Connect items that are not connected. This only needs to be called once, + * and only if this was created with the semi-copy constructor. + */ + void connectAll(); + + /** + * Override to emit rangeEliminated() signals. + */ + virtual void clear(); + + /** + * Automanage is a combination of autodeleting of the objects and + * removing of any eliminated() ranges. + */ + bool autoManage() const; + + /** + * @sa autoManage() + */ + void setAutoManage(bool autoManage); + + /** + * This is just a straight-forward list so that there is no confusion about whether + * this list should be auto-managed (ie. it shouldn't, to avoid double deletions). + */ + QPtrList<KateSuperRange> rangesIncluding(const KateTextCursor& cursor); + QPtrList<KateSuperRange> rangesIncluding(uint line); + + /** + * @retval true if one of the ranges in the list includes @p cursor + * @retval false otherwise + */ + bool rangesInclude(const KateTextCursor& cursor); + + /** + * Construct a list of boundaries, and return the first, or 0L if there are none. + * If @p start is defined, the first boundary returned will be at or after @p start. + * + * @returns the first boundary location + */ + KateSuperCursor* firstBoundary(const KateTextCursor* start = 0L); + + /** + * @returns the next boundary, or 0L if there are no more. + */ + KateSuperCursor* nextBoundary(); + + /** + * @returns the current boundary + */ + KateSuperCursor* currentBoundary(); + +signals: + /** + * The range now contains no characters (ie. the start and end cursors are the same). + * If autoManage() is true, the range will be deleted after the signal has processed. + */ + void rangeEliminated(KateSuperRange* range); + + /** + * There are no ranges left. + */ + void listEmpty(); + + /** + * Connected to all ranges if connect()ed. + */ + void tagRange(KateSuperRange* range); + +protected: + /** + * internal reimplementation + */ + virtual int compareItems(QPtrCollection::Item item1, QPtrCollection::Item item2); + + /** + * internal reimplementation + */ + virtual QPtrCollection::Item newItem(QPtrCollection::Item d); + +private slots: + void slotEliminated(); + void slotDeleted(QObject* range); + +private: + bool m_autoManage; + bool m_connect; + + KateSuperCursorList m_columnBoundaries; + bool m_trackingBoundaries; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katesyntaxdocument.cpp b/kate/part/katesyntaxdocument.cpp new file mode 100644 index 000000000..e5c18c8ef --- /dev/null +++ b/kate/part/katesyntaxdocument.cpp @@ -0,0 +1,475 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2000 Scott Manson <sdmanson@alltel.net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "katesyntaxdocument.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <kdebug.h> +#include <kstandarddirs.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kconfig.h> + +#include <qfile.h> + +KateSyntaxDocument::KateSyntaxDocument(bool force) + : QDomDocument() +{ + // Let's build the Mode List (katesyntaxhighlightingrc) + setupModeList(force); +} + +KateSyntaxDocument::~KateSyntaxDocument() +{ + for (uint i=0; i < myModeList.size(); i++) + delete myModeList[i]; +} + +/** If the open hl file is different from the one needed, it opens + the new one and assign some other things. + identifier = File name and path of the new xml needed +*/ +bool KateSyntaxDocument::setIdentifier(const QString& identifier) +{ + // if the current file is the same as the new one don't do anything. + if(currentFile != identifier) + { + // let's open the new file + QFile f( identifier ); + + if ( f.open(IO_ReadOnly) ) + { + // Let's parse the contets of the xml file + /* The result of this function should be check for robustness, + a false returned means a parse error */ + QString errorMsg; + int line, col; + bool success=setContent(&f,&errorMsg,&line,&col); + + // Ok, now the current file is the pretended one (identifier) + currentFile = identifier; + + // Close the file, is not longer needed + f.close(); + + if (!success) + { + KMessageBox::error(0L,i18n("<qt>The error <b>%4</b><br> has been detected in the file %1 at %2/%3</qt>").arg(identifier) + .arg(line).arg(col).arg(i18n("QXml",errorMsg.utf8()))); + return false; + } + } + else + { + // Oh o, we couldn't open the file. + KMessageBox::error( 0L, i18n("Unable to open %1").arg(identifier) ); + return false; + } + } + return true; +} + +/** + * Jump to the next group, KateSyntaxContextData::currentGroup will point to the next group + */ +bool KateSyntaxDocument::nextGroup( KateSyntaxContextData* data) +{ + if(!data) + return false; + + // No group yet so go to first child + if (data->currentGroup.isNull()) + { + // Skip over non-elements. So far non-elements are just comments + QDomNode node = data->parent.firstChild(); + while (node.isComment()) + node = node.nextSibling(); + + data->currentGroup = node.toElement(); + } + else + { + // common case, iterate over siblings, skipping comments as we go + QDomNode node = data->currentGroup.nextSibling(); + while (node.isComment()) + node = node.nextSibling(); + + data->currentGroup = node.toElement(); + } + + return !data->currentGroup.isNull(); +} + +/** + * Jump to the next item, KateSyntaxContextData::item will point to the next item + */ +bool KateSyntaxDocument::nextItem( KateSyntaxContextData* data) +{ + if(!data) + return false; + + if (data->item.isNull()) + { + QDomNode node = data->currentGroup.firstChild(); + while (node.isComment()) + node = node.nextSibling(); + + data->item = node.toElement(); + } + else + { + QDomNode node = data->item.nextSibling(); + while (node.isComment()) + node = node.nextSibling(); + + data->item = node.toElement(); + } + + return !data->item.isNull(); +} + +/** + * This function is used to fetch the atributes of the tags of the item in a KateSyntaxContextData. + */ +QString KateSyntaxDocument::groupItemData( const KateSyntaxContextData* data, const QString& name){ + if(!data) + return QString::null; + + // If there's no name just return the tag name of data->item + if ( (!data->item.isNull()) && (name.isEmpty())) + { + return data->item.tagName(); + } + + // if name is not empty return the value of the attribute name + if (!data->item.isNull()) + { + return data->item.attribute(name); + } + + return QString::null; + +} + +QString KateSyntaxDocument::groupData( const KateSyntaxContextData* data,const QString& name) +{ + if(!data) + return QString::null; + + if (!data->currentGroup.isNull()) + { + return data->currentGroup.attribute(name); + } + else + { + return QString::null; + } +} + +void KateSyntaxDocument::freeGroupInfo( KateSyntaxContextData* data) +{ + if (data) + delete data; +} + +KateSyntaxContextData* KateSyntaxDocument::getSubItems(KateSyntaxContextData* data) +{ + KateSyntaxContextData *retval = new KateSyntaxContextData; + + if (data != 0) + { + retval->parent = data->currentGroup; + retval->currentGroup = data->item; + } + + return retval; +} + +bool KateSyntaxDocument::getElement (QDomElement &element, const QString &mainGroupName, const QString &config) +{ + kdDebug(13010) << "Looking for \"" << mainGroupName << "\" -> \"" << config << "\"." << endl; + + QDomNodeList nodes = documentElement().childNodes(); + + // Loop over all these child nodes looking for mainGroupName + for (unsigned int i=0; i<nodes.count(); i++) + { + QDomElement elem = nodes.item(i).toElement(); + if (elem.tagName() == mainGroupName) + { + // Found mainGroupName ... + QDomNodeList subNodes = elem.childNodes(); + + // ... so now loop looking for config + for (unsigned int j=0; j<subNodes.count(); j++) + { + QDomElement subElem = subNodes.item(j).toElement(); + if (subElem.tagName() == config) + { + // Found it! + element = subElem; + return true; + } + } + + kdDebug(13010) << "WARNING: \""<< config <<"\" wasn't found!" << endl; + return false; + } + } + + kdDebug(13010) << "WARNING: \""<< mainGroupName <<"\" wasn't found!" << endl; + return false; +} + +/** + * Get the KateSyntaxContextData of the QDomElement Config inside mainGroupName + * KateSyntaxContextData::item will contain the QDomElement found + */ +KateSyntaxContextData* KateSyntaxDocument::getConfig(const QString& mainGroupName, const QString &config) +{ + QDomElement element; + if (getElement(element, mainGroupName, config)) + { + KateSyntaxContextData *data = new KateSyntaxContextData; + data->item = element; + return data; + } + return 0; +} + +/** + * Get the KateSyntaxContextData of the QDomElement Config inside mainGroupName + * KateSyntaxContextData::parent will contain the QDomElement found + */ +KateSyntaxContextData* KateSyntaxDocument::getGroupInfo(const QString& mainGroupName, const QString &group) +{ + QDomElement element; + if (getElement(element, mainGroupName, group+"s")) + { + KateSyntaxContextData *data = new KateSyntaxContextData; + data->parent = element; + return data; + } + return 0; +} + +/** + * Returns a list with all the keywords inside the list type + */ +QStringList& KateSyntaxDocument::finddata(const QString& mainGroup, const QString& type, bool clearList) +{ + kdDebug(13010)<<"Create a list of keywords \""<<type<<"\" from \""<<mainGroup<<"\"."<<endl; + if (clearList) + m_data.clear(); + + for(QDomNode node = documentElement().firstChild(); !node.isNull(); node = node.nextSibling()) + { + QDomElement elem = node.toElement(); + if (elem.tagName() == mainGroup) + { + kdDebug(13010)<<"\""<<mainGroup<<"\" found."<<endl; + QDomNodeList nodelist1 = elem.elementsByTagName("list"); + + for (uint l=0; l<nodelist1.count(); l++) + { + if (nodelist1.item(l).toElement().attribute("name") == type) + { + kdDebug(13010)<<"List with attribute name=\""<<type<<"\" found."<<endl; + QDomNodeList childlist = nodelist1.item(l).toElement().childNodes(); + + for (uint i=0; i<childlist.count(); i++) + { + QString element = childlist.item(i).toElement().text().stripWhiteSpace(); + if (element.isEmpty()) + continue; +#ifndef NDEBUG + if (i<6) + { + kdDebug(13010)<<"\""<<element<<"\" added to the list \""<<type<<"\""<<endl; + } + else if(i==6) + { + kdDebug(13010)<<"... The list continues ..."<<endl; + } +#endif + m_data += element; + } + + break; + } + } + break; + } + } + + return m_data; +} + +// Private +/** Generate the list of hl modes, store them in myModeList + force: if true forces to rebuild the Mode List from the xml files (instead of katesyntax...rc) +*/ +void KateSyntaxDocument::setupModeList (bool force) +{ + // If there's something in myModeList the Mode List was already built so, don't do it again + if (!myModeList.isEmpty()) + return; + + // We'll store the ModeList in katesyntaxhighlightingrc + KConfig config("katesyntaxhighlightingrc", false, false); + + // figure our if the kate install is too new + config.setGroup ("General"); + if (config.readNumEntry ("Version") > config.readNumEntry ("CachedVersion")) + { + config.writeEntry ("CachedVersion", config.readNumEntry ("Version")); + force = true; + } + + // Let's get a list of all the xml files for hl + QStringList list = KGlobal::dirs()->findAllResources("data","katepart/syntax/*.xml",false,true); + + // Let's iterate through the list and build the Mode List + for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it ) + { + // Each file has a group called: + QString Group="Cache "+ *it; + + // Let's go to this group + config.setGroup(Group); + + // stat the file + struct stat sbuf; + memset (&sbuf, 0, sizeof(sbuf)); + stat(QFile::encodeName(*it), &sbuf); + + // If the group exist and we're not forced to read the xml file, let's build myModeList for katesyntax..rc + if (!force && config.hasGroup(Group) && (sbuf.st_mtime == config.readNumEntry("lastModified"))) + { + // Let's make a new KateSyntaxModeListItem to instert in myModeList from the information in katesyntax..rc + KateSyntaxModeListItem *mli=new KateSyntaxModeListItem; + mli->name = config.readEntry("name"); + mli->nameTranslated = i18n("Language",mli->name.utf8()); + mli->section = i18n("Language Section",config.readEntry("section").utf8()); + mli->mimetype = config.readEntry("mimetype"); + mli->extension = config.readEntry("extension"); + mli->version = config.readEntry("version"); + mli->priority = config.readEntry("priority"); + mli->author = config.readEntry("author"); + mli->license = config.readEntry("license"); + mli->hidden = config.readBoolEntry("hidden"); + mli->identifier = *it; + + // Apend the item to the list + myModeList.append(mli); + } + else + { + kdDebug (13010) << "UPDATE hl cache for: " << *it << endl; + + // We're forced to read the xml files or the mode doesn't exist in the katesyntax...rc + QFile f(*it); + + if (f.open(IO_ReadOnly)) + { + // Ok we opened the file, let's read the contents and close the file + /* the return of setContent should be checked because a false return shows a parsing error */ + QString errMsg; + int line, col; + + bool success = setContent(&f,&errMsg,&line,&col); + + f.close(); + + if (success) + { + QDomElement root = documentElement(); + + if (!root.isNull()) + { + // If the 'first' tag is language, go on + if (root.tagName()=="language") + { + // let's make the mode list item. + KateSyntaxModeListItem *mli = new KateSyntaxModeListItem; + + mli->name = root.attribute("name"); + mli->section = root.attribute("section"); + mli->mimetype = root.attribute("mimetype"); + mli->extension = root.attribute("extensions"); + mli->version = root.attribute("version"); + mli->priority = root.attribute("priority"); + mli->author = root.attribute("author"); + mli->license = root.attribute("license"); + + QString hidden = root.attribute("hidden"); + mli->hidden = (hidden == "true" || hidden == "TRUE"); + + mli->identifier = *it; + + // Now let's write or overwrite (if force==true) the entry in katesyntax...rc + config.setGroup(Group); + config.writeEntry("name",mli->name); + config.writeEntry("section",mli->section); + config.writeEntry("mimetype",mli->mimetype); + config.writeEntry("extension",mli->extension); + config.writeEntry("version",mli->version); + config.writeEntry("priority",mli->priority); + config.writeEntry("author",mli->author); + config.writeEntry("license",mli->license); + config.writeEntry("hidden",mli->hidden); + + // modified time to keep cache in sync + config.writeEntry("lastModified", sbuf.st_mtime); + + // Now that the data is in the config file, translate section + mli->section = i18n("Language Section",mli->section.utf8()); + mli->nameTranslated = i18n("Language",mli->name.utf8()); + + // Append the new item to the list. + myModeList.append(mli); + } + } + } + else + { + KateSyntaxModeListItem *emli=new KateSyntaxModeListItem; + + emli->section=i18n("Errors!"); + emli->mimetype="invalid_file/invalid_file"; + emli->extension="invalid_file.invalid_file"; + emli->version="1."; + emli->name=QString ("Error: %1").arg(*it); // internal + emli->nameTranslated=i18n("Error: %1").arg(*it); // translated + emli->identifier=(*it); + + myModeList.append(emli); + } + } + } + } + + // Syncronize with the file katesyntax...rc + config.sync(); +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katesyntaxdocument.h b/kate/part/katesyntaxdocument.h new file mode 100644 index 000000000..1e5171390 --- /dev/null +++ b/kate/part/katesyntaxdocument.h @@ -0,0 +1,164 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2000 Scott Manson <sdmanson@alltel.net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __KATE_SYNTAXDOCUMENT_H__ +#define __KATE_SYNTAXDOCUMENT_H__ + +#include <qdom.h> +#include <qstringlist.h> + +/** + * Information about each syntax hl Mode + */ +class KateSyntaxModeListItem +{ + public: + QString name; + QString nameTranslated; + QString section; + QString mimetype; + QString extension; + QString identifier; + QString version; + QString priority; + QString author; + QString license; + bool hidden; +}; + +/** + * List of the KateSyntaxModeListItems holding all the syntax mode list items + */ +typedef QValueList<KateSyntaxModeListItem*> KateSyntaxModeList; + +/** + * Class holding the data around the current QDomElement + */ +class KateSyntaxContextData +{ + public: + QDomElement parent; + QDomElement currentGroup; + QDomElement item; +}; + +/** + * Store and manage the information about Syntax Highlighting. + */ +class KateSyntaxDocument : public QDomDocument +{ + public: + /** + * Constructor + * Sets the current file to nothing and build the ModeList (katesyntaxhighlightingrc) + * @param force fore the update of the hl cache + */ + KateSyntaxDocument(bool force = false); + + /** + * Desctructor + */ + ~KateSyntaxDocument(); + + /** + * If the open hl file is different from the one needed, it opens + * the new one and assign some other things. + * @param identifier file name and path of the new xml needed + * @return success + */ + bool setIdentifier(const QString& identifier); + + /** + * Get the mode list + * @return mode list + */ + const KateSyntaxModeList &modeList() { return myModeList; } + + /** + * Jump to the next group, KateSyntaxContextData::currentGroup will point to the next group + * @param data context + * @return success + */ + bool nextGroup(KateSyntaxContextData* data); + + /** + * Jump to the next item, KateSyntaxContextData::item will point to the next item + * @param data context + * @return success + */ + bool nextItem(KateSyntaxContextData* data); + + /** + * This function is used to fetch the atributes of the tags. + */ + QString groupItemData(const KateSyntaxContextData* data,const QString& name); + QString groupData(const KateSyntaxContextData* data,const QString& name); + + void freeGroupInfo(KateSyntaxContextData* data); + KateSyntaxContextData* getSubItems(KateSyntaxContextData* data); + + /** + * Get the KateSyntaxContextData of the DomElement Config inside mainGroupName + * It just fills KateSyntaxContextData::item + */ + KateSyntaxContextData* getConfig(const QString& mainGroupName, const QString &config); + + /** + * Get the KateSyntaxContextData of the QDomElement Config inside mainGroupName + * KateSyntaxContextData::parent will contain the QDomElement found + */ + KateSyntaxContextData* getGroupInfo(const QString& mainGroupName, const QString &group); + + /** + * Returns a list with all the keywords inside the list type + */ + QStringList& finddata(const QString& mainGroup,const QString& type,bool clearList=true); + + private: + /** + * Generate the list of hl modes, store them in myModeList + * @param force if true forces to rebuild the Mode List from the xml files (instead of katesyntax...rc) + */ + void setupModeList(bool force); + + /** + * Used by getConfig and getGroupInfo to traverse the xml nodes and + * evenually return the found element + */ + bool getElement (QDomElement &element, const QString &mainGroupName, const QString &config); + + /** + * List of mode items + */ + KateSyntaxModeList myModeList; + + /** + * current parsed filename + */ + QString currentFile; + + /** + * last found data out of the xml + */ + QStringList m_data; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katetemplatehandler.cpp b/kate/part/katetemplatehandler.cpp new file mode 100644 index 000000000..3ca86ff70 --- /dev/null +++ b/kate/part/katetemplatehandler.cpp @@ -0,0 +1,342 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004 Joseph Wenninger <jowenn@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include "katetemplatehandler.h" +#include "katetemplatehandler.moc" +#include "katedocument.h" +#include "katesupercursor.h" +#include "katearbitraryhighlight.h" +#include "kateview.h" +#include <qregexp.h> +#include <kdebug.h> +#include <qvaluelist.h> + +KateTemplateHandler::KateTemplateHandler( + KateDocument *doc, + uint line, uint column, + const QString &templateString, + const QMap<QString, QString> &initialValues ) + : QObject( doc ) + , KateKeyInterceptorFunctor() + , m_doc( doc ) + , m_currentTabStop( -1 ) + , m_currentRange( 0 ) + , m_initOk( false ) + , m_recursion( false ) +{ + connect( m_doc, SIGNAL( destroyed() ), this, SLOT( slotDocumentDestroyed() ) ); + m_ranges = new KateSuperRangeList( false, this ); //false/*,this*/); + + if ( !m_doc->setTabInterceptor( this ) ) + { + deleteLater(); + return ; + } + + KateArbitraryHighlight *kah = doc->arbitraryHL(); + /*KateArbitraryHighlightRange *hlr=new KateArbitraryHighlightRange(doc,KateTextCursor(line,column), + KateTextCursor(line,column+3)); + hlr->setUnderline(true); + hlr->setOverline(true); + l->append(hlr);*/ + QValueList<KateTemplateHandlerPlaceHolderInfo> buildList; + QRegExp rx( "([$%])\\{([^}\\s]+)\\}" ); + rx.setMinimal( true ); + int pos = 0; + int opos = 0; + QString insertString = templateString; + + while ( pos >= 0 ) + { + pos = rx.search( insertString, pos ); + + if ( pos > -1 ) + { + if ( ( pos - opos ) > 0 ) + { + if ( insertString[ pos - 1 ] == '\\' ) + { + insertString.remove( pos - 1, 1 ); + opos = pos; + continue; + } + } + + QString placeholder = rx.cap( 2 ); + QString value = initialValues[ placeholder ]; + + // don't add %{MACRO} to the tab navigation, unless there was not value + if ( rx.cap( 1 ) != "%" || placeholder == value ) + buildList.append( KateTemplateHandlerPlaceHolderInfo( pos, value.length(), placeholder ) ); + + insertString.replace( pos, rx.matchedLength(), value ); + pos += value.length(); + opos = pos; + } + } + + doc->editStart(); + + if ( !doc->insertText( line, column, insertString ) ) + { + deleteLater(); + doc->editEnd(); + return ; + } + + if ( buildList.isEmpty() ) + { + m_initOk = true; + deleteLater(); + doc->editEnd(); + return ; + } + + doc->undoSafePoint(); + doc->editEnd(); + generateRangeTable( line, column, insertString, buildList ); + kah->addHighlightToDocument( m_ranges ); + + for ( KateSuperRangeList::const_iterator it = m_ranges->begin();it != m_ranges->end();++it ) + { + m_doc->tagLines( ( *it ) ->start().line(), ( *it ) ->end().line() ); + } + + /* connect(doc,SIGNAL(charactersInteractivelyInserted(int ,int ,const QString&)),this, + SLOT(slotCharactersInteractivlyInserted(int,int,const QString&))); + connect(doc,SIGNAL(charactersSemiInteractivelyInserted(int ,int ,const QString&)),this, + SLOT(slotCharactersInteractivlyInserted(int,int,const QString&)));*/ + connect( doc, SIGNAL( textInserted( int, int ) ), this, SLOT( slotTextInserted( int, int ) ) ); + connect( doc, SIGNAL( aboutToRemoveText( const KateTextRange& ) ), this, SLOT( slotAboutToRemoveText( const KateTextRange& ) ) ); + connect( doc, SIGNAL( textRemoved() ), this, SLOT( slotTextRemoved() ) ); + + ( *this ) ( Qt::Key_Tab ); +} + +KateTemplateHandler::~KateTemplateHandler() +{ + m_ranges->setAutoManage( true ); + + if ( m_doc ) + { + m_doc->removeTabInterceptor( this ); + + for ( KateSuperRangeList::const_iterator it = m_ranges->begin();it != m_ranges->end();++it ) + { + m_doc->tagLines( ( *it ) ->start().line(), ( *it ) ->end().line() ); + } + } + + m_ranges->clear(); +} + +void KateTemplateHandler::slotDocumentDestroyed() {m_doc = 0;} + +void KateTemplateHandler::generateRangeTable( uint insertLine, uint insertCol, const QString& insertString, const QValueList<KateTemplateHandlerPlaceHolderInfo> &buildList ) +{ + uint line = insertLine; + uint col = insertCol; + uint colInText = 0; + + for ( QValueList<KateTemplateHandlerPlaceHolderInfo>::const_iterator it = buildList.begin();it != buildList.end();++it ) + { + KateTemplatePlaceHolder *ph = m_dict[ ( *it ).placeholder ]; + + if ( !ph ) + { + ph = new KateTemplatePlaceHolder; + ph->isInitialValue = true; + ph->isCursor = ( ( *it ).placeholder == "cursor" ); + m_dict.insert( ( *it ).placeholder, ph ); + + if ( !ph->isCursor ) m_tabOrder.append( ph ); + + ph->ranges.setAutoManage( false ); + } + + // FIXME handle space/tab replacement correctly make it use of the indenter + while ( colInText < ( *it ).begin ) + { + ++col; + + if ( insertString.at( colInText ) == '\n' ) + { + col = 0; + line++; + } + + ++colInText; + } + + KateArbitraryHighlightRange *hlr = new KateArbitraryHighlightRange( m_doc, KateTextCursor( line, col ), + KateTextCursor( line, ( *it ).len + col ) ); + colInText += ( *it ).len; + col += ( *it ).len; + hlr->allowZeroLength(); + hlr->setUnderline( true ); + hlr->setOverline( true ); + //hlr->setBehaviour(KateSuperRange::ExpandRight); + ph->ranges.append( hlr ); + m_ranges->append( hlr ); + } + + KateTemplatePlaceHolder *cursor = m_dict[ "cursor" ]; + + if ( cursor ) m_tabOrder.append( cursor ); +} + +void KateTemplateHandler::slotTextInserted( int line, int col ) +{ +#ifdef __GNUC__ +#warning FIXME undo/redo detection +#endif + + if ( m_recursion ) return ; + + //if (m_editSessionNumber!=0) return; // assume that this is due an udno/redo operation right now + KateTextCursor cur( line, col ); + + if ( ( !m_currentRange ) || + ( ( !m_currentRange->includes( cur ) ) && ( ! ( ( m_currentRange->start() == m_currentRange->end() ) && m_currentRange->end() == cur ) ) + ) ) locateRange( cur ); + + if ( !m_currentRange ) return ; + + KateTemplatePlaceHolder *ph = m_tabOrder.at( m_currentTabStop ); + + QString sourceText = m_doc->text ( m_currentRange->start().line(), m_currentRange->start().col(), + m_currentRange->end().line(), m_currentRange->end().col(), false ); + + ph->isInitialValue = false; + bool undoDontMerge = m_doc->m_undoDontMerge; + Q_ASSERT( m_doc->editSessionNumber == 0 ); + m_recursion = true; + + m_doc->editStart( /*false*/ ); + + for ( KateSuperRangeList::const_iterator it = ph->ranges.begin();it != ph->ranges.end();++it ) + { + if ( ( *it ) == m_currentRange ) continue; + + KateTextCursor start = ( *it ) ->start(); + KateTextCursor end = ( *it ) ->end(); + m_doc->removeText( start.line(), start.col(), end.line(), end.col(), false ); + m_doc->insertText( start.line(), start.col(), sourceText ); + } + + m_doc->m_undoDontMerge = false; + m_doc->m_undoComplexMerge = true; + m_doc->undoSafePoint(); + m_doc->editEnd(); + m_doc->m_undoDontMerge = undoDontMerge; + m_recursion = false; + + if ( ph->isCursor ) deleteLater(); +} + +void KateTemplateHandler::locateRange( const KateTextCursor& cursor ) +{ + /* if (m_currentRange) { + m_doc->tagLines(m_currentRange->start().line(),m_currentRange->end().line()); + + }*/ + + for ( uint i = 0;i < m_tabOrder.count();i++ ) + { + KateTemplatePlaceHolder *ph = m_tabOrder.at( i ); + + for ( KateSuperRangeList::const_iterator it = ph->ranges.begin();it != ph->ranges.end();++it ) + { + if ( ( *it ) ->includes( cursor ) ) + { + m_currentTabStop = i; + m_currentRange = ( *it ); + //m_doc->tagLines(m_currentRange->start().line(),m_currentRange->end().line()); + return ; + } + } + + } + + m_currentRange = 0; + /*while (m_ranges->count()>0) + delete (m_ranges->take(0)); + disconnect(m_ranges,0,0,0); + delete m_ranges;*/ + deleteLater(); +} + + +bool KateTemplateHandler::operator() ( KKey key ) +{ + if ( key==Qt::Key_Tab ) + { + m_currentTabStop++; + + if ( m_currentTabStop >= ( int ) m_tabOrder.count() ) + m_currentTabStop = 0; + } + else + { + m_currentTabStop--; + + if ( m_currentTabStop < 0 ) m_currentTabStop = m_tabOrder.count() - 1; + } + + m_currentRange = m_tabOrder.at( m_currentTabStop ) ->ranges.at( 0 ); + + if ( m_tabOrder.at( m_currentTabStop ) ->isInitialValue ) + { + m_doc->activeView()->setSelection( m_currentRange->start(), m_currentRange->end() ); + } + else m_doc->activeView()->setSelection( m_currentRange->end(), m_currentRange->end() ); + + m_doc->activeView() ->setCursorPositionReal( m_currentRange->end().line(), m_currentRange->end().col() ); + m_doc->activeView() ->tagLine( m_currentRange->end() ); + + return true; +} + +void KateTemplateHandler::slotAboutToRemoveText( const KateTextRange &range ) +{ + if ( m_recursion ) return ; + + if ( m_currentRange && ( !m_currentRange->includes( range.start() ) ) ) locateRange( range.start() ); + + if ( m_currentRange != 0 ) + { + if ( m_currentRange->end() <= range.end() ) return ; + } + + if ( m_doc ) + { + disconnect( m_doc, SIGNAL( textInserted( int, int ) ), this, SLOT( slotTextInserted( int, int ) ) ); + disconnect( m_doc, SIGNAL( aboutToRemoveText( const KateTextRange& ) ), this, SLOT( slotAboutToRemoveText( const KateTextRange& ) ) ); + disconnect( m_doc, SIGNAL( textRemoved() ), this, SLOT( slotTextRemoved() ) ); + } + + deleteLater(); +} + +void KateTemplateHandler::slotTextRemoved() +{ + if ( m_recursion ) return ; + if ( !m_currentRange ) return ; + + slotTextInserted( m_currentRange->start().line(), m_currentRange->start().col() ); +} + diff --git a/kate/part/katetemplatehandler.h b/kate/part/katetemplatehandler.h new file mode 100644 index 000000000..a2f41f518 --- /dev/null +++ b/kate/part/katetemplatehandler.h @@ -0,0 +1,68 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004 Joseph Wenninger <jowenn@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef _KATE_TEMPLATE_HANDLER_H_ +#define _KATE_TEMPLATE_HANDLER_H_ + +#include "katesupercursor.h" +#include "katekeyinterceptorfunctor.h" +#include <qobject.h> +#include <qmap.h> +#include <qdict.h> +#include <qptrlist.h> +#include <qstring.h> + +class KateDocument; + +class KateTemplateHandler: public QObject, public KateKeyInterceptorFunctor { + Q_OBJECT + public: + KateTemplateHandler(KateDocument *doc,uint line,uint column, const QString &templateString, const QMap<QString,QString> &initialValues); + virtual ~KateTemplateHandler(); + inline bool initOk() {return m_initOk;} + virtual bool operator()(KKey key); + private: + struct KateTemplatePlaceHolder { + KateSuperRangeList ranges; + bool isCursor; + bool isInitialValue; + }; + class KateTemplateHandlerPlaceHolderInfo{ + public: + KateTemplateHandlerPlaceHolderInfo():begin(0),len(0),placeholder(""){}; + KateTemplateHandlerPlaceHolderInfo(uint begin_,uint len_,const QString& placeholder_):begin(begin_),len(len_),placeholder(placeholder_){} + uint begin; + uint len; + QString placeholder; + }; + class KateSuperRangeList *m_ranges; + class KateDocument *m_doc; + QPtrList<KateTemplatePlaceHolder> m_tabOrder; + QDict<KateTemplatePlaceHolder> m_dict; + void generateRangeTable(uint insertLine,uint insertCol, const QString& insertString, const QValueList<KateTemplateHandlerPlaceHolderInfo> &buildList); + int m_currentTabStop; + KateSuperRange *m_currentRange; + void locateRange(const KateTextCursor &cursor ); + bool m_initOk; + bool m_recursion; + private slots: + void slotTextInserted(int,int); + void slotDocumentDestroyed(); + void slotAboutToRemoveText(const KateTextRange &range); + void slotTextRemoved(); +}; +#endif diff --git a/kate/part/katetextline.cpp b/kate/part/katetextline.cpp new file mode 100644 index 000000000..250be5236 --- /dev/null +++ b/kate/part/katetextline.cpp @@ -0,0 +1,443 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001-2003 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2002 Joseph Wenninger <jowenn@kde.org> + + Based on: + KateTextLine : Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "katetextline.h" +#include "katerenderer.h" + +#include <kglobal.h> + +#include <qregexp.h> + +KateTextLine::KateTextLine () + : m_flags(0) +{ +} + +KateTextLine::~KateTextLine() +{ +} + +void KateTextLine::insertText (uint pos, uint insLen, const QChar *insText, uchar *insAttribs) +{ + // nothing to do + if (insLen == 0) + return; + + // calc new textLen, store old + uint oldTextLen = m_text.length(); + m_text.insert (pos, insText, insLen); + uint textLen = m_text.length(); + + // resize the array + m_attributes.resize (textLen); + + // HA, insert behind text end, fill with spaces + if (pos >= oldTextLen) + { + for (uint z = oldTextLen; z < pos; z++) + m_attributes[z] = 0; + } + // HA, insert in text, move the old text behind pos + else if (oldTextLen > 0) + { + for (int z = oldTextLen -1; z >= (int) pos; z--) + m_attributes[z+insLen] = m_attributes[z]; + } + + // BUH, actually insert the new text + for (uint z = 0; z < insLen; z++) + { + if (insAttribs == 0) + m_attributes[z+pos] = 0; + else + m_attributes[z+pos] = insAttribs[z]; + } +} + +void KateTextLine::removeText (uint pos, uint delLen) +{ + // nothing to do + if (delLen == 0) + return; + + uint textLen = m_text.length(); + + if (textLen == 0) + return; // uh, again nothing real to do ;) + + if (pos >= textLen) + return; + + if ((pos + delLen) > textLen) + delLen = textLen - pos; + + // BU, MOVE THE OLD TEXT AROUND + for (uint z = pos; z < textLen - delLen; z++) + m_attributes[z] = m_attributes[z+delLen]; + + m_text.remove (pos, delLen); + m_attributes.resize (m_text.length ()); +} + +void KateTextLine::truncate(uint newLen) +{ + if (newLen < m_text.length()) + { + m_text.truncate (newLen); + m_attributes.truncate (newLen); + } +} + +int KateTextLine::nextNonSpaceChar(uint pos) const +{ + const uint len = m_text.length(); + const QChar *unicode = m_text.unicode(); + + for(uint i = pos; i < len; i++) + { + if(!unicode[i].isSpace()) + return i; + } + + return -1; +} + +int KateTextLine::previousNonSpaceChar(uint pos) const +{ + const int len = m_text.length(); + + if (pos >= (uint)len) + pos = len - 1; + + const QChar *unicode = m_text.unicode(); + + for(int i = pos; i >= 0; i--) + { + if(!unicode[i].isSpace()) + return i; + } + + return -1; +} + +int KateTextLine::firstChar() const +{ + return nextNonSpaceChar(0); +} + +int KateTextLine::lastChar() const +{ + return previousNonSpaceChar(m_text.length() - 1); +} + +const QChar *KateTextLine::firstNonSpace() const +{ + int first = firstChar(); + return (first > -1) ? ((QChar*)m_text.unicode())+first : m_text.unicode(); +} + +uint KateTextLine::indentDepth (uint tabwidth) const +{ + uint d = 0; + const uint len = m_text.length(); + const QChar *unicode = m_text.unicode(); + + for(uint i = 0; i < len; i++) + { + if(unicode[i].isSpace()) + { + if (unicode[i] == QChar('\t')) + d += tabwidth - (d % tabwidth); + else + d++; + } + else + return d; + } + + return d; +} + +bool KateTextLine::stringAtPos(uint pos, const QString& match) const +{ + const uint len = m_text.length(); + const uint matchlen = match.length(); + + if ((pos+matchlen) > len) + return false; + + // (pos > len) in case the uint pos was assigned a signed -1, pos+matchlen can + // overflow again which (pos+matchlen > len) does not catch; see bugs #129263 and #129580 + Q_ASSERT(pos < len); + + const QChar *unicode = m_text.unicode(); + const QChar *matchUnicode = match.unicode(); + + for (uint i=0; i < matchlen; i++) + if (unicode[i+pos] != matchUnicode[i]) + return false; + + return true; +} + +bool KateTextLine::startingWith(const QString& match) const +{ + const uint matchlen = match.length(); + + if (matchlen > m_text.length()) + return false; + + const QChar *unicode = m_text.unicode(); + const QChar *matchUnicode = match.unicode(); + + for (uint i=0; i < matchlen; i++) + if (unicode[i] != matchUnicode[i]) + return false; + + return true; +} + +bool KateTextLine::endingWith(const QString& match) const +{ + const uint matchlen = match.length(); + + if (matchlen > m_text.length()) + return false; + + const QChar *unicode = m_text.unicode(); + const QChar *matchUnicode = match.unicode(); + + uint start = m_text.length() - matchlen; + for (uint i=0; i < matchlen; i++) + if (unicode[start+i] != matchUnicode[i]) + return false; + + return true; +} + +int KateTextLine::cursorX(uint pos, uint tabChars) const +{ + uint x = 0; + + const uint n = kMin (pos, m_text.length()); + const QChar *unicode = m_text.unicode(); + + for ( uint z = 0; z < n; z++) + { + if (unicode[z] == QChar('\t')) + x += tabChars - (x % tabChars); + else + x++; + } + + return x; +} + + +uint KateTextLine::lengthWithTabs (uint tabChars) const +{ + uint x = 0; + const uint len = m_text.length(); + const QChar *unicode = m_text.unicode(); + + for ( uint z = 0; z < len; z++) + { + if (unicode[z] == QChar('\t')) + x += tabChars - (x % tabChars); + else + x++; + } + + return x; +} + +bool KateTextLine::searchText (uint startCol, const QString &text, uint *foundAtCol, uint *matchLen, bool casesensitive, bool backwards) +{ + int index; + + if (backwards) + { + int col = startCol; + uint l = text.length(); + // allow finding the string ending at eol + if ( col == (int) m_text.length() ) ++startCol; + + do { + index = m_text.findRev( text, col, casesensitive ); + col--; + } while ( col >= 0 && l + index >= startCol ); + } + else + index = m_text.find (text, startCol, casesensitive); + + if (index > -1) + { + if (foundAtCol) + (*foundAtCol) = index; + if (matchLen) + (*matchLen)=text.length(); + return true; + } + + return false; +} + +bool KateTextLine::searchText (uint startCol, const QRegExp ®exp, uint *foundAtCol, uint *matchLen, bool backwards) +{ + int index; + + if (backwards) + { + int col = startCol; + + // allow finding the string ending at eol + if ( col == (int) m_text.length() ) ++startCol; + do { + index = regexp.searchRev (m_text, col); + col--; + } while ( col >= 0 && regexp.matchedLength() + index >= (int)startCol ); + } + else + index = regexp.search (m_text, startCol); + + if (index > -1) + { + if (foundAtCol) + (*foundAtCol) = index; + + if (matchLen) + (*matchLen)=regexp.matchedLength(); + return true; + } + + return false; +} + +char *KateTextLine::dump (char *buf, bool withHighlighting) const +{ + uint l = m_text.length(); + char f = m_flags; + + if (!withHighlighting) + f = f | KateTextLine::flagNoOtherData; + + memcpy(buf, (char *) &f, 1); + buf += 1; + + memcpy(buf, &l, sizeof(uint)); + buf += sizeof(uint); + + memcpy(buf, (char *) m_text.unicode(), sizeof(QChar)*l); + buf += sizeof(QChar) * l; + + if (!withHighlighting) + return buf; + + memcpy(buf, (char *)m_attributes.data(), sizeof(uchar) * l); + buf += sizeof (uchar) * l; + + uint lctx = m_ctx.size(); + uint lfold = m_foldingList.size(); + uint lind = m_indentationDepth.size(); + + memcpy(buf, &lctx, sizeof(uint)); + buf += sizeof(uint); + + memcpy(buf, &lfold, sizeof(uint)); + buf += sizeof(uint); + + memcpy(buf, &lind, sizeof(uint)); + buf += sizeof(uint); + + memcpy(buf, (char *)m_ctx.data(), sizeof(short) * lctx); + buf += sizeof (short) * lctx; + + memcpy(buf, (char *)m_foldingList.data(), sizeof(uint)*lfold); + buf += sizeof (uint) * lfold; + + memcpy(buf, (char *)m_indentationDepth.data(), sizeof(unsigned short) * lind); + buf += sizeof (unsigned short) * lind; + + return buf; +} + +char *KateTextLine::restore (char *buf) +{ + uint l = 0; + char f = 0; + + memcpy((char *) &f, buf, 1); + buf += 1; + + // text + context length read + memcpy((char *) &l, buf, sizeof(uint)); + buf += sizeof(uint); + + // text + attributes + m_text.setUnicode ((QChar *) buf, l); + buf += sizeof(QChar) * l; + + // we just restore a KateTextLine from a buffer first time + if (f & KateTextLine::flagNoOtherData) + { + m_flags = 0; + + if (f & KateTextLine::flagAutoWrapped) + m_flags = m_flags | KateTextLine::flagAutoWrapped; + + // fill with clean empty attribs ! + m_attributes.fill (0, l); + + return buf; + } + else + m_flags = f; + + m_attributes.duplicate ((uchar *) buf, l); + buf += sizeof(uchar) * l; + + uint lctx = 0; + uint lfold = 0; + uint lind = 0; + + memcpy((char *) &lctx, buf, sizeof(uint)); + buf += sizeof(uint); + + memcpy((char *) &lfold, buf, sizeof(uint)); + buf += sizeof(uint); + + memcpy((char *) &lind, buf, sizeof(uint)); + buf += sizeof(uint); + + m_ctx.duplicate ((short *) buf, lctx); + buf += sizeof(short) * lctx; + + m_foldingList.duplicate ((uint *) buf, lfold); + buf += sizeof(uint)*lfold; + + m_indentationDepth.duplicate ((unsigned short *) buf, lind); + buf += sizeof(unsigned short) * lind; + + return buf; +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katetextline.h b/kate/part/katetextline.h new file mode 100644 index 000000000..9a6ed5e4d --- /dev/null +++ b/kate/part/katetextline.h @@ -0,0 +1,456 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001-2003 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2002 Joseph Wenninger <jowenn@kde.org> + + Based on: + KateTextLine : Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __KATE_TEXTLINE_H__ +#define __KATE_TEXTLINE_H__ + +#include <ksharedptr.h> + +#include <qmemarray.h> +#include <qstring.h> + +class KateRenderer; +class QTextStream; + +/** + * The KateTextLine represents a line of text. A text line that contains the + * text, an attribute for each character, an attribute for the free space + * behind the last character and a context number for the syntax highlight. + * The attribute stores the index to a table that contains fonts and colors + * and also if a character is selected. + */ +class KateTextLine : public KShared +{ + public: + /** + * Define a Shared-Pointer type + */ + typedef KSharedPtr<KateTextLine> Ptr; + + public: + /** + * Used Flags + */ + enum Flags + { + flagNoOtherData = 1, // ONLY INTERNAL USE, NEVER EVER SET THAT !!!! + flagHlContinue = 2, + flagAutoWrapped = 4, + flagFoldingColumnsOutdated = 8, + flagNoIndentationBasedFolding = 16, + flagNoIndentationBasedFoldingAtStart = 32 + }; + + public: + /** + * Constructor + * Creates an empty text line with given attribute and syntax highlight + * context + */ + KateTextLine (); + + /** + * Destructor + */ + ~KateTextLine (); + + /** + * Methods to get data + */ + public: + /** + * Set the flag that only positions have changed, not folding region begins/ends themselve + */ + inline void setFoldingColumnsOutdated(bool set) { if (set) m_flags |= KateTextLine::flagFoldingColumnsOutdated; else m_flags&= + (~KateTextLine::flagFoldingColumnsOutdated);} + + /** + * folding columns outdated ? + * @return folding columns outdated? + */ + inline bool foldingColumnsOutdated() { return m_flags & KateTextLine::flagFoldingColumnsOutdated; } + + + /** + * Returns the length + * @return length of text in line + */ + inline uint length() const { return m_text.length(); } + + /** + * has the line the hl continue flag set + * @return hl continue set? + */ + inline bool hlLineContinue () const { return m_flags & KateTextLine::flagHlContinue; } + + /** + * was this line automagically wrapped + * @return line auto-wrapped + */ + inline bool isAutoWrapped () const { return m_flags & KateTextLine::flagAutoWrapped; } + + /** + * Returns the position of the first non-whitespace character + * @return position of first non-whitespace char or -1 if there is none + */ + int firstChar() const; + + /** + * Returns the position of the last non-whitespace character + * @return position of last non-whitespace char or -1 if there is none + */ + int lastChar() const; + + /** + * Find the position of the next char that is not a space. + * @param pos Column of the character which is examined first. + * @return True if the specified or a following character is not a space + * Otherwise false. + */ + int nextNonSpaceChar(uint pos) const; + + /** + * Find the position of the previous char that is not a space. + * @param pos Column of the character which is examined first. + * @return The position of the first none-whitespace character preceeding pos, + * or -1 if none is found. + */ + int previousNonSpaceChar(uint pos) const; + + /** + * Gets the char at the given position + * @param pos position + * @return character at the given position or QChar::null if position is + * beyond the length of the string + */ + inline QChar getChar (uint pos) const { return m_text[pos]; } + + /** + * Gets the text as a unicode representation + * @return text of this line as QChar array + */ + inline const QChar *text() const { return m_text.unicode(); } + + /** + * Highlighting array + * The size of this is string().length() + * + * This contains the index for the attributes (so you can only + * have a maximum of 2^8 different highlighting styles in a document) + * + * To turn this into actual attributes (bold, green, etc), + * you need to feed these values into KRenderer::attributes + * + * e.g: m_renderer->attributes(attributes[3]); + * + * @return hl-attributes array + */ + inline uchar *attributes () const { return m_attributes.data(); } + + /** + * Gets a QString + * @return text of line as QString reference + */ + inline const QString& string() const { return m_text; } + + /** + * Gets a substring. + * @param startCol start column of substring + * @param length length of substring + * @return wanted substring + */ + inline QString string(uint startCol, uint length) const + { return m_text.mid(startCol, length); } + + /** + * Gets a null terminated pointer to first non space char + * @return array of QChars starting at first non-whitespace char + */ + const QChar *firstNonSpace() const; + + /** + * indentation depth of this line + * @param tabwidth width of the tabulators + * @return indentation width + */ + uint indentDepth (uint tabwidth) const; + + /** + * Returns the x position of the cursor at the given position, which + * depends on the number of tab characters + * @param pos position in chars + * @param tabChars tabulator width in chars + * @return position with tabulators calculated + */ + int cursorX(uint pos, uint tabChars) const; + + /** + * Returns the text length with tabs calced in + * @param tabChars tabulator width in chars + * @return text length + */ + uint lengthWithTabs (uint tabChars) const; + + /** + * Can we find the given string at the given position + * @param pos startpostion of given string + * @param match string to match at given pos + * @return did the string match? + */ + bool stringAtPos(uint pos, const QString& match) const; + + /** + * Is the line starting with the given string + * @param match string to test + * @return does line start with given string? + */ + bool startingWith(const QString& match) const; + + /** + * Is the line ending with the given string + * @param match string to test + * @return does the line end with given string? + */ + bool endingWith(const QString& match) const; + + /** + * search given string + * @param startCol column to start search + * @param text string to search for + * @param foundAtCol column where text was found + * @param matchLen length of matching + * @param casesensitive should search be case-sensitive + * @param backwards search backwards? + * @return string found? + */ + bool searchText (uint startCol, const QString &text, + uint *foundAtCol, uint *matchLen, + bool casesensitive = true, + bool backwards = false); + + /** + * search given regexp + * @param startCol column to start search + * @param regexp regex to search for + * @param foundAtCol column where text was found + * @param matchLen length of matching + * @param backwards search backwards? + * @return regexp found? + */ + bool searchText (uint startCol, const QRegExp ®exp, + uint *foundAtCol, uint *matchLen, + bool backwards = false); + + /** + * Gets the attribute at the given position + * use KRenderer::attributes to get the KTextAttribute for this. + * + * @param pos position of attribute requested + * @return value of attribute + * @see attributes + */ + inline uchar attribute (uint pos) const + { + if (pos < m_attributes.size()) return m_attributes[pos]; + return 0; + } + + /** + * context stack + * @return context stack + */ + inline const QMemArray<short> &ctxArray () const { return m_ctx; }; + + /** + * @return true if any context at the line end has the noIndentBasedFolding flag set + */ + inline const bool noIndentBasedFolding() const { return m_flags & KateTextLine::flagNoIndentationBasedFolding; }; + inline const bool noIndentBasedFoldingAtStart() const { return m_flags & KateTextLine::flagNoIndentationBasedFoldingAtStart; }; + /** + * folding list + * @return folding array + */ + inline const QMemArray<uint> &foldingListArray () const { return m_foldingList; }; + + /** + * indentation stack + * @return indentation array + */ + inline const QMemArray<unsigned short> &indentationDepthArray () const { return m_indentationDepth; }; + + /** + * insert text into line + * @param pos insert position + * @param insLen insert length + * @param insText text to insert + * @param insAttribs attributes for the insert text + */ + void insertText (uint pos, uint insLen, const QChar *insText, uchar *insAttribs = 0); + + /** + * remove text at given position + * @param pos start position of remove + * @param delLen length to remove + */ + void removeText (uint pos, uint delLen); + + /** + * Truncates the textline to the new length + * @param newLen new length of line + */ + void truncate(uint newLen); + + /** + * set hl continue flag + * @param cont continue flag? + */ + inline void setHlLineContinue (bool cont) + { + if (cont) m_flags = m_flags | KateTextLine::flagHlContinue; + else m_flags = m_flags & ~ KateTextLine::flagHlContinue; + } + + /** + * auto-wrapped + * @param wrapped line was wrapped? + */ + inline void setAutoWrapped (bool wrapped) + { + if (wrapped) m_flags = m_flags | KateTextLine::flagAutoWrapped; + else m_flags = m_flags & ~ KateTextLine::flagAutoWrapped; + } + + /** + * Sets the syntax highlight context number + * @param val new context array + */ + inline void setContext (QMemArray<short> &val) { m_ctx.assign (val); } + + /** + * sets if for the next line indent based folding should be disabled + */ + inline void setNoIndentBasedFolding(bool val) + { + if (val) m_flags = m_flags | KateTextLine::flagNoIndentationBasedFolding; + else m_flags = m_flags & ~ KateTextLine::flagNoIndentationBasedFolding; + } + + inline void setNoIndentBasedFoldingAtStart(bool val) + { + if (val) m_flags = m_flags | KateTextLine::flagNoIndentationBasedFoldingAtStart; + else m_flags = m_flags & ~ KateTextLine::flagNoIndentationBasedFoldingAtStart; + } + + /** + * update folding list + * @param val new folding list + */ + inline void setFoldingList (QMemArray<uint> &val) { m_foldingList.assign (val); m_foldingList.detach(); } + + /** + * update indentation stack + * @param val new indentation stack + */ + inline void setIndentationDepth (QMemArray<unsigned short> &val) { m_indentationDepth.assign (val); } + + /** + * Methodes for dump/restore of the line in the buffer + */ + public: + /** + * Dumpsize in bytes + * @param withHighlighting should we dump the hl, too? + * @return size of line for dumping + */ + inline uint dumpSize (bool withHighlighting) const + { + return ( 1 + + sizeof(uint) + + (m_text.length() * sizeof(QChar)) + + ( withHighlighting ? + ( (3 * sizeof(uint)) + + (m_text.length() * sizeof(uchar)) + + (m_ctx.size() * sizeof(short)) + + (m_foldingList.size() * sizeof(uint)) + + (m_indentationDepth.size() * sizeof(unsigned short)) + ) : 0 + ) + ); + } + + /** + * Dumps the line to *buf and counts buff dumpSize bytes up + * as return value + * @param buf buffer to dump to + * @param withHighlighting dump hl data, too? + * @return buffer index after dumping + */ + char *dump (char *buf, bool withHighlighting) const; + + /** + * Restores the line from *buf and counts buff dumpSize bytes up + * as return value + * @param buf buffer to restore from + * @return buffer index after restoring + */ + char *restore (char *buf); + + /** + * REALLY PRIVATE ;) please no new friend classes + */ + private: + /** + * text of line as unicode + */ + QString m_text; + + /** + * array of highlighting attributes + * This is exactly the same size as m_text.length() + * Each letter in m_text has a uchar attribute + */ + QMemArray<uchar> m_attributes; + + /** + * context stack + */ + QMemArray<short> m_ctx; + + /** + * list of folding starts/ends + */ + QMemArray<uint> m_foldingList; + + /** + * indentation stack + */ + QMemArray<unsigned short> m_indentationDepth; + + /** + * flags + */ + uchar m_flags; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateundo.cpp b/kate/part/kateundo.cpp new file mode 100644 index 000000000..0f8d3599f --- /dev/null +++ b/kate/part/kateundo.cpp @@ -0,0 +1,392 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002 John Firebaugh <jfirebaugh@kde.org> + Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kateundo.h" + +#include "katedocument.h" +#include "kateview.h" +#include "katecursor.h" + +/** + * Private class, only for KateUndoGroup, no need to use it elsewhere + */ + class KateUndo +{ + public: + /** + * Constructor + * @param type undo item type + * @param line line affected + * @param col start column + * @param len lenght of change + * @param text text removed/inserted + */ + KateUndo (KateUndoGroup::UndoType type, uint line, uint col, uint len, const QString &text); + + /** + * Destructor + */ + ~KateUndo (); + + public: + /** + * Invalid examples: insert / remove 0 length text + * I could probably fix this in KateDocument, but it's more work there + * (and probably better here too) + * @return validity + */ + bool isValid(); + + /** + * merge an undo item + * Saves a bit of memory and potentially many calls when undo/redoing. + * @param u undo item to merge + * @return success + */ + bool merge(KateUndo* u); + + /** + * undo this item at given doc + * @param doc document + */ + void undo (KateDocument *doc); + + /** + * redo this item at given doc + * @param doc document + */ + void redo (KateDocument *doc); + + /** + * The cursor before the action took place + */ + KateTextCursor cursorBefore() const; + + /** + * The cursor after the action took place + */ + KateTextCursor cursorAfter() const; + + /** + * type of item + * @return type + */ + inline KateUndoGroup::UndoType type() const { return m_type; } + + /** + * line of changes + * @return line + */ + inline uint line () const { return m_line; } + + /** + * startcol of changes + * @return column + */ + inline uint col () const { return m_col; } + + /** + * length of changes + * @return length + */ + inline uint len() const { return m_len; } + + /** + * text inserted/removed + * @return text + */ + inline const QString& text() const { return m_text; }; + + private: + /** + * type + */ + KateUndoGroup::UndoType m_type; + + /** + * line + */ + uint m_line; + + /** + * column + */ + uint m_col; + + /** + * length + */ + uint m_len; + + /** + * text + */ + QString m_text; +}; + +KateUndo::KateUndo (KateUndoGroup::UndoType type, uint line, uint col, uint len, const QString &text) +: m_type (type), + m_line (line), + m_col (col), + m_len (len), + m_text (text) +{ +} + +KateUndo::~KateUndo () +{ +} + +bool KateUndo::isValid() +{ + if (m_type == KateUndoGroup::editInsertText || m_type == KateUndoGroup::editRemoveText) + if (len() == 0) + return false; + + return true; +} + +bool KateUndo::merge(KateUndo* u) +{ + if (m_type != u->type()) + return false; + + if (m_type == KateUndoGroup::editInsertText + && m_line == u->line() + && (m_col + m_len) == u->col()) + { + m_text += u->text(); + m_len += u->len(); + return true; + } + else if (m_type == KateUndoGroup::editRemoveText + && m_line == u->line() + && m_col == (u->col() + u->len())) + { + m_text.prepend(u->text()); + m_col = u->col(); + m_len += u->len(); + return true; + } + + return false; +} + +void KateUndo::undo (KateDocument *doc) +{ + if (m_type == KateUndoGroup::editInsertText) + { + doc->editRemoveText (m_line, m_col, m_len); + } + else if (m_type == KateUndoGroup::editRemoveText) + { + doc->editInsertText (m_line, m_col, m_text); + } + else if (m_type == KateUndoGroup::editWrapLine) + { + doc->editUnWrapLine (m_line, (m_text == "1"), m_len); + } + else if (m_type == KateUndoGroup::editUnWrapLine) + { + doc->editWrapLine (m_line, m_col, (m_text == "1")); + } + else if (m_type == KateUndoGroup::editInsertLine) + { + doc->editRemoveLine (m_line); + } + else if (m_type == KateUndoGroup::editRemoveLine) + { + doc->editInsertLine (m_line, m_text); + } + else if (m_type == KateUndoGroup::editMarkLineAutoWrapped) + { + doc->editMarkLineAutoWrapped (m_line, m_col == 0); + } +} + +void KateUndo::redo (KateDocument *doc) +{ + if (m_type == KateUndoGroup::editRemoveText) + { + doc->editRemoveText (m_line, m_col, m_len); + } + else if (m_type == KateUndoGroup::editInsertText) + { + doc->editInsertText (m_line, m_col, m_text); + } + else if (m_type == KateUndoGroup::editUnWrapLine) + { + doc->editUnWrapLine (m_line, (m_text == "1"), m_len); + } + else if (m_type == KateUndoGroup::editWrapLine) + { + doc->editWrapLine (m_line, m_col, (m_text == "1")); + } + else if (m_type == KateUndoGroup::editRemoveLine) + { + doc->editRemoveLine (m_line); + } + else if (m_type == KateUndoGroup::editInsertLine) + { + doc->editInsertLine (m_line, m_text); + } + else if (m_type == KateUndoGroup::editMarkLineAutoWrapped) + { + doc->editMarkLineAutoWrapped (m_line, m_col == 1); + } +} + +KateTextCursor KateUndo::cursorBefore() const +{ + if (m_type == KateUndoGroup::editInsertLine || m_type == KateUndoGroup::editUnWrapLine) + return KateTextCursor(m_line+1, m_col); + else if (m_type == KateUndoGroup::editRemoveText) + return KateTextCursor(m_line, m_col+m_len); + + return KateTextCursor(m_line, m_col); +} + +KateTextCursor KateUndo::cursorAfter() const +{ + if (m_type == KateUndoGroup::editRemoveLine || m_type == KateUndoGroup::editWrapLine) + return KateTextCursor(m_line+1, m_col); + else if (m_type == KateUndoGroup::editInsertText) + return KateTextCursor(m_line, m_col+m_len); + + return KateTextCursor(m_line, m_col); +} + +KateUndoGroup::KateUndoGroup (KateDocument *doc) +: m_doc (doc),m_safePoint(false) +{ + m_items.setAutoDelete (true); +} + +KateUndoGroup::~KateUndoGroup () +{ +} + +void KateUndoGroup::undo () +{ + if (m_items.count() == 0) + return; + + m_doc->editStart (false); + + for (KateUndo* u = m_items.last(); u; u = m_items.prev()) + u->undo(m_doc); + + if (m_doc->activeView()) + { + for (uint z=0; z < m_items.count(); z++) + if (m_items.at(z)->type() != KateUndoGroup::editMarkLineAutoWrapped) + { + m_doc->activeView()->editSetCursor (m_items.at(z)->cursorBefore()); + break; + } + } + + m_doc->editEnd (); +} + +void KateUndoGroup::redo () +{ + if (m_items.count() == 0) + return; + + m_doc->editStart (false); + + for (KateUndo* u = m_items.first(); u; u = m_items.next()) + u->redo(m_doc); + + if (m_doc->activeView()) + { + for (uint z=0; z < m_items.count(); z++) + if (m_items.at(z)->type() != KateUndoGroup::editMarkLineAutoWrapped) + { + m_doc->activeView()->editSetCursor (m_items.at(z)->cursorAfter()); + break; + } + } + + m_doc->editEnd (); +} + +void KateUndoGroup::addItem (KateUndoGroup::UndoType type, uint line, uint col, uint len, const QString &text) +{ + addItem(new KateUndo(type, line, col, len, text)); +} + +void KateUndoGroup::addItem(KateUndo* u) +{ + if (!u->isValid()) + delete u; + else if (m_items.last() && m_items.last()->merge(u)) + delete u; + else + m_items.append(u); +} + +bool KateUndoGroup::merge(KateUndoGroup* newGroup,bool complex) +{ + if (m_safePoint) return false; + if (newGroup->isOnlyType(singleType()) || complex) { + // Take all of its items first -> last + KateUndo* u = newGroup->m_items.take(0); + while (u) { + addItem(u); + u = newGroup->m_items.take(0); + } + if (newGroup->m_safePoint) safePoint(); + return true; + } + return false; +} + +void KateUndoGroup::safePoint (bool safePoint) { + m_safePoint=safePoint; +} + +KateUndoGroup::UndoType KateUndoGroup::singleType() +{ + KateUndoGroup::UndoType ret = editInvalid; + + for (KateUndo* u = m_items.first(); u; u = m_items.next()) { + if (ret == editInvalid) + ret = u->type(); + else if (ret != u->type()) + return editInvalid; + } + + return ret; +} + +bool KateUndoGroup::isOnlyType(KateUndoGroup::UndoType type) +{ + if (type == editInvalid) return false; + + for (KateUndo* u = m_items.first(); u; u = m_items.next()) + if (u->type() != type) + return false; + + return true; +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateundo.h b/kate/part/kateundo.h new file mode 100644 index 000000000..66905f5cd --- /dev/null +++ b/kate/part/kateundo.h @@ -0,0 +1,141 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002 John Firebaugh <jfirebaugh@kde.org> + Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef kate_undo_h +#define kate_undo_h + +#include <qptrlist.h> +#include <qstring.h> + +class KateDocument; +class KateUndo; + +/** + * Class to manage a group of undo items + */ +class KateUndoGroup +{ + public: + /** + * Constructor + * @param doc document to belong to + */ + KateUndoGroup (KateDocument *doc); + + /** + * Destructor + */ + ~KateUndoGroup (); + + public: + /** + * Undo the contained undo items + */ + void undo (); + + /** + * Redo the contained undo items + */ + void redo (); + + public: + /** + * Types for undo items + */ + enum UndoType + { + editInsertText, + editRemoveText, + editWrapLine, + editUnWrapLine, + editInsertLine, + editRemoveLine, + editMarkLineAutoWrapped, + editInvalid + }; + + /** + * add an item to the group + * @param type undo item type + * @param line line affected + * @param col start column + * @param len lenght of change + * @param text text removed/inserted + */ + void addItem (KateUndoGroup::UndoType type, uint line, uint col, uint len, const QString &text); + + /** + * merge this group with an other + * @param newGroup group to merge into this one + * @param complex set if a complex undo + * @return success + */ + bool merge(KateUndoGroup* newGroup,bool complex); + + /** + * set group as as savepoint. the next group will not merge with this one + */ + void safePoint (bool safePoint=true); + + /** + * is this undogroup empty? + */ + bool isEmpty () const { return m_items.isEmpty(); } + + private: + /** + * singleType + * @return the type if it's only one type, or editInvalid if it contains multiple types. + */ + KateUndoGroup::UndoType singleType(); + + /** + * are we only of this type ? + * @param type type to query + * @return we contain only the given type + */ + bool isOnlyType(KateUndoGroup::UndoType type); + + /** + * add an undo item + * @param u item to add + */ + void addItem (KateUndo *u); + + private: + /** + * Document we belong to + */ + KateDocument *m_doc; + + /** + * list of items contained + */ + QPtrList<KateUndo> m_items; + + /** + * prohibit merging with the next group + */ + bool m_safePoint; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateview.cpp b/kate/part/kateview.cpp new file mode 100644 index 000000000..6592bc8cc --- /dev/null +++ b/kate/part/kateview.cpp @@ -0,0 +1,1920 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Hamish Rodda <rodda@kde.org> + Copyright (C) 2002 John Firebaugh <jfirebaugh@kde.org> + Copyright (C) 2001-2004 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#define DEBUGACCELS + +//BEGIN includes +#include "kateview.h" +#include "kateview.moc" + +#include "kateviewinternal.h" +#include "kateviewhelpers.h" +#include "katerenderer.h" +#include "katedocument.h" +#include "katedocumenthelpers.h" +#include "katefactory.h" +#include "katehighlight.h" +#include "katedialogs.h" +#include "katetextline.h" +#include "katecodefoldinghelpers.h" +#include "katecodecompletion.h" +#include "katesearch.h" +#include "kateschema.h" +#include "katebookmarks.h" +#include "katesearch.h" +#include "kateconfig.h" +#include "katefiletype.h" +#include "kateautoindent.h" +#include "katespell.h" + +#include <ktexteditor/plugin.h> + +#include <kparts/event.h> + +#include <kio/netaccess.h> + +#include <kconfig.h> +#include <kurldrag.h> +#include <kdebug.h> +#include <kapplication.h> +#include <kcursor.h> +#include <klocale.h> +#include <kglobal.h> +#include <kcharsets.h> +#include <kmessagebox.h> +#include <kaction.h> +#include <kstdaction.h> +#include <kxmlguifactory.h> +#include <kaccel.h> +#include <klibloader.h> +#include <kencodingfiledialog.h> +#include <kmultipledrag.h> +#include <ktempfile.h> +#include <ksavefile.h> + +#include <qfont.h> +#include <qfileinfo.h> +#include <qstyle.h> +#include <qevent.h> +#include <qpopupmenu.h> +#include <qlayout.h> +#include <qclipboard.h> +#include <qstylesheet.h> +//END includes + +KateView::KateView( KateDocument *doc, QWidget *parent, const char * name ) + : Kate::View( doc, parent, name ) + , m_doc( doc ) + , m_search( new KateSearch( this ) ) + , m_spell( new KateSpell( this ) ) + , m_bookmarks( new KateBookmarks( this ) ) + , m_cmdLine (0) + , m_cmdLineOn (false) + , m_active( false ) + , m_hasWrap( false ) + , m_startingUp (true) + , m_updatingDocumentConfig (false) + , selectStart (m_doc, true) + , selectEnd (m_doc, true) + , blockSelect (false) + , m_imStartLine( 0 ) + , m_imStart( 0 ) + , m_imEnd( 0 ) + , m_imSelStart( 0 ) + , m_imSelEnd( 0 ) + , m_imComposeEvent( false ) +{ + KateFactory::self()->registerView( this ); + m_config = new KateViewConfig (this); + + m_renderer = new KateRenderer(doc, this); + + m_grid = new QGridLayout (this, 3, 3); + + m_grid->setRowStretch ( 0, 10 ); + m_grid->setRowStretch ( 1, 0 ); + m_grid->setColStretch ( 0, 0 ); + m_grid->setColStretch ( 1, 10 ); + m_grid->setColStretch ( 2, 0 ); + + m_viewInternal = new KateViewInternal( this, doc ); + m_grid->addWidget (m_viewInternal, 0, 1); + + setClipboardInterfaceDCOPSuffix (viewDCOPSuffix()); + setCodeCompletionInterfaceDCOPSuffix (viewDCOPSuffix()); + setDynWordWrapInterfaceDCOPSuffix (viewDCOPSuffix()); + setPopupMenuInterfaceDCOPSuffix (viewDCOPSuffix()); + setSessionConfigInterfaceDCOPSuffix (viewDCOPSuffix()); + setViewCursorInterfaceDCOPSuffix (viewDCOPSuffix()); + setViewStatusMsgInterfaceDCOPSuffix (viewDCOPSuffix()); + + setInstance( KateFactory::self()->instance() ); + doc->addView( this ); + + setFocusProxy( m_viewInternal ); + setFocusPolicy( StrongFocus ); + + if (!doc->singleViewMode()) { + setXMLFile( "katepartui.rc" ); + } else { + if( doc->readOnly() ) + setXMLFile( "katepartreadonlyui.rc" ); + else + setXMLFile( "katepartui.rc" ); + } + + setupConnections(); + setupActions(); + setupEditActions(); + setupCodeFolding(); + setupCodeCompletion(); + + // enable the plugins of this view + m_doc->enableAllPluginsGUI (this); + + // update the enabled state of the undo/redo actions... + slotNewUndo(); + + m_startingUp = false; + updateConfig (); + + slotHlChanged(); + /*test texthint + connect(this,SIGNAL(needTextHint(int, int, QString &)), + this,SLOT(slotNeedTextHint(int, int, QString &))); + enableTextHints(1000); + test texthint*/ +} + +KateView::~KateView() +{ + if (!m_doc->singleViewMode()) + m_doc->disableAllPluginsGUI (this); + + m_doc->removeView( this ); + + // its a QObject. don't double-delete + //delete m_viewInternal; + //delete m_codeCompletion; + + delete m_renderer; + m_renderer = 0; + + delete m_config; + m_config = 0; + KateFactory::self()->deregisterView (this); +} + +void KateView::setupConnections() +{ + connect( m_doc, SIGNAL(undoChanged()), + this, SLOT(slotNewUndo()) ); + connect( m_doc, SIGNAL(hlChanged()), + this, SLOT(slotHlChanged()) ); + connect( m_doc, SIGNAL(canceled(const QString&)), + this, SLOT(slotSaveCanceled(const QString&)) ); + connect( m_viewInternal, SIGNAL(dropEventPass(QDropEvent*)), + this, SIGNAL(dropEventPass(QDropEvent*)) ); + connect(this,SIGNAL(cursorPositionChanged()),this,SLOT(slotStatusMsg())); + connect(this,SIGNAL(newStatus()),this,SLOT(slotStatusMsg())); + connect(m_doc, SIGNAL(undoChanged()), this, SLOT(slotStatusMsg())); + + if ( m_doc->browserView() ) + { + connect( this, SIGNAL(dropEventPass(QDropEvent*)), + this, SLOT(slotDropEventPass(QDropEvent*)) ); + } +} + +void KateView::setupActions() +{ + KActionCollection *ac = this->actionCollection (); + KAction *a; + + m_toggleWriteLock = 0; + + m_cut = a=KStdAction::cut(this, SLOT(cut()), ac); + a->setWhatsThis(i18n("Cut the selected text and move it to the clipboard")); + + m_paste = a=KStdAction::pasteText(this, SLOT(paste()), ac); + a->setWhatsThis(i18n("Paste previously copied or cut clipboard contents")); + + m_copy = a=KStdAction::copy(this, SLOT(copy()), ac); + a->setWhatsThis(i18n( "Use this command to copy the currently selected text to the system clipboard.")); + + m_copyHTML = a = new KAction(i18n("Copy as &HTML"), "editcopy", 0, this, SLOT(copyHTML()), ac, "edit_copy_html"); + a->setWhatsThis(i18n( "Use this command to copy the currently selected text as HTML to the system clipboard.")); + + if (!m_doc->readOnly()) + { + a=KStdAction::save(this, SLOT(save()), ac); + a->setWhatsThis(i18n("Save the current document")); + + a=m_editUndo = KStdAction::undo(m_doc, SLOT(undo()), ac); + a->setWhatsThis(i18n("Revert the most recent editing actions")); + + a=m_editRedo = KStdAction::redo(m_doc, SLOT(redo()), ac); + a->setWhatsThis(i18n("Revert the most recent undo operation")); + + (new KAction(i18n("&Word Wrap Document"), "", 0, this, SLOT(applyWordWrap()), ac, "tools_apply_wordwrap"))->setWhatsThis( + i18n("Use this command to wrap all lines of the current document which are longer than the width of the" + " current view, to fit into this view.<br><br> This is a static word wrap, meaning it is not updated" + " when the view is resized.")); + + // setup Tools menu + a=new KAction(i18n("&Indent"), "indent", Qt::CTRL+Qt::Key_I, this, SLOT(indent()), ac, "tools_indent"); + a->setWhatsThis(i18n("Use this to indent a selected block of text.<br><br>" + "You can configure whether tabs should be honored and used or replaced with spaces, in the configuration dialog.")); + a=new KAction(i18n("&Unindent"), "unindent", Qt::CTRL+Qt::SHIFT+Qt::Key_I, this, SLOT(unIndent()), ac, "tools_unindent"); + a->setWhatsThis(i18n("Use this to unindent a selected block of text.")); + + a=new KAction(i18n("&Clean Indentation"), 0, this, SLOT(cleanIndent()), ac, "tools_cleanIndent"); + a->setWhatsThis(i18n("Use this to clean the indentation of a selected block of text (only tabs/only spaces)<br><br>" + "You can configure whether tabs should be honored and used or replaced with spaces, in the configuration dialog.")); + + a=new KAction(i18n("&Align"), 0, this, SLOT(align()), ac, "tools_align"); + a->setWhatsThis(i18n("Use this to align the current line or block of text to its proper indent level.")); + + a=new KAction(i18n("C&omment"), CTRL+Qt::Key_D, this, SLOT(comment()), + ac, "tools_comment"); + a->setWhatsThis(i18n("This command comments out the current line or a selected block of text.<BR><BR>" + "The characters for single/multiple line comments are defined within the language's highlighting.")); + + a=new KAction(i18n("Unco&mment"), CTRL+SHIFT+Qt::Key_D, this, SLOT(uncomment()), + ac, "tools_uncomment"); + a->setWhatsThis(i18n("This command removes comments from the current line or a selected block of text.<BR><BR>" + "The characters for single/multiple line comments are defined within the language's highlighting.")); + a = m_toggleWriteLock = new KToggleAction( + i18n("&Read Only Mode"), 0, 0, + this, SLOT( toggleWriteLock() ), + ac, "tools_toggle_write_lock" ); + a->setWhatsThis( i18n("Lock/unlock the document for writing") ); + + a = new KAction( i18n("Uppercase"), CTRL + Qt::Key_U, this, + SLOT(uppercase()), ac, "tools_uppercase" ); + a->setWhatsThis( i18n("Convert the selection to uppercase, or the character to the " + "right of the cursor if no text is selected.") ); + + a = new KAction( i18n("Lowercase"), CTRL + SHIFT + Qt::Key_U, this, + SLOT(lowercase()), ac, "tools_lowercase" ); + a->setWhatsThis( i18n("Convert the selection to lowercase, or the character to the " + "right of the cursor if no text is selected.") ); + + a = new KAction( i18n("Capitalize"), CTRL + ALT + Qt::Key_U, this, + SLOT(capitalize()), ac, "tools_capitalize" ); + a->setWhatsThis( i18n("Capitalize the selection, or the word under the " + "cursor if no text is selected.") ); + + a = new KAction( i18n("Join Lines"), CTRL + Qt::Key_J, this, + SLOT( joinLines() ), ac, "tools_join_lines" ); + } + else + { + m_cut->setEnabled (false); + m_paste->setEnabled (false); + m_editUndo = 0; + m_editRedo = 0; + } + + a=KStdAction::print( m_doc, SLOT(print()), ac ); + a->setWhatsThis(i18n("Print the current document.")); + + a=new KAction(i18n("Reloa&d"), "reload", KStdAccel::reload(), this, SLOT(reloadFile()), ac, "file_reload"); + a->setWhatsThis(i18n("Reload the current document from disk.")); + + a=KStdAction::saveAs(this, SLOT(saveAs()), ac); + a->setWhatsThis(i18n("Save the current document to disk, with a name of your choice.")); + + a=KStdAction::gotoLine(this, SLOT(gotoLine()), ac); + a->setWhatsThis(i18n("This command opens a dialog and lets you choose a line that you want the cursor to move to.")); + + a=new KAction(i18n("&Configure Editor..."), 0, m_doc, SLOT(configDialog()),ac, "set_confdlg"); + a->setWhatsThis(i18n("Configure various aspects of this editor.")); + + KateViewHighlightAction *menu = new KateViewHighlightAction (i18n("&Highlighting"), ac, "set_highlight"); + menu->setWhatsThis(i18n("Here you can choose how the current document should be highlighted.")); + menu->updateMenu (m_doc); + + KateViewFileTypeAction *ftm = new KateViewFileTypeAction (i18n("&Filetype"),ac,"set_filetype"); + ftm->updateMenu (m_doc); + + KateViewSchemaAction *schemaMenu = new KateViewSchemaAction (i18n("&Schema"),ac,"view_schemas"); + schemaMenu->updateMenu (this); + + // indentation menu + new KateViewIndentationAction (m_doc, i18n("&Indentation"),ac,"tools_indentation"); + + // html export + a = new KAction(i18n("E&xport as HTML..."), 0, 0, this, SLOT(exportAsHTML()), ac, "file_export_html"); + a->setWhatsThis(i18n("This command allows you to export the current document" + " with all highlighting information into a HTML document.")); + + m_selectAll = a=KStdAction::selectAll(this, SLOT(selectAll()), ac); + a->setWhatsThis(i18n("Select the entire text of the current document.")); + + m_deSelect = a=KStdAction::deselect(this, SLOT(clearSelection()), ac); + a->setWhatsThis(i18n("If you have selected something within the current document, this will no longer be selected.")); + + a=new KAction(i18n("Enlarge Font"), "viewmag+", 0, m_viewInternal, SLOT(slotIncFontSizes()), ac, "incFontSizes"); + a->setWhatsThis(i18n("This increases the display font size.")); + + a=new KAction(i18n("Shrink Font"), "viewmag-", 0, m_viewInternal, SLOT(slotDecFontSizes()), ac, "decFontSizes"); + a->setWhatsThis(i18n("This decreases the display font size.")); + + a= m_toggleBlockSelection = new KToggleAction( + i18n("Bl&ock Selection Mode"), CTRL + SHIFT + Key_B, + this, SLOT(toggleBlockSelectionMode()), + ac, "set_verticalSelect"); + a->setWhatsThis(i18n("This command allows switching between the normal (line based) selection mode and the block selection mode.")); + + a= m_toggleInsert = new KToggleAction( + i18n("Overwr&ite Mode"), Key_Insert, + this, SLOT(toggleInsert()), + ac, "set_insert" ); + a->setWhatsThis(i18n("Choose whether you want the text you type to be inserted or to overwrite existing text.")); + + KToggleAction *toggleAction; + a= m_toggleDynWrap = toggleAction = new KToggleAction( + i18n("&Dynamic Word Wrap"), Key_F10, + this, SLOT(toggleDynWordWrap()), + ac, "view_dynamic_word_wrap" ); + a->setWhatsThis(i18n("If this option is checked, the text lines will be wrapped at the view border on the screen.")); + + a= m_setDynWrapIndicators = new KSelectAction(i18n("Dynamic Word Wrap Indicators"), 0, ac, "dynamic_word_wrap_indicators"); + a->setWhatsThis(i18n("Choose when the Dynamic Word Wrap Indicators should be displayed")); + + connect(m_setDynWrapIndicators, SIGNAL(activated(int)), this, SLOT(setDynWrapIndicators(int))); + QStringList list2; + list2.append(i18n("&Off")); + list2.append(i18n("Follow &Line Numbers")); + list2.append(i18n("&Always On")); + m_setDynWrapIndicators->setItems(list2); + + a= toggleAction=m_toggleFoldingMarkers = new KToggleAction( + i18n("Show Folding &Markers"), Key_F9, + this, SLOT(toggleFoldingMarkers()), + ac, "view_folding_markers" ); + a->setWhatsThis(i18n("You can choose if the codefolding marks should be shown, if codefolding is possible.")); + toggleAction->setCheckedState(i18n("Hide Folding &Markers")); + + a= m_toggleIconBar = toggleAction = new KToggleAction( + i18n("Show &Icon Border"), Key_F6, + this, SLOT(toggleIconBorder()), + ac, "view_border"); + a=toggleAction; + a->setWhatsThis(i18n("Show/hide the icon border.<BR><BR> The icon border shows bookmark symbols, for instance.")); + toggleAction->setCheckedState(i18n("Hide &Icon Border")); + + a= toggleAction=m_toggleLineNumbers = new KToggleAction( + i18n("Show &Line Numbers"), Key_F11, + this, SLOT(toggleLineNumbersOn()), + ac, "view_line_numbers" ); + a->setWhatsThis(i18n("Show/hide the line numbers on the left hand side of the view.")); + toggleAction->setCheckedState(i18n("Hide &Line Numbers")); + + a= m_toggleScrollBarMarks = toggleAction = new KToggleAction( + i18n("Show Scroll&bar Marks"), 0, + this, SLOT(toggleScrollBarMarks()), + ac, "view_scrollbar_marks"); + a->setWhatsThis(i18n("Show/hide the marks on the vertical scrollbar.<BR><BR>The marks, for instance, show bookmarks.")); + toggleAction->setCheckedState(i18n("Hide Scroll&bar Marks")); + + a = toggleAction = m_toggleWWMarker = new KToggleAction( + i18n("Show Static &Word Wrap Marker"), 0, + this, SLOT( toggleWWMarker() ), + ac, "view_word_wrap_marker" ); + a->setWhatsThis( i18n( + "Show/hide the Word Wrap Marker, a vertical line drawn at the word " + "wrap column as defined in the editing properties" )); + toggleAction->setCheckedState(i18n("Hide Static &Word Wrap Marker")); + + a= m_switchCmdLine = new KAction( + i18n("Switch to Command Line"), Key_F7, + this, SLOT(switchToCmdLine()), + ac, "switch_to_cmd_line" ); + a->setWhatsThis(i18n("Show/hide the command line on the bottom of the view.")); + + a=m_setEndOfLine = new KSelectAction(i18n("&End of Line"), 0, ac, "set_eol"); + a->setWhatsThis(i18n("Choose which line endings should be used, when you save the document")); + QStringList list; + list.append("&UNIX"); + list.append("&Windows/DOS"); + list.append("&Macintosh"); + m_setEndOfLine->setItems(list); + m_setEndOfLine->setCurrentItem (m_doc->config()->eol()); + connect(m_setEndOfLine, SIGNAL(activated(int)), this, SLOT(setEol(int))); + + // encoding menu + new KateViewEncodingAction (m_doc, this, i18n("E&ncoding"), ac, "set_encoding"); + + m_search->createActions( ac ); + m_spell->createActions( ac ); + m_bookmarks->createActions( ac ); + + slotSelectionChanged (); + + connect (this, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged())); +} + +void KateView::setupEditActions() +{ + m_editActions = new KActionCollection( m_viewInternal, this, "edit_actions" ); + KActionCollection* ac = m_editActions; + + new KAction( + i18n("Move Word Left"), CTRL + Key_Left, + this,SLOT(wordLeft()), + ac, "word_left" ); + new KAction( + i18n("Select Character Left"), SHIFT + Key_Left, + this,SLOT(shiftCursorLeft()), + ac, "select_char_left" ); + new KAction( + i18n("Select Word Left"), SHIFT + CTRL + Key_Left, + this, SLOT(shiftWordLeft()), + ac, "select_word_left" ); + + new KAction( + i18n("Move Word Right"), CTRL + Key_Right, + this, SLOT(wordRight()), + ac, "word_right" ); + new KAction( + i18n("Select Character Right"), SHIFT + Key_Right, + this, SLOT(shiftCursorRight()), + ac, "select_char_right" ); + new KAction( + i18n("Select Word Right"), SHIFT + CTRL + Key_Right, + this,SLOT(shiftWordRight()), + ac, "select_word_right" ); + + new KAction( + i18n("Move to Beginning of Line"), Key_Home, + this, SLOT(home()), + ac, "beginning_of_line" ); + new KAction( + i18n("Move to Beginning of Document"), KStdAccel::home(), + this, SLOT(top()), + ac, "beginning_of_document" ); + new KAction( + i18n("Select to Beginning of Line"), SHIFT + Key_Home, + this, SLOT(shiftHome()), + ac, "select_beginning_of_line" ); + new KAction( + i18n("Select to Beginning of Document"), SHIFT + CTRL + Key_Home, + this, SLOT(shiftTop()), + ac, "select_beginning_of_document" ); + + new KAction( + i18n("Move to End of Line"), Key_End, + this, SLOT(end()), + ac, "end_of_line" ); + new KAction( + i18n("Move to End of Document"), KStdAccel::end(), + this, SLOT(bottom()), + ac, "end_of_document" ); + new KAction( + i18n("Select to End of Line"), SHIFT + Key_End, + this, SLOT(shiftEnd()), + ac, "select_end_of_line" ); + new KAction( + i18n("Select to End of Document"), SHIFT + CTRL + Key_End, + this, SLOT(shiftBottom()), + ac, "select_end_of_document" ); + + new KAction( + i18n("Select to Previous Line"), SHIFT + Key_Up, + this, SLOT(shiftUp()), + ac, "select_line_up" ); + new KAction( + i18n("Scroll Line Up"),"", CTRL + Key_Up, + this, SLOT(scrollUp()), + ac, "scroll_line_up" ); + + new KAction(i18n("Move to Next Line"), Key_Down, this, SLOT(down()), + ac, "move_line_down"); + + new KAction(i18n("Move to Previous Line"), Key_Up, this, SLOT(up()), + ac, "move_line_up"); + + new KAction(i18n("Move Character Right"), Key_Right, this, + SLOT(cursorRight()), ac, "move_cursor_right"); + + new KAction(i18n("Move Character Left"), Key_Left, this, SLOT(cursorLeft()), + ac, "move_cusor_left"); + + new KAction( + i18n("Select to Next Line"), SHIFT + Key_Down, + this, SLOT(shiftDown()), + ac, "select_line_down" ); + new KAction( + i18n("Scroll Line Down"), CTRL + Key_Down, + this, SLOT(scrollDown()), + ac, "scroll_line_down" ); + + new KAction( + i18n("Scroll Page Up"), KStdAccel::prior(), + this, SLOT(pageUp()), + ac, "scroll_page_up" ); + new KAction( + i18n("Select Page Up"), SHIFT + Key_PageUp, + this, SLOT(shiftPageUp()), + ac, "select_page_up" ); + new KAction( + i18n("Move to Top of View"), CTRL + Key_PageUp, + this, SLOT(topOfView()), + ac, "move_top_of_view" ); + new KAction( + i18n("Select to Top of View"), CTRL + SHIFT + Key_PageUp, + this, SLOT(shiftTopOfView()), + ac, "select_top_of_view" ); + + new KAction( + i18n("Scroll Page Down"), KStdAccel::next(), + this, SLOT(pageDown()), + ac, "scroll_page_down" ); + new KAction( + i18n("Select Page Down"), SHIFT + Key_PageDown, + this, SLOT(shiftPageDown()), + ac, "select_page_down" ); + new KAction( + i18n("Move to Bottom of View"), CTRL + Key_PageDown, + this, SLOT(bottomOfView()), + ac, "move_bottom_of_view" ); + new KAction( + i18n("Select to Bottom of View"), CTRL + SHIFT + Key_PageDown, + this, SLOT(shiftBottomOfView()), + ac, "select_bottom_of_view" ); + new KAction( + i18n("Move to Matching Bracket"), CTRL + Key_6, + this, SLOT(toMatchingBracket()), + ac, "to_matching_bracket" ); + new KAction( + i18n("Select to Matching Bracket"), SHIFT + CTRL + Key_6, + this, SLOT(shiftToMatchingBracket()), + ac, "select_matching_bracket" ); + + // anders: shortcuts doing any changes should not be created in browserextension + if ( !m_doc->readOnly() ) + { + new KAction( + i18n("Transpose Characters"), CTRL + Key_T, + this, SLOT(transpose()), + ac, "transpose_char" ); + + new KAction( + i18n("Delete Line"), CTRL + Key_K, + this, SLOT(killLine()), + ac, "delete_line" ); + + new KAction( + i18n("Delete Word Left"), KStdAccel::deleteWordBack(), + this, SLOT(deleteWordLeft()), + ac, "delete_word_left" ); + + new KAction( + i18n("Delete Word Right"), KStdAccel::deleteWordForward(), + this, SLOT(deleteWordRight()), + ac, "delete_word_right" ); + + new KAction(i18n("Delete Next Character"), Key_Delete, + this, SLOT(keyDelete()), + ac, "delete_next_character"); + + KAction *a = new KAction(i18n("Backspace"), Key_Backspace, + this, SLOT(backspace()), + ac, "backspace"); + KShortcut cut = a->shortcut(); + cut.append( KKey( SHIFT + Key_Backspace ) ); + a->setShortcut( cut ); + } + + connect( this, SIGNAL(gotFocus(Kate::View*)), + this, SLOT(slotGotFocus()) ); + connect( this, SIGNAL(lostFocus(Kate::View*)), + this, SLOT(slotLostFocus()) ); + + m_editActions->readShortcutSettings( "Katepart Shortcuts" ); + + if( hasFocus() ) + slotGotFocus(); + else + slotLostFocus(); + + +} + +void KateView::setupCodeFolding() +{ + KActionCollection *ac=this->actionCollection(); + new KAction( i18n("Collapse Toplevel"), CTRL+SHIFT+Key_Minus, + m_doc->foldingTree(),SLOT(collapseToplevelNodes()),ac,"folding_toplevel"); + new KAction( i18n("Expand Toplevel"), CTRL+SHIFT+Key_Plus, + this,SLOT(slotExpandToplevel()),ac,"folding_expandtoplevel"); + new KAction( i18n("Collapse One Local Level"), CTRL+Key_Minus, + this,SLOT(slotCollapseLocal()),ac,"folding_collapselocal"); + new KAction( i18n("Expand One Local Level"), CTRL+Key_Plus, + this,SLOT(slotExpandLocal()),ac,"folding_expandlocal"); + +#ifdef DEBUGACCELS + KAccel* debugAccels = new KAccel(this,this); + debugAccels->insert("KATE_DUMP_REGION_TREE",i18n("Show the code folding region tree"),"","Ctrl+Shift+Alt+D",m_doc,SLOT(dumpRegionTree())); + debugAccels->insert("KATE_TEMPLATE_TEST",i18n("Basic template code test"),"","Ctrl+Shift+Alt+T",m_doc,SLOT(testTemplateCode())); + debugAccels->setEnabled(true); +#endif +} + +void KateView::slotExpandToplevel() +{ + m_doc->foldingTree()->expandToplevelNodes(m_doc->numLines()); +} + +void KateView::slotCollapseLocal() +{ + int realLine = m_doc->foldingTree()->collapseOne(cursorLine()); + if (realLine != -1) + // TODO rodda: fix this to only set line and allow internal view to chose column + // Explicitly call internal because we want this to be registered as an internal call + setCursorPositionInternal(realLine, cursorColumn(), tabWidth(), false); +} + +void KateView::slotExpandLocal() +{ + m_doc->foldingTree()->expandOne(cursorLine(), m_doc->numLines()); +} + +void KateView::setupCodeCompletion() +{ + m_codeCompletion = new KateCodeCompletion(this); + connect( m_codeCompletion, SIGNAL(completionAborted()), + this, SIGNAL(completionAborted())); + connect( m_codeCompletion, SIGNAL(completionDone()), + this, SIGNAL(completionDone())); + connect( m_codeCompletion, SIGNAL(argHintHidden()), + this, SIGNAL(argHintHidden())); + connect( m_codeCompletion, SIGNAL(completionDone(KTextEditor::CompletionEntry)), + this, SIGNAL(completionDone(KTextEditor::CompletionEntry))); + connect( m_codeCompletion, SIGNAL(filterInsertString(KTextEditor::CompletionEntry*,QString*)), + this, SIGNAL(filterInsertString(KTextEditor::CompletionEntry*,QString*))); +} + +void KateView::slotGotFocus() +{ + m_editActions->accel()->setEnabled( true ); + + slotStatusMsg (); +} + +void KateView::slotLostFocus() +{ + m_editActions->accel()->setEnabled( false ); +} + +void KateView::setDynWrapIndicators(int mode) +{ + config()->setDynWordWrapIndicators (mode); +} + +void KateView::slotStatusMsg () +{ + QString ovrstr; + if (m_doc->isReadWrite()) + { + if (m_doc->config()->configFlags() & KateDocument::cfOvr) + ovrstr = i18n(" OVR "); + else + ovrstr = i18n(" INS "); + } + else + ovrstr = i18n(" R/O "); + + uint r = cursorLine() + 1; + uint c = cursorColumn() + 1; + + QString s1 = i18n(" Line: %1").arg(KGlobal::locale()->formatNumber(r, 0)); + QString s2 = i18n(" Col: %1").arg(KGlobal::locale()->formatNumber(c, 0)); + + QString modstr = m_doc->isModified() ? QString (" * ") : QString (" "); + QString blockstr = blockSelectionMode() ? i18n(" BLK ") : i18n(" NORM "); + + emit viewStatusMsg (s1 + s2 + " " + ovrstr + blockstr + modstr); +} + +void KateView::slotSelectionTypeChanged() +{ + m_toggleBlockSelection->setChecked( blockSelectionMode() ); + + emit newStatus(); +} + +bool KateView::isOverwriteMode() const +{ + return m_doc->config()->configFlags() & KateDocument::cfOvr; +} + +void KateView::reloadFile() +{ + m_doc->reloadFile(); + emit newStatus(); +} + +void KateView::slotUpdate() +{ + emit newStatus(); + + slotNewUndo(); +} + +void KateView::slotReadWriteChanged () +{ + if ( m_toggleWriteLock ) + m_toggleWriteLock->setChecked( ! m_doc->isReadWrite() ); + + m_cut->setEnabled (m_doc->isReadWrite()); + m_paste->setEnabled (m_doc->isReadWrite()); + + QStringList l; + + l << "edit_replace" << "set_insert" << "tools_spelling" << "tools_indent" + << "tools_unindent" << "tools_cleanIndent" << "tools_align" << "tools_comment" + << "tools_uncomment" << "tools_uppercase" << "tools_lowercase" + << "tools_capitalize" << "tools_join_lines" << "tools_apply_wordwrap" + << "edit_undo" << "edit_redo" << "tools_spelling_from_cursor" + << "tools_spelling_selection"; + + KAction *a = 0; + for (uint z = 0; z < l.size(); z++) + if ((a = actionCollection()->action( l[z].ascii() ))) + a->setEnabled (m_doc->isReadWrite()); +} + +void KateView::slotNewUndo() +{ + if (m_doc->readOnly()) + return; + + if ((m_doc->undoCount() > 0) != m_editUndo->isEnabled()) + m_editUndo->setEnabled(m_doc->undoCount() > 0); + + if ((m_doc->redoCount() > 0) != m_editRedo->isEnabled()) + m_editRedo->setEnabled(m_doc->redoCount() > 0); +} + +void KateView::slotDropEventPass( QDropEvent * ev ) +{ + KURL::List lstDragURLs; + bool ok = KURLDrag::decode( ev, lstDragURLs ); + + KParts::BrowserExtension * ext = KParts::BrowserExtension::childObject( doc() ); + if ( ok && ext ) + emit ext->openURLRequest( lstDragURLs.first() ); +} + +void KateView::contextMenuEvent( QContextMenuEvent *ev ) +{ + if ( !m_doc || !m_doc->browserExtension() ) + return; + emit m_doc->browserExtension()->popupMenu( /*this, */ev->globalPos(), m_doc->url(), + QString::fromLatin1( "text/plain" ) ); + ev->accept(); +} + +bool KateView::setCursorPositionInternal( uint line, uint col, uint tabwidth, bool calledExternally ) +{ + KateTextLine::Ptr l = m_doc->kateTextLine( line ); + + if (!l) + return false; + + QString line_str = m_doc->textLine( line ); + + uint z; + uint x = 0; + for (z = 0; z < line_str.length() && z < col; z++) { + if (line_str[z] == QChar('\t')) x += tabwidth - (x % tabwidth); else x++; + } + + m_viewInternal->updateCursor( KateTextCursor( line, x ), false, true, calledExternally ); + + return true; +} + +void KateView::setOverwriteMode( bool b ) +{ + if ( isOverwriteMode() && !b ) + m_doc->setConfigFlags( m_doc->config()->configFlags() ^ KateDocument::cfOvr ); + else + m_doc->setConfigFlags( m_doc->config()->configFlags() | KateDocument::cfOvr ); + + m_toggleInsert->setChecked (isOverwriteMode ()); +} + +void KateView::toggleInsert() +{ + m_doc->setConfigFlags(m_doc->config()->configFlags() ^ KateDocument::cfOvr); + m_toggleInsert->setChecked (isOverwriteMode ()); + + emit newStatus(); +} + +bool KateView::canDiscard() +{ + return m_doc->closeURL(); +} + +void KateView::flush() +{ + m_doc->closeURL(); +} + +KateView::saveResult KateView::save() +{ + if( !m_doc->url().isValid() || !doc()->isReadWrite() ) + return saveAs(); + + if( m_doc->save() ) + return SAVE_OK; + + return SAVE_ERROR; +} + +KateView::saveResult KateView::saveAs() +{ + + KEncodingFileDialog::Result res=KEncodingFileDialog::getSaveURLAndEncoding(doc()->config()->encoding(), + m_doc->url().url(),QString::null,this,i18n("Save File")); + +// kdDebug()<<"urllist is emtpy?"<<res.URLs.isEmpty()<<endl; +// kdDebug()<<"url is:"<<res.URLs.first()<<endl; + if( res.URLs.isEmpty() || !checkOverwrite( res.URLs.first() ) ) + return SAVE_CANCEL; + + m_doc->config()->setEncoding( res.encoding ); + + if( m_doc->saveAs( res.URLs.first() ) ) + return SAVE_OK; + + return SAVE_ERROR; +} + +bool KateView::checkOverwrite( KURL u ) +{ + if( !u.isLocalFile() ) + return true; + + QFileInfo info( u.path() ); + if( !info.exists() ) + return true; + + return KMessageBox::Continue + == KMessageBox::warningContinueCancel + ( this, + i18n( "A file named \"%1\" already exists. Are you sure you want to overwrite it?" ).arg( info.fileName() ), + i18n( "Overwrite File?" ), + KGuiItem( i18n( "&Overwrite" ), "filesave", i18n( "Overwrite the file" ) ) + ); +} + +void KateView::slotSaveCanceled( const QString& error ) +{ + if ( !error.isEmpty() ) // happens when cancelling a job + KMessageBox::error( this, error ); +} + +void KateView::gotoLine() +{ + KateGotoLineDialog *dlg = new KateGotoLineDialog (this, m_viewInternal->getCursor().line() + 1, m_doc->numLines()); + + if (dlg->exec() == QDialog::Accepted) + gotoLineNumber( dlg->getLine() - 1 ); + + delete dlg; +} + +void KateView::gotoLineNumber( int line ) +{ + // clear selection, unless we are in persistent selection mode + if ( !config()->persistentSelection() ) + clearSelection(); + setCursorPositionInternal ( line, 0, 1 ); +} + +void KateView::joinLines() +{ + int first = selStartLine(); + int last = selEndLine(); + //int left = m_doc->textLine( last ).length() - m_doc->selEndCol(); + if ( first == last ) + { + first = cursorLine(); + last = first + 1; + } + m_doc->joinLines( first, last ); +} + +void KateView::readSessionConfig(KConfig *config) +{ + setCursorPositionInternal (config->readNumEntry("CursorLine"), config->readNumEntry("CursorColumn"), 1); +} + +void KateView::writeSessionConfig(KConfig *config) +{ + config->writeEntry("CursorLine",m_viewInternal->cursor.line()); + config->writeEntry("CursorColumn",m_viewInternal->cursor.col()); +} + +int KateView::getEol() +{ + return m_doc->config()->eol(); +} + +void KateView::setEol(int eol) +{ + if (!doc()->isReadWrite()) + return; + + if (m_updatingDocumentConfig) + return; + + m_doc->config()->setEol (eol); +} + +void KateView::setIconBorder( bool enable ) +{ + config()->setIconBar (enable); +} + +void KateView::toggleIconBorder() +{ + config()->setIconBar (!config()->iconBar()); +} + +void KateView::setLineNumbersOn( bool enable ) +{ + config()->setLineNumbers (enable); +} + +void KateView::toggleLineNumbersOn() +{ + config()->setLineNumbers (!config()->lineNumbers()); +} + +void KateView::setScrollBarMarks( bool enable ) +{ + config()->setScrollBarMarks (enable); +} + +void KateView::toggleScrollBarMarks() +{ + config()->setScrollBarMarks (!config()->scrollBarMarks()); +} + +void KateView::toggleDynWordWrap() +{ + config()->setDynWordWrap( !config()->dynWordWrap() ); +} + +void KateView::setDynWordWrap( bool b ) +{ + config()->setDynWordWrap( b ); +} + +void KateView::toggleWWMarker() +{ + m_renderer->config()->setWordWrapMarker (!m_renderer->config()->wordWrapMarker()); +} + +void KateView::setFoldingMarkersOn( bool enable ) +{ + config()->setFoldingBar ( enable ); +} + +void KateView::toggleFoldingMarkers() +{ + config()->setFoldingBar ( !config()->foldingBar() ); +} + +bool KateView::iconBorder() { + return m_viewInternal->leftBorder->iconBorderOn(); +} + +bool KateView::lineNumbersOn() { + return m_viewInternal->leftBorder->lineNumbersOn(); +} + +bool KateView::scrollBarMarks() { + return m_viewInternal->m_lineScroll->showMarks(); +} + +int KateView::dynWrapIndicators() { + return m_viewInternal->leftBorder->dynWrapIndicators(); +} + +bool KateView::foldingMarkersOn() { + return m_viewInternal->leftBorder->foldingMarkersOn(); +} + +void KateView::showCmdLine ( bool enabled ) +{ + if (enabled == m_cmdLineOn) + return; + + if (enabled) + { + if (!m_cmdLine) + { + m_cmdLine = new KateCmdLine (this); + m_grid->addMultiCellWidget (m_cmdLine, 2, 2, 0, 2); + } + + m_cmdLine->show (); + m_cmdLine->setFocus(); + } + else { + m_cmdLine->hide (); + //m_toggleCmdLine->setChecked(false); + } + + m_cmdLineOn = enabled; +} + +void KateView::toggleCmdLine () +{ + m_config->setCmdLine (!m_config->cmdLine ()); +} + +void KateView::toggleWriteLock() +{ + m_doc->setReadWrite( ! m_doc->isReadWrite() ); +} + +void KateView::enableTextHints(int timeout) +{ + m_viewInternal->enableTextHints(timeout); +} + +void KateView::disableTextHints() +{ + m_viewInternal->disableTextHints(); +} + +void KateView::applyWordWrap () +{ + if (hasSelection()) + m_doc->wrapText (selectStart.line(), selectEnd.line()); + else + m_doc->wrapText (0, m_doc->lastLine()); +} + +void KateView::slotNeedTextHint(int line, int col, QString &text) +{ + text=QString("test %1 %2").arg(line).arg(col); +} + +void KateView::find() +{ + m_search->find(); +} + +void KateView::find( const QString& pattern, long flags, bool add ) +{ + m_search->find( pattern, flags, add ); +} + +void KateView::replace() +{ + m_search->replace(); +} + +void KateView::replace( const QString &pattern, const QString &replacement, long flags ) +{ + m_search->replace( pattern, replacement, flags ); +} + +void KateView::findAgain( bool back ) +{ + m_search->findAgain( back ); +} + +void KateView::slotSelectionChanged () +{ + m_copy->setEnabled (hasSelection()); + m_copyHTML->setEnabled (hasSelection()); + m_deSelect->setEnabled (hasSelection()); + + if (m_doc->readOnly()) + return; + + m_cut->setEnabled (hasSelection()); + + m_spell->updateActions (); +} + +void KateView::switchToCmdLine () +{ + if (!m_cmdLineOn) + m_config->setCmdLine (true); + else { + if (m_cmdLine->hasFocus()) { + this->setFocus(); + return; + } + } + m_cmdLine->setFocus (); +} + +void KateView::showArgHint( QStringList arg1, const QString& arg2, const QString& arg3 ) +{ + m_codeCompletion->showArgHint( arg1, arg2, arg3 ); +} + +void KateView::showCompletionBox( QValueList<KTextEditor::CompletionEntry> arg1, int offset, bool cs ) +{ + emit aboutToShowCompletionBox(); + m_codeCompletion->showCompletionBox( arg1, offset, cs ); +} + +KateRenderer *KateView::renderer () +{ + return m_renderer; +} + +void KateView::updateConfig () +{ + if (m_startingUp) + return; + + m_editActions->readShortcutSettings( "Katepart Shortcuts" ); + + // dyn. word wrap & markers + if (m_hasWrap != config()->dynWordWrap()) { + m_viewInternal->prepareForDynWrapChange(); + + m_hasWrap = config()->dynWordWrap(); + + m_viewInternal->dynWrapChanged(); + + m_setDynWrapIndicators->setEnabled(config()->dynWordWrap()); + m_toggleDynWrap->setChecked( config()->dynWordWrap() ); + } + + m_viewInternal->leftBorder->setDynWrapIndicators( config()->dynWordWrapIndicators() ); + m_setDynWrapIndicators->setCurrentItem( config()->dynWordWrapIndicators() ); + + // line numbers + m_viewInternal->leftBorder->setLineNumbersOn( config()->lineNumbers() ); + m_toggleLineNumbers->setChecked( config()->lineNumbers() ); + + // icon bar + m_viewInternal->leftBorder->setIconBorderOn( config()->iconBar() ); + m_toggleIconBar->setChecked( config()->iconBar() ); + + // scrollbar marks + m_viewInternal->m_lineScroll->setShowMarks( config()->scrollBarMarks() ); + m_toggleScrollBarMarks->setChecked( config()->scrollBarMarks() ); + + // cmd line + showCmdLine (config()->cmdLine()); + //m_toggleCmdLine->setChecked( config()->cmdLine() ); + + // misc edit + m_toggleBlockSelection->setChecked( blockSelectionMode() ); + m_toggleInsert->setChecked( isOverwriteMode() ); + + updateFoldingConfig (); + + // bookmark + m_bookmarks->setSorting( (KateBookmarks::Sorting) config()->bookmarkSort() ); + + m_viewInternal->setAutoCenterLines(config()->autoCenterLines ()); +} + +void KateView::updateDocumentConfig() +{ + if (m_startingUp) + return; + + m_updatingDocumentConfig = true; + + m_setEndOfLine->setCurrentItem (m_doc->config()->eol()); + + m_updatingDocumentConfig = false; + + m_viewInternal->updateView (true); + + m_renderer->setTabWidth (m_doc->config()->tabWidth()); + m_renderer->setIndentWidth (m_doc->config()->indentationWidth()); +} + +void KateView::updateRendererConfig() +{ + if (m_startingUp) + return; + + m_toggleWWMarker->setChecked( m_renderer->config()->wordWrapMarker() ); + + // update the text area + m_viewInternal->updateView (true); + m_viewInternal->repaint (); + + // update the left border right, for example linenumbers + m_viewInternal->leftBorder->updateFont(); + m_viewInternal->leftBorder->repaint (); + +// @@ showIndentLines is not cached anymore. +// m_renderer->setShowIndentLines (m_renderer->config()->showIndentationLines()); +} + +void KateView::updateFoldingConfig () +{ + // folding bar + bool doit = config()->foldingBar() && m_doc->highlight() && m_doc->highlight()->allowsFolding(); + m_viewInternal->leftBorder->setFoldingMarkersOn(doit); + m_toggleFoldingMarkers->setChecked( doit ); + m_toggleFoldingMarkers->setEnabled( m_doc->highlight() && m_doc->highlight()->allowsFolding() ); + + QStringList l; + + l << "folding_toplevel" << "folding_expandtoplevel" + << "folding_collapselocal" << "folding_expandlocal"; + + KAction *a = 0; + for (uint z = 0; z < l.size(); z++) + if ((a = actionCollection()->action( l[z].ascii() ))) + a->setEnabled (m_doc->highlight() && m_doc->highlight()->allowsFolding()); +} + +//BEGIN EDIT STUFF +void KateView::editStart () +{ + m_viewInternal->editStart (); +} + +void KateView::editEnd (int editTagLineStart, int editTagLineEnd, bool tagFrom) +{ + m_viewInternal->editEnd (editTagLineStart, editTagLineEnd, tagFrom); +} + +void KateView::editSetCursor (const KateTextCursor &cursor) +{ + m_viewInternal->editSetCursor (cursor); +} +//END + +//BEGIN TAG & CLEAR +bool KateView::tagLine (const KateTextCursor& virtualCursor) +{ + return m_viewInternal->tagLine (virtualCursor); +} + +bool KateView::tagLines (int start, int end, bool realLines) +{ + return m_viewInternal->tagLines (start, end, realLines); +} + +bool KateView::tagLines (KateTextCursor start, KateTextCursor end, bool realCursors) +{ + return m_viewInternal->tagLines (start, end, realCursors); +} + +void KateView::tagAll () +{ + m_viewInternal->tagAll (); +} + +void KateView::clear () +{ + m_viewInternal->clear (); +} + +void KateView::repaintText (bool paintOnlyDirty) +{ + m_viewInternal->paintText(0,0,m_viewInternal->width(),m_viewInternal->height(), paintOnlyDirty); +} + +void KateView::updateView (bool changed) +{ + m_viewInternal->updateView (changed); + m_viewInternal->leftBorder->update(); +} + +//END + +void KateView::slotHlChanged() +{ + KateHighlighting *hl = m_doc->highlight(); + bool ok ( !hl->getCommentStart(0).isEmpty() || !hl->getCommentSingleLineStart(0).isEmpty() ); + + if (actionCollection()->action("tools_comment")) + actionCollection()->action("tools_comment")->setEnabled( ok ); + + if (actionCollection()->action("tools_uncomment")) + actionCollection()->action("tools_uncomment")->setEnabled( ok ); + + // show folding bar if "view defaults" says so, otherwise enable/disable only the menu entry + updateFoldingConfig (); +} + +uint KateView::cursorColumn() +{ + uint r = m_doc->currentColumn(m_viewInternal->getCursor()); + if ( !( m_doc->config()->configFlags() & KateDocumentConfig::cfWrapCursor ) && + (uint)m_viewInternal->getCursor().col() > m_doc->textLine( m_viewInternal->getCursor().line() ).length() ) + r += m_viewInternal->getCursor().col() - m_doc->textLine( m_viewInternal->getCursor().line() ).length(); + + return r; +} + +//BEGIN KTextEditor::SelectionInterface stuff + +bool KateView::setSelection( const KateTextCursor& start, const KateTextCursor& end ) +{ + KateTextCursor oldSelectStart = selectStart; + KateTextCursor oldSelectEnd = selectEnd; + + if (start <= end) { + selectStart.setPos(start); + selectEnd.setPos(end); + } else { + selectStart.setPos(end); + selectEnd.setPos(start); + } + + tagSelection(oldSelectStart, oldSelectEnd); + + repaintText(true); + + emit selectionChanged (); + emit m_doc->selectionChanged (); + + return true; +} + +bool KateView::setSelection( uint startLine, uint startCol, uint endLine, uint endCol ) +{ + if (hasSelection()) + clearSelection(false, false); + + return setSelection( KateTextCursor(startLine, startCol), KateTextCursor(endLine, endCol) ); +} + +void KateView::syncSelectionCache() +{ + m_viewInternal->selStartCached = selectStart; + m_viewInternal->selEndCached = selectEnd; + m_viewInternal->selectAnchor = selectEnd; +} + +bool KateView::clearSelection() +{ + return clearSelection(true); +} + +bool KateView::clearSelection(bool redraw, bool finishedChangingSelection) +{ + if( !hasSelection() ) + return false; + + KateTextCursor oldSelectStart = selectStart; + KateTextCursor oldSelectEnd = selectEnd; + + selectStart.setPos(-1, -1); + selectEnd.setPos(-1, -1); + + tagSelection(oldSelectStart, oldSelectEnd); + + oldSelectStart = selectStart; + oldSelectEnd = selectEnd; + + if (redraw) + repaintText(true); + + if (finishedChangingSelection) + { + emit selectionChanged(); + emit m_doc->selectionChanged (); + } + + return true; +} + +bool KateView::hasSelection() const +{ + return selectStart != selectEnd; +} + +QString KateView::selection() const +{ + int sc = selectStart.col(); + int ec = selectEnd.col(); + + if ( blockSelect ) + { + if (sc > ec) + { + uint tmp = sc; + sc = ec; + ec = tmp; + } + } + return m_doc->text (selectStart.line(), sc, selectEnd.line(), ec, blockSelect); +} + +bool KateView::removeSelectedText () +{ + if (!hasSelection()) + return false; + + m_doc->editStart (); + + int sc = selectStart.col(); + int ec = selectEnd.col(); + + if ( blockSelect ) + { + if (sc > ec) + { + uint tmp = sc; + sc = ec; + ec = tmp; + } + } + + m_doc->removeText (selectStart.line(), sc, selectEnd.line(), ec, blockSelect); + + // don't redraw the cleared selection - that's done in editEnd(). + clearSelection(false); + + m_doc->editEnd (); + + return true; +} + +bool KateView::selectAll() +{ + setBlockSelectionMode (false); + + return setSelection (0, 0, m_doc->lastLine(), m_doc->lineLength(m_doc->lastLine())); +} + +bool KateView::lineColSelected (int line, int col) +{ + if ( (!blockSelect) && (col < 0) ) + col = 0; + + KateTextCursor cursor(line, col); + + if (blockSelect) + return cursor.line() >= selectStart.line() && cursor.line() <= selectEnd.line() && cursor.col() >= selectStart.col() && cursor.col() < selectEnd.col(); + else + return (cursor >= selectStart) && (cursor < selectEnd); +} + +bool KateView::lineSelected (int line) +{ + return (!blockSelect) + && (selectStart <= KateTextCursor(line, 0)) + && (line < selectEnd.line()); +} + +bool KateView::lineEndSelected (int line, int endCol) +{ + return (!blockSelect) + && (line > selectStart.line() || (line == selectStart.line() && (selectStart.col() < endCol || endCol == -1))) + && (line < selectEnd.line() || (line == selectEnd.line() && (endCol <= selectEnd.col() && endCol != -1))); +} + +bool KateView::lineHasSelected (int line) +{ + return (selectStart < selectEnd) + && (line >= selectStart.line()) + && (line <= selectEnd.line()); +} + +bool KateView::lineIsSelection (int line) +{ + return (line == selectStart.line() && line == selectEnd.line()); +} + +void KateView::tagSelection(const KateTextCursor &oldSelectStart, const KateTextCursor &oldSelectEnd) +{ + if (hasSelection()) { + if (oldSelectStart.line() == -1) { + // We have to tag the whole lot if + // 1) we have a selection, and: + // a) it's new; or + tagLines(selectStart, selectEnd, true); + + } else if (blockSelectionMode() && (oldSelectStart.col() != selectStart.col() || oldSelectEnd.col() != selectEnd.col())) { + // b) we're in block selection mode and the columns have changed + tagLines(selectStart, selectEnd, true); + tagLines(oldSelectStart, oldSelectEnd, true); + + } else { + if (oldSelectStart != selectStart) { + if (oldSelectStart < selectStart) + tagLines(oldSelectStart, selectStart, true); + else + tagLines(selectStart, oldSelectStart, true); + } + + if (oldSelectEnd != selectEnd) { + if (oldSelectEnd < selectEnd) + tagLines(oldSelectEnd, selectEnd, true); + else + tagLines(selectEnd, oldSelectEnd, true); + } + } + + } else { + // No more selection, clean up + tagLines(oldSelectStart, oldSelectEnd, true); + } +} + +void KateView::selectWord( const KateTextCursor& cursor ) +{ + int start, end, len; + + KateTextLine::Ptr textLine = m_doc->plainKateTextLine(cursor.line()); + + if (!textLine) + return; + + len = textLine->length(); + start = end = cursor.col(); + while (start > 0 && m_doc->highlight()->isInWord(textLine->getChar(start - 1), textLine->attribute(start - 1))) start--; + while (end < len && m_doc->highlight()->isInWord(textLine->getChar(end), textLine->attribute(start - 1))) end++; + if (end <= start) return; + + setSelection (cursor.line(), start, cursor.line(), end); +} + +void KateView::selectLine( const KateTextCursor& cursor ) +{ + if (cursor.line()+1 >= m_doc->numLines()) + setSelection (cursor.line(), 0, cursor.line(), m_doc->lineLength(cursor.line())); + else + setSelection (cursor.line(), 0, cursor.line()+1, 0); +} + +void KateView::selectLength( const KateTextCursor& cursor, int length ) +{ + int start, end; + + KateTextLine::Ptr textLine = m_doc->plainKateTextLine(cursor.line()); + + if (!textLine) + return; + + start = cursor.col(); + end = start + length; + if (end <= start) return; + + setSelection (cursor.line(), start, cursor.line(), end); +} + +void KateView::paste() +{ + m_doc->paste( this ); + emit selectionChanged(); + m_viewInternal->repaint(); +} + +void KateView::cut() +{ + if (!hasSelection()) + return; + + copy(); + removeSelectedText(); +} + +void KateView::copy() const +{ + if (!hasSelection()) + return; + + QApplication::clipboard()->setText(selection ()); +} + +void KateView::copyHTML() +{ + if (!hasSelection()) + return; + + KMultipleDrag *drag = new KMultipleDrag(); + + QTextDrag *htmltextdrag = new QTextDrag(selectionAsHtml()) ; + htmltextdrag->setSubtype("html"); + + drag->addDragObject( htmltextdrag); + drag->addDragObject( new QTextDrag( selection())); + + QApplication::clipboard()->setData(drag); +} + +QString KateView::selectionAsHtml() +{ + int sc = selectStart.col(); + int ec = selectEnd.col(); + + if ( blockSelect ) + { + if (sc > ec) + { + uint tmp = sc; + sc = ec; + ec = tmp; + } + } + + return textAsHtml (selectStart.line(), sc, selectEnd.line(), ec, blockSelect); +} + +QString KateView::textAsHtml ( uint startLine, uint startCol, uint endLine, uint endCol, bool blockwise) +{ + kdDebug(13020) << "textAsHtml" << endl; + if ( blockwise && (startCol > endCol) ) + return QString (); + + QString s; + QTextStream ts( &s, IO_WriteOnly ); + ts.setEncoding(QTextStream::UnicodeUTF8); + ts << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"DTD/xhtml1-strict.dtd\">" << endl; + ts << "<html xmlns=\"http://www.w3.org/1999/xhtml\">" << endl; + ts << "<head>" << endl; + ts << "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />" << endl; + ts << "<meta name=\"Generator\" content=\"Kate, the KDE Advanced Text Editor\" />" << endl; + ts << "</head>" << endl; + + ts << "<body>" << endl; + textAsHtmlStream(startLine, startCol, endLine, endCol, blockwise, &ts); + + ts << "</body>" << endl; + ts << "</html>" << endl; + kdDebug(13020) << "html is: " << s << endl; + return s; +} + +void KateView::textAsHtmlStream ( uint startLine, uint startCol, uint endLine, uint endCol, bool blockwise, QTextStream *ts) +{ + if ( (blockwise || startLine == endLine) && (startCol > endCol) ) + return; + + if (startLine == endLine) + { + KateTextLine::Ptr textLine = m_doc->kateTextLine(startLine); + if ( !textLine ) + return; + + (*ts) << "<pre>" << endl; + + lineAsHTML(textLine, startCol, endCol-startCol, ts); + } + else + { + (*ts) << "<pre>" << endl; + + for (uint i = startLine; (i <= endLine) && (i < m_doc->numLines()); i++) + { + KateTextLine::Ptr textLine = m_doc->kateTextLine(i); + + if ( !blockwise ) + { + if (i == startLine) + lineAsHTML(textLine, startCol, textLine->length()-startCol, ts); + else if (i == endLine) + lineAsHTML(textLine, 0, endCol, ts); + else + lineAsHTML(textLine, 0, textLine->length(), ts); + } + else + { + lineAsHTML( textLine, startCol, endCol-startCol, ts); + } + + if ( i < endLine ) + (*ts) << "\n"; //we are inside a <pre>, so a \n is a new line + } + } + (*ts) << "</pre>"; +} + +// fully rewritten to use only inline CSS and support all used attribs. +// anders, 2005-11-01 23:39:43 +void KateView::lineAsHTML (KateTextLine::Ptr line, uint startCol, uint length, QTextStream *outputStream) +{ + if(length == 0) + return; + + // do not recalculate the style strings again and again + QMap<uchar,QString> stylecache; + // do not insert equally styled characters one by one + QString textcache; + + KateAttribute *charAttributes = 0; + + for (uint curPos=startCol;curPos<(length+startCol);curPos++) + { + if ( curPos == 0 || line->attribute( curPos ) != line->attribute( curPos - 1 ) && + // Since many highlight files contains itemdatas that have the exact + // same styles, join those to keep the HTML text size down + KateAttribute(*charAttributes) != KateAttribute(*m_renderer->attribute(line->attribute(curPos))) ) + { + (*outputStream) << textcache; + textcache.truncate(0); + + if ( curPos > startCol ) + (*outputStream) << "</span>"; + + charAttributes = m_renderer->attribute(line->attribute(curPos)); + + if ( ! stylecache.contains( line->attribute(curPos) ) ) + { + QString textdecoration; + QString style; + + if ( charAttributes->bold() ) + style.append("font-weight: bold;"); + if ( charAttributes->italic() ) + style.append("font-style: italic;"); + if ( charAttributes->underline() ) + textdecoration = "underline"; + if ( charAttributes->overline() ) + textdecoration.append(" overline" ); + if ( charAttributes->strikeOut() ) + textdecoration.append(" line-trough" ); + if ( !textdecoration.isEmpty() ) + style.append("text-decoration: %1;").arg(textdecoration); + // QColor::name() returns a string in the form "#RRGGBB" in Qt 3. + // NOTE Qt 4 returns "#AARRGGBB" + if ( charAttributes->itemSet(KateAttribute::BGColor) ) + style.append(QString("background-color: %1;").arg(charAttributes->bgColor().name())); + if ( charAttributes->itemSet(KateAttribute::TextColor) ) + style.append(QString("color: %1;").arg(charAttributes->textColor().name())); + + stylecache[line->attribute(curPos)] = style; + } + (*outputStream)<<"<span style=\"" + << stylecache[line->attribute(curPos)] + << "\">"; + } + + QString s( line->getChar(curPos) ); + if ( s == "&" ) s = "&"; + else if ( s == "<" ) s = "<"; + else if ( s == ">" ) s = ">"; + textcache.append( s ); + } + + (*outputStream) << textcache << "</span>"; +} + +void KateView::exportAsHTML () +{ + KURL url = KFileDialog::getSaveURL(m_doc->docName(),"text/html",0,i18n("Export File as HTML")); + + if ( url.isEmpty() ) + return; + + QString filename; + KTempFile tmp; // ### only used for network export + + if ( url.isLocalFile() ) + filename = url.path(); + else + filename = tmp.name(); + + KSaveFile *savefile=new KSaveFile(filename); + if (!savefile->status()) + { + QTextStream *outputStream = savefile->textStream(); + + outputStream->setEncoding(QTextStream::UnicodeUTF8); + + // let's write the HTML header : + (*outputStream) << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << endl; + (*outputStream) << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"DTD/xhtml1-strict.dtd\">" << endl; + (*outputStream) << "<html xmlns=\"http://www.w3.org/1999/xhtml\">" << endl; + (*outputStream) << "<head>" << endl; + (*outputStream) << "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />" << endl; + (*outputStream) << "<meta name=\"Generator\" content=\"Kate, the KDE Advanced Text Editor\" />" << endl; + // for the title, we write the name of the file (/usr/local/emmanuel/myfile.cpp -> myfile.cpp) + (*outputStream) << "<title>" << m_doc->docName () << "</title>" << endl; + (*outputStream) << "</head>" << endl; + (*outputStream) << "<body>" << endl; + + textAsHtmlStream(0,0, m_doc->lastLine(), m_doc->lineLength(m_doc->lastLine()), false, outputStream); + + (*outputStream) << "</body>" << endl; + (*outputStream) << "</html>" << endl; + + + savefile->close(); + //if (!savefile->status()) --> Error + } +// else +// {/*ERROR*/} + delete savefile; + + if ( url.isLocalFile() ) + return; + + KIO::NetAccess::upload( filename, url, 0 ); +} +//END + +//BEGIN KTextEditor::BlockSelectionInterface stuff + +bool KateView::blockSelectionMode () +{ + return blockSelect; +} + +bool KateView::setBlockSelectionMode (bool on) +{ + if (on != blockSelect) + { + blockSelect = on; + + KateTextCursor oldSelectStart = selectStart; + KateTextCursor oldSelectEnd = selectEnd; + + clearSelection(false, false); + + setSelection(oldSelectStart, oldSelectEnd); + + slotSelectionTypeChanged(); + } + + return true; +} + +bool KateView::toggleBlockSelectionMode () +{ + m_toggleBlockSelection->setChecked (!blockSelect); + return setBlockSelectionMode (!blockSelect); +} + +bool KateView::wrapCursor () +{ + return !blockSelectionMode() && (m_doc->configFlags() & KateDocument::cfWrapCursor); +} + +//END + +//BEGIN IM INPUT STUFF +void KateView::setIMSelectionValue( uint imStartLine, uint imStart, uint imEnd, + uint imSelStart, uint imSelEnd, bool imComposeEvent ) +{ + m_imStartLine = imStartLine; + m_imStart = imStart; + m_imEnd = imEnd; + m_imSelStart = imSelStart; + m_imSelEnd = imSelEnd; + m_imComposeEvent = imComposeEvent; +} + +bool KateView::isIMSelection( int _line, int _column ) +{ + return ( ( int( m_imStartLine ) == _line ) && ( m_imSelStart < m_imSelEnd ) && ( _column >= int( m_imSelStart ) ) && + ( _column < int( m_imSelEnd ) ) ); +} + +bool KateView::isIMEdit( int _line, int _column ) +{ + return ( ( int( m_imStartLine ) == _line ) && ( m_imStart < m_imEnd ) && ( _column >= int( m_imStart ) ) && + ( _column < int( m_imEnd ) ) ); +} + +void KateView::getIMSelectionValue( uint *imStartLine, uint *imStart, uint *imEnd, + uint *imSelStart, uint *imSelEnd ) +{ + *imStartLine = m_imStartLine; + *imStart = m_imStart; + *imEnd = m_imEnd; + *imSelStart = m_imSelStart; + *imSelEnd = m_imSelEnd; +} +//END IM INPUT STUFF + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateview.h b/kate/part/kateview.h new file mode 100644 index 000000000..0e2f310b8 --- /dev/null +++ b/kate/part/kateview.h @@ -0,0 +1,571 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002 John Firebaugh <jfirebaugh@kde.org> + Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef kate_view_h +#define kate_view_h + +#include "katedocument.h" +#include "kateviewinternal.h" +#include "kateconfig.h" + +#include "../interfaces/view.h" + +#include <ktexteditor/sessionconfiginterface.h> +#include <ktexteditor/viewstatusmsginterface.h> +#include <ktexteditor/texthintinterface.h> + +#include <qguardedptr.h> + +class KateDocument; +class KateBookmarks; +class KateSearch; +class KateCmdLine; +class KateCodeCompletion; +class KateViewConfig; +class KateViewSchemaAction; +class KateRenderer; +class KateSpell; + +class KToggleAction; +class KAction; +class KRecentFilesAction; +class KSelectAction; + +class QGridLayout; + +// +// Kate KTextEditor::View class ;) +// +class KateView : public Kate::View, + public KTextEditor::SessionConfigInterface, + public KTextEditor::ViewStatusMsgInterface, + public KTextEditor::TextHintInterface, + public KTextEditor::SelectionInterface, + public KTextEditor::SelectionInterfaceExt, + public KTextEditor::BlockSelectionInterface +{ + Q_OBJECT + + friend class KateViewInternal; + friend class KateIconBorder; + friend class KateCodeCompletion; + + public: + KateView( KateDocument* doc, QWidget* parent = 0L, const char* name = 0 ); + ~KateView (); + + // + // KTextEditor::View + // + public: + KTextEditor::Document* document() const { return m_doc; } + + // + // KTextEditor::ClipboardInterface + // + public slots: + // TODO: Factor out of m_viewInternal + void paste(); + void cut(); + void copy() const; + + /** + * internal use, copy text as HTML to clipboard + */ + void copyHTML(); + + // helper to export text as html stuff + private: + QString selectionAsHtml (); + QString textAsHtml ( uint startLine, uint startCol, uint endLine, uint endCol, bool blockwise); + void textAsHtmlStream ( uint startLine, uint startCol, uint endLine, uint endCol, bool blockwise, QTextStream *ts); + + /** + * Gets a substring in valid-xml html. + * Example: "<b>const</b> b = <i>34</i>" + * It won't contain <p> or <body> or <html> or anything like that. + * + * @param startCol start column of substring + * @param length length of substring + * @param renderer The katerenderer. This will have the schema + * information that describes how to render the + * attributes. + * @param outputStream A stream to write the html to + */ + void lineAsHTML (KateTextLine::Ptr line, uint startCol, uint length, QTextStream *outputStream); + + public slots: + void exportAsHTML (); + + // + // KTextEditor::PopupMenuInterface + // + public: + void installPopup( QPopupMenu* menu ) { m_rmbMenu = menu; } + QPopupMenu* popup() const { return m_rmbMenu; } + + // + // KTextEditor::ViewCursorInterface + // + public slots: + QPoint cursorCoordinates() + { return m_viewInternal->cursorCoordinates(); } + void cursorPosition( uint* l, uint* c ) + { if( l ) *l = cursorLine(); if( c ) *c = cursorColumn(); } + void cursorPositionReal( uint* l, uint* c ) + { if( l ) *l = cursorLine(); if( c ) *c = cursorColumnReal(); } + bool setCursorPosition( uint line, uint col ) + { return setCursorPositionInternal( line, col, tabWidth(), true ); } + bool setCursorPositionReal( uint line, uint col) + { return setCursorPositionInternal( line, col, 1, true ); } + uint cursorLine() + { return m_viewInternal->getCursor().line(); } + uint cursorColumn(); + uint cursorColumnReal() + { return m_viewInternal->getCursor().col(); } + + signals: + void cursorPositionChanged(); + + // + // KTextEditor::CodeCompletionInterface + // + public slots: + void showArgHint( QStringList arg1, const QString& arg2, const QString& arg3 ); + void showCompletionBox( QValueList<KTextEditor::CompletionEntry> arg1, int offset = 0, bool cs = true ); + + signals: + void completionAborted(); + void completionDone(); + void argHintHidden(); + void completionDone(KTextEditor::CompletionEntry); + void filterInsertString(KTextEditor::CompletionEntry*,QString *); + void aboutToShowCompletionBox(); + + // + // KTextEditor::TextHintInterface + // + public: + void enableTextHints(int timeout); + void disableTextHints(); + + signals: + void needTextHint(int line, int col, QString &text); + + // + // KTextEditor::DynWordWrapInterface + // + public: + void setDynWordWrap( bool b ); + bool dynWordWrap() const { return m_hasWrap; } + + // + // KTextEditor::SelectionInterface stuff + // + public slots: + bool setSelection ( const KateTextCursor & start, + const KateTextCursor & end ); + bool setSelection ( uint startLine, uint startCol, + uint endLine, uint endCol ); + bool clearSelection (); + bool clearSelection (bool redraw, bool finishedChangingSelection = true); + + bool hasSelection () const; + QString selection () const ; + + bool removeSelectedText (); + + bool selectAll(); + + // + // KTextEditor::SelectionInterfaceExt + // + int selStartLine() { return selectStart.line(); }; + int selStartCol() { return selectStart.col(); }; + int selEndLine() { return selectEnd.line(); }; + int selEndCol() { return selectEnd.col(); }; + + signals: + void selectionChanged (); + + // + // internal helper stuff, for katerenderer and so on + // + public: + /** + * accessors to the selection start + * @return selection start cursor (read-only) + */ + inline const KateSuperCursor &selStart () const { return selectStart; } + + /** + * accessors to the selection end + * @return selection end cursor (read-only) + */ + inline const KateSuperCursor &selEnd () const { return selectEnd; } + + // should cursor be wrapped ? take config + blockselection state in account + bool wrapCursor (); + + // some internal functions to get selection state of a line/col + bool lineColSelected (int line, int col); + bool lineSelected (int line); + bool lineEndSelected (int line, int endCol); + bool lineHasSelected (int line); + bool lineIsSelection (int line); + + void tagSelection (const KateTextCursor &oldSelectStart, const KateTextCursor &oldSelectEnd); + + void selectWord( const KateTextCursor& cursor ); + void selectLine( const KateTextCursor& cursor ); + void selectLength( const KateTextCursor& cursor, int length ); + + // this method will sync the KateViewInternal's sel{Start,End}Cached and selectAnchor + // with the current selection, to make it "stick" instead of reverting back to sel*Cached + void syncSelectionCache(); + + // + // KTextEditor::BlockSelectionInterface stuff + // + public slots: + bool blockSelectionMode (); + bool setBlockSelectionMode (bool on); + bool toggleBlockSelectionMode (); + + + //BEGIN EDIT STUFF + public: + void editStart (); + void editEnd (int editTagLineStart, int editTagLineEnd, bool tagFrom); + + void editSetCursor (const KateTextCursor &cursor); + //END + + //BEGIN TAG & CLEAR + public: + bool tagLine (const KateTextCursor& virtualCursor); + + bool tagLines (int start, int end, bool realLines = false ); + bool tagLines (KateTextCursor start, KateTextCursor end, bool realCursors = false); + + void tagAll (); + + void clear (); + + void repaintText (bool paintOnlyDirty = false); + + void updateView (bool changed = false); + //END + + // + // Kate::View + // + public: + bool isOverwriteMode() const; + void setOverwriteMode( bool b ); + + QString currentTextLine() + { return getDoc()->textLine( cursorLine() ); } + QString currentWord() + { return m_doc->getWord( m_viewInternal->getCursor() ); } + void insertText( const QString& mark ) + { getDoc()->insertText( cursorLine(), cursorColumnReal(), mark ); } + bool canDiscard(); + int tabWidth() { return m_doc->config()->tabWidth(); } + void setTabWidth( int w ) { m_doc->config()->setTabWidth(w); } + void setEncoding( QString e ) { m_doc->setEncoding(e); } + bool isLastView() { return m_doc->isLastView(1); } + + public slots: + void flush(); + saveResult save(); + saveResult saveAs(); + + void indent() { m_doc->indent( this, cursorLine(), 1 ); } + void unIndent() { m_doc->indent( this, cursorLine(), -1 ); } + void cleanIndent() { m_doc->indent( this, cursorLine(), 0 ); } + void align() { m_doc->align( this, cursorLine() ); } + void comment() { m_doc->comment( this, cursorLine(), cursorColumnReal(), 1 ); } + void uncomment() { m_doc->comment( this, cursorLine(), cursorColumnReal(),-1 ); } + void killLine() { m_doc->removeLine( cursorLine() ); } + + /** + Uppercases selected text, or an alphabetic character next to the cursor. + */ + void uppercase() { m_doc->transform( this, m_viewInternal->cursor, KateDocument::Uppercase ); } + /** + Lowercases selected text, or an alphabetic character next to the cursor. + */ + void lowercase() { m_doc->transform( this, m_viewInternal->cursor, KateDocument::Lowercase ); } + /** + Capitalizes the selection (makes each word start with an uppercase) or + the word under the cursor. + */ + void capitalize() { m_doc->transform( this, m_viewInternal->cursor, KateDocument::Capitalize ); } + /** + Joins lines touched by the selection + */ + void joinLines(); + + + void keyReturn() { m_viewInternal->doReturn(); } + void backspace() { m_viewInternal->doBackspace(); } + void deleteWordLeft() { m_viewInternal->doDeleteWordLeft(); } + void keyDelete() { m_viewInternal->doDelete(); } + void deleteWordRight() { m_viewInternal->doDeleteWordRight(); } + void transpose() { m_viewInternal->doTranspose(); } + void cursorLeft() { m_viewInternal->cursorLeft(); } + void shiftCursorLeft() { m_viewInternal->cursorLeft(true); } + void cursorRight() { m_viewInternal->cursorRight(); } + void shiftCursorRight() { m_viewInternal->cursorRight(true); } + void wordLeft() { m_viewInternal->wordLeft(); } + void shiftWordLeft() { m_viewInternal->wordLeft(true); } + void wordRight() { m_viewInternal->wordRight(); } + void shiftWordRight() { m_viewInternal->wordRight(true); } + void home() { m_viewInternal->home(); } + void shiftHome() { m_viewInternal->home(true); } + void end() { m_viewInternal->end(); } + void shiftEnd() { m_viewInternal->end(true); } + void up() { m_viewInternal->cursorUp(); } + void shiftUp() { m_viewInternal->cursorUp(true); } + void down() { m_viewInternal->cursorDown(); } + void shiftDown() { m_viewInternal->cursorDown(true); } + void scrollUp() { m_viewInternal->scrollUp(); } + void scrollDown() { m_viewInternal->scrollDown(); } + void topOfView() { m_viewInternal->topOfView(); } + void shiftTopOfView() { m_viewInternal->topOfView(true); } + void bottomOfView() { m_viewInternal->bottomOfView(); } + void shiftBottomOfView() { m_viewInternal->bottomOfView(true); } + void pageUp() { m_viewInternal->pageUp(); } + void shiftPageUp() { m_viewInternal->pageUp(true); } + void pageDown() { m_viewInternal->pageDown(); } + void shiftPageDown() { m_viewInternal->pageDown(true); } + void top() { m_viewInternal->top_home(); } + void shiftTop() { m_viewInternal->top_home(true); } + void bottom() { m_viewInternal->bottom_end(); } + void shiftBottom() { m_viewInternal->bottom_end(true); } + void toMatchingBracket() { m_viewInternal->cursorToMatchingBracket();} + void shiftToMatchingBracket() { m_viewInternal->cursorToMatchingBracket(true);} + + void gotoLine(); + void gotoLineNumber( int linenumber ); + + // config file / session management functions + public: + void readSessionConfig(KConfig *); + void writeSessionConfig(KConfig *); + + public slots: + int getEol(); + void setEol( int eol ); + void find(); + void find( const QString&, long, bool add=true ); ///< proxy for KateSearch + void replace(); + void replace( const QString&, const QString &, long ); ///< proxy for KateSearch + /** Highly confusing but KateSearch::findAgain() is backwards too. */ + void findAgain( bool back ); + void findAgain() { findAgain( false ); } + void findPrev() { findAgain( true ); } + + void setFoldingMarkersOn( bool enable ); // Not in Kate::View, but should be + void setIconBorder( bool enable ); + void setLineNumbersOn( bool enable ); + void setScrollBarMarks( bool enable ); + void showCmdLine ( bool enable ); + void toggleFoldingMarkers(); + void toggleIconBorder(); + void toggleLineNumbersOn(); + void toggleScrollBarMarks(); + void toggleDynWordWrap (); + void toggleCmdLine (); + void setDynWrapIndicators(int mode); + + void applyWordWrap (); + + public: + KateRenderer *renderer (); + + bool iconBorder(); + bool lineNumbersOn(); + bool scrollBarMarks(); + int dynWrapIndicators(); + bool foldingMarkersOn(); + Kate::Document* getDoc() { return m_doc; } + + void setActive( bool b ) { m_active = b; } + bool isActive() { return m_active; } + + public slots: + void gotoMark( KTextEditor::Mark* mark ) { setCursorPositionInternal ( mark->line, 0, 1 ); } + void slotSelectionChanged (); + + signals: + void gotFocus( Kate::View* ); + void lostFocus( Kate::View* ); + void newStatus(); // Not in Kate::View, but should be (Kate app connects to it) + + // + // Extras + // + public: + // Is it really necessary to have 3 methods for this?! :) + KateDocument* doc() const { return m_doc; } + + KActionCollection* editActionCollection() const { return m_editActions; } + + public slots: + void slotNewUndo(); + void slotUpdate(); + void toggleInsert(); + void reloadFile(); + void toggleWWMarker(); + void toggleWriteLock(); + void switchToCmdLine (); + void slotReadWriteChanged (); + + signals: + void dropEventPass(QDropEvent*); + void viewStatusMsg (const QString &msg); + + public: + bool setCursorPositionInternal( uint line, uint col, uint tabwidth = 1, bool calledExternally = false ); + + protected: + void contextMenuEvent( QContextMenuEvent* ); + bool checkOverwrite( KURL ); + + public slots: + void slotSelectionTypeChanged(); + + private slots: + void slotGotFocus(); + void slotLostFocus(); + void slotDropEventPass( QDropEvent* ev ); + void slotStatusMsg(); + void slotSaveCanceled( const QString& error ); + void slotExpandToplevel(); + void slotCollapseLocal(); + void slotExpandLocal(); + + private: + void setupConnections(); + void setupActions(); + void setupEditActions(); + void setupCodeFolding(); + void setupCodeCompletion(); + + KActionCollection* m_editActions; + KAction* m_editUndo; + KAction* m_editRedo; + KRecentFilesAction* m_fileRecent; + KToggleAction* m_toggleFoldingMarkers; + KToggleAction* m_toggleIconBar; + KToggleAction* m_toggleLineNumbers; + KToggleAction* m_toggleScrollBarMarks; + KToggleAction* m_toggleDynWrap; + KSelectAction* m_setDynWrapIndicators; + KToggleAction* m_toggleWWMarker; + KAction* m_switchCmdLine; + + KSelectAction* m_setEndOfLine; + + KAction *m_cut; + KAction *m_copy; + KAction *m_copyHTML; + KAction *m_paste; + KAction *m_selectAll; + KAction *m_deSelect; + + KToggleAction *m_toggleBlockSelection; + KToggleAction *m_toggleInsert; + KToggleAction *m_toggleWriteLock; + + KateDocument* m_doc; + KateViewInternal* m_viewInternal; + KateRenderer* m_renderer; + KateSearch* m_search; + KateSpell *m_spell; + KateBookmarks* m_bookmarks; + QGuardedPtr<QPopupMenu> m_rmbMenu; + KateCodeCompletion* m_codeCompletion; + + KateCmdLine *m_cmdLine; + bool m_cmdLineOn; + + QGridLayout *m_grid; + + bool m_active; + bool m_hasWrap; + + private slots: + void slotNeedTextHint(int line, int col, QString &text); + void slotHlChanged(); + + /** + * Configuration + */ + public: + inline KateViewConfig *config () { return m_config; }; + + void updateConfig (); + + void updateDocumentConfig(); + + void updateRendererConfig(); + + private slots: + void updateFoldingConfig (); + + private: + KateViewConfig *m_config; + bool m_startingUp; + bool m_updatingDocumentConfig; + + private: + // stores the current selection + KateSuperCursor selectStart; + KateSuperCursor selectEnd; + + // do we select normal or blockwise ? + bool blockSelect; + + /** + * IM input stuff + */ + public: + void setIMSelectionValue( uint imStartLine, uint imStart, uint imEnd, + uint imSelStart, uint imSelEnd, bool m_imComposeEvent ); + void getIMSelectionValue( uint *imStartLine, uint *imStart, uint *imEnd, + uint *imSelStart, uint *imSelEnd ); + bool isIMSelection( int _line, int _column ); + bool isIMEdit( int _line, int _column ); + bool imComposeEvent () const { return m_imComposeEvent; } + + private: + uint m_imStartLine; + uint m_imStart; + uint m_imEnd; + uint m_imSelStart; + uint m_imSelEnd; + bool m_imComposeEvent; +}; + +#endif diff --git a/kate/part/kateviewhelpers.cpp b/kate/part/kateviewhelpers.cpp new file mode 100644 index 000000000..4ca753486 --- /dev/null +++ b/kate/part/kateviewhelpers.cpp @@ -0,0 +1,1205 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002 John Firebaugh <jfirebaugh@kde.org> + Copyright (C) 2001 Anders Lund <anders@alweb.dk> + Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kateviewhelpers.h" +#include "kateviewhelpers.moc" + +#include "../interfaces/document.h" +#include "../interfaces/katecmd.h" +#include "kateattribute.h" +#include "katecodefoldinghelpers.h" +#include "kateconfig.h" +#include "katedocument.h" +#include "katefactory.h" +#include "katerenderer.h" +#include "kateview.h" +#include "kateviewinternal.h" + +#include <kapplication.h> +#include <kglobalsettings.h> +#include <klocale.h> +#include <knotifyclient.h> +#include <kglobal.h> +#include <kcharsets.h> +#include <kpopupmenu.h> + +#include <qcursor.h> +#include <qpainter.h> +#include <qpopupmenu.h> +#include <qstyle.h> +#include <qtimer.h> +#include <qwhatsthis.h> +#include <qregexp.h> +#include <qtextcodec.h> + +#include <math.h> + +#include <kdebug.h> + +//BEGIN KateScrollBar +KateScrollBar::KateScrollBar (Orientation orientation, KateViewInternal* parent, const char* name) + : QScrollBar (orientation, parent->m_view, name) + , m_middleMouseDown (false) + , m_view(parent->m_view) + , m_doc(parent->m_doc) + , m_viewInternal(parent) + , m_topMargin(-1) + , m_bottomMargin(-1) + , m_savVisibleLines(0) + , m_showMarks(false) +{ + connect(this, SIGNAL(valueChanged(int)), SLOT(sliderMaybeMoved(int))); + connect(m_doc, SIGNAL(marksChanged()), this, SLOT(marksChanged())); + + m_lines.setAutoDelete(true); +} + +void KateScrollBar::mousePressEvent(QMouseEvent* e) +{ + if (e->button() == MidButton) + m_middleMouseDown = true; + + QScrollBar::mousePressEvent(e); + + redrawMarks(); +} + +void KateScrollBar::mouseReleaseEvent(QMouseEvent* e) +{ + QScrollBar::mouseReleaseEvent(e); + + m_middleMouseDown = false; + + redrawMarks(); +} + +void KateScrollBar::mouseMoveEvent(QMouseEvent* e) +{ + QScrollBar::mouseMoveEvent(e); + + if (e->state() | LeftButton) + redrawMarks(); +} + +void KateScrollBar::paintEvent(QPaintEvent *e) +{ + QScrollBar::paintEvent(e); + redrawMarks(); +} + +void KateScrollBar::resizeEvent(QResizeEvent *e) +{ + QScrollBar::resizeEvent(e); + recomputeMarksPositions(); +} + +void KateScrollBar::styleChange(QStyle &s) +{ + QScrollBar::styleChange(s); + m_topMargin = -1; + recomputeMarksPositions(); +} + +void KateScrollBar::valueChange() +{ + QScrollBar::valueChange(); + redrawMarks(); +} + +void KateScrollBar::rangeChange() +{ + QScrollBar::rangeChange(); + recomputeMarksPositions(); +} + +void KateScrollBar::marksChanged() +{ + recomputeMarksPositions(true); +} + +void KateScrollBar::redrawMarks() +{ + if (!m_showMarks) + return; + + QPainter painter(this); + QRect rect = sliderRect(); + for (QIntDictIterator<QColor> it(m_lines); it.current(); ++it) + { + if (it.currentKey() < rect.top() || it.currentKey() > rect.bottom()) + { + painter.setPen(*it.current()); + painter.drawLine(0, it.currentKey(), width(), it.currentKey()); + } + } +} + +void KateScrollBar::recomputeMarksPositions(bool forceFullUpdate) +{ + if (m_topMargin == -1) + watchScrollBarSize(); + + m_lines.clear(); + m_savVisibleLines = m_doc->visibleLines(); + + int realHeight = frameGeometry().height() - m_topMargin - m_bottomMargin; + + QPtrList<KTextEditor::Mark> marks = m_doc->marks(); + KateCodeFoldingTree *tree = m_doc->foldingTree(); + + for (KTextEditor::Mark *mark = marks.first(); mark; mark = marks.next()) + { + uint line = mark->line; + + if (tree) + { + KateCodeFoldingNode *node = tree->findNodeForLine(line); + + while (node) + { + if (!node->isVisible()) + line = tree->getStartLine(node); + node = node->getParentNode(); + } + } + + line = m_doc->getVirtualLine(line); + + double d = (double)line / (m_savVisibleLines - 1); + m_lines.insert(m_topMargin + (int)(d * realHeight), + new QColor(KateRendererConfig::global()->lineMarkerColor((KTextEditor::MarkInterface::MarkTypes)mark->type))); + } + + if (forceFullUpdate) + update(); + else + redrawMarks(); +} + +void KateScrollBar::watchScrollBarSize() +{ + int savMax = maxValue(); + setMaxValue(0); + QRect rect = sliderRect(); + setMaxValue(savMax); + + m_topMargin = rect.top(); + m_bottomMargin = frameGeometry().height() - rect.bottom(); +} + +void KateScrollBar::sliderMaybeMoved(int value) +{ + if (m_middleMouseDown) + emit sliderMMBMoved(value); +} +//END + +//BEGIN KateCmdLnWhatsThis +class KateCmdLnWhatsThis : public QWhatsThis +{ + public: + KateCmdLnWhatsThis( KateCmdLine *parent ) + : QWhatsThis( parent ) + , m_parent( parent ) {;} + + QString text( const QPoint & ) + { + QString beg = "<qt background=\"white\"><div><table width=\"100%\"><tr><td bgcolor=\"brown\"><font color=\"white\"><b>Help: <big>"; + QString mid = "</big></b></font></td></tr><tr><td>"; + QString end = "</td></tr></table></div><qt>"; + + QString t = m_parent->text(); + QRegExp re( "\\s*help\\s+(.*)" ); + if ( re.search( t ) > -1 ) + { + QString s; + // get help for command + QString name = re.cap( 1 ); + if ( name == "list" ) + { + return beg + i18n("Available Commands") + mid + + KateCmd::self()->cmds().join(" ") + + i18n("<p>For help on individual commands, do <code>'help <command>'</code></p>") + + end; + } + else if ( ! name.isEmpty() ) + { + Kate::Command *cmd = KateCmd::self()->queryCommand( name ); + if ( cmd ) + { + if ( cmd->help( (Kate::View*)m_parent->parentWidget(), name, s ) ) + return beg + name + mid + s + end; + else + return beg + name + mid + i18n("No help for '%1'").arg( name ) + end; + } + else + return beg + mid + i18n("No such command <b>%1</b>").arg(name) + end; + } + } + + return beg + mid + i18n( + "<p>This is the Katepart <b>command line</b>.<br>" + "Syntax: <code><b>command [ arguments ]</b></code><br>" + "For a list of available commands, enter <code><b>help list</b></code><br>" + "For help for individual commands, enter <code><b>help <command></b></code></p>") + + end; + } + + private: + KateCmdLine *m_parent; +}; +//END KateCmdLnWhatsThis + +//BEGIN KateCmdLineFlagCompletion +/** + * This class provide completion of flags. It shows a short description of + * each flag, and flags are appended. + */ +class KateCmdLineFlagCompletion : public KCompletion +{ + public: + KateCmdLineFlagCompletion() {;} + + QString makeCompletion( const QString & string ) + { + return QString::null; + } + +}; +//END KateCmdLineFlagCompletion + +//BEGIN KateCmdLine +KateCmdLine::KateCmdLine (KateView *view) + : KLineEdit (view) + , m_view (view) + , m_msgMode (false) + , m_histpos( 0 ) + , m_cmdend( 0 ) + , m_command( 0L ) + , m_oldCompletionObject( 0L ) +{ + connect (this, SIGNAL(returnPressed(const QString &)), + this, SLOT(slotReturnPressed(const QString &))); + + completionObject()->insertItems (KateCmd::self()->cmds()); + setAutoDeleteCompletionObject( false ); + m_help = new KateCmdLnWhatsThis( this ); +} + +void KateCmdLine::slotReturnPressed ( const QString& text ) +{ + + // silently ignore leading space + uint n = 0; + while( text[n].isSpace() ) + n++; + + QString cmd = text.mid( n ); + + // Built in help: if the command starts with "help", [try to] show some help + if ( cmd.startsWith( "help" ) ) + { + m_help->display( m_help->text( QPoint() ), mapToGlobal(QPoint(0,0)) ); + clear(); + KateCmd::self()->appendHistory( cmd ); + m_histpos = KateCmd::self()->historyLength(); + m_oldText = QString (); + return; + } + + if (cmd.length () > 0) + { + Kate::Command *p = KateCmd::self()->queryCommand (cmd); + + m_oldText = cmd; + m_msgMode = true; + + if (p) + { + QString msg; + + if (p->exec (m_view, cmd, msg)) + { + KateCmd::self()->appendHistory( cmd ); + m_histpos = KateCmd::self()->historyLength(); + m_oldText = QString (); + + if (msg.length() > 0) + setText (i18n ("Success: ") + msg); + else + setText (i18n ("Success")); + } + else + { + if (msg.length() > 0) + setText (i18n ("Error: ") + msg); + else + setText (i18n ("Command \"%1\" failed.").arg (cmd)); + KNotifyClient::beep(); + } + } + else + { + setText (i18n ("No such command: \"%1\"").arg (cmd)); + KNotifyClient::beep(); + } + } + + // clean up + if ( m_oldCompletionObject ) + { + KCompletion *c = completionObject(); + setCompletionObject( m_oldCompletionObject ); + m_oldCompletionObject = 0; + delete c; + c = 0; + } + m_command = 0; + m_cmdend = 0; + + m_view->setFocus (); + QTimer::singleShot( 4000, this, SLOT(hideMe()) ); +} + +void KateCmdLine::hideMe () // unless i have focus ;) +{ + if ( isVisibleTo(parentWidget()) && ! hasFocus() ) { + m_view->toggleCmdLine (); + } +} + +void KateCmdLine::focusInEvent ( QFocusEvent *ev ) +{ + if (m_msgMode) + { + m_msgMode = false; + setText (m_oldText); + selectAll(); + } + + KLineEdit::focusInEvent (ev); +} + +void KateCmdLine::keyPressEvent( QKeyEvent *ev ) +{ + if (ev->key() == Key_Escape) + { + m_view->setFocus (); + hideMe(); + } + else if ( ev->key() == Key_Up ) + fromHistory( true ); + else if ( ev->key() == Key_Down ) + fromHistory( false ); + + uint cursorpos = cursorPosition(); + KLineEdit::keyPressEvent (ev); + + // during typing, let us see if we have a valid command + if ( ! m_cmdend || cursorpos <= m_cmdend ) + { + QChar c; + if ( ! ev->text().isEmpty() ) + c = ev->text()[0]; + + if ( ! m_cmdend && ! c.isNull() ) // we have no command, so lets see if we got one + { + if ( ! c.isLetterOrNumber() && c != '-' && c != '_' ) + { + m_command = KateCmd::self()->queryCommand( text().stripWhiteSpace() ); + if ( m_command ) + { + //kdDebug(13025)<<"keypress in commandline: We have a command! "<<m_command<<". text is '"<<text()<<"'"<<endl; + // if the typed character is ":", + // we try if the command has flag completions + m_cmdend = cursorpos; + //kdDebug(13025)<<"keypress in commandline: Set m_cmdend to "<<m_cmdend<<endl; + } + else + m_cmdend = 0; + } + } + else // since cursor is inside the command name, we reconsider it + { + kdDebug(13025)<<"keypress in commandline: \\W -- text is "<<text()<<endl; + m_command = KateCmd::self()->queryCommand( text().stripWhiteSpace() ); + if ( m_command ) + { + //kdDebug(13025)<<"keypress in commandline: We have a command! "<<m_command<<endl; + QString t = text(); + m_cmdend = 0; + bool b = false; + for ( ; m_cmdend < t.length(); m_cmdend++ ) + { + if ( t[m_cmdend].isLetter() ) + b = true; + if ( b && ( ! t[m_cmdend].isLetterOrNumber() && t[m_cmdend] != '-' && t[m_cmdend] != '_' ) ) + break; + } + + if ( c == ':' && cursorpos == m_cmdend ) + { + // check if this command wants to complete flags + //kdDebug(13025)<<"keypress in commandline: Checking if flag completion is desired!"<<endl; + } + } + else + { + // clean up if needed + if ( m_oldCompletionObject ) + { + KCompletion *c = completionObject(); + setCompletionObject( m_oldCompletionObject ); + m_oldCompletionObject = 0; + delete c; + c = 0; + } + + m_cmdend = 0; + } + } + + // if we got a command, check if it wants to do semething. + if ( m_command ) + { + //kdDebug(13025)<<"Checking for CommandExtension.."<<endl; + Kate::CommandExtension *ce = dynamic_cast<Kate::CommandExtension*>(m_command); + if ( ce ) + { + KCompletion *cmpl = ce->completionObject( text().left( m_cmdend ).stripWhiteSpace(), m_view ); + if ( cmpl ) + { + // save the old completion object and use what the command provides + // instead. We also need to prepend the current command name + flag string + // when completion is done + //kdDebug(13025)<<"keypress in commandline: Setting completion object!"<<endl; + if ( ! m_oldCompletionObject ) + m_oldCompletionObject = completionObject(); + + setCompletionObject( cmpl ); + } + } + } + } + else if ( m_command )// check if we should call the commands processText() + { + Kate::CommandExtension *ce = dynamic_cast<Kate::CommandExtension*>( m_command ); + if ( ce && ce->wantsToProcessText( text().left( m_cmdend ).stripWhiteSpace() ) + && ! ( ev->text().isNull() || ev->text().isEmpty() ) ) + ce->processText( m_view, text() ); + } +} + +void KateCmdLine::fromHistory( bool up ) +{ + if ( ! KateCmd::self()->historyLength() ) + return; + + QString s; + + if ( up ) + { + if ( m_histpos > 0 ) + { + m_histpos--; + s = KateCmd::self()->fromHistory( m_histpos ); + } + } + else + { + if ( m_histpos < ( KateCmd::self()->historyLength() - 1 ) ) + { + m_histpos++; + s = KateCmd::self()->fromHistory( m_histpos ); + } + else + { + m_histpos = KateCmd::self()->historyLength(); + setText( m_oldText ); + } + } + if ( ! s.isEmpty() ) + { + // Select the argument part of the command, so that it is easy to overwrite + setText( s ); + static QRegExp reCmd = QRegExp(".*[\\w\\-]+(?:[^a-zA-Z0-9_-]|:\\w+)(.*)"); + if ( reCmd.search( text() ) == 0 ) + setSelection( text().length() - reCmd.cap(1).length(), reCmd.cap(1).length() ); + } +} +//END KateCmdLine + +//BEGIN KateIconBorder +using namespace KTextEditor; + +static const char* const plus_xpm[] = { +"11 11 3 1", +" c None", +". c #000000", +"+ c #FFFFFF", +"...........", +".+++++++++.", +".+++++++++.", +".++++.++++.", +".++++.++++.", +".++.....++.", +".++++.++++.", +".++++.++++.", +".+++++++++.", +".+++++++++.", +"..........."}; + +static const char* const minus_xpm[] = { +"11 11 3 1", +" c None", +". c #000000", +"+ c #FFFFFF", +"...........", +".+++++++++.", +".+++++++++.", +".+++++++++.", +".+++++++++.", +".++.....++.", +".+++++++++.", +".+++++++++.", +".+++++++++.", +".+++++++++.", +"..........."}; + +static const char * const bookmark_xpm[] = { +"14 13 82 1", +" c None", +". c #F27D01", +"+ c #EF7901", +"@ c #F3940F", +"# c #EE8F12", +"$ c #F9C834", +"% c #F5C33A", +"& c #F09110", +"* c #FCEE3E", +"= c #FBEB3F", +"- c #E68614", +"; c #FA8700", +"> c #F78703", +", c #F4920E", +"' c #F19113", +") c #F6C434", +"! c #FDF938", +"~ c #FDF839", +"{ c #F1BC3A", +"] c #E18017", +"^ c #DA7210", +"/ c #D5680B", +"( c #CA5404", +"_ c #FD8F06", +": c #FCB62D", +"< c #FDE049", +"[ c #FCE340", +"} c #FBE334", +"| c #FDF035", +"1 c #FEF834", +"2 c #FCEF36", +"3 c #F8DF32", +"4 c #F7DC3D", +"5 c #F5CE3E", +"6 c #DE861B", +"7 c #C64C03", +"8 c #F78C07", +"9 c #F8B019", +"0 c #FDE12D", +"a c #FEE528", +"b c #FEE229", +"c c #FBD029", +"d c #E18814", +"e c #CB5605", +"f c #EF8306", +"g c #F3A00E", +"h c #FBC718", +"i c #FED31C", +"j c #FED11D", +"k c #F8B91C", +"l c #E07D0D", +"m c #CB5301", +"n c #ED8A0E", +"o c #F7A90D", +"p c #FEC113", +"q c #FEC013", +"r c #F09B0E", +"s c #D35E03", +"t c #EF9213", +"u c #F9A208", +"v c #FEAA0C", +"w c #FCA10B", +"x c #FCA70B", +"y c #FEAF0B", +"z c #F39609", +"A c #D86203", +"B c #F08C0D", +"C c #FA9004", +"D c #F17F04", +"E c #E36D04", +"F c #E16F03", +"G c #EE8304", +"H c #F88C04", +"I c #DC6202", +"J c #E87204", +"K c #E66A01", +"L c #DC6001", +"M c #D15601", +"N c #DA5D01", +"O c #D25200", +"P c #DA5F00", +"Q c #BC3C00", +" .+ ", +" @# ", +" $% ", +" &*=- ", +" ;>,')!~{]^/( ", +"_:<[}|11234567", +" 890aaaaabcde ", +" fghiiijklm ", +" nopqpqrs ", +" tuvwxyzA ", +" BCDEFGHI ", +" JKL MNO ", +" P Q "}; + +const int iconPaneWidth = 16; +const int halfIPW = 8; + +KateIconBorder::KateIconBorder ( KateViewInternal* internalView, QWidget *parent ) + : QWidget(parent, "", Qt::WStaticContents | Qt::WRepaintNoErase | Qt::WResizeNoErase ) + , m_view( internalView->m_view ) + , m_doc( internalView->m_doc ) + , m_viewInternal( internalView ) + , m_iconBorderOn( false ) + , m_lineNumbersOn( false ) + , m_foldingMarkersOn( false ) + , m_dynWrapIndicatorsOn( false ) + , m_dynWrapIndicators( 0 ) + , m_cachedLNWidth( 0 ) + , m_maxCharWidth( 0 ) +{ + setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Minimum ) ); + + setBackgroundMode( NoBackground ); + + m_doc->setDescription( MarkInterface::markType01, i18n("Bookmark") ); + m_doc->setPixmap( MarkInterface::markType01, QPixmap((const char**)bookmark_xpm) ); + + updateFont(); +} + +void KateIconBorder::setIconBorderOn( bool enable ) +{ + if( enable == m_iconBorderOn ) + return; + + m_iconBorderOn = enable; + + updateGeometry(); + + QTimer::singleShot( 0, this, SLOT(update()) ); +} + +void KateIconBorder::setLineNumbersOn( bool enable ) +{ + if( enable == m_lineNumbersOn ) + return; + + m_lineNumbersOn = enable; + m_dynWrapIndicatorsOn = (m_dynWrapIndicators == 1) ? enable : m_dynWrapIndicators; + + updateGeometry(); + + QTimer::singleShot( 0, this, SLOT(update()) ); +} + +void KateIconBorder::setDynWrapIndicators( int state ) +{ + if (state == m_dynWrapIndicators ) + return; + + m_dynWrapIndicators = state; + m_dynWrapIndicatorsOn = (state == 1) ? m_lineNumbersOn : state; + + updateGeometry (); + + QTimer::singleShot( 0, this, SLOT(update()) ); +} + +void KateIconBorder::setFoldingMarkersOn( bool enable ) +{ + if( enable == m_foldingMarkersOn ) + return; + + m_foldingMarkersOn = enable; + + updateGeometry(); + + QTimer::singleShot( 0, this, SLOT(update()) ); +} + +QSize KateIconBorder::sizeHint() const +{ + int w = 0; + + if (m_iconBorderOn) + w += iconPaneWidth + 1; + + if (m_lineNumbersOn || (m_view->dynWordWrap() && m_dynWrapIndicatorsOn)) { + w += lineNumberWidth(); + } + + if (m_foldingMarkersOn) + w += iconPaneWidth; + + w += 4; + + return QSize( w, 0 ); +} + +// This function (re)calculates the maximum width of any of the digit characters (0 -> 9) +// for graceful handling of variable-width fonts as the linenumber font. +void KateIconBorder::updateFont() +{ + const QFontMetrics *fm = m_view->renderer()->config()->fontMetrics(); + m_maxCharWidth = 0; + // Loop to determine the widest numeric character in the current font. + // 48 is ascii '0' + for (int i = 48; i < 58; i++) { + int charWidth = fm->width( QChar(i) ); + m_maxCharWidth = kMax(m_maxCharWidth, charWidth); + } +} + +int KateIconBorder::lineNumberWidth() const +{ + int width = m_lineNumbersOn ? ((int)log10((double)(m_view->doc()->numLines())) + 1) * m_maxCharWidth + 4 : 0; + + if (m_view->dynWordWrap() && m_dynWrapIndicatorsOn) { + width = kMax(style().scrollBarExtent().width() + 4, width); + + if (m_cachedLNWidth != width || m_oldBackgroundColor != m_view->renderer()->config()->iconBarColor()) { + int w = style().scrollBarExtent().width(); + int h = m_view->renderer()->config()->fontMetrics()->height(); + + QSize newSize(w, h); + if ((m_arrow.size() != newSize || m_oldBackgroundColor != m_view->renderer()->config()->iconBarColor()) && !newSize.isEmpty()) { + m_arrow.resize(newSize); + + QPainter p(&m_arrow); + p.fillRect( 0, 0, w, h, m_view->renderer()->config()->iconBarColor() ); + + h = m_view->renderer()->config()->fontMetrics()->ascent(); + + p.setPen(m_view->renderer()->attribute(0)->textColor()); + p.drawLine(w/2, h/2, w/2, 0); +#if 1 + p.lineTo(w/4, h/4); + p.lineTo(0, 0); + p.lineTo(0, h/2); + p.lineTo(w/2, h-1); + p.lineTo(w*3/4, h-1); + p.lineTo(w-1, h*3/4); + p.lineTo(w*3/4, h/2); + p.lineTo(0, h/2); +#else + p.lineTo(w*3/4, h/4); + p.lineTo(w-1,0); + p.lineTo(w-1, h/2); + p.lineTo(w/2, h-1); + p.lineTo(w/4,h-1); + p.lineTo(0, h*3/4); + p.lineTo(w/4, h/2); + p.lineTo(w-1, h/2); +#endif + } + } + } + + return width; +} + +void KateIconBorder::paintEvent(QPaintEvent* e) +{ + paintBorder(e->rect().x(), e->rect().y(), e->rect().width(), e->rect().height()); +} + +void KateIconBorder::paintBorder (int /*x*/, int y, int /*width*/, int height) +{ + static QPixmap minus_px ((const char**)minus_xpm); + static QPixmap plus_px ((const char**)plus_xpm); + + uint h = m_view->renderer()->config()->fontStruct()->fontHeight; + uint startz = (y / h); + uint endz = startz + 1 + (height / h); + uint lineRangesSize = m_viewInternal->lineRanges.size(); + + // center the folding boxes + int m_px = (h - 11) / 2; + if (m_px < 0) + m_px = 0; + + int lnWidth( 0 ); + if ( m_lineNumbersOn || (m_view->dynWordWrap() && m_dynWrapIndicatorsOn) ) // avoid calculating unless needed ;-) + { + lnWidth = lineNumberWidth(); + if ( lnWidth != m_cachedLNWidth || m_oldBackgroundColor != m_view->renderer()->config()->iconBarColor() ) + { + // we went from n0 ->n9 lines or vice verca + // this causes an extra updateGeometry() first time the line numbers + // are displayed, but sizeHint() is supposed to be const so we can't set + // the cached value there. + m_cachedLNWidth = lnWidth; + m_oldBackgroundColor = m_view->renderer()->config()->iconBarColor(); + updateGeometry(); + update (); + return; + } + } + + int w( this->width() ); // sane value/calc only once + + QPainter p ( this ); + p.setFont ( *m_view->renderer()->config()->font() ); // for line numbers + // the line number color is for the line numbers, vertical separator lines + // and for for the code folding lines. + p.setPen ( m_view->renderer()->config()->lineNumberColor() ); + + KateLineInfo oldInfo; + if (startz < lineRangesSize) + { + if ((m_viewInternal->lineRanges[startz].line-1) < 0) + oldInfo.topLevel = true; + else + m_doc->lineInfo(&oldInfo,m_viewInternal->lineRanges[startz].line-1); + } + + for (uint z=startz; z <= endz; z++) + { + int y = h * z; + int realLine = -1; + + if (z < lineRangesSize) + realLine = m_viewInternal->lineRanges[z].line; + + int lnX ( 0 ); + + p.fillRect( 0, y, w-4, h, m_view->renderer()->config()->iconBarColor() ); + p.fillRect( w-4, y, 4, h, m_view->renderer()->config()->backgroundColor() ); + + // icon pane + if( m_iconBorderOn ) + { + p.drawLine(lnX+iconPaneWidth, y, lnX+iconPaneWidth, y+h); + + if( (realLine > -1) && (m_viewInternal->lineRanges[z].startCol == 0) ) + { + uint mrk ( m_doc->mark( realLine ) ); // call only once + + if ( mrk ) + { + for( uint bit = 0; bit < 32; bit++ ) + { + MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes)(1<<bit); + if( mrk & markType ) + { + QPixmap *px_mark (m_doc->markPixmap( markType )); + + if (px_mark) + { + // center the mark pixmap + int x_px = (iconPaneWidth - px_mark->width()) / 2; + if (x_px < 0) + x_px = 0; + + int y_px = (h - px_mark->height()) / 2; + if (y_px < 0) + y_px = 0; + + p.drawPixmap( lnX+x_px, y+y_px, *px_mark); + } + } + } + } + } + + lnX += iconPaneWidth + 1; + } + + // line number + if( m_lineNumbersOn || (m_view->dynWordWrap() && m_dynWrapIndicatorsOn) ) + { + lnX +=2; + + if (realLine > -1) + if (m_viewInternal->lineRanges[z].startCol == 0) { + if (m_lineNumbersOn) + p.drawText( lnX + 1, y, lnWidth-4, h, Qt::AlignRight|Qt::AlignVCenter, QString("%1").arg( realLine + 1 ) ); + } else if (m_view->dynWordWrap() && m_dynWrapIndicatorsOn) { + p.drawPixmap(lnX + lnWidth - m_arrow.width() - 4, y, m_arrow); + } + + lnX += lnWidth; + } + + // folding markers + if( m_foldingMarkersOn ) + { + if( realLine > -1 ) + { + KateLineInfo info; + m_doc->lineInfo(&info,realLine); + + if (!info.topLevel) + { + if (info.startsVisibleBlock && (m_viewInternal->lineRanges[z].startCol == 0)) + { + if (oldInfo.topLevel) + p.drawLine(lnX+halfIPW,y+m_px,lnX+halfIPW,y+h-1); + else + p.drawLine(lnX+halfIPW,y,lnX+halfIPW,y+h-1); + + p.drawPixmap(lnX+3,y+m_px,minus_px); + } + else if (info.startsInVisibleBlock) + { + if (m_viewInternal->lineRanges[z].startCol == 0) + { + if (oldInfo.topLevel) + p.drawLine(lnX+halfIPW,y+m_px,lnX+halfIPW,y+h-1); + else + p.drawLine(lnX+halfIPW,y,lnX+halfIPW,y+h-1); + + p.drawPixmap(lnX+3,y+m_px,plus_px); + } + else + { + p.drawLine(lnX+halfIPW,y,lnX+halfIPW,y+h-1); + } + + if (!m_viewInternal->lineRanges[z].wrap) + p.drawLine(lnX+halfIPW,y+h-1,lnX+iconPaneWidth-2,y+h-1); + } + else + { + p.drawLine(lnX+halfIPW,y,lnX+halfIPW,y+h-1); + + if (info.endsBlock && !m_viewInternal->lineRanges[z].wrap) + p.drawLine(lnX+halfIPW,y+h-1,lnX+iconPaneWidth-2,y+h-1); + } + } + + oldInfo = info; + } + + lnX += iconPaneWidth; + } + } +} + +KateIconBorder::BorderArea KateIconBorder::positionToArea( const QPoint& p ) const +{ + int x = 0; + if( m_iconBorderOn ) { + x += iconPaneWidth; + if( p.x() <= x ) + return IconBorder; + } + if( m_lineNumbersOn || m_dynWrapIndicators ) { + x += lineNumberWidth(); + if( p.x() <= x ) + return LineNumbers; + } + if( m_foldingMarkersOn ) { + x += iconPaneWidth; + if( p.x() <= x ) + return FoldingMarkers; + } + return None; +} + +void KateIconBorder::mousePressEvent( QMouseEvent* e ) +{ + m_lastClickedLine = m_viewInternal->yToKateLineRange(e->y()).line; + + if ( positionToArea( e->pos() ) != IconBorder ) + { + QMouseEvent forward( QEvent::MouseButtonPress, + QPoint( 0, e->y() ), e->button(), e->state() ); + m_viewInternal->mousePressEvent( &forward ); + } + e->accept(); +} + +void KateIconBorder::mouseMoveEvent( QMouseEvent* e ) +{ + if ( positionToArea( e->pos() ) != IconBorder ) + { + QMouseEvent forward( QEvent::MouseMove, + QPoint( 0, e->y() ), e->button(), e->state() ); + m_viewInternal->mouseMoveEvent( &forward ); + } +} + +void KateIconBorder::mouseReleaseEvent( QMouseEvent* e ) +{ + uint cursorOnLine = m_viewInternal->yToKateLineRange(e->y()).line; + + if (cursorOnLine == m_lastClickedLine && + cursorOnLine <= m_doc->lastLine() ) + { + BorderArea area = positionToArea( e->pos() ); + if( area == IconBorder) { + if (e->button() == LeftButton) { + if( m_doc->editableMarks() & KateViewConfig::global()->defaultMarkType() ) { + if( m_doc->mark( cursorOnLine ) & KateViewConfig::global()->defaultMarkType() ) + m_doc->removeMark( cursorOnLine, KateViewConfig::global()->defaultMarkType() ); + else + m_doc->addMark( cursorOnLine, KateViewConfig::global()->defaultMarkType() ); + } else { + showMarkMenu( cursorOnLine, QCursor::pos() ); + } + } + else + if (e->button() == RightButton) { + showMarkMenu( cursorOnLine, QCursor::pos() ); + } + } + + if ( area == FoldingMarkers) { + KateLineInfo info; + m_doc->lineInfo(&info,cursorOnLine); + if ((info.startsVisibleBlock) || (info.startsInVisibleBlock)) { + emit toggleRegionVisibility(cursorOnLine); + } + } + } + + QMouseEvent forward( QEvent::MouseButtonRelease, + QPoint( 0, e->y() ), e->button(), e->state() ); + m_viewInternal->mouseReleaseEvent( &forward ); +} + +void KateIconBorder::mouseDoubleClickEvent( QMouseEvent* e ) +{ + QMouseEvent forward( QEvent::MouseButtonDblClick, + QPoint( 0, e->y() ), e->button(), e->state() ); + m_viewInternal->mouseDoubleClickEvent( &forward ); +} + +void KateIconBorder::showMarkMenu( uint line, const QPoint& pos ) +{ + QPopupMenu markMenu; + QPopupMenu selectDefaultMark; + + typedef QValueVector<int> MarkTypeVector; + MarkTypeVector vec( 33 ); + int i=1; + + for( uint bit = 0; bit < 32; bit++ ) { + MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes)(1<<bit); + if( !(m_doc->editableMarks() & markType) ) + continue; + + if( !m_doc->markDescription( markType ).isEmpty() ) { + markMenu.insertItem( m_doc->markDescription( markType ), i ); + selectDefaultMark.insertItem( m_doc->markDescription( markType ), i+100); + } else { + markMenu.insertItem( i18n("Mark Type %1").arg( bit + 1 ), i ); + selectDefaultMark.insertItem( i18n("Mark Type %1").arg( bit + 1 ), i+100); + } + + if( m_doc->mark( line ) & markType ) + markMenu.setItemChecked( i, true ); + + if( markType & KateViewConfig::global()->defaultMarkType() ) + selectDefaultMark.setItemChecked( i+100, true ); + + vec[i++] = markType; + } + + if( markMenu.count() == 0 ) + return; + + if( markMenu.count() > 1 ) + markMenu.insertItem( i18n("Set Default Mark Type" ), &selectDefaultMark); + + int result = markMenu.exec( pos ); + if( result <= 0 ) + return; + + if ( result > 100) + { + KateViewConfig::global()->setDefaultMarkType (vec[result-100]); + // flush config, otherwise it isn't nessecarily done + KConfig *config = kapp->config(); + config->setGroup("Kate View Defaults"); + KateViewConfig::global()->writeConfig( config ); + } + else + { + MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes) vec[result]; + if( m_doc->mark( line ) & markType ) { + m_doc->removeMark( line, markType ); + } else { + m_doc->addMark( line, markType ); + } + } +} +//END KateIconBorder + +KateViewEncodingAction::KateViewEncodingAction(KateDocument *_doc, KateView *_view, const QString& text, QObject* parent, const char* name) + : KActionMenu (text, parent, name), doc(_doc), view (_view) +{ + connect(popupMenu(),SIGNAL(aboutToShow()),this,SLOT(slotAboutToShow())); +} + +void KateViewEncodingAction::slotAboutToShow() +{ + QStringList modes (KGlobal::charsets()->descriptiveEncodingNames()); + + popupMenu()->clear (); + for (uint z=0; z<modes.size(); ++z) + { + popupMenu()->insertItem ( modes[z], this, SLOT(setMode(int)), 0, z); + + bool found = false; + QTextCodec *codecForEnc = KGlobal::charsets()->codecForName(KGlobal::charsets()->encodingForName(modes[z]), found); + + if (found && codecForEnc) + { + if (codecForEnc->name() == doc->config()->codec()->name()) + popupMenu()->setItemChecked (z, true); + } + } +} + +void KateViewEncodingAction::setMode (int mode) +{ + QStringList modes (KGlobal::charsets()->descriptiveEncodingNames()); + doc->config()->setEncoding( KGlobal::charsets()->encodingForName( modes[mode] ) ); + // now we don't want the encoding changed again unless the user does so using the menu. + doc->setEncodingSticky( true ); + doc->reloadFile(); +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateviewhelpers.h b/kate/part/kateviewhelpers.h new file mode 100644 index 000000000..4687365f6 --- /dev/null +++ b/kate/part/kateviewhelpers.h @@ -0,0 +1,207 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002 John Firebaugh <jfirebaugh@kde.org> + Copyright (C) 2001 Anders Lund <anders@alweb.dk> + Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __KATE_VIEW_HELPERS_H__ +#define __KATE_VIEW_HELPERS_H__ + +#include <kaction.h> +#include <klineedit.h> + +#include <qwidget.h> +#include <qpixmap.h> +#include <qcolor.h> +#include <qscrollbar.h> +#include <qintdict.h> + +class KateDocument; +class KateView; +class KateViewInternal; + +namespace Kate { + class Command; +} + +/** + * This class is required because QScrollBar's sliderMoved() signal is + * really supposed to be a sliderDragged() signal... so this way we can capture + * MMB slider moves as well + * + * Also, it adds some usefull indicators on the scrollbar. + */ +class KateScrollBar : public QScrollBar +{ + Q_OBJECT + + public: + KateScrollBar(Orientation orientation, class KateViewInternal *parent, const char* name = 0L); + + inline bool showMarks() { return m_showMarks; }; + inline void setShowMarks(bool b) { m_showMarks = b; update(); }; + + signals: + void sliderMMBMoved(int value); + + protected: + virtual void mousePressEvent(QMouseEvent* e); + virtual void mouseReleaseEvent(QMouseEvent* e); + virtual void mouseMoveEvent (QMouseEvent* e); + virtual void paintEvent(QPaintEvent *); + virtual void resizeEvent(QResizeEvent *); + virtual void styleChange(QStyle &oldStyle); + virtual void valueChange(); + virtual void rangeChange(); + + protected slots: + void sliderMaybeMoved(int value); + void marksChanged(); + + private: + void redrawMarks(); + void recomputeMarksPositions(bool forceFullUpdate = false); + void watchScrollBarSize(); + + bool m_middleMouseDown; + + KateView *m_view; + KateDocument *m_doc; + class KateViewInternal *m_viewInternal; + + int m_topMargin; + int m_bottomMargin; + uint m_savVisibleLines; + + QIntDict<QColor> m_lines; + + bool m_showMarks; +}; + +class KateCmdLine : public KLineEdit +{ + Q_OBJECT + + public: + KateCmdLine (KateView *view); + + private slots: + void slotReturnPressed ( const QString& cmd ); + void hideMe (); + + protected: + void focusInEvent ( QFocusEvent *ev ); + void keyPressEvent( QKeyEvent *ev ); + + private: + void fromHistory( bool up ); + KateView *m_view; + bool m_msgMode; + QString m_oldText; + uint m_histpos; ///< position in the history + uint m_cmdend; ///< the point where a command ends in the text, if we have a valid one. + Kate::Command *m_command; ///< For completing flags/args and interactiveness + class KCompletion *m_oldCompletionObject; ///< save while completing command args. + class KateCmdLnWhatsThis *m_help; +}; + +class KateIconBorder : public QWidget +{ + Q_OBJECT + + public: + KateIconBorder( KateViewInternal* internalView, QWidget *parent ); + + // VERY IMPORTANT ;) + virtual QSize sizeHint() const; + + void updateFont(); + int lineNumberWidth() const; + + void setIconBorderOn( bool enable ); + void setLineNumbersOn( bool enable ); + void setDynWrapIndicators(int state ); + int dynWrapIndicators() const { return m_dynWrapIndicators; } + bool dynWrapIndicatorsOn() const { return m_dynWrapIndicatorsOn; } + void setFoldingMarkersOn( bool enable ); + void toggleIconBorder() { setIconBorderOn( !iconBorderOn() ); } + void toggleLineNumbers() { setLineNumbersOn( !lineNumbersOn() ); } + void toggleFoldingMarkers() { setFoldingMarkersOn( !foldingMarkersOn() ); } + bool iconBorderOn() const { return m_iconBorderOn; } + bool lineNumbersOn() const { return m_lineNumbersOn; } + bool foldingMarkersOn() const { return m_foldingMarkersOn; } + + enum BorderArea { None, LineNumbers, IconBorder, FoldingMarkers }; + BorderArea positionToArea( const QPoint& ) const; + + signals: + void toggleRegionVisibility( unsigned int ); + + private: + void paintEvent( QPaintEvent* ); + void paintBorder (int x, int y, int width, int height); + + void mousePressEvent( QMouseEvent* ); + void mouseMoveEvent( QMouseEvent* ); + void mouseReleaseEvent( QMouseEvent* ); + void mouseDoubleClickEvent( QMouseEvent* ); + + void showMarkMenu( uint line, const QPoint& pos ); + + KateView *m_view; + KateDocument *m_doc; + KateViewInternal *m_viewInternal; + + bool m_iconBorderOn:1; + bool m_lineNumbersOn:1; + bool m_foldingMarkersOn:1; + bool m_dynWrapIndicatorsOn:1; + int m_dynWrapIndicators; + + uint m_lastClickedLine; + + int m_cachedLNWidth; + + int m_maxCharWidth; + + mutable QPixmap m_arrow; + mutable QColor m_oldBackgroundColor; +}; + +class KateViewEncodingAction : public KActionMenu +{ + Q_OBJECT + + public: + KateViewEncodingAction(KateDocument *_doc, KateView *_view, const QString& text, QObject* parent = 0, const char* name = 0); + + ~KateViewEncodingAction(){;}; + + private: + KateDocument* doc; + KateView *view; + + public slots: + void slotAboutToShow(); + + private slots: + void setMode (int mode); +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateviewinternal.cpp b/kate/part/kateviewinternal.cpp new file mode 100644 index 000000000..96edc1a9c --- /dev/null +++ b/kate/part/kateviewinternal.cpp @@ -0,0 +1,3496 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002 John Firebaugh <jfirebaugh@kde.org> + Copyright (C) 2002 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2002,2003 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2002,2003 Hamish Rodda <rodda@kde.org> + Copyright (C) 2003 Anakim Border <aborder@sources.sourceforge.net> + + Based on: + KWriteView : Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kateviewinternal.h" +#include "kateviewinternal.moc" + +#include "kateview.h" +#include "katecodefoldinghelpers.h" +#include "kateviewhelpers.h" +#include "katehighlight.h" +#include "katesupercursor.h" +#include "katerenderer.h" +#include "katecodecompletion.h" +#include "kateconfig.h" + +#include <kcursor.h> +#include <kdebug.h> +#include <kapplication.h> +#include <kglobalsettings.h> +#include <kurldrag.h> + +#include <qstyle.h> +#include <qdragobject.h> +#include <qpopupmenu.h> +#include <qdropsite.h> +#include <qpainter.h> +#include <qlayout.h> +#include <qclipboard.h> +#include <qpixmap.h> +#include <qvbox.h> + +KateViewInternal::KateViewInternal(KateView *view, KateDocument *doc) + : QWidget (view, "", Qt::WStaticContents | Qt::WRepaintNoErase | Qt::WResizeNoErase ) + , editSessionNumber (0) + , editIsRunning (false) + , m_view (view) + , m_doc (doc) + , cursor (doc, true, 0, 0, this) + , possibleTripleClick (false) + , m_dummy (0) + , m_startPos(doc, true, 0,0) + , m_madeVisible(false) + , m_shiftKeyPressed (false) + , m_autoCenterLines (false) + , m_selChangedByUser (false) + , selectAnchor (-1, -1) + , m_selectionMode( Default ) + , m_preserveMaxX(false) + , m_currentMaxX(0) + , m_usePlainLines(false) + , m_updatingView(true) + , m_cachedMaxStartPos(-1, -1) + , m_dragScrollTimer(this) + , m_scrollTimer (this) + , m_cursorTimer (this) + , m_textHintTimer (this) + , m_textHintEnabled(false) + , m_textHintMouseX(-1) + , m_textHintMouseY(-1) + , m_imPreeditStartLine(0) + , m_imPreeditStart(0) + , m_imPreeditLength(0) + , m_imPreeditSelStart(0) +{ + setMinimumSize (0,0); + + // cursor + cursor.setMoveOnInsert (true); + + // invalidate selStartCached, or keyb selection is screwed initially + selStartCached.setLine( -1 ); + // + // scrollbar for lines + // + m_lineScroll = new KateScrollBar(QScrollBar::Vertical, this); + m_lineScroll->show(); + m_lineScroll->setTracking (true); + + m_lineLayout = new QVBoxLayout(); + m_colLayout = new QHBoxLayout(); + + m_colLayout->addWidget(m_lineScroll); + m_lineLayout->addLayout(m_colLayout); + + // bottom corner box + m_dummy = new QWidget(m_view); + m_dummy->setFixedHeight(style().scrollBarExtent().width()); + + if (m_view->dynWordWrap()) + m_dummy->hide(); + else + m_dummy->show(); + + m_lineLayout->addWidget(m_dummy); + + // Hijack the line scroller's controls, so we can scroll nicely for word-wrap + connect(m_lineScroll, SIGNAL(prevPage()), SLOT(scrollPrevPage())); + connect(m_lineScroll, SIGNAL(nextPage()), SLOT(scrollNextPage())); + + connect(m_lineScroll, SIGNAL(prevLine()), SLOT(scrollPrevLine())); + connect(m_lineScroll, SIGNAL(nextLine()), SLOT(scrollNextLine())); + + connect(m_lineScroll, SIGNAL(sliderMoved(int)), SLOT(scrollLines(int))); + connect(m_lineScroll, SIGNAL(sliderMMBMoved(int)), SLOT(scrollLines(int))); + + // catch wheel events, completing the hijack + m_lineScroll->installEventFilter(this); + + // + // scrollbar for columns + // + m_columnScroll = new QScrollBar(QScrollBar::Horizontal,m_view); + + // hide the column scrollbar in the dynamic word wrap mode + if (m_view->dynWordWrap()) + m_columnScroll->hide(); + else + m_columnScroll->show(); + + m_columnScroll->setTracking(true); + m_startX = 0; + + connect( m_columnScroll, SIGNAL( valueChanged (int) ), + this, SLOT( scrollColumns (int) ) ); + + // + // iconborder ;) + // + leftBorder = new KateIconBorder( this, m_view ); + leftBorder->show (); + + connect( leftBorder, SIGNAL(toggleRegionVisibility(unsigned int)), + m_doc->foldingTree(), SLOT(toggleRegionVisibility(unsigned int))); + + connect( doc->foldingTree(), SIGNAL(regionVisibilityChangedAt(unsigned int)), + this, SLOT(slotRegionVisibilityChangedAt(unsigned int))); + connect( doc, SIGNAL(codeFoldingUpdated()), + this, SLOT(slotCodeFoldingChanged()) ); + + displayCursor.setPos(0, 0); + cursor.setPos(0, 0); + cXPos = 0; + + setAcceptDrops( true ); + setBackgroundMode( NoBackground ); + + // event filter + installEventFilter(this); + + // im + setInputMethodEnabled(true); + + // set initial cursor + setCursor( KCursor::ibeamCursor() ); + m_mouseCursor = IbeamCursor; + + // call mouseMoveEvent also if no mouse button is pressed + setMouseTracking(true); + + dragInfo.state = diNone; + + // timers + connect( &m_dragScrollTimer, SIGNAL( timeout() ), + this, SLOT( doDragScroll() ) ); + + connect( &m_scrollTimer, SIGNAL( timeout() ), + this, SLOT( scrollTimeout() ) ); + + connect( &m_cursorTimer, SIGNAL( timeout() ), + this, SLOT( cursorTimeout() ) ); + + connect( &m_textHintTimer, SIGNAL( timeout() ), + this, SLOT( textHintTimeout() ) ); + + // selection changed to set anchor + connect( m_view, SIGNAL( selectionChanged() ), + this, SLOT( viewSelectionChanged() ) ); + + +// this is a work arround for RTL desktops +// should be changed in kde 3.3 +// BTW: this comment has been "ported" from 3.1.X tree +// any hacker with BIDI knowlege is welcomed to fix kate problems :) + if (QApplication::reverseLayout()){ + m_view->m_grid->addMultiCellWidget(leftBorder, 0, 1, 2, 2); + m_view->m_grid->addMultiCellWidget(m_columnScroll, 1, 1, 0, 1); + m_view->m_grid->addMultiCellLayout(m_lineLayout, 0, 0, 0, 0); + } + else{ + m_view->m_grid->addMultiCellLayout(m_lineLayout, 0, 1, 2, 2); + m_view->m_grid->addMultiCellWidget(m_columnScroll, 1, 1, 0, 1); + m_view->m_grid->addWidget(leftBorder, 0, 0); + } + + updateView (); +} + +KateViewInternal::~KateViewInternal () +{ +} + +void KateViewInternal::prepareForDynWrapChange() +{ + // Which is the current view line? + m_wrapChangeViewLine = displayViewLine(displayCursor, true); +} + +void KateViewInternal::dynWrapChanged() +{ + if (m_view->dynWordWrap()) + { + m_columnScroll->hide(); + m_dummy->hide (); + } + else + { + m_columnScroll->show(); + m_dummy->show (); + } + + tagAll(); + updateView(); + + if (m_view->dynWordWrap()) + scrollColumns(0); + + // Determine where the cursor should be to get the cursor on the same view line + if (m_wrapChangeViewLine != -1) { + KateTextCursor newStart = viewLineOffset(displayCursor, -m_wrapChangeViewLine); + makeVisible(newStart, newStart.col(), true); + } else { + update(); + } +} + +KateTextCursor KateViewInternal::endPos() const +{ + int viewLines = linesDisplayed() - 1; + + if (viewLines < 0) { + kdDebug(13030) << "WARNING: viewLines wrong!" << endl; + viewLines = 0; + } + + // Check to make sure that lineRanges isn't invalid + if (!lineRanges.count() || lineRanges[0].line == -1 || viewLines >= (int)lineRanges.count()) { + // Switch off use of the cache + return KateTextCursor(m_doc->numVisLines() - 1, m_doc->lineLength(m_doc->getRealLine(m_doc->numVisLines() - 1))); + } + + for (int i = viewLines; i >= 0; i--) { + KateLineRange& thisRange = lineRanges[i]; + + if (thisRange.line == -1) continue; + + if (thisRange.virtualLine >= (int)m_doc->numVisLines()) { + // Cache is too out of date + return KateTextCursor(m_doc->numVisLines() - 1, m_doc->lineLength(m_doc->getRealLine(m_doc->numVisLines() - 1))); + } + + return KateTextCursor(thisRange.virtualLine, thisRange.wrap ? thisRange.endCol - 1 : thisRange.endCol); + } + + Q_ASSERT(false); + kdDebug(13030) << "WARNING: could not find a lineRange at all" << endl; + return KateTextCursor(-1, -1); +} + +uint KateViewInternal::endLine() const +{ + return endPos().line(); +} + +KateLineRange KateViewInternal::yToKateLineRange(uint y) const +{ + uint range = y / m_view->renderer()->fontHeight(); + + // lineRanges is always bigger than 0, after the initial updateView call + if (range >= lineRanges.size()) + return lineRanges[lineRanges.size()-1]; + + return lineRanges[range]; +} + +int KateViewInternal::lineToY(uint viewLine) const +{ + return (viewLine-startLine()) * m_view->renderer()->fontHeight(); +} + +void KateViewInternal::slotIncFontSizes() +{ + m_view->renderer()->increaseFontSizes(); +} + +void KateViewInternal::slotDecFontSizes() +{ + m_view->renderer()->decreaseFontSizes(); +} + +/** + * Line is the real line number to scroll to. + */ +void KateViewInternal::scrollLines ( int line ) +{ + KateTextCursor newPos(line, 0); + scrollPos(newPos); +} + +// This can scroll less than one true line +void KateViewInternal::scrollViewLines(int offset) +{ + KateTextCursor c = viewLineOffset(startPos(), offset); + scrollPos(c); + + m_lineScroll->blockSignals(true); + m_lineScroll->setValue(startLine()); + m_lineScroll->blockSignals(false); +} + +void KateViewInternal::scrollNextPage() +{ + scrollViewLines(kMax( (int)linesDisplayed() - 1, 0 )); +} + +void KateViewInternal::scrollPrevPage() +{ + scrollViewLines(-kMax( (int)linesDisplayed() - 1, 0 )); +} + +void KateViewInternal::scrollPrevLine() +{ + scrollViewLines(-1); +} + +void KateViewInternal::scrollNextLine() +{ + scrollViewLines(1); +} + +KateTextCursor KateViewInternal::maxStartPos(bool changed) +{ + m_usePlainLines = true; + + if (m_cachedMaxStartPos.line() == -1 || changed) + { + KateTextCursor end(m_doc->numVisLines() - 1, m_doc->lineLength(m_doc->getRealLine(m_doc->numVisLines() - 1))); + + m_cachedMaxStartPos = viewLineOffset(end, -((int)linesDisplayed() - 1)); + } + + m_usePlainLines = false; + + return m_cachedMaxStartPos; +} + +// c is a virtual cursor +void KateViewInternal::scrollPos(KateTextCursor& c, bool force, bool calledExternally) +{ + if (!force && ((!m_view->dynWordWrap() && c.line() == (int)startLine()) || c == startPos())) + return; + + if (c.line() < 0) + c.setLine(0); + + KateTextCursor limit = maxStartPos(); + if (c > limit) { + c = limit; + + // Re-check we're not just scrolling to the same place + if (!force && ((!m_view->dynWordWrap() && c.line() == (int)startLine()) || c == startPos())) + return; + } + + int viewLinesScrolled = 0; + + // only calculate if this is really used and usefull, could be wrong here, please recheck + // for larger scrolls this makes 2-4 seconds difference on my xeon with dyn. word wrap on + // try to get it really working ;) + bool viewLinesScrolledUsable = !force + && (c.line() >= (int)startLine()-(int)linesDisplayed()-1) + && (c.line() <= (int)endLine()+(int)linesDisplayed()+1); + + if (viewLinesScrolledUsable) + viewLinesScrolled = displayViewLine(c); + + m_startPos.setPos(c); + + // set false here but reversed if we return to makeVisible + m_madeVisible = false; + + if (viewLinesScrolledUsable) + { + int lines = linesDisplayed(); + if ((int)m_doc->numVisLines() < lines) { + KateTextCursor end(m_doc->numVisLines() - 1, m_doc->lineLength(m_doc->getRealLine(m_doc->numVisLines() - 1))); + lines = kMin((int)linesDisplayed(), displayViewLine(end) + 1); + } + + Q_ASSERT(lines >= 0); + + if (!calledExternally && QABS(viewLinesScrolled) < lines) + { + updateView(false, viewLinesScrolled); + + int scrollHeight = -(viewLinesScrolled * (int)m_view->renderer()->fontHeight()); + int scrollbarWidth = style().scrollBarExtent().width(); + + // + // updates are for working around the scrollbar leaving blocks in the view + // + scroll(0, scrollHeight); + update(0, height()+scrollHeight-scrollbarWidth, width(), 2*scrollbarWidth); + + leftBorder->scroll(0, scrollHeight); + leftBorder->update(0, leftBorder->height()+scrollHeight-scrollbarWidth, leftBorder->width(), 2*scrollbarWidth); + + return; + } + } + + updateView(); + update(); + leftBorder->update(); +} + +void KateViewInternal::scrollColumns ( int x ) +{ + if (x == m_startX) + return; + + if (x < 0) + x = 0; + + int dx = m_startX - x; + m_startX = x; + + if (QABS(dx) < width()) + scroll(dx, 0); + else + update(); + + m_columnScroll->blockSignals(true); + m_columnScroll->setValue(m_startX); + m_columnScroll->blockSignals(false); +} + +// If changed is true, the lines that have been set dirty have been updated. +void KateViewInternal::updateView(bool changed, int viewLinesScrolled) +{ + m_updatingView = true; + + uint contentLines = m_doc->visibleLines(); + + m_lineScroll->blockSignals(true); + + KateTextCursor maxStart = maxStartPos(changed); + int maxLineScrollRange = maxStart.line(); + if (m_view->dynWordWrap() && maxStart.col() != 0) + maxLineScrollRange++; + m_lineScroll->setRange(0, maxLineScrollRange); + + m_lineScroll->setValue(startPos().line()); + m_lineScroll->setSteps(1, height() / m_view->renderer()->fontHeight()); + m_lineScroll->blockSignals(false); + + uint oldSize = lineRanges.size (); + uint newSize = (height() / m_view->renderer()->fontHeight()) + 1; + if (oldSize != newSize) { + lineRanges.resize((height() / m_view->renderer()->fontHeight()) + 1); + if (newSize > oldSize) { + static KateLineRange blank; + for (uint i = oldSize; i < newSize; i++) { + lineRanges[i] = blank; + } + } + } + + if (oldSize < lineRanges.size ()) + { + for (uint i=oldSize; i < lineRanges.size(); i++) + lineRanges[i].dirty = true; + } + + // Move the lineRanges data if we've just scrolled... + if (viewLinesScrolled != 0) { + // loop backwards if we've just scrolled up... + bool forwards = viewLinesScrolled >= 0 ? true : false; + for (uint z = forwards ? 0 : lineRanges.count() - 1; z < lineRanges.count(); forwards ? z++ : z--) { + uint oldZ = z + viewLinesScrolled; + if (oldZ < lineRanges.count()) { + lineRanges[z] = lineRanges[oldZ]; + } else { + lineRanges[z].dirty = true; + } + } + } + + if (m_view->dynWordWrap()) + { + KateTextCursor realStart = startPos(); + realStart.setLine(m_doc->getRealLine(realStart.line())); + + KateLineRange startRange = range(realStart); + uint line = startRange.virtualLine; + int realLine = startRange.line; + uint oldLine = line; + int startCol = startRange.startCol; + int startX = startRange.startX; + int endX = startRange.startX; + int shiftX = startRange.startCol ? startRange.shiftX : 0; + bool wrap = false; + int newViewLine = startRange.viewLine; + // z is the current display view line + KateTextLine::Ptr text = textLine(realLine); + + bool alreadyDirty = false; + + for (uint z = 0; z < lineRanges.size(); z++) + { + if (oldLine != line) { + realLine = (int)m_doc->getRealLine(line); + + if (z) + lineRanges[z-1].startsInvisibleBlock = (realLine != lineRanges[z-1].line + 1); + + text = textLine(realLine); + startCol = 0; + startX = 0; + endX = 0; + shiftX = 0; + newViewLine = 0; + oldLine = line; + } + + if (line >= contentLines || !text) + { + if (lineRanges[z].line != -1) + lineRanges[z].dirty = true; + + lineRanges[z].clear(); + + line++; + } + else + { + if (lineRanges[z].line != realLine || lineRanges[z].startCol != startCol) + alreadyDirty = lineRanges[z].dirty = true; + + if (lineRanges[z].dirty || changed || alreadyDirty) { + alreadyDirty = true; + + lineRanges[z].virtualLine = line; + lineRanges[z].line = realLine; + lineRanges[z].startsInvisibleBlock = false; + + int tempEndX = 0; + + int endCol = m_view->renderer()->textWidth(text, startCol, width() - shiftX, &wrap, &tempEndX); + + endX += tempEndX; + + if (wrap) + { + if (m_view->config()->dynWordWrapAlignIndent() > 0) + { + if (startX == 0) + { + int pos = text->nextNonSpaceChar(0); + + if (pos > 0) + shiftX = m_view->renderer()->textWidth(text, pos); + + if (shiftX > ((double)width() / 100 * m_view->config()->dynWordWrapAlignIndent())) + shiftX = 0; + } + } + + if ((lineRanges[z].startX != startX) || (lineRanges[z].endX != endX) || + (lineRanges[z].startCol != startCol) || (lineRanges[z].endCol != endCol) || + (lineRanges[z].shiftX != shiftX)) + lineRanges[z].dirty = true; + + lineRanges[z].startCol = startCol; + lineRanges[z].endCol = endCol; + lineRanges[z].startX = startX; + lineRanges[z].endX = endX; + lineRanges[z].viewLine = newViewLine; + lineRanges[z].wrap = true; + + startCol = endCol; + startX = endX; + } + else + { + if ((lineRanges[z].startX != startX) || (lineRanges[z].endX != endX) || + (lineRanges[z].startCol != startCol) || (lineRanges[z].endCol != endCol)) + lineRanges[z].dirty = true; + + lineRanges[z].startCol = startCol; + lineRanges[z].endCol = endCol; + lineRanges[z].startX = startX; + lineRanges[z].endX = endX; + lineRanges[z].viewLine = newViewLine; + lineRanges[z].wrap = false; + + line++; + } + + lineRanges[z].shiftX = shiftX; + + } else { + // The cached data is still intact + if (lineRanges[z].wrap) { + startCol = lineRanges[z].endCol; + startX = lineRanges[z].endX; + endX = lineRanges[z].endX; + } else { + line++; + } + shiftX = lineRanges[z].shiftX; + } + } + newViewLine++; + } + } + else + { + uint z = 0; + + for(; (z + startLine() < contentLines) && (z < lineRanges.size()); z++) + { + if (lineRanges[z].dirty || lineRanges[z].line != (int)m_doc->getRealLine(z + startLine())) { + lineRanges[z].dirty = true; + + lineRanges[z].line = m_doc->getRealLine( z + startLine() ); + if (z) + lineRanges[z-1].startsInvisibleBlock = (lineRanges[z].line != lineRanges[z-1].line + 1); + + lineRanges[z].virtualLine = z + startLine(); + lineRanges[z].startCol = 0; + lineRanges[z].endCol = m_doc->lineLength(lineRanges[z].line); + lineRanges[z].startX = 0; + lineRanges[z].endX = m_view->renderer()->textWidth( textLine( lineRanges[z].line ), -1 ); + lineRanges[z].shiftX = 0; + lineRanges[z].viewLine = 0; + lineRanges[z].wrap = false; + } + else if (z && lineRanges[z-1].dirty) + { + lineRanges[z-1].startsInvisibleBlock = (lineRanges[z].line != lineRanges[z-1].line + 1); + } + } + + for (; z < lineRanges.size(); z++) + { + if (lineRanges[z].line != -1) + lineRanges[z].dirty = true; + + lineRanges[z].clear(); + } + + int max = maxLen(startLine()) - width(); + if (max < 0) + max = 0; + + // if we lose the ability to scroll horizontally, move view to the far-left + if (max == 0) + { + scrollColumns(0); + } + + m_columnScroll->blockSignals(true); + + // disable scrollbar + m_columnScroll->setDisabled (max == 0); + + m_columnScroll->setRange(0, max); + + m_columnScroll->setValue(m_startX); + + // Approximate linescroll + m_columnScroll->setSteps(m_view->renderer()->config()->fontMetrics()->width('a'), width()); + + m_columnScroll->blockSignals(false); + } + + m_updatingView = false; + + if (changed) + paintText(0, 0, width(), height(), true); +} + +void KateViewInternal::paintText (int x, int y, int width, int height, bool paintOnlyDirty) +{ + //kdDebug() << k_funcinfo << x << " " << y << " " << width << " " << height << " " << paintOnlyDirty << endl; + int xStart = startX() + x; + int xEnd = xStart + width; + uint h = m_view->renderer()->fontHeight(); + uint startz = (y / h); + uint endz = startz + 1 + (height / h); + uint lineRangesSize = lineRanges.size(); + + static QPixmap drawBuffer; + + if (drawBuffer.width() < KateViewInternal::width() || drawBuffer.height() < (int)h) + drawBuffer.resize(KateViewInternal::width(), (int)h); + + if (drawBuffer.isNull()) + return; + + QPainter paint(this); + QPainter paintDrawBuffer(&drawBuffer); + + // TODO put in the proper places + m_view->renderer()->setCaretStyle(m_view->isOverwriteMode() ? KateRenderer::Replace : KateRenderer::Insert); + m_view->renderer()->setShowTabs(m_doc->configFlags() & KateDocument::cfShowTabs); + + for (uint z=startz; z <= endz; z++) + { + if ( (z >= lineRangesSize) || ((lineRanges[z].line == -1) && (!paintOnlyDirty || lineRanges[z].dirty)) ) + { + if (!(z >= lineRangesSize)) + lineRanges[z].dirty = false; + + paint.fillRect( x, z * h, width, h, m_view->renderer()->config()->backgroundColor() ); + } + else if (!paintOnlyDirty || lineRanges[z].dirty) + { + lineRanges[z].dirty = false; + + m_view->renderer()->paintTextLine(paintDrawBuffer, &lineRanges[z], xStart, xEnd, &cursor, &bm); + + paint.drawPixmap (x, z * h, drawBuffer, 0, 0, width, h); + } + } +} + +/** + * this function ensures a certain location is visible on the screen. + * if endCol is -1, ignore making the columns visible. + */ +void KateViewInternal::makeVisible (const KateTextCursor& c, uint endCol, bool force, bool center, bool calledExternally) +{ + //kdDebug() << "MakeVisible start [" << startPos().line << "," << startPos().col << "] end [" << endPos().line << "," << endPos().col << "] -> request: [" << c.line << "," << c.col << "]" <<endl;// , new start [" << scroll.line << "," << scroll.col << "] lines " << (linesDisplayed() - 1) << " height " << height() << endl; + // if the line is in a folded region, unfold all the way up + //if ( m_doc->foldingTree()->findNodeForLine( c.line )->visible ) + // kdDebug()<<"line ("<<c.line<<") should be visible"<<endl; + + if ( force ) + { + KateTextCursor scroll = c; + scrollPos(scroll, force, calledExternally); + } + else if (center && (c < startPos() || c > endPos())) + { + KateTextCursor scroll = viewLineOffset(c, -int(linesDisplayed()) / 2); + scrollPos(scroll, false, calledExternally); + } + else if ( c > viewLineOffset(endPos(), -m_minLinesVisible) ) + { + KateTextCursor scroll = viewLineOffset(c, -((int)linesDisplayed() - m_minLinesVisible - 1)); + scrollPos(scroll, false, calledExternally); + } + else if ( c < viewLineOffset(startPos(), m_minLinesVisible) ) + { + KateTextCursor scroll = viewLineOffset(c, -m_minLinesVisible); + scrollPos(scroll, false, calledExternally); + } + else + { + // Check to see that we're not showing blank lines + KateTextCursor max = maxStartPos(); + if (startPos() > max) { + scrollPos(max, max.col(), calledExternally); + } + } + + if (!m_view->dynWordWrap() && endCol != (uint)-1) + { + int sX = (int)m_view->renderer()->textWidth (textLine( m_doc->getRealLine( c.line() ) ), c.col() ); + + int sXborder = sX-8; + if (sXborder < 0) + sXborder = 0; + + if (sX < m_startX) + scrollColumns (sXborder); + else if (sX > m_startX + width()) + scrollColumns (sX - width() + 8); + } + + m_madeVisible = !force; +} + +void KateViewInternal::slotRegionVisibilityChangedAt(unsigned int) +{ + kdDebug(13030) << "slotRegionVisibilityChangedAt()" << endl; + m_cachedMaxStartPos.setLine(-1); + KateTextCursor max = maxStartPos(); + if (startPos() > max) + scrollPos(max); + + updateView(); + update(); + leftBorder->update(); +} + +void KateViewInternal::slotCodeFoldingChanged() +{ + leftBorder->update(); +} + +void KateViewInternal::slotRegionBeginEndAddedRemoved(unsigned int) +{ + kdDebug(13030) << "slotRegionBeginEndAddedRemoved()" << endl; + // FIXME: performance problem + leftBorder->update(); +} + +void KateViewInternal::showEvent ( QShowEvent *e ) +{ + updateView (); + + QWidget::showEvent (e); +} + +uint KateViewInternal::linesDisplayed() const +{ + int h = height(); + int fh = m_view->renderer()->fontHeight(); + + return (h - (h % fh)) / fh; +} + +QPoint KateViewInternal::cursorCoordinates() +{ + int viewLine = displayViewLine(displayCursor, true); + + if (viewLine == -1) + return QPoint(-1, -1); + + uint y = viewLine * m_view->renderer()->fontHeight(); + uint x = cXPos - m_startX - lineRanges[viewLine].startX + leftBorder->width() + lineRanges[viewLine].xOffset(); + + return QPoint(x, y); +} + +void KateViewInternal::updateMicroFocusHint() +{ + int line = displayViewLine(displayCursor, true); + /* Check for hasFocus() to avoid crashes in QXIMInputContext as in bug #131266. + This is only a workaround until somebody can find the real reason of the crash + (probably it's in Qt). */ + if (line == -1 || !hasFocus()) + return; + + KateRenderer *renderer = m_view->renderer(); + + // Cursor placement code is changed for Asian input method that + // shows candidate window. This behavior is same as Qt/E 2.3.7 + // which supports Asian input methods. Asian input methods need + // start point of IM selection text to place candidate window as + // adjacent to the selection text. + uint preeditStrLen = renderer->textWidth(textLine(m_imPreeditStartLine), cursor.col()) - renderer->textWidth(textLine(m_imPreeditStartLine), m_imPreeditSelStart); + uint x = cXPos - m_startX - lineRanges[line].startX + lineRanges[line].xOffset() - preeditStrLen; + uint y = line * renderer->fontHeight(); + + setMicroFocusHint(x, y, 0, renderer->fontHeight()); +} + +void KateViewInternal::doReturn() +{ + KateTextCursor c = cursor; + m_doc->newLine( c, this ); + updateCursor( c ); + updateView(); +} + +void KateViewInternal::doDelete() +{ + m_doc->del( m_view, cursor ); + if (m_view->m_codeCompletion->codeCompletionVisible()) { + m_view->m_codeCompletion->updateBox(); + } +} + +void KateViewInternal::doBackspace() +{ + m_doc->backspace( m_view, cursor ); + if (m_view->m_codeCompletion->codeCompletionVisible()) { + m_view->m_codeCompletion->updateBox(); + } +} + +void KateViewInternal::doTranspose() +{ + m_doc->transpose( cursor ); +} + +void KateViewInternal::doDeleteWordLeft() +{ + wordLeft( true ); + m_view->removeSelectedText(); + update(); +} + +void KateViewInternal::doDeleteWordRight() +{ + wordRight( true ); + m_view->removeSelectedText(); + update(); +} + +class CalculatingCursor : public KateTextCursor { +public: + CalculatingCursor(KateViewInternal* vi) + : KateTextCursor() + , m_vi(vi) + { + Q_ASSERT(valid()); + } + + CalculatingCursor(KateViewInternal* vi, const KateTextCursor& c) + : KateTextCursor(c) + , m_vi(vi) + { + Q_ASSERT(valid()); + } + + // This one constrains its arguments to valid positions + CalculatingCursor(KateViewInternal* vi, uint line, uint col) + : KateTextCursor(line, col) + , m_vi(vi) + { + makeValid(); + } + + + virtual CalculatingCursor& operator+=( int n ) = 0; + + virtual CalculatingCursor& operator-=( int n ) = 0; + + CalculatingCursor& operator++() { return operator+=( 1 ); } + + CalculatingCursor& operator--() { return operator-=( 1 ); } + + void makeValid() { + m_line = kMax( 0, kMin( int( m_vi->m_doc->numLines() - 1 ), line() ) ); + if (m_vi->m_view->wrapCursor()) + m_col = kMax( 0, kMin( m_vi->m_doc->lineLength( line() ), col() ) ); + else + m_col = kMax( 0, col() ); + Q_ASSERT( valid() ); + } + + void toEdge( Bias bias ) { + if( bias == left ) m_col = 0; + else if( bias == right ) m_col = m_vi->m_doc->lineLength( line() ); + } + + bool atEdge() const { return atEdge( left ) || atEdge( right ); } + + bool atEdge( Bias bias ) const { + switch( bias ) { + case left: return col() == 0; + case none: return atEdge(); + case right: return col() == m_vi->m_doc->lineLength( line() ); + default: Q_ASSERT(false); return false; + } + } + +protected: + bool valid() const { + return line() >= 0 && + uint( line() ) < m_vi->m_doc->numLines() && + col() >= 0 && + (!m_vi->m_view->wrapCursor() || col() <= m_vi->m_doc->lineLength( line() )); + } + KateViewInternal* m_vi; +}; + +class BoundedCursor : public CalculatingCursor { +public: + BoundedCursor(KateViewInternal* vi) + : CalculatingCursor( vi ) {}; + BoundedCursor(KateViewInternal* vi, const KateTextCursor& c ) + : CalculatingCursor( vi, c ) {}; + BoundedCursor(KateViewInternal* vi, uint line, uint col ) + : CalculatingCursor( vi, line, col ) {}; + virtual CalculatingCursor& operator+=( int n ) { + m_col += n; + + if (n > 0 && m_vi->m_view->dynWordWrap()) { + // Need to constrain to current visible text line for dynamic wrapping mode + if (m_col > m_vi->m_doc->lineLength(m_line)) { + KateLineRange currentRange = m_vi->range(*this); + + int endX; + bool crap; + m_vi->m_view->renderer()->textWidth(m_vi->textLine(m_line), currentRange.startCol, m_vi->width() - currentRange.xOffset(), &crap, &endX); + endX += (m_col - currentRange.endCol + 1) * m_vi->m_view->renderer()->spaceWidth(); + + // Constraining if applicable NOTE: some code duplication in KateViewInternal::resize() + if (endX >= m_vi->width() - currentRange.xOffset()) { + m_col -= n; + if ( uint( line() ) < m_vi->m_doc->numLines() - 1 ) { + m_line++; + m_col = 0; + } + } + } + + } else if (n < 0 && col() < 0 && line() > 0 ) { + m_line--; + m_col = m_vi->m_doc->lineLength( line() ); + } + + m_col = kMax( 0, col() ); + + Q_ASSERT( valid() ); + return *this; + } + virtual CalculatingCursor& operator-=( int n ) { + return operator+=( -n ); + } +}; + +class WrappingCursor : public CalculatingCursor { +public: + WrappingCursor(KateViewInternal* vi) + : CalculatingCursor( vi) {}; + WrappingCursor(KateViewInternal* vi, const KateTextCursor& c ) + : CalculatingCursor( vi, c ) {}; + WrappingCursor(KateViewInternal* vi, uint line, uint col ) + : CalculatingCursor( vi, line, col ) {}; + + virtual CalculatingCursor& operator+=( int n ) { + if( n < 0 ) return operator-=( -n ); + int len = m_vi->m_doc->lineLength( line() ); + if( col() + n <= len ) { + m_col += n; + } else if( uint( line() ) < m_vi->m_doc->numLines() - 1 ) { + n -= len - col() + 1; + m_col = 0; + m_line++; + operator+=( n ); + } else { + m_col = len; + } + Q_ASSERT( valid() ); + return *this; + } + virtual CalculatingCursor& operator-=( int n ) { + if( n < 0 ) return operator+=( -n ); + if( col() - n >= 0 ) { + m_col -= n; + } else if( line() > 0 ) { + n -= col() + 1; + m_line--; + m_col = m_vi->m_doc->lineLength( line() ); + operator-=( n ); + } else { + m_col = 0; + } + Q_ASSERT( valid() ); + return *this; + } +}; + +void KateViewInternal::moveChar( Bias bias, bool sel ) +{ + KateTextCursor c; + if ( m_view->wrapCursor() ) { + c = WrappingCursor( this, cursor ) += bias; + } else { + c = BoundedCursor( this, cursor ) += bias; + } + + updateSelection( c, sel ); + updateCursor( c ); +} + +void KateViewInternal::cursorLeft( bool sel ) +{ + if ( ! m_view->wrapCursor() && cursor.col() == 0 ) + return; + + moveChar( left, sel ); + if (m_view->m_codeCompletion->codeCompletionVisible()) { + m_view->m_codeCompletion->updateBox(); + } +} + +void KateViewInternal::cursorRight( bool sel ) +{ + moveChar( right, sel ); + if (m_view->m_codeCompletion->codeCompletionVisible()) { + m_view->m_codeCompletion->updateBox(); + } +} + +void KateViewInternal::wordLeft ( bool sel ) +{ + WrappingCursor c( this, cursor ); + + // First we skip backwards all space. + // Then we look up into which category the current position falls: + // 1. a "word" character + // 2. a "non-word" character (except space) + // 3. the beginning of the line + // and skip all preceding characters that fall into this class. + // The code assumes that space is never part of the word character class. + + KateHighlighting* h = m_doc->highlight(); + if( !c.atEdge( left ) ) { + + while( !c.atEdge( left ) && m_doc->textLine( c.line() )[ c.col() - 1 ].isSpace() ) + --c; + } + if( c.atEdge( left ) ) + { + --c; + } + else if( h->isInWord( m_doc->textLine( c.line() )[ c.col() - 1 ] ) ) + { + while( !c.atEdge( left ) && h->isInWord( m_doc->textLine( c.line() )[ c.col() - 1 ] ) ) + --c; + } + else + { + while( !c.atEdge( left ) + && !h->isInWord( m_doc->textLine( c.line() )[ c.col() - 1 ] ) + // in order to stay symmetric to wordLeft() + // we must not skip space preceding a non-word sequence + && !m_doc->textLine( c.line() )[ c.col() - 1 ].isSpace() ) + { + --c; + } + } + + updateSelection( c, sel ); + updateCursor( c ); +} + +void KateViewInternal::wordRight( bool sel ) +{ + WrappingCursor c( this, cursor ); + + // We look up into which category the current position falls: + // 1. a "word" character + // 2. a "non-word" character (except space) + // 3. the end of the line + // and skip all following characters that fall into this class. + // If the skipped characters are followed by space, we skip that too. + // The code assumes that space is never part of the word character class. + + KateHighlighting* h = m_doc->highlight(); + if( c.atEdge( right ) ) + { + ++c; + } + else if( h->isInWord( m_doc->textLine( c.line() )[ c.col() ] ) ) + { + while( !c.atEdge( right ) && h->isInWord( m_doc->textLine( c.line() )[ c.col() ] ) ) + ++c; + } + else + { + while( !c.atEdge( right ) + && !h->isInWord( m_doc->textLine( c.line() )[ c.col() ] ) + // we must not skip space, because if that space is followed + // by more non-word characters, we would skip them, too + && !m_doc->textLine( c.line() )[ c.col() ].isSpace() ) + { + ++c; + } + } + + while( !c.atEdge( right ) && m_doc->textLine( c.line() )[ c.col() ].isSpace() ) + ++c; + + updateSelection( c, sel ); + updateCursor( c ); +} + +void KateViewInternal::moveEdge( Bias bias, bool sel ) +{ + BoundedCursor c( this, cursor ); + c.toEdge( bias ); + updateSelection( c, sel ); + updateCursor( c ); +} + +void KateViewInternal::home( bool sel ) +{ + if (m_view->m_codeCompletion->codeCompletionVisible()) { + QKeyEvent e(QEvent::KeyPress, Qt::Key_Home, 0, 0); + m_view->m_codeCompletion->handleKey(&e); + return; + } + + if (m_view->dynWordWrap() && currentRange().startCol) { + // Allow us to go to the real start if we're already at the start of the view line + if (cursor.col() != currentRange().startCol) { + KateTextCursor c(cursor.line(), currentRange().startCol); + updateSelection( c, sel ); + updateCursor( c ); + return; + } + } + + if( !(m_doc->configFlags() & KateDocument::cfSmartHome) ) { + moveEdge( left, sel ); + return; + } + + KateTextLine::Ptr l = textLine( cursor.line() ); + + if (!l) + return; + + KateTextCursor c = cursor; + int lc = l->firstChar(); + + if( lc < 0 || c.col() == lc ) { + c.setCol(0); + } else { + c.setCol(lc); + } + + updateSelection( c, sel ); + updateCursor( c, true ); +} + +void KateViewInternal::end( bool sel ) +{ + if (m_view->m_codeCompletion->codeCompletionVisible()) { + QKeyEvent e(QEvent::KeyPress, Qt::Key_End, 0, 0); + m_view->m_codeCompletion->handleKey(&e); + return; + } + + KateLineRange range = currentRange(); + + if (m_view->dynWordWrap() && range.wrap) { + // Allow us to go to the real end if we're already at the end of the view line + if (cursor.col() < range.endCol - 1) { + KateTextCursor c(cursor.line(), range.endCol - 1); + updateSelection( c, sel ); + updateCursor( c ); + return; + } + } + + if( !(m_doc->configFlags() & KateDocument::cfSmartHome) ) { + moveEdge( right, sel ); + return; + } + + KateTextLine::Ptr l = textLine( cursor.line() ); + + if (!l) + return; + + // "Smart End", as requested in bugs #78258 and #106970 + KateTextCursor c = cursor; + + // If the cursor is already the real end, jump to last non-space character. + // Otherwise, go to the real end ... obviously. + if (c.col() == m_doc->lineLength(c.line())) { + c.setCol(l->lastChar() + 1); + updateSelection(c, sel); + updateCursor(c, true); + } else { + moveEdge(right, sel); + } +} + +KateLineRange KateViewInternal::range(int realLine, const KateLineRange* previous) +{ + // look at the cache first + if (!m_updatingView && realLine >= lineRanges[0].line && realLine <= lineRanges[lineRanges.count() - 1].line) + for (uint i = 0; i < lineRanges.count(); i++) + if (realLine == lineRanges[i].line) + if (!m_view->dynWordWrap() || (!previous && lineRanges[i].startCol == 0) || (previous && lineRanges[i].startCol == previous->endCol)) + return lineRanges[i]; + + // Not in the cache, we have to create it + KateLineRange ret; + + KateTextLine::Ptr text = textLine(realLine); + if (!text) { + return KateLineRange(); + } + + if (!m_view->dynWordWrap()) { + Q_ASSERT(!previous); + ret.line = realLine; + ret.virtualLine = m_doc->getVirtualLine(realLine); + ret.startCol = 0; + ret.endCol = m_doc->lineLength(realLine); + ret.startX = 0; + ret.endX = m_view->renderer()->textWidth(text, -1); + ret.viewLine = 0; + ret.wrap = false; + return ret; + } + + ret.endCol = (int)m_view->renderer()->textWidth(text, previous ? previous->endCol : 0, width() - (previous ? previous->shiftX : 0), &ret.wrap, &ret.endX); + + Q_ASSERT(ret.endCol > ret.startCol); + + ret.line = realLine; + + if (previous) { + ret.virtualLine = previous->virtualLine; + ret.startCol = previous->endCol; + ret.startX = previous->endX; + ret.endX += previous->endX; + ret.shiftX = previous->shiftX; + ret.viewLine = previous->viewLine + 1; + + } else { + // TODO worthwhile optimising this to get the data out of the initial textWidth call? + if (m_view->config()->dynWordWrapAlignIndent() > 0) { + int pos = text->nextNonSpaceChar(0); + + if (pos > 0) + ret.shiftX = m_view->renderer()->textWidth(text, pos); + + if (ret.shiftX > ((double)width() / 100 * m_view->config()->dynWordWrapAlignIndent())) + ret.shiftX = 0; + } + + ret.virtualLine = m_doc->getVirtualLine(realLine); + ret.startCol = 0; + ret.startX = 0; + ret.viewLine = 0; + } + + return ret; +} + +KateLineRange KateViewInternal::currentRange() +{ +// Q_ASSERT(m_view->dynWordWrap()); + + return range(cursor); +} + +KateLineRange KateViewInternal::previousRange() +{ + uint currentViewLine = viewLine(cursor); + + if (currentViewLine) + return range(cursor.line(), currentViewLine - 1); + else + return range(m_doc->getRealLine(displayCursor.line() - 1), -1); +} + +KateLineRange KateViewInternal::nextRange() +{ + uint currentViewLine = viewLine(cursor) + 1; + + if (currentViewLine >= viewLineCount(cursor.line())) { + currentViewLine = 0; + return range(cursor.line() + 1, currentViewLine); + } else { + return range(cursor.line(), currentViewLine); + } +} + +KateLineRange KateViewInternal::range(const KateTextCursor& realCursor) +{ +// Q_ASSERT(m_view->dynWordWrap()); + + KateLineRange thisRange; + bool first = true; + + do { + thisRange = range(realCursor.line(), first ? 0L : &thisRange); + first = false; + } while (thisRange.wrap && !(realCursor.col() >= thisRange.startCol && realCursor.col() < thisRange.endCol) && thisRange.startCol != thisRange.endCol); + + return thisRange; +} + +KateLineRange KateViewInternal::range(uint realLine, int viewLine) +{ +// Q_ASSERT(m_view->dynWordWrap()); + + KateLineRange thisRange; + bool first = true; + + do { + thisRange = range(realLine, first ? 0L : &thisRange); + first = false; + } while (thisRange.wrap && viewLine != thisRange.viewLine && thisRange.startCol != thisRange.endCol); + + if (viewLine != -1 && viewLine != thisRange.viewLine) + kdDebug(13030) << "WARNING: viewLine " << viewLine << " of line " << realLine << " does not exist." << endl; + + return thisRange; +} + +/** + * This returns the view line upon which realCursor is situated. + * The view line is the number of lines in the view from the first line + * The supplied cursor should be in real lines. + */ +uint KateViewInternal::viewLine(const KateTextCursor& realCursor) +{ + if (!m_view->dynWordWrap()) return 0; + + if (realCursor.col() == 0) return 0; + + KateLineRange thisRange; + bool first = true; + + do { + thisRange = range(realCursor.line(), first ? 0L : &thisRange); + first = false; + } while (thisRange.wrap && !(realCursor.col() >= thisRange.startCol && realCursor.col() < thisRange.endCol) && thisRange.startCol != thisRange.endCol); + + return thisRange.viewLine; +} + +int KateViewInternal::displayViewLine(const KateTextCursor& virtualCursor, bool limitToVisible) +{ + KateTextCursor work = startPos(); + + int limit = linesDisplayed(); + + // Efficient non-word-wrapped path + if (!m_view->dynWordWrap()) { + int ret = virtualCursor.line() - startLine(); + if (limitToVisible && (ret < 0 || ret > limit)) + return -1; + else + return ret; + } + + if (work == virtualCursor) { + return 0; + } + + int ret = -(int)viewLine(work); + bool forwards = (work < virtualCursor) ? true : false; + + // FIXME switch to using ranges? faster? + if (forwards) { + while (work.line() != virtualCursor.line()) { + ret += viewLineCount(m_doc->getRealLine(work.line())); + work.setLine(work.line() + 1); + if (limitToVisible && ret > limit) + return -1; + } + } else { + while (work.line() != virtualCursor.line()) { + work.setLine(work.line() - 1); + ret -= viewLineCount(m_doc->getRealLine(work.line())); + if (limitToVisible && ret < 0) + return -1; + } + } + + // final difference + KateTextCursor realCursor = virtualCursor; + realCursor.setLine(m_doc->getRealLine(realCursor.line())); + if (realCursor.col() == -1) realCursor.setCol(m_doc->lineLength(realCursor.line())); + ret += viewLine(realCursor); + + if (limitToVisible && (ret < 0 || ret > limit)) + return -1; + + return ret; +} + +uint KateViewInternal::lastViewLine(uint realLine) +{ + if (!m_view->dynWordWrap()) return 0; + + KateLineRange thisRange; + bool first = true; + + do { + thisRange = range(realLine, first ? 0L : &thisRange); + first = false; + } while (thisRange.wrap && thisRange.startCol != thisRange.endCol); + + return thisRange.viewLine; +} + +uint KateViewInternal::viewLineCount(uint realLine) +{ + return lastViewLine(realLine) + 1; +} + +/* + * This returns the cursor which is offset by (offset) view lines. + * This is the main function which is called by code not specifically dealing with word-wrap. + * The opposite conversion (cursor to offset) can be done with displayViewLine. + * + * The cursors involved are virtual cursors (ie. equivalent to displayCursor) + */ +KateTextCursor KateViewInternal::viewLineOffset(const KateTextCursor& virtualCursor, int offset, bool keepX) +{ + if (!m_view->dynWordWrap()) { + KateTextCursor ret(kMin((int)m_doc->visibleLines() - 1, virtualCursor.line() + offset), 0); + + if (ret.line() < 0) + ret.setLine(0); + + if (keepX) { + int realLine = m_doc->getRealLine(ret.line()); + ret.setCol(m_doc->lineLength(realLine) - 1); + + if (m_currentMaxX > cXPos) + cXPos = m_currentMaxX; + + if (m_view->wrapCursor()) + cXPos = kMin(cXPos, (int)m_view->renderer()->textWidth(textLine(realLine), m_doc->lineLength(realLine))); + + m_view->renderer()->textWidth(ret, cXPos); + } + + return ret; + } + + KateTextCursor realCursor = virtualCursor; + realCursor.setLine(m_doc->getRealLine(virtualCursor.line())); + + uint cursorViewLine = viewLine(realCursor); + + int currentOffset = 0; + int virtualLine = 0; + + bool forwards = (offset > 0) ? true : false; + + if (forwards) { + currentOffset = lastViewLine(realCursor.line()) - cursorViewLine; + if (offset <= currentOffset) { + // the answer is on the same line + KateLineRange thisRange = range(realCursor.line(), cursorViewLine + offset); + Q_ASSERT(thisRange.virtualLine == virtualCursor.line()); + return KateTextCursor(virtualCursor.line(), thisRange.startCol); + } + + virtualLine = virtualCursor.line() + 1; + + } else { + offset = -offset; + currentOffset = cursorViewLine; + if (offset <= currentOffset) { + // the answer is on the same line + KateLineRange thisRange = range(realCursor.line(), cursorViewLine - offset); + Q_ASSERT(thisRange.virtualLine == virtualCursor.line()); + return KateTextCursor(virtualCursor.line(), thisRange.startCol); + } + + virtualLine = virtualCursor.line() - 1; + } + + currentOffset++; + + while (virtualLine >= 0 && virtualLine < (int)m_doc->visibleLines()) + { + KateLineRange thisRange; + bool first = true; + int realLine = m_doc->getRealLine(virtualLine); + + do { + thisRange = range(realLine, first ? 0L : &thisRange); + first = false; + + if (offset == currentOffset) { + if (!forwards) { + // We actually want it the other way around + int requiredViewLine = lastViewLine(realLine) - thisRange.viewLine; + if (requiredViewLine != thisRange.viewLine) { + thisRange = range(realLine, requiredViewLine); + } + } + + KateTextCursor ret(virtualLine, thisRange.startCol); + + // keep column position + if (keepX) { + ret.setCol(thisRange.endCol - 1); + KateTextCursor realCursorTemp(m_doc->getRealLine(virtualCursor.line()), virtualCursor.col()); + int visibleX = m_view->renderer()->textWidth(realCursorTemp) - range(realCursorTemp).startX; + int xOffset = thisRange.startX; + + if (m_currentMaxX > visibleX) + visibleX = m_currentMaxX; + + cXPos = xOffset + visibleX; + + cXPos = kMin(cXPos, lineMaxCursorX(thisRange)); + + m_view->renderer()->textWidth(ret, cXPos); + } + + return ret; + } + + currentOffset++; + + } while (thisRange.wrap); + + if (forwards) + virtualLine++; + else + virtualLine--; + } + + // Looks like we were asked for something a bit exotic. + // Return the max/min valid position. + if (forwards) + return KateTextCursor(m_doc->visibleLines() - 1, m_doc->lineLength(m_doc->visibleLines() - 1)); + else + return KateTextCursor(0, 0); +} + +int KateViewInternal::lineMaxCursorX(const KateLineRange& range) +{ + if (!m_view->wrapCursor() && !range.wrap) + return INT_MAX; + + int maxX = range.endX; + + if (maxX && range.wrap) { + QChar lastCharInLine = textLine(range.line)->getChar(range.endCol - 1); + + if (lastCharInLine == QChar('\t')) { + int lineSize = 0; + int lastTabSize = 0; + for(int i = range.startCol; i < range.endCol; i++) { + if (textLine(range.line)->getChar(i) == QChar('\t')) { + lastTabSize = m_view->tabWidth() - (lineSize % m_view->tabWidth()); + lineSize += lastTabSize; + } else { + lineSize++; + } + } + maxX -= lastTabSize * m_view->renderer()->spaceWidth(); + } else { + maxX -= m_view->renderer()->config()->fontMetrics()->width(lastCharInLine); + } + } + + return maxX; +} + +int KateViewInternal::lineMaxCol(const KateLineRange& range) +{ + int maxCol = range.endCol; + + if (maxCol && range.wrap) + maxCol--; + + return maxCol; +} + +void KateViewInternal::cursorUp(bool sel) +{ + if (m_view->m_codeCompletion->codeCompletionVisible()) { + QKeyEvent e(QEvent::KeyPress, Qt::Key_Up, 0, 0); + m_view->m_codeCompletion->handleKey(&e); + return; + } + + if (displayCursor.line() == 0 && (!m_view->dynWordWrap() || viewLine(cursor) == 0)) + return; + + int newLine = cursor.line(), newCol = 0, xOffset = 0, startCol = 0; + m_preserveMaxX = true; + + if (m_view->dynWordWrap()) { + // Dynamic word wrapping - navigate on visual lines rather than real lines + KateLineRange thisRange = currentRange(); + // This is not the first line because that is already simplified out above + KateLineRange pRange = previousRange(); + + // Ensure we're in the right spot + Q_ASSERT((cursor.line() == thisRange.line) && + (cursor.col() >= thisRange.startCol) && + (!thisRange.wrap || cursor.col() < thisRange.endCol)); + + // VisibleX is the distance from the start of the text to the cursor on the current line. + int visibleX = m_view->renderer()->textWidth(cursor) - thisRange.startX; + int currentLineVisibleX = visibleX; + + // Translate to new line + visibleX += thisRange.xOffset(); + visibleX -= pRange.xOffset(); + + // Limit to >= 0 + visibleX = kMax(0, visibleX); + + startCol = pRange.startCol; + xOffset = pRange.startX; + newLine = pRange.line; + + // Take into account current max X (ie. if the current line was smaller + // than the last definitely specified width) + if (thisRange.xOffset() && !pRange.xOffset() && currentLineVisibleX == 0) // Special case for where xOffset may be > m_currentMaxX + visibleX = m_currentMaxX; + else if (visibleX < m_currentMaxX - pRange.xOffset()) + visibleX = m_currentMaxX - pRange.xOffset(); + + cXPos = xOffset + visibleX; + + cXPos = kMin(cXPos, lineMaxCursorX(pRange)); + + newCol = kMin((int)m_view->renderer()->textPos(newLine, visibleX, startCol), lineMaxCol(pRange)); + + } else { + newLine = m_doc->getRealLine(displayCursor.line() - 1); + + if ((m_view->wrapCursor()) && m_currentMaxX > cXPos) + cXPos = m_currentMaxX; + } + + KateTextCursor c(newLine, newCol); + m_view->renderer()->textWidth(c, cXPos); + + updateSelection( c, sel ); + updateCursor( c ); +} + +void KateViewInternal::cursorDown(bool sel) +{ + if (m_view->m_codeCompletion->codeCompletionVisible()) { + QKeyEvent e(QEvent::KeyPress, Qt::Key_Down, 0, 0); + m_view->m_codeCompletion->handleKey(&e); + return; + } + + if ((displayCursor.line() >= (int)m_doc->numVisLines() - 1) && (!m_view->dynWordWrap() || viewLine(cursor) == lastViewLine(cursor.line()))) + return; + + int newLine = cursor.line(), newCol = 0, xOffset = 0, startCol = 0; + m_preserveMaxX = true; + + if (m_view->dynWordWrap()) { + // Dynamic word wrapping - navigate on visual lines rather than real lines + KateLineRange thisRange = currentRange(); + // This is not the last line because that is already simplified out above + KateLineRange nRange = nextRange(); + + // Ensure we're in the right spot + Q_ASSERT((cursor.line() == thisRange.line) && + (cursor.col() >= thisRange.startCol) && + (!thisRange.wrap || cursor.col() < thisRange.endCol)); + + // VisibleX is the distance from the start of the text to the cursor on the current line. + int visibleX = m_view->renderer()->textWidth(cursor) - thisRange.startX; + int currentLineVisibleX = visibleX; + + // Translate to new line + visibleX += thisRange.xOffset(); + visibleX -= nRange.xOffset(); + + // Limit to >= 0 + visibleX = kMax(0, visibleX); + + if (!thisRange.wrap) { + newLine = m_doc->getRealLine(displayCursor.line() + 1); + } else { + startCol = thisRange.endCol; + xOffset = thisRange.endX; + } + + // Take into account current max X (ie. if the current line was smaller + // than the last definitely specified width) + if (thisRange.xOffset() && !nRange.xOffset() && currentLineVisibleX == 0) // Special case for where xOffset may be > m_currentMaxX + visibleX = m_currentMaxX; + else if (visibleX < m_currentMaxX - nRange.xOffset()) + visibleX = m_currentMaxX - nRange.xOffset(); + + cXPos = xOffset + visibleX; + + cXPos = kMin(cXPos, lineMaxCursorX(nRange)); + + newCol = kMin((int)m_view->renderer()->textPos(newLine, visibleX, startCol), lineMaxCol(nRange)); + + } else { + newLine = m_doc->getRealLine(displayCursor.line() + 1); + + if ((m_view->wrapCursor()) && m_currentMaxX > cXPos) + cXPos = m_currentMaxX; + } + + KateTextCursor c(newLine, newCol); + m_view->renderer()->textWidth(c, cXPos); + + updateSelection(c, sel); + updateCursor(c); +} + +void KateViewInternal::cursorToMatchingBracket( bool sel ) +{ + KateTextCursor start( cursor ), end; + + if( !m_doc->findMatchingBracket( start, end ) ) + return; + + // The cursor is now placed just to the left of the matching bracket. + // If it's an ending bracket, put it to the right (so we can easily + // get back to the original bracket). + if( end > start ) + end.setCol(end.col() + 1); + + updateSelection( end, sel ); + updateCursor( end ); +} + +void KateViewInternal::topOfView( bool sel ) +{ + KateTextCursor c = viewLineOffset(startPos(), m_minLinesVisible); + updateSelection( c, sel ); + updateCursor( c ); +} + +void KateViewInternal::bottomOfView( bool sel ) +{ + // FIXME account for wordwrap + KateTextCursor c = viewLineOffset(endPos(), -m_minLinesVisible); + updateSelection( c, sel ); + updateCursor( c ); +} + +// lines is the offset to scroll by +void KateViewInternal::scrollLines( int lines, bool sel ) +{ + KateTextCursor c = viewLineOffset(displayCursor, lines, true); + + // Fix the virtual cursor -> real cursor + c.setLine(m_doc->getRealLine(c.line())); + + updateSelection( c, sel ); + updateCursor( c ); +} + +// This is a bit misleading... it's asking for the view to be scrolled, not the cursor +void KateViewInternal::scrollUp() +{ + KateTextCursor newPos = viewLineOffset(m_startPos, -1); + scrollPos(newPos); +} + +void KateViewInternal::scrollDown() +{ + KateTextCursor newPos = viewLineOffset(m_startPos, 1); + scrollPos(newPos); +} + +void KateViewInternal::setAutoCenterLines(int viewLines, bool updateView) +{ + m_autoCenterLines = viewLines; + m_minLinesVisible = kMin(int((linesDisplayed() - 1)/2), m_autoCenterLines); + if (updateView) + KateViewInternal::updateView(); +} + +void KateViewInternal::pageUp( bool sel ) +{ + if (m_view->m_codeCompletion->codeCompletionVisible()) { + QKeyEvent e(QEvent::KeyPress, Qt::Key_PageUp, 0, 0); + m_view->m_codeCompletion->handleKey(&e); + return; + } + + // remember the view line and x pos + int viewLine = displayViewLine(displayCursor); + bool atTop = (startPos().line() == 0 && startPos().col() == 0); + + // Adjust for an auto-centering cursor + int lineadj = 2 * m_minLinesVisible; + int cursorStart = (linesDisplayed() - 1) - viewLine; + if (cursorStart < m_minLinesVisible) + lineadj -= m_minLinesVisible - cursorStart; + + int linesToScroll = -kMax( ((int)linesDisplayed() - 1) - lineadj, 0 ); + m_preserveMaxX = true; + + if (!m_doc->pageUpDownMovesCursor () && !atTop) { + int xPos = m_view->renderer()->textWidth(cursor) - currentRange().startX; + + KateTextCursor newStartPos = viewLineOffset(startPos(), linesToScroll - 1); + scrollPos(newStartPos); + + // put the cursor back approximately where it was + KateTextCursor newPos = viewLineOffset(newStartPos, viewLine, true); + newPos.setLine(m_doc->getRealLine(newPos.line())); + + KateLineRange newLine = range(newPos); + + if (m_currentMaxX - newLine.xOffset() > xPos) + xPos = m_currentMaxX - newLine.xOffset(); + + cXPos = kMin(newLine.startX + xPos, lineMaxCursorX(newLine)); + + m_view->renderer()->textWidth( newPos, cXPos ); + + m_preserveMaxX = true; + updateSelection( newPos, sel ); + updateCursor(newPos); + + } else { + scrollLines( linesToScroll, sel ); + } +} + +void KateViewInternal::pageDown( bool sel ) +{ + if (m_view->m_codeCompletion->codeCompletionVisible()) { + QKeyEvent e(QEvent::KeyPress, Qt::Key_PageDown, 0, 0); + m_view->m_codeCompletion->handleKey(&e); + return; + } + + // remember the view line + int viewLine = displayViewLine(displayCursor); + bool atEnd = startPos() >= m_cachedMaxStartPos; + + // Adjust for an auto-centering cursor + int lineadj = 2 * m_minLinesVisible; + int cursorStart = m_minLinesVisible - viewLine; + if (cursorStart > 0) + lineadj -= cursorStart; + + int linesToScroll = kMax( ((int)linesDisplayed() - 1) - lineadj, 0 ); + m_preserveMaxX = true; + + if (!m_doc->pageUpDownMovesCursor () && !atEnd) { + int xPos = m_view->renderer()->textWidth(cursor) - currentRange().startX; + + KateTextCursor newStartPos = viewLineOffset(startPos(), linesToScroll + 1); + scrollPos(newStartPos); + + // put the cursor back approximately where it was + KateTextCursor newPos = viewLineOffset(newStartPos, viewLine, true); + newPos.setLine(m_doc->getRealLine(newPos.line())); + + KateLineRange newLine = range(newPos); + + if (m_currentMaxX - newLine.xOffset() > xPos) + xPos = m_currentMaxX - newLine.xOffset(); + + cXPos = kMin(newLine.startX + xPos, lineMaxCursorX(newLine)); + + m_view->renderer()->textWidth( newPos, cXPos ); + + m_preserveMaxX = true; + updateSelection( newPos, sel ); + updateCursor(newPos); + + } else { + scrollLines( linesToScroll, sel ); + } +} + +int KateViewInternal::maxLen(uint startLine) +{ +// Q_ASSERT(!m_view->dynWordWrap()); + + int displayLines = (m_view->height() / m_view->renderer()->fontHeight()) + 1; + + int maxLen = 0; + + for (int z = 0; z < displayLines; z++) { + int virtualLine = startLine + z; + + if (virtualLine < 0 || virtualLine >= (int)m_doc->visibleLines()) + break; + + KateLineRange thisRange = range((int)m_doc->getRealLine(virtualLine)); + + maxLen = kMax(maxLen, thisRange.endX); + } + + return maxLen; +} + +void KateViewInternal::top( bool sel ) +{ + KateTextCursor c( 0, cursor.col() ); + m_view->renderer()->textWidth( c, cXPos ); + updateSelection( c, sel ); + updateCursor( c ); +} + +void KateViewInternal::bottom( bool sel ) +{ + KateTextCursor c( m_doc->lastLine(), cursor.col() ); + m_view->renderer()->textWidth( c, cXPos ); + updateSelection( c, sel ); + updateCursor( c ); +} + +void KateViewInternal::top_home( bool sel ) +{ + if (m_view->m_codeCompletion->codeCompletionVisible()) { + QKeyEvent e(QEvent::KeyPress, Qt::Key_Home, 0, 0); + m_view->m_codeCompletion->handleKey(&e); + return; + } + KateTextCursor c( 0, 0 ); + updateSelection( c, sel ); + updateCursor( c ); +} + +void KateViewInternal::bottom_end( bool sel ) +{ + if (m_view->m_codeCompletion->codeCompletionVisible()) { + QKeyEvent e(QEvent::KeyPress, Qt::Key_End, 0, 0); + m_view->m_codeCompletion->handleKey(&e); + return; + } + KateTextCursor c( m_doc->lastLine(), m_doc->lineLength( m_doc->lastLine() ) ); + updateSelection( c, sel ); + updateCursor( c ); +} + +void KateViewInternal::updateSelection( const KateTextCursor& _newCursor, bool keepSel ) +{ + KateTextCursor newCursor = _newCursor; + if( keepSel ) + { + if ( !m_view->hasSelection() || (selectAnchor.line() == -1) + || (m_view->config()->persistentSelection() + && ((cursor < m_view->selectStart) || (cursor > m_view->selectEnd))) ) + { + selectAnchor = cursor; + m_view->setSelection( cursor, newCursor ); + } + else + { + bool doSelect = true; + switch (m_selectionMode) + { + case Word: + { + // Restore selStartCached if needed. It gets nuked by + // viewSelectionChanged if we drag the selection into non-existence, + // which can legitimately happen if a shift+DC selection is unable to + // set a "proper" (i.e. non-empty) cached selection, e.g. because the + // start was on something that isn't a word. Word select mode relies + // on the cached selection being set properly, even if it is empty + // (i.e. selStartCached == selEndCached). + if ( selStartCached.line() == -1 ) + selStartCached = selEndCached; + + int c; + if ( newCursor > selEndCached ) + { + selectAnchor = selStartCached; + + KateTextLine::Ptr l = m_doc->kateTextLine( newCursor.line() ); + + c = newCursor.col(); + if ( c > 0 && m_doc->highlight()->isInWord( l->getChar( c-1 ) ) ) { + for (; c < l->length(); c++ ) + if ( !m_doc->highlight()->isInWord( l->getChar( c ) ) ) + break; + } + + newCursor.setCol( c ); + } + else if ( newCursor < selStartCached ) + { + selectAnchor = selEndCached; + + KateTextLine::Ptr l = m_doc->kateTextLine( newCursor.line() ); + + c = newCursor.col(); + if ( c > 0 && c < m_doc->textLine( newCursor.line() ).length() + && m_doc->highlight()->isInWord( l->getChar( c ) ) + && m_doc->highlight()->isInWord( l->getChar( c-1 ) ) ) { + for ( c -= 2; c >= 0; c-- ) + if ( !m_doc->highlight()->isInWord( l->getChar( c ) ) ) + break; + newCursor.setCol( c+1 ); + } + + } + else + doSelect = false; + + } + break; + case Line: + if ( newCursor.line() > selStartCached.line() ) + { + if ( newCursor.line()+1 >= m_doc->numLines() ) + newCursor.setCol( m_doc->textLine( newCursor.line() ).length() ); + else + newCursor.setPos( newCursor.line() + 1, 0 ); + // Grow to include entire line + selectAnchor = selStartCached; + selectAnchor.setCol( 0 ); + } + else if ( newCursor.line() < selStartCached.line() ) + { + newCursor.setCol( 0 ); + // Grow to include entire line + selectAnchor = selEndCached; + if ( selectAnchor.col() > 0 ) + { + if ( selectAnchor.line()+1 >= m_doc->numLines() ) + selectAnchor.setCol( m_doc->textLine( selectAnchor.line() ).length() ); + else + selectAnchor.setPos( selectAnchor.line() + 1, 0 ); + } + } + else // same line, ignore + doSelect = false; + break; + case Mouse: + { + if ( selStartCached.line() < 0 ) // invalid + break; + + if ( newCursor > selEndCached ) + selectAnchor = selStartCached; + else if ( newCursor < selStartCached ) + selectAnchor = selEndCached; + else + doSelect = false; + } + break; + default: + { + if ( selectAnchor.line() < 0 ) // invalid + break; + } + } + + if ( doSelect ) + m_view->setSelection( selectAnchor, newCursor); + else if ( selStartCached.line() >= 0 ) // we have a cached selection, so we restore that + m_view->setSelection( selStartCached, selEndCached ); + } + + m_selChangedByUser = true; + } + else if ( !m_view->config()->persistentSelection() ) + { + m_view->clearSelection(); + selStartCached.setLine( -1 ); + selectAnchor.setLine( -1 ); + } +} + +void KateViewInternal::updateCursor( const KateTextCursor& newCursor, bool force, bool center, bool calledExternally ) +{ + if ( !force && (cursor == newCursor) ) + { + if ( !m_madeVisible && m_view == m_doc->activeView() ) + { + // unfold if required + m_doc->foldingTree()->ensureVisible( newCursor.line() ); + + makeVisible ( displayCursor, displayCursor.col(), false, center, calledExternally ); + } + + return; + } + + // unfold if required + m_doc->foldingTree()->ensureVisible( newCursor.line() ); + + KateTextCursor oldDisplayCursor = displayCursor; + + cursor.setPos (newCursor); + displayCursor.setPos (m_doc->getVirtualLine(cursor.line()), cursor.col()); + + cXPos = m_view->renderer()->textWidth( cursor ); + if (m_view == m_doc->activeView()) + makeVisible ( displayCursor, displayCursor.col(), false, center, calledExternally ); + + updateBracketMarks(); + + // It's efficient enough to just tag them both without checking to see if they're on the same view line + tagLine(oldDisplayCursor); + tagLine(displayCursor); + + updateMicroFocusHint(); + + if (m_cursorTimer.isActive ()) + { + if ( KApplication::cursorFlashTime() > 0 ) + m_cursorTimer.start( KApplication::cursorFlashTime() / 2 ); + m_view->renderer()->setDrawCaret(true); + } + + // Remember the maximum X position if requested + if (m_preserveMaxX) + m_preserveMaxX = false; + else + if (m_view->dynWordWrap()) + m_currentMaxX = m_view->renderer()->textWidth(displayCursor) - currentRange().startX + currentRange().xOffset(); + else + m_currentMaxX = cXPos; + + //kdDebug() << "m_currentMaxX: " << m_currentMaxX << " (was "<< oldmaxx << "), cXPos: " << cXPos << endl; + //kdDebug(13030) << "Cursor now located at real " << cursor.line << "," << cursor.col << ", virtual " << displayCursor.line << ", " << displayCursor.col << "; Top is " << startLine() << ", " << startPos().col << endl; + + paintText(0, 0, width(), height(), true); + + emit m_view->cursorPositionChanged(); +} + +void KateViewInternal::updateBracketMarks() +{ + if ( bm.isValid() ) { + KateTextCursor bmStart(m_doc->getVirtualLine(bm.start().line()), bm.start().col()); + KateTextCursor bmEnd(m_doc->getVirtualLine(bm.end().line()), bm.end().col()); + + if( bm.getMinIndent() != 0 ) + { + // @@ Do this only when cursor near start/end. + if( bmStart > bmEnd ) + { + tagLines(bmEnd, bmStart); + } + else + { + tagLines(bmStart, bmEnd); + } + } + else + { + tagLine(bmStart); + tagLine(bmEnd); + } + } + + // add some limit to this, this is really endless on big files without limit + int maxLines = linesDisplayed () * 3; + m_doc->newBracketMark( cursor, bm, maxLines ); + + if ( bm.isValid() ) { + KateTextCursor bmStart(m_doc->getVirtualLine(bm.start().line()), bm.start().col()); + KateTextCursor bmEnd(m_doc->getVirtualLine(bm.end().line()), bm.end().col()); + + if( bm.getMinIndent() != 0 ) + { + // @@ Do this only when cursor near start/end. + if( bmStart > bmEnd ) + { + tagLines(bmEnd, bmStart); + } + else + { + tagLines(bmStart, bmEnd); + } + } + else + { + tagLine(bmStart); + tagLine(bmEnd); + } + } +} + +bool KateViewInternal::tagLine(const KateTextCursor& virtualCursor) +{ + int viewLine = displayViewLine(virtualCursor, true); + if (viewLine >= 0 && viewLine < (int)lineRanges.count()) { + lineRanges[viewLine].dirty = true; + leftBorder->update (0, lineToY(viewLine), leftBorder->width(), m_view->renderer()->fontHeight()); + return true; + } + return false; +} + +bool KateViewInternal::tagLines( int start, int end, bool realLines ) +{ + return tagLines(KateTextCursor(start, 0), KateTextCursor(end, -1), realLines); +} + +bool KateViewInternal::tagLines(KateTextCursor start, KateTextCursor end, bool realCursors) +{ + if (realCursors) + { + //kdDebug()<<"realLines is true"<<endl; + start.setLine(m_doc->getVirtualLine( start.line() )); + end.setLine(m_doc->getVirtualLine( end.line() )); + } + + if (end.line() < (int)startLine()) + { + //kdDebug()<<"end<startLine"<<endl; + return false; + } + if (start.line() > (int)endLine()) + { + //kdDebug()<<"start> endLine"<<start<<" "<<((int)endLine())<<endl; + return false; + } + + //kdDebug(13030) << "tagLines( [" << start.line << "," << start.col << "], [" << end.line << "," << end.col << "] )\n"; + + bool ret = false; + + for (uint z = 0; z < lineRanges.size(); z++) + { + if ((lineRanges[z].virtualLine > start.line() || (lineRanges[z].virtualLine == start.line() && lineRanges[z].endCol >= start.col() && start.col() != -1)) && (lineRanges[z].virtualLine < end.line() || (lineRanges[z].virtualLine == end.line() && (lineRanges[z].startCol <= end.col() || end.col() == -1)))) { + ret = lineRanges[z].dirty = true; + //kdDebug() << "Tagged line " << lineRanges[z].line << endl; + } + } + + if (!m_view->dynWordWrap()) + { + int y = lineToY( start.line() ); + // FIXME is this enough for when multiple lines are deleted + int h = (end.line() - start.line() + 2) * m_view->renderer()->fontHeight(); + if (end.line() == (int)m_doc->numVisLines() - 1) + h = height(); + + leftBorder->update (0, y, leftBorder->width(), h); + } + else + { + // FIXME Do we get enough good info in editRemoveText to optimise this more? + //bool justTagged = false; + for (uint z = 0; z < lineRanges.size(); z++) + { + if ((lineRanges[z].virtualLine > start.line() || (lineRanges[z].virtualLine == start.line() && lineRanges[z].endCol >= start.col() && start.col() != -1)) && (lineRanges[z].virtualLine < end.line() || (lineRanges[z].virtualLine == end.line() && (lineRanges[z].startCol <= end.col() || end.col() == -1)))) + { + //justTagged = true; + leftBorder->update (0, z * m_view->renderer()->fontHeight(), leftBorder->width(), leftBorder->height()); + break; + } + /*else if (justTagged) + { + justTagged = false; + leftBorder->update (0, z * m_doc->viewFont.fontHeight, leftBorder->width(), m_doc->viewFont.fontHeight); + break; + }*/ + } + } + + return ret; +} + +void KateViewInternal::tagAll() +{ + //kdDebug(13030) << "tagAll()" << endl; + for (uint z = 0; z < lineRanges.size(); z++) + { + lineRanges[z].dirty = true; + } + + leftBorder->updateFont(); + leftBorder->update (); +} + +void KateViewInternal::paintCursor() +{ + if (tagLine(displayCursor)) + paintText (0,0,width(), height(), true); +} + +// Point in content coordinates +void KateViewInternal::placeCursor( const QPoint& p, bool keepSelection, bool updateSelection ) +{ + KateLineRange thisRange = yToKateLineRange(p.y()); + + if (thisRange.line == -1) { + for (int i = (p.y() / m_view->renderer()->fontHeight()); i >= 0; i--) { + thisRange = lineRanges[i]; + if (thisRange.line != -1) + break; + } + Q_ASSERT(thisRange.line != -1); + } + + int realLine = thisRange.line; + int visibleLine = thisRange.virtualLine; + uint startCol = thisRange.startCol; + + visibleLine = kMax( 0, kMin( visibleLine, int(m_doc->numVisLines()) - 1 ) ); + + KateTextCursor c(realLine, 0); + + int x = kMin(kMax(-m_startX, p.x() - thisRange.xOffset()), lineMaxCursorX(thisRange) - thisRange.startX); + + m_view->renderer()->textWidth( c, startX() + x, startCol); + + if (updateSelection) + KateViewInternal::updateSelection( c, keepSelection ); + + updateCursor( c ); +} + +// Point in content coordinates +bool KateViewInternal::isTargetSelected( const QPoint& p ) +{ + KateLineRange thisRange = yToKateLineRange(p.y()); + + KateTextLine::Ptr l = textLine( thisRange.line ); + if( !l ) + return false; + + int col = m_view->renderer()->textPos( l, startX() + p.x() - thisRange.xOffset(), thisRange.startCol, false ); + + return m_view->lineColSelected( thisRange.line, col ); +} + +//BEGIN EVENT HANDLING STUFF + +bool KateViewInternal::eventFilter( QObject *obj, QEvent *e ) +{ + if (obj == m_lineScroll) + { + // the second condition is to make sure a scroll on the vertical bar doesn't cause a horizontal scroll ;) + if (e->type() == QEvent::Wheel && m_lineScroll->minValue() != m_lineScroll->maxValue()) + { + wheelEvent((QWheelEvent*)e); + return true; + } + + // continue processing + return QWidget::eventFilter( obj, e ); + } + + switch( e->type() ) + { + case QEvent::KeyPress: + { + QKeyEvent *k = (QKeyEvent *)e; + + if (m_view->m_codeCompletion->codeCompletionVisible ()) + { + kdDebug (13030) << "hint around" << endl; + + if( k->key() == Key_Escape ) + m_view->m_codeCompletion->abortCompletion(); + } + + if ((k->key() == Qt::Key_Escape) && !m_view->config()->persistentSelection() ) + { + m_view->clearSelection(); + return true; + } + else if ( !((k->state() & ControlButton) || (k->state() & AltButton)) ) + { + keyPressEvent( k ); + return k->isAccepted(); + } + + } break; + + case QEvent::DragMove: + { + QPoint currentPoint = ((QDragMoveEvent*) e)->pos(); + + QRect doNotScrollRegion( scrollMargin, scrollMargin, + width() - scrollMargin * 2, + height() - scrollMargin * 2 ); + + if ( !doNotScrollRegion.contains( currentPoint ) ) + { + startDragScroll(); + // Keep sending move events + ( (QDragMoveEvent*)e )->accept( QRect(0,0,0,0) ); + } + + dragMoveEvent((QDragMoveEvent*)e); + } break; + + case QEvent::DragLeave: + // happens only when pressing ESC while dragging + stopDragScroll(); + break; + + case QEvent::WindowBlocked: + // next focus originates from an internal dialog: + // don't show the modonhd prompt + m_doc->m_isasking = -1; + break; + + default: + break; + } + + return QWidget::eventFilter( obj, e ); +} + +void KateViewInternal::keyPressEvent( QKeyEvent* e ) +{ + KKey key(e); + + bool codeComp = m_view->m_codeCompletion->codeCompletionVisible (); + + if (codeComp) + { + kdDebug (13030) << "hint around" << endl; + + if( e->key() == Key_Enter || e->key() == Key_Return || + (key == SHIFT + Qt::Key_Return) || (key == SHIFT + Qt::Key_Enter)) { + m_view->m_codeCompletion->doComplete(); + e->accept(); + return; + } + } + + if( !m_doc->isReadWrite() ) + { + e->ignore(); + return; + } + + if ((key == Qt::Key_Return) || (key == Qt::Key_Enter)) + { + m_view->keyReturn(); + e->accept(); + return; + } + + if ((key == SHIFT + Qt::Key_Return) || (key == SHIFT + Qt::Key_Enter)) + { + uint ln = cursor.line(); + int col = cursor.col(); + KateTextLine::Ptr line = m_doc->kateTextLine( ln ); + int pos = line->firstChar(); + if (pos > cursor.col()) pos = cursor.col(); + if (pos != -1) { + while ((int)line->length() > pos && + !line->getChar(pos).isLetterOrNumber() && + pos < cursor.col()) ++pos; + } else { + pos = line->length(); // stay indented + } + m_doc->editStart(); + m_doc->insertText( cursor.line(), line->length(), "\n" + line->string(0, pos) + + line->string().right( line->length() - cursor.col() ) ); + cursor.setPos(ln + 1, pos); + if (col < int(line->length())) + m_doc->editRemoveText(ln, col, line->length() - col); + m_doc->editEnd(); + updateCursor(cursor, true); + updateView(); + e->accept(); + + return; + } + + if (key == Qt::Key_Backspace || key == SHIFT + Qt::Key_Backspace) + { + m_view->backspace(); + e->accept(); + + if (codeComp) + m_view->m_codeCompletion->updateBox (); + + return; + } + + if (key == Qt::Key_Tab || key == SHIFT+Qt::Key_Backtab || key == Qt::Key_Backtab) + { + if (m_doc->invokeTabInterceptor(key)) { + e->accept(); + return; + } else + if (m_doc->configFlags() & KateDocumentConfig::cfTabIndents) + { + if( key == Qt::Key_Tab ) + { + if (m_view->hasSelection() || (m_doc->configFlags() & KateDocumentConfig::cfTabIndentsMode)) + m_doc->indent( m_view, cursor.line(), 1 ); + else if (m_doc->configFlags() & KateDocumentConfig::cfTabInsertsTab) + m_doc->typeChars ( m_view, QString ("\t") ); + else + m_doc->insertIndentChars ( m_view ); + + e->accept(); + + if (codeComp) + m_view->m_codeCompletion->updateBox (); + + return; + } + + if (key == SHIFT+Qt::Key_Backtab || key == Qt::Key_Backtab) + { + m_doc->indent( m_view, cursor.line(), -1 ); + e->accept(); + + if (codeComp) + m_view->m_codeCompletion->updateBox (); + + return; + } + } +} + if ( !(e->state() & ControlButton) && !(e->state() & AltButton) + && m_doc->typeChars ( m_view, e->text() ) ) + { + e->accept(); + + if (codeComp) + m_view->m_codeCompletion->updateBox (); + + return; + } + + e->ignore(); +} + +void KateViewInternal::keyReleaseEvent( QKeyEvent* e ) +{ + KKey key(e); + + if (key == SHIFT) + m_shiftKeyPressed = true; + else + { + if (m_shiftKeyPressed) + { + m_shiftKeyPressed = false; + + if (m_selChangedByUser) + { + QApplication::clipboard()->setSelectionMode( true ); + m_view->copy(); + QApplication::clipboard()->setSelectionMode( false ); + + m_selChangedByUser = false; + } + } + } + + e->ignore(); + return; +} + +void KateViewInternal::contextMenuEvent ( QContextMenuEvent * e ) +{ + // try to show popup menu + + QPoint p = e->pos(); + + if ( m_view->m_doc->browserView() ) + { + m_view->contextMenuEvent( e ); + return; + } + + if ( e->reason() == QContextMenuEvent::Keyboard ) + { + makeVisible( cursor, 0 ); + p = cursorCoordinates(); + } + else if ( ! m_view->hasSelection() || m_view->config()->persistentSelection() ) + placeCursor( e->pos() ); + + // popup is a qguardedptr now + if (m_view->popup()) { + m_view->popup()->popup( mapToGlobal( p ) ); + e->accept (); + } +} + +void KateViewInternal::mousePressEvent( QMouseEvent* e ) +{ + switch (e->button()) + { + case LeftButton: + m_selChangedByUser = false; + + if (possibleTripleClick) + { + possibleTripleClick = false; + + m_selectionMode = Line; + + if ( e->state() & Qt::ShiftButton ) + { + updateSelection( cursor, true ); + } + else + { + m_view->selectLine( cursor ); + } + + QApplication::clipboard()->setSelectionMode( true ); + m_view->copy(); + QApplication::clipboard()->setSelectionMode( false ); + + // Keep the line at the select anchor selected during further + // mouse selection + if ( selectAnchor.line() > m_view->selectStart.line() ) + { + // Preserve the last selected line + if ( selectAnchor == m_view->selectEnd && selectAnchor.col() == 0 ) + selStartCached = KateTextCursor( selectAnchor.line()-1, 0 ); + else + selStartCached = KateTextCursor( selectAnchor.line(), 0 ); + selEndCached = m_view->selectEnd; + } + else + { + // Preserve the first selected line + selStartCached = m_view->selectStart; + if ( m_view->selectEnd.line() > m_view->selectStart.line() ) + selEndCached = KateTextCursor( m_view->selectStart.line()+1, 0 ); + else + selEndCached = m_view->selectEnd; + } + + // Set cursor to edge of selection... which edge depends on what + // "direction" the selection was made in + if ( m_view->selectStart < selectAnchor + && selectAnchor.line() != m_view->selectStart.line() ) + updateCursor( m_view->selectStart ); + else + updateCursor( m_view->selectEnd ); + + e->accept (); + return; + } + else if (m_selectionMode == Default) + { + m_selectionMode = Mouse; + } + + if ( e->state() & Qt::ShiftButton ) + { + if (selectAnchor.line() < 0) + selectAnchor = cursor; + } + else + { + selStartCached.setLine( -1 ); // invalidate + } + + if( !( e->state() & Qt::ShiftButton ) && isTargetSelected( e->pos() ) ) + { + dragInfo.state = diPending; + dragInfo.start = e->pos(); + } + else + { + dragInfo.state = diNone; + + if ( e->state() & Qt::ShiftButton ) + { + placeCursor( e->pos(), true, false ); + if ( selStartCached.line() >= 0 ) + { + if ( cursor > selEndCached ) + { + m_view->setSelection( selStartCached, cursor ); + selectAnchor = selStartCached; + } + else if ( cursor < selStartCached ) + { + m_view->setSelection( cursor, selEndCached ); + selectAnchor = selEndCached; + } + else + { + m_view->setSelection( selStartCached, cursor ); + } + } + else + { + m_view->setSelection( selectAnchor, cursor ); + } + } + else + { + placeCursor( e->pos() ); + } + + scrollX = 0; + scrollY = 0; + + m_scrollTimer.start (50); + } + + e->accept (); + break; + + default: + e->ignore (); + break; + } +} + +void KateViewInternal::mouseDoubleClickEvent(QMouseEvent *e) +{ + switch (e->button()) + { + case LeftButton: + m_selectionMode = Word; + + if ( e->state() & Qt::ShiftButton ) + { + KateTextCursor oldSelectStart = m_view->selectStart; + KateTextCursor oldSelectEnd = m_view->selectEnd; + + // Now select the word under the select anchor + int cs, ce; + KateTextLine::Ptr l = m_doc->kateTextLine( selectAnchor.line() ); + + ce = selectAnchor.col(); + if ( ce > 0 && m_doc->highlight()->isInWord( l->getChar( ce ) ) ) { + for (; ce < l->length(); ce++ ) + if ( !m_doc->highlight()->isInWord( l->getChar( ce ) ) ) + break; + } + + cs = selectAnchor.col() - 1; + if ( cs < m_doc->textLine( selectAnchor.line() ).length() + && m_doc->highlight()->isInWord( l->getChar( cs ) ) ) { + for ( cs--; cs >= 0; cs-- ) + if ( !m_doc->highlight()->isInWord( l->getChar( cs ) ) ) + break; + } + + // ...and keep it selected + if (cs+1 < ce) + { + selStartCached = KateTextCursor( selectAnchor.line(), cs+1 ); + selEndCached = KateTextCursor( selectAnchor.line(), ce ); + } + else + { + selStartCached = selectAnchor; + selEndCached = selectAnchor; + } + // Now word select to the mouse cursor + placeCursor( e->pos(), true ); + } + else + { + // first clear the selection, otherwise we run into bug #106402 + // ...and set the cursor position, for the same reason (otherwise there + // are *other* idiosyncrasies we can't fix without reintroducing said + // bug) + // Parameters: 1st false: don't redraw + // 2nd false: don't emit selectionChanged signals, as + // selectWord() emits this already + m_view->clearSelection( false, false ); + placeCursor( e->pos() ); + m_view->selectWord( cursor ); + if (m_view->hasSelection()) + { + selectAnchor = selStartCached = m_view->selectStart; + selEndCached = m_view->selectEnd; + } + else + { + // if we didn't actually select anything, restore the selection mode + // -- see bug #131369 (kling) + m_selectionMode = Default; + } + } + + // Move cursor to end (or beginning) of selected word + if (m_view->hasSelection()) + { + QApplication::clipboard()->setSelectionMode( true ); + m_view->copy(); + QApplication::clipboard()->setSelectionMode( false ); + + // Shift+DC before the "cached" word should move the cursor to the + // beginning of the selection, not the end + if (m_view->selectStart < selStartCached) + updateCursor( m_view->selectStart ); + else + updateCursor( m_view->selectEnd ); + } + + possibleTripleClick = true; + QTimer::singleShot ( QApplication::doubleClickInterval(), this, SLOT(tripleClickTimeout()) ); + + scrollX = 0; + scrollY = 0; + + m_scrollTimer.start (50); + + e->accept (); + break; + + default: + e->ignore (); + break; + } +} + +void KateViewInternal::tripleClickTimeout() +{ + possibleTripleClick = false; +} + +void KateViewInternal::mouseReleaseEvent( QMouseEvent* e ) +{ + switch (e->button()) + { + case LeftButton: + m_selectionMode = Default; +// selStartCached.setLine( -1 ); + + if (m_selChangedByUser) + { + QApplication::clipboard()->setSelectionMode( true ); + m_view->copy(); + QApplication::clipboard()->setSelectionMode( false ); + // Set cursor to edge of selection... which edge depends on what + // "direction" the selection was made in + if ( m_view->selectStart < selectAnchor ) + updateCursor( m_view->selectStart ); + else + updateCursor( m_view->selectEnd ); + + m_selChangedByUser = false; + } + + if (dragInfo.state == diPending) + placeCursor( e->pos(), e->state() & ShiftButton ); + else if (dragInfo.state == diNone) + m_scrollTimer.stop (); + + dragInfo.state = diNone; + + e->accept (); + break; + + case MidButton: + placeCursor( e->pos() ); + + if( m_doc->isReadWrite() ) + { + QApplication::clipboard()->setSelectionMode( true ); + m_view->paste (); + QApplication::clipboard()->setSelectionMode( false ); + } + + e->accept (); + break; + + default: + e->ignore (); + break; + } +} + +void KateViewInternal::mouseMoveEvent( QMouseEvent* e ) +{ + if( e->state() & LeftButton ) + { + if (dragInfo.state == diPending) + { + // we had a mouse down, but haven't confirmed a drag yet + // if the mouse has moved sufficiently, we will confirm + QPoint p( e->pos() - dragInfo.start ); + + // we've left the drag square, we can start a real drag operation now + if( p.manhattanLength() > KGlobalSettings::dndEventDelay() ) + doDrag(); + + return; + } + else if (dragInfo.state == diDragging) + { + // Don't do anything after a canceled drag until the user lets go of + // the mouse button! + return; + } + + mouseX = e->x(); + mouseY = e->y(); + + scrollX = 0; + scrollY = 0; + int d = m_view->renderer()->fontHeight(); + + if (mouseX < 0) + scrollX = -d; + + if (mouseX > width()) + scrollX = d; + + if (mouseY < 0) + { + mouseY = 0; + scrollY = -d; + } + + if (mouseY > height()) + { + mouseY = height(); + scrollY = d; + } + + placeCursor( QPoint( mouseX, mouseY ), true ); + + } + else + { + if (isTargetSelected( e->pos() ) ) { + // mouse is over selected text. indicate that the text is draggable by setting + // the arrow cursor as other Qt text editing widgets do + if (m_mouseCursor != ArrowCursor) { + setCursor( KCursor::arrowCursor() ); + m_mouseCursor = ArrowCursor; + } + } else { + // normal text cursor + if (m_mouseCursor != IbeamCursor) { + setCursor( KCursor::ibeamCursor() ); + m_mouseCursor = IbeamCursor; + } + } + + if (m_textHintEnabled) + { + m_textHintTimer.start(m_textHintTimeout); + m_textHintMouseX=e->x(); + m_textHintMouseY=e->y(); + } + } +} + +void KateViewInternal::paintEvent(QPaintEvent *e) +{ + paintText(e->rect().x(), e->rect().y(), e->rect().width(), e->rect().height()); +} + +void KateViewInternal::resizeEvent(QResizeEvent* e) +{ + bool expandedHorizontally = width() > e->oldSize().width(); + bool expandedVertically = height() > e->oldSize().height(); + bool heightChanged = height() != e->oldSize().height(); + + m_madeVisible = false; + + if (heightChanged) { + setAutoCenterLines(m_autoCenterLines, false); + m_cachedMaxStartPos.setPos(-1, -1); + } + + if (m_view->dynWordWrap()) { + bool dirtied = false; + + for (uint i = 0; i < lineRanges.count(); i++) { + // find the first dirty line + // the word wrap updateView algorithm is forced to check all lines after a dirty one + if (lineRanges[i].wrap || + (!expandedHorizontally && (lineRanges[i].endX - lineRanges[i].startX) > width())) { + dirtied = lineRanges[i].dirty = true; + break; + } + } + + if (dirtied || heightChanged) { + updateView(true); + leftBorder->update(); + } + + if (width() < e->oldSize().width()) { + if (!m_view->wrapCursor()) { + // May have to restrain cursor to new smaller width... + if (cursor.col() > m_doc->lineLength(cursor.line())) { + KateLineRange thisRange = currentRange(); + + KateTextCursor newCursor(cursor.line(), thisRange.endCol + ((width() - thisRange.xOffset() - (thisRange.endX - thisRange.startX)) / m_view->renderer()->spaceWidth()) - 1); + updateCursor(newCursor); + } + } + } + + } else { + updateView(); + + if (expandedHorizontally && startX() > 0) + scrollColumns(startX() - (width() - e->oldSize().width())); + } + + if (expandedVertically) { + KateTextCursor max = maxStartPos(); + if (startPos() > max) + scrollPos(max); + } +} + +void KateViewInternal::scrollTimeout () +{ + if (scrollX || scrollY) + { + scrollLines (startPos().line() + (scrollY / (int)m_view->renderer()->fontHeight())); + placeCursor( QPoint( mouseX, mouseY ), true ); + } +} + +void KateViewInternal::cursorTimeout () +{ + m_view->renderer()->setDrawCaret(!m_view->renderer()->drawCaret()); + paintCursor(); +} + +void KateViewInternal::textHintTimeout () +{ + m_textHintTimer.stop (); + + KateLineRange thisRange = yToKateLineRange(m_textHintMouseY); + + if (thisRange.line == -1) return; + + if (m_textHintMouseX> (lineMaxCursorX(thisRange) - thisRange.startX)) return; + + int realLine = thisRange.line; + int startCol = thisRange.startCol; + + KateTextCursor c(realLine, 0); + m_view->renderer()->textWidth( c, startX() + m_textHintMouseX, startCol); + + QString tmp; + + emit m_view->needTextHint(c.line(), c.col(), tmp); + + if (!tmp.isEmpty()) kdDebug(13030)<<"Hint text: "<<tmp<<endl; +} + +void KateViewInternal::focusInEvent (QFocusEvent *) +{ + if (KApplication::cursorFlashTime() > 0) + m_cursorTimer.start ( KApplication::cursorFlashTime() / 2 ); + + if (m_textHintEnabled) + m_textHintTimer.start( m_textHintTimeout ); + + paintCursor(); + + m_doc->setActiveView( m_view ); + + emit m_view->gotFocus( m_view ); +} + +void KateViewInternal::focusOutEvent (QFocusEvent *) +{ + if( m_view->renderer() && ! m_view->m_codeCompletion->codeCompletionVisible() ) + { + m_cursorTimer.stop(); + + m_view->renderer()->setDrawCaret(true); + paintCursor(); + emit m_view->lostFocus( m_view ); + } + + m_textHintTimer.stop(); +} + +void KateViewInternal::doDrag() +{ + dragInfo.state = diDragging; + dragInfo.dragObject = new QTextDrag(m_view->selection(), this); + dragInfo.dragObject->drag(); +} + +void KateViewInternal::dragEnterEvent( QDragEnterEvent* event ) +{ + event->accept( (QTextDrag::canDecode(event) && m_doc->isReadWrite()) || + KURLDrag::canDecode(event) ); +} + +void KateViewInternal::dragMoveEvent( QDragMoveEvent* event ) +{ + // track the cursor to the current drop location + placeCursor( event->pos(), true, false ); + + // important: accept action to switch between copy and move mode + // without this, the text will always be copied. + event->acceptAction(); +} + +void KateViewInternal::dropEvent( QDropEvent* event ) +{ + if ( KURLDrag::canDecode(event) ) { + + emit dropEventPass(event); + + } else if ( QTextDrag::canDecode(event) && m_doc->isReadWrite() ) { + + QString text; + + if (!QTextDrag::decode(event, text)) + return; + + // is the source our own document? + bool priv = false; + if (event->source() && event->source()->inherits("KateViewInternal")) + priv = m_doc->ownedView( ((KateViewInternal*)(event->source()))->m_view ); + + // dropped on a text selection area? + bool selected = isTargetSelected( event->pos() ); + + if( priv && selected ) { + // this is a drag that we started and dropped on our selection + // ignore this case + return; + } + + // use one transaction + m_doc->editStart (); + + // on move: remove selected text; on copy: duplicate text + if ( event->action() != QDropEvent::Copy ) + m_view->removeSelectedText(); + + m_doc->insertText( cursor.line(), cursor.col(), text ); + + m_doc->editEnd (); + + placeCursor( event->pos() ); + + event->acceptAction(); + updateView(); + } + + // finally finish drag and drop mode + dragInfo.state = diNone; + // important, because the eventFilter`s DragLeave does not occur + stopDragScroll(); +} +//END EVENT HANDLING STUFF + +void KateViewInternal::clear() +{ + cursor.setPos(0, 0); + displayCursor.setPos(0, 0); +} + +void KateViewInternal::wheelEvent(QWheelEvent* e) +{ + if (m_lineScroll->minValue() != m_lineScroll->maxValue() && e->orientation() != Qt::Horizontal) { + // React to this as a vertical event + if ( ( e->state() & ControlButton ) || ( e->state() & ShiftButton ) ) { + if (e->delta() > 0) + scrollPrevPage(); + else + scrollNextPage(); + } else { + scrollViewLines(-((e->delta() / 120) * QApplication::wheelScrollLines())); + // maybe a menu was opened or a bubbled window title is on us -> we shall erase it + update(); + leftBorder->update(); + } + + } else if (columnScrollingPossible()) { + QWheelEvent copy = *e; + QApplication::sendEvent(m_columnScroll, ©); + + } else { + e->ignore(); + } +} + +void KateViewInternal::startDragScroll() +{ + if ( !m_dragScrollTimer.isActive() ) { + m_dragScrollTimer.start( scrollTime ); + } +} + +void KateViewInternal::stopDragScroll() +{ + m_dragScrollTimer.stop(); + updateView(); +} + +void KateViewInternal::doDragScroll() +{ + QPoint p = this->mapFromGlobal( QCursor::pos() ); + + int dx = 0, dy = 0; + if ( p.y() < scrollMargin ) { + dy = p.y() - scrollMargin; + } else if ( p.y() > height() - scrollMargin ) { + dy = scrollMargin - (height() - p.y()); + } + + if ( p.x() < scrollMargin ) { + dx = p.x() - scrollMargin; + } else if ( p.x() > width() - scrollMargin ) { + dx = scrollMargin - (width() - p.x()); + } + + dy /= 4; + + if (dy) + scrollLines(startPos().line() + dy); + + if (columnScrollingPossible () && dx) + scrollColumns(kMin (m_startX + dx, m_columnScroll->maxValue())); + + if (!dy && !dx) + stopDragScroll(); +} + +void KateViewInternal::enableTextHints(int timeout) +{ + m_textHintTimeout=timeout; + m_textHintEnabled=true; + m_textHintTimer.start(timeout); +} + +void KateViewInternal::disableTextHints() +{ + m_textHintEnabled=false; + m_textHintTimer.stop (); +} + +bool KateViewInternal::columnScrollingPossible () +{ + return !m_view->dynWordWrap() && m_columnScroll->isEnabled() && (m_columnScroll->maxValue() > 0); +} + +//BEGIN EDIT STUFF +void KateViewInternal::editStart() +{ + editSessionNumber++; + + if (editSessionNumber > 1) + return; + + editIsRunning = true; + editOldCursor = cursor; +} + +void KateViewInternal::editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom) +{ + if (editSessionNumber == 0) + return; + + editSessionNumber--; + + if (editSessionNumber > 0) + return; + + if (tagFrom && (editTagLineStart <= int(m_doc->getRealLine(startLine())))) + tagAll(); + else + tagLines (editTagLineStart, tagFrom ? m_doc->lastLine() : editTagLineEnd, true); + + if (editOldCursor == cursor) + updateBracketMarks(); + + if (m_imPreeditLength <= 0) + updateView(true); + + if ((editOldCursor != cursor) && (m_imPreeditLength <= 0)) + { + m_madeVisible = false; + updateCursor ( cursor, true ); + } + else if ( m_view == m_doc->activeView() ) + { + makeVisible(displayCursor, displayCursor.col()); + } + + editIsRunning = false; +} + +void KateViewInternal::editSetCursor (const KateTextCursor &cursor) +{ + if (this->cursor != cursor) + { + this->cursor.setPos (cursor); + } +} +//END + +void KateViewInternal::viewSelectionChanged () +{ + if (!m_view->hasSelection()) + { + selectAnchor.setPos (-1, -1); + selStartCached.setPos (-1, -1); + } +} + +//BEGIN IM INPUT STUFF +void KateViewInternal::imStartEvent( QIMEvent *e ) +{ + if ( m_doc->m_bReadOnly ) { + e->ignore(); + return; + } + + if ( m_view->hasSelection() ) + m_view->removeSelectedText(); + + m_imPreeditStartLine = cursor.line(); + m_imPreeditStart = cursor.col(); + m_imPreeditLength = 0; + m_imPreeditSelStart = m_imPreeditStart; + + m_view->setIMSelectionValue( m_imPreeditStartLine, m_imPreeditStart, 0, 0, 0, true ); +} + +void KateViewInternal::imComposeEvent( QIMEvent *e ) +{ + if ( m_doc->m_bReadOnly ) { + e->ignore(); + return; + } + + // remove old preedit + if ( m_imPreeditLength > 0 ) { + cursor.setPos( m_imPreeditStartLine, m_imPreeditStart ); + m_doc->removeText( m_imPreeditStartLine, m_imPreeditStart, + m_imPreeditStartLine, m_imPreeditStart + m_imPreeditLength ); + } + + m_imPreeditLength = e->text().length(); + m_imPreeditSelStart = m_imPreeditStart + e->cursorPos(); + + // update selection + m_view->setIMSelectionValue( m_imPreeditStartLine, m_imPreeditStart, m_imPreeditStart + m_imPreeditLength, + m_imPreeditSelStart, m_imPreeditSelStart + e->selectionLength(), + true ); + + // insert new preedit + m_doc->insertText( m_imPreeditStartLine, m_imPreeditStart, e->text() ); + + + // update cursor + cursor.setPos( m_imPreeditStartLine, m_imPreeditSelStart ); + updateCursor( cursor, true ); + + updateView( true ); +} + +void KateViewInternal::imEndEvent( QIMEvent *e ) +{ + if ( m_doc->m_bReadOnly ) { + e->ignore(); + return; + } + + if ( m_imPreeditLength > 0 ) { + cursor.setPos( m_imPreeditStartLine, m_imPreeditStart ); + m_doc->removeText( m_imPreeditStartLine, m_imPreeditStart, + m_imPreeditStartLine, m_imPreeditStart + m_imPreeditLength ); + } + + m_view->setIMSelectionValue( m_imPreeditStartLine, m_imPreeditStart, 0, 0, 0, false ); + + if ( e->text().length() > 0 ) { + m_doc->insertText( cursor.line(), cursor.col(), e->text() ); + + if ( !m_cursorTimer.isActive() && KApplication::cursorFlashTime() > 0 ) + m_cursorTimer.start ( KApplication::cursorFlashTime() / 2 ); + + updateView( true ); + updateCursor( cursor, true ); + } + + m_imPreeditStart = 0; + m_imPreeditLength = 0; + m_imPreeditSelStart = 0; +} +//END IM INPUT STUFF + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateviewinternal.h b/kate/part/kateviewinternal.h new file mode 100644 index 000000000..c5004d6be --- /dev/null +++ b/kate/part/kateviewinternal.h @@ -0,0 +1,397 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002 John Firebaugh <jfirebaugh@kde.org> + Copyright (C) 2002 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2002 Christoph Cullmann <cullmann@kde.org> + + Based on: + KWriteView : Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef _KATE_VIEW_INTERNAL_ +#define _KATE_VIEW_INTERNAL_ + +#include "katecursor.h" +#include "katesupercursor.h" +#include "katelinerange.h" +#include "katetextline.h" +#include "katedocument.h" + +#include <qpoint.h> +#include <qtimer.h> +#include <qintdict.h> + +class KateView; +class KateIconBorder; +class KateScrollBar; + +class QHBoxLayout; +class QVBoxLayout; +class QScrollBar; + +enum Bias +{ + left = -1, + none = 0, + right = 1 +}; + +class KateViewInternal : public QWidget +{ + Q_OBJECT + + friend class KateView; + friend class KateIconBorder; + friend class KateScrollBar; + friend class CalculatingCursor; + friend class BoundedCursor; + friend class WrappingCursor; + + public: + KateViewInternal ( KateView *view, KateDocument *doc ); + ~KateViewInternal (); + + //BEGIN EDIT STUFF + public: + void editStart (); + void editEnd (int editTagLineStart, int editTagLineEnd, bool tagFrom); + + void editSetCursor (const KateTextCursor &cursor); + + private: + uint editSessionNumber; + bool editIsRunning; + KateTextCursor editOldCursor; + //END + + //BEGIN TAG & CLEAR & UPDATE STUFF + public: + bool tagLine (const KateTextCursor& virtualCursor); + + bool tagLines (int start, int end, bool realLines = false); + bool tagLines (KateTextCursor start, KateTextCursor end, bool realCursors = false); + + void tagAll (); + + void clear (); + //END + + private: + void updateView (bool changed = false, int viewLinesScrolled = 0); + void makeVisible (const KateTextCursor& c, uint endCol, bool force = false, bool center = false, bool calledExternally = false); + + public: + inline const KateTextCursor& startPos() const { return m_startPos; } + inline uint startLine () const { return m_startPos.line(); } + inline uint startX () const { return m_startX; } + + KateTextCursor endPos () const; + uint endLine () const; + + KateLineRange yToKateLineRange(uint y) const; + + void prepareForDynWrapChange(); + void dynWrapChanged(); + + KateView *view () { return m_view; } + + public slots: + void slotIncFontSizes(); + void slotDecFontSizes(); + + private slots: + void scrollLines(int line); // connected to the sliderMoved of the m_lineScroll + void scrollViewLines(int offset); + void scrollNextPage(); + void scrollPrevPage(); + void scrollPrevLine(); + void scrollNextLine(); + void scrollColumns (int x); // connected to the valueChanged of the m_columnScroll + void viewSelectionChanged (); + + public: + void doReturn(); + void doDelete(); + void doBackspace(); + void doTranspose(); + void doDeleteWordLeft(); + void doDeleteWordRight(); + + void cursorLeft(bool sel=false); + void cursorRight(bool sel=false); + void wordLeft(bool sel=false); + void wordRight(bool sel=false); + void home(bool sel=false); + void end(bool sel=false); + void cursorUp(bool sel=false); + void cursorDown(bool sel=false); + void cursorToMatchingBracket(bool sel=false); + void scrollUp(); + void scrollDown(); + void topOfView(bool sel=false); + void bottomOfView(bool sel=false); + void pageUp(bool sel=false); + void pageDown(bool sel=false); + void top(bool sel=false); + void bottom(bool sel=false); + void top_home(bool sel=false); + void bottom_end(bool sel=false); + + inline const KateTextCursor& getCursor() { return cursor; } + QPoint cursorCoordinates(); + + void paintText (int x, int y, int width, int height, bool paintOnlyDirty = false); + + // EVENT HANDLING STUFF - IMPORTANT + protected: + void paintEvent(QPaintEvent *e); + bool eventFilter( QObject *obj, QEvent *e ); + void keyPressEvent( QKeyEvent* ); + void keyReleaseEvent( QKeyEvent* ); + void resizeEvent( QResizeEvent* ); + void mousePressEvent( QMouseEvent* ); + void mouseDoubleClickEvent( QMouseEvent* ); + void mouseReleaseEvent( QMouseEvent* ); + void mouseMoveEvent( QMouseEvent* ); + void dragEnterEvent( QDragEnterEvent* ); + void dragMoveEvent( QDragMoveEvent* ); + void dropEvent( QDropEvent* ); + void showEvent ( QShowEvent *); + void wheelEvent(QWheelEvent* e); + void focusInEvent (QFocusEvent *); + void focusOutEvent (QFocusEvent *); + + void contextMenuEvent ( QContextMenuEvent * e ); + + private slots: + void tripleClickTimeout(); + + signals: + // emitted when KateViewInternal is not handling its own URI drops + void dropEventPass(QDropEvent*); + + private slots: + void slotRegionVisibilityChangedAt(unsigned int); + void slotRegionBeginEndAddedRemoved(unsigned int); + void slotCodeFoldingChanged(); + + private: + void moveChar( Bias bias, bool sel ); + void moveEdge( Bias bias, bool sel ); + KateTextCursor maxStartPos(bool changed = false); + void scrollPos(KateTextCursor& c, bool force = false, bool calledExternally = false); + void scrollLines( int lines, bool sel ); + + uint linesDisplayed() const; + + int lineToY(uint viewLine) const; + + void updateSelection( const KateTextCursor&, bool keepSel ); + void updateCursor( const KateTextCursor& newCursor, bool force = false, bool center = false, bool calledExternally = false ); + void updateBracketMarks(); + + void paintCursor(); + + void updateMicroFocusHint(); + + void placeCursor( const QPoint& p, bool keepSelection = false, bool updateSelection = true ); + bool isTargetSelected( const QPoint& p ); + + void doDrag(); + + KateView *m_view; + KateDocument* m_doc; + class KateIconBorder *leftBorder; + + int mouseX; + int mouseY; + int scrollX; + int scrollY; + + Qt::CursorShape m_mouseCursor; + + KateSuperCursor cursor; + KateTextCursor displayCursor; + int cXPos; + + bool possibleTripleClick; + + // Bracket mark + KateBracketRange bm; + + enum DragState { diNone, diPending, diDragging }; + + struct _dragInfo { + DragState state; + QPoint start; + QTextDrag* dragObject; + } dragInfo; + + uint iconBorderHeight; + + // + // line scrollbar + first visible (virtual) line in the current view + // + KateScrollBar *m_lineScroll; + QWidget* m_dummy; + QVBoxLayout* m_lineLayout; + QHBoxLayout* m_colLayout; + + // These are now cursors to account for word-wrap. + KateSuperCursor m_startPos; + + // This is set to false on resize or scroll (other than that called by makeVisible), + // so that makeVisible is again called when a key is pressed and the cursor is in the same spot + bool m_madeVisible; + bool m_shiftKeyPressed; + + // How many lines to should be kept visible above/below the cursor when possible + void setAutoCenterLines(int viewLines, bool updateView = true); + int m_autoCenterLines; + int m_minLinesVisible; + + // + // column scrollbar + x position + // + QScrollBar *m_columnScroll; + int m_startX; + + // has selection changed while your mouse or shift key is pressed + bool m_selChangedByUser; + KateTextCursor selectAnchor; + + enum SelectionMode { Default=0, Word, Line, Mouse }; ///< for drag selection. @since 2.3 + uint m_selectionMode; + // when drag selecting after double/triple click, keep the initial selected + // word/line independant of direction. + // They get set in the event of a double click, and is used with mouse move + leftbutton + KateTextCursor selStartCached; + KateTextCursor selEndCached; + + // + // lines Ranges, mostly useful to speedup + dyn. word wrap + // + QMemArray<KateLineRange> lineRanges; + + // maximal lenght of textlines visible from given startLine + int maxLen(uint startLine); + + // are we allowed to scroll columns? + bool columnScrollingPossible (); + + // returns the maximum X value / col value a cursor can take for a specific line range + int lineMaxCursorX(const KateLineRange& range); + int lineMaxCol(const KateLineRange& range); + + // get the values for a specific range. + // specify lastLine to get the next line of a range. + KateLineRange range(int realLine, const KateLineRange* previous = 0L); + + KateLineRange currentRange(); + KateLineRange previousRange(); + KateLineRange nextRange(); + + // Finds the lineRange currently occupied by the cursor. + KateLineRange range(const KateTextCursor& realCursor); + + // Returns the lineRange of the specified realLine + viewLine. + KateLineRange range(uint realLine, int viewLine); + + // find the view line of cursor c (0 = same line, 1 = down one, etc.) + uint viewLine(const KateTextCursor& realCursor); + + // find the view line of the cursor, relative to the display (0 = top line of view, 1 = second line, etc.) + // if limitToVisible is true, only lines which are currently visible will be searched for, and -1 returned if the line is not visible. + int displayViewLine(const KateTextCursor& virtualCursor, bool limitToVisible = false); + + // find the index of the last view line for a specific line + uint lastViewLine(uint realLine); + + // count the number of view lines for a real line + uint viewLineCount(uint realLine); + + // find the cursor offset by (offset) view lines from a cursor. + // when keepX is true, the column position will be calculated based on the x + // position of the specified cursor. + KateTextCursor viewLineOffset(const KateTextCursor& virtualCursor, int offset, bool keepX = false); + + // These variable holds the most recent maximum real & visible column number + bool m_preserveMaxX; + int m_currentMaxX; + + bool m_usePlainLines; // accept non-highlighted lines if this is set + + inline KateTextLine::Ptr textLine( int realLine ) + { + if (m_usePlainLines) + return m_doc->plainKateTextLine(realLine); + else + return m_doc->kateTextLine(realLine); + } + + bool m_updatingView; + int m_wrapChangeViewLine; + KateTextCursor m_cachedMaxStartPos; + + private slots: + void doDragScroll(); + void startDragScroll(); + void stopDragScroll(); + + private: + // Timers + QTimer m_dragScrollTimer; + QTimer m_scrollTimer; + QTimer m_cursorTimer; + QTimer m_textHintTimer; + + static const int scrollTime = 30; + static const int scrollMargin = 16; + + private slots: + void scrollTimeout (); + void cursorTimeout (); + void textHintTimeout (); + + //TextHint + public: + void enableTextHints(int timeout); + void disableTextHints(); + + private: + bool m_textHintEnabled; + int m_textHintTimeout; + int m_textHintMouseX; + int m_textHintMouseY; + + /** + * IM input stuff + */ + protected: + void imStartEvent( QIMEvent *e ); + void imComposeEvent( QIMEvent *e ); + void imEndEvent( QIMEvent *e ); + + private: + int m_imPreeditStartLine; + int m_imPreeditStart; + int m_imPreeditLength; + int m_imPreeditSelStart; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/test_regression.cpp b/kate/part/test_regression.cpp new file mode 100644 index 000000000..af36f65f9 --- /dev/null +++ b/kate/part/test_regression.cpp @@ -0,0 +1,1344 @@ +/** + * This file is part of the KDE project + * + * Copyright (C) 2001,2003 Peter Kelly (pmk@post.com) + * Copyright (C) 2003,2004 Stephan Kulow (coolo@kde.org) + * Copyright (C) 2004 Dirk Mueller ( mueller@kde.org ) + * Copyright 2006 Leo Savernik (l.savernik@aon.at) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include <stdlib.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/types.h> +#include <unistd.h> +#include <pwd.h> +#include <signal.h> + +#include <kapplication.h> +#include <kstandarddirs.h> +#include <qimage.h> +#include <qfile.h> +#include "test_regression.h" +#include <unistd.h> +#include <stdio.h> + +#include <kaction.h> +#include <kcmdlineargs.h> +#include "katefactory.h" +#include <kio/job.h> +#include <kmainwindow.h> +#include <ksimpleconfig.h> +#include <kglobalsettings.h> + +#include <qcolor.h> +#include <qcursor.h> +#include <qdir.h> +#include <qevent.h> +#include <qobject.h> +#include <qpushbutton.h> +#include <qscrollview.h> +#include <qstring.h> +#include <qregexp.h> +#include <qtextstream.h> +#include <qvaluelist.h> +#include <qwidget.h> +#include <qfileinfo.h> +#include <qtimer.h> +#include <kstatusbar.h> +#include <qfileinfo.h> + +#include "katedocument.h" +#include "kateview.h" +#include <kparts/browserextension.h> +#include "katejscript.h" +#include "katedocumenthelpers.h" +#include "kateconfig.h" +#include "../interfaces/katecmd.h" + +using namespace KJS; + +#define BASE_DIR_CONFIG "/.testkateregression-3.5" + +//BEGIN TestJScriptEnv + +TestJScriptEnv::TestJScriptEnv(KateDocument *part) { + ExecState *exec = m_interpreter->globalExec(); + + KJS::ObjectImp *wd = wrapDocument(m_interpreter->globalExec(), part); + KateView *v = static_cast<KateView *>(part->widget()); + KJS::ObjectImp *wv = new KateViewObject(exec, v, wrapView(m_interpreter->globalExec(), v)); + + *m_view = KJS::Object(wv); + *m_document = KJS::Object(wd); + m_output = new OutputObject(exec, part, v); + m_output->ref(); + + // recreate properties + m_interpreter->globalObject().put(exec, "document", *m_document); + m_interpreter->globalObject().put(exec, "view", *m_view); + // create new properties + m_interpreter->globalObject().put(exec, "output", KJS::Object(m_output)); + // add convenience shortcuts + m_interpreter->globalObject().put(exec, "d", *m_document); + m_interpreter->globalObject().put(exec, "v", *m_view); + m_interpreter->globalObject().put(exec, "out", KJS::Object(m_output)); + m_interpreter->globalObject().put(exec, "o", KJS::Object(m_output)); +} + +TestJScriptEnv::~TestJScriptEnv() { + m_output->deref(); +} + +//END TestJScriptEnv + +//BEGIN KateViewObject + +KateViewObject::KateViewObject(ExecState *exec, KateView *v, ObjectImp *fallback) + : view(v), fallback(fallback) +{ +// put a function +#define PUT_FUNC(name, enumval) \ + putDirect(#name, new KateViewFunction(exec,v,KateViewFunction::enumval,1), DontEnum) + fallback->ref(); + + PUT_FUNC(keyReturn, KeyReturn); + PUT_FUNC(enter, KeyReturn); + PUT_FUNC(type, Type); + PUT_FUNC(keyDelete, KeyDelete); + PUT_FUNC(deleteWordRight, DeleteWordRight); + PUT_FUNC(transpose, Transpose); + PUT_FUNC(cursorLeft, CursorLeft); + PUT_FUNC(cursorPrev, CursorLeft); + PUT_FUNC(left, CursorLeft); + PUT_FUNC(prev, CursorLeft); + PUT_FUNC(shiftCursorLeft, ShiftCursorLeft); + PUT_FUNC(shiftCursorPrev, ShiftCursorLeft); + PUT_FUNC(shiftLeft, ShiftCursorLeft); + PUT_FUNC(shiftPrev, ShiftCursorLeft); + PUT_FUNC(cursorRight, CursorRight); + PUT_FUNC(cursorNext, CursorRight); + PUT_FUNC(right, CursorRight); + PUT_FUNC(next, CursorRight); + PUT_FUNC(shiftCursorRight, ShiftCursorRight); + PUT_FUNC(shiftCursorNext, ShiftCursorRight); + PUT_FUNC(shiftRight, ShiftCursorRight); + PUT_FUNC(shiftNext, ShiftCursorRight); + PUT_FUNC(wordLeft, WordLeft); + PUT_FUNC(wordPrev, WordLeft); + PUT_FUNC(shiftWordLeft, ShiftWordLeft); + PUT_FUNC(shiftWordPrev, ShiftWordLeft); + PUT_FUNC(wordRight, WordRight); + PUT_FUNC(wordNext, WordRight); + PUT_FUNC(shiftWordRight, ShiftWordRight); + PUT_FUNC(shiftWordNext, ShiftWordRight); + PUT_FUNC(home, Home); + PUT_FUNC(shiftHome, ShiftHome); + PUT_FUNC(end, End); + PUT_FUNC(shiftEnd, ShiftEnd); + PUT_FUNC(up, Up); + PUT_FUNC(shiftUp, ShiftUp); + PUT_FUNC(down, Down); + PUT_FUNC(shiftDown, ShiftDown); + PUT_FUNC(scrollUp, ScrollUp); + PUT_FUNC(scrollDown, ScrollDown); + PUT_FUNC(topOfView, TopOfView); + PUT_FUNC(shiftTopOfView, ShiftTopOfView); + PUT_FUNC(bottomOfView, BottomOfView); + PUT_FUNC(shiftBottomOfView, ShiftBottomOfView); + PUT_FUNC(pageUp, PageUp); + PUT_FUNC(shiftPageUp, ShiftPageUp); + PUT_FUNC(pageDown, PageDown); + PUT_FUNC(shiftPageDown, ShiftPageDown); + PUT_FUNC(top, Top); + PUT_FUNC(shiftTop, ShiftTop); + PUT_FUNC(bottom, Bottom); + PUT_FUNC(shiftBottom, ShiftBottom); + PUT_FUNC(toMatchingBracket, ToMatchingBracket); + PUT_FUNC(shiftToMatchingBracket, ShiftToMatchingBracket); +#undef PUT_FUNC +} + +KateViewObject::~KateViewObject() +{ + fallback->deref(); +} + +const ClassInfo *KateViewObject::classInfo() const { + // evil hack II: disguise as fallback, otherwise we can't fall back + return fallback->classInfo(); +} + +Value KateViewObject::get(ExecState *exec, const Identifier &propertyName) const +{ + ValueImp *val = getDirect(propertyName); + if (val) + return Value(val); + + return fallback->get(exec, propertyName); +} + +//END KateViewObject + +//BEGIN KateViewFunction + +KateViewFunction::KateViewFunction(ExecState */*exec*/, KateView *v, int _id, int length) +{ + m_view = v; + id = _id; + putDirect("length",length); +} + +bool KateViewFunction::implementsCall() const +{ + return true; +} + +Value KateViewFunction::call(ExecState *exec, Object &/*thisObj*/, const List &args) +{ + // calls a function repeatedly as specified by its first parameter (once + // if not specified). +#define REP_CALL(enumval, func) \ + case enumval: {\ + int cnt = 1;\ + if (args.size() > 0) cnt = args[0].toInt32(exec);\ + while (cnt-- > 0) { m_view->func(); }\ + return Undefined();\ + } + switch (id) { + REP_CALL(KeyReturn, keyReturn); + REP_CALL(KeyDelete, keyDelete); + REP_CALL(DeleteWordRight, deleteWordRight); + REP_CALL(Transpose, transpose); + REP_CALL(CursorLeft, cursorLeft); + REP_CALL(ShiftCursorLeft, shiftCursorLeft); + REP_CALL(CursorRight, cursorRight); + REP_CALL(ShiftCursorRight, shiftCursorRight); + REP_CALL(WordLeft, wordLeft); + REP_CALL(ShiftWordLeft, shiftWordLeft); + REP_CALL(WordRight, wordRight); + REP_CALL(ShiftWordRight, shiftWordRight); + REP_CALL(Home, home); + REP_CALL(ShiftHome, shiftHome); + REP_CALL(End, end); + REP_CALL(ShiftEnd, shiftEnd); + REP_CALL(Up, up); + REP_CALL(ShiftUp, shiftUp); + REP_CALL(Down, down); + REP_CALL(ShiftDown, shiftDown); + REP_CALL(ScrollUp, scrollUp); + REP_CALL(ScrollDown, scrollDown); + REP_CALL(TopOfView, topOfView); + REP_CALL(ShiftTopOfView, shiftTopOfView); + REP_CALL(BottomOfView, bottomOfView); + REP_CALL(ShiftBottomOfView, shiftBottomOfView); + REP_CALL(PageUp, pageUp); + REP_CALL(ShiftPageUp, shiftPageUp); + REP_CALL(PageDown, pageDown); + REP_CALL(ShiftPageDown, shiftPageDown); + REP_CALL(Top, top); + REP_CALL(ShiftTop, shiftTop); + REP_CALL(Bottom, bottom); + REP_CALL(ShiftBottom, shiftBottom); + REP_CALL(ToMatchingBracket, toMatchingBracket); + REP_CALL(ShiftToMatchingBracket, shiftToMatchingBracket); + case Type: { + UString str = args[0].toString(exec); + QString res = str.qstring(); + return Boolean(m_view->doc()->typeChars(m_view, res)); + } + } + + return Undefined(); +#undef REP_CALL +} + +//END KateViewFunction + +//BEGIN OutputObject + +OutputObject::OutputObject(KJS::ExecState *exec, KateDocument *d, KateView *v) : doc(d), view(v), changed(0), outstr(0) { + putDirect("write", new OutputFunction(exec,this,OutputFunction::Write,-1), DontEnum); + putDirect("print", new OutputFunction(exec,this,OutputFunction::Write,-1), DontEnum); + putDirect("writeln", new OutputFunction(exec,this,OutputFunction::Writeln,-1), DontEnum); + putDirect("println", new OutputFunction(exec,this,OutputFunction::Writeln,-1), DontEnum); + putDirect("writeLn", new OutputFunction(exec,this,OutputFunction::Writeln,-1), DontEnum); + putDirect("printLn", new OutputFunction(exec,this,OutputFunction::Writeln,-1), DontEnum); + + putDirect("writeCursorPosition", new OutputFunction(exec,this,OutputFunction::WriteCursorPosition,-1), DontEnum); + putDirect("cursorPosition", new OutputFunction(exec,this,OutputFunction::WriteCursorPosition,-1), DontEnum); + putDirect("pos", new OutputFunction(exec,this,OutputFunction::WriteCursorPosition,-1), DontEnum); + putDirect("writeCursorPositionln", new OutputFunction(exec,this,OutputFunction::WriteCursorPositionln,-1), DontEnum); + putDirect("cursorPositionln", new OutputFunction(exec,this,OutputFunction::WriteCursorPositionln,-1), DontEnum); + putDirect("posln", new OutputFunction(exec,this,OutputFunction::WriteCursorPositionln,-1), DontEnum); + +} + +OutputObject::~OutputObject() { +} + +KJS::UString OutputObject::className() const { + return UString("OutputObject"); +} + +//END OutputObject + +//BEGIN OutputFunction + +OutputFunction::OutputFunction(KJS::ExecState *exec, OutputObject *output, int _id, int length) + : o(output) +{ + id = _id; + if (length >= 0) + putDirect("length",length); +} + +bool OutputFunction::implementsCall() const +{ + return true; +} + +KJS::Value OutputFunction::call(KJS::ExecState *exec, KJS::Object &thisObj, const KJS::List &args) +{ + if (!*o->changed) *o->outstr = QString(); + + switch (id) { + case Write: + case Writeln: { + // Gather all parameters and concatenate to string + QString res; + for (int i = 0; i < args.size(); i++) { + res += args[i].toString(exec).qstring(); + } + + if (id == Writeln) + res += "\n"; + + *o->outstr += res; + break; + } + case WriteCursorPositionln: + case WriteCursorPosition: { + // Gather all parameters and concatenate to string + QString res; + for (int i = 0; i < args.size(); i++) { + res += args[i].toString(exec).qstring(); + } + + // Append cursor position + uint l, c; + o->view->cursorPosition(&l, &c); + res += "(" + QString::number(l) + "," + QString::number(c) + ")"; + + if (id == WriteCursorPositionln) + res += "\n"; + + *o->outstr += res; + break; + } + } + + *o->changed = true; + return Undefined(); +} + +//END OutputFunction + +// ------------------------------------------------------------------------- + +const char failureSnapshotPrefix[] = "testkateregressionrc-FS."; + +static QString findMostRecentFailureSnapshot() { + QDir dir(kapp->dirs()->saveLocation("config"), + QString(failureSnapshotPrefix)+"*", + QDir::Time, QDir::Files); + return dir[0].mid(sizeof failureSnapshotPrefix - 1); +} + +static KCmdLineOptions options[] = +{ + { "b", 0, 0 }, + { "base <base_dir>", "Directory containing tests, basedir and output directories.", 0}, + { "cmp-failures <snapshot>", "Compare failures of this testrun against snapshot <snapshot>. Defaults to the most recently captured failure snapshot or none if none exists.", 0 }, + { "d", 0, 0 }, + { "debug", "Do not supress debug output", 0}, + { "g", 0, 0 } , + { "genoutput", "Regenerate baseline (instead of checking)", 0 } , + { "keep-output", "Keep output files even on success", 0 }, + { "save-failures <snapshot>", "Save failures of this testrun as failure snapshot <snapshot>", 0 }, + { "s", 0, 0 } , + { "show", "Show the window while running tests", 0 } , + { "t", 0, 0 } , + { "test <filename>", "Only run a single test. Multiple options allowed.", 0 } , + { "o", 0, 0 }, + { "output <directory>", "Put output in <directory> instead of <base_dir>/output", 0 } , + { "+[base_dir]", "Directory containing tests,basedir and output directories. Only regarded if -b is not specified.", 0 } , + { "+[testcases]", "Relative path to testcase, or directory of testcases to be run (equivalent to -t).", 0 } , + KCmdLineLastOption +}; + +int main(int argc, char *argv[]) +{ + // forget about any settings + passwd* pw = getpwuid( getuid() ); + if (!pw) { + fprintf(stderr, "dang, I don't even know who I am.\n"); + exit(1); + } + + QString kh("/var/tmp/%1_kate_non_existent"); + kh = kh.arg( pw->pw_name ); + setenv( "KDEHOME", kh.latin1(), 1 ); + setenv( "LC_ALL", "C", 1 ); + setenv( "LANG", "C", 1 ); + +// signal( SIGALRM, signal_handler ); + + KCmdLineArgs::init(argc, argv, "testregression", "TestRegression", + "Regression tester for kate", "1.0"); + KCmdLineArgs::addCmdLineOptions(options); + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs( ); + + QCString baseDir = args->getOption("base"); + QCString baseDirConfigFile(::getenv("HOME") + QCString(BASE_DIR_CONFIG)); + { + QFile baseDirConfig(baseDirConfigFile); + if (baseDirConfig.open(IO_ReadOnly)) { + QTextStream bds(&baseDirConfig); + baseDir = bds.readLine().latin1(); + } + } + + if ( args->count() < 1 && baseDir.isEmpty() ) { + printf("For regression testing, make sure to have checked out the kate regression\n" + "testsuite from svn:\n" + "\tsvn co \"https://<user>@svn.kde.org:/home/kde/trunk/tests/katetests/regression\"\n" + "Remember the root path into which you checked out the testsuite.\n" + "\n"); + printf("%s needs the root path of the kate regression\n" + "testsuite to function properly\n" + "By default, the root path is looked up in the file\n" + "\t%s\n" + "If it doesn't exist yet, create it by invoking\n" + "\techo \"<root-path>\" > %s\n" + "You may override the location by specifying the root explicitly on the\n" + "command line with option -b\n" + "", KCmdLineArgs::appName(), + (const char *)baseDirConfigFile, + (const char *)baseDirConfigFile); + ::exit( 1 ); + } + + int testcase_index = 0; + if (baseDir.isEmpty()) baseDir = args->arg(testcase_index++); + + QFileInfo bdInfo(baseDir); + baseDir = QFile::encodeName(bdInfo.absFilePath()); + + const char *subdirs[] = {"tests", "baseline", "output", "resources"}; + for ( int i = 0; i < 2; i++ ) { + QFileInfo sourceDir(QFile::encodeName( baseDir ) + "/" + subdirs[i]); + if ( !sourceDir.exists() || !sourceDir.isDir() ) { + fprintf(stderr,"ERROR: Source directory \"%s/%s\": no such directory.\n", (const char *)baseDir, subdirs[i]); + exit(1); + } + } + + KApplication a; + a.disableAutoDcopRegistration(); + a.setStyle("windows"); + KSimpleConfig cfg( "testkateregressionrc" ); + cfg.setGroup("Kate Document Defaults"); + cfg.writeEntry("Basic Config Flags", + KateDocumentConfig::cfBackspaceIndents +// | KateDocumentConfig::cfWordWrap +// | KateDocumentConfig::cfRemoveSpaces + | KateDocumentConfig::cfWrapCursor +// | KateDocumentConfig::cfAutoBrackets +// | KateDocumentConfig::cfTabIndentsMode +// | KateDocumentConfig::cfOvr + | KateDocumentConfig::cfKeepIndentProfile + | KateDocumentConfig::cfKeepExtraSpaces + | KateDocumentConfig::cfTabIndents + | KateDocumentConfig::cfShowTabs + | KateDocumentConfig::cfSpaceIndent + | KateDocumentConfig::cfSmartHome + | KateDocumentConfig::cfTabInsertsTab +// | KateDocumentConfig::cfReplaceTabsDyn +// | KateDocumentConfig::cfRemoveTrailingDyn + | KateDocumentConfig::cfDoxygenAutoTyping +// | KateDocumentConfig::cfMixedIndent + | KateDocumentConfig::cfIndentPastedText + ); + cfg.sync(); + + int rv = 1; + + { + KSimpleConfig dc( "kdebugrc" ); + // FIXME adapt to kate + static int areas[] = { 1000, 13000, 13001, 13002, 13010, + 13020, 13025, 13030, 13033, 13035, + 13040, 13050, 13051, 7000, 7006, 170, + 171, 7101, 7002, 7019, 7027, 7014, + 7001, 7011, 6070, 6080, 6090, 0}; + int channel = args->isSet( "debug" ) ? 2 : 4; + for ( int i = 0; areas[i]; ++i ) { + dc.setGroup( QString::number( areas[i] ) ); + dc.writeEntry( "InfoOutput", channel ); + } + dc.sync(); + + kdClearDebugConfig(); + } + + // create widgets + KateFactory *fac = KateFactory::self(); + KMainWindow *toplevel = new KMainWindow(); + KateDocument *part = new KateDocument(/*bSingleViewMode*/true, + /*bBrowserView*/false, + /*bReadOnly*/false, + /*parentWidget*/toplevel, + /*widgetName*/"testkate"); + part->readConfig(&cfg); + + toplevel->setCentralWidget( part->widget() ); + + Q_ASSERT(part->config()->configFlags() & KateDocumentConfig::cfDoxygenAutoTyping); + + bool visual = false; + if (args->isSet("show")) + visual = true; + + a.setTopWidget(part->widget()); + a.setMainWidget( toplevel ); + if ( visual ) + toplevel->show(); + + // we're not interested + toplevel->statusBar()->hide(); + + if (!getenv("KDE_DEBUG")) { + // set ulimits + rlimit vmem_limit = { 256*1024*1024, RLIM_INFINITY }; // 256Mb Memory should suffice + setrlimit(RLIMIT_AS, &vmem_limit); + rlimit stack_limit = { 8*1024*1024, RLIM_INFINITY }; // 8Mb Memory should suffice + setrlimit(RLIMIT_STACK, &stack_limit); + } + + // run the tests + RegressionTest *regressionTest = new RegressionTest(part, + &cfg, + baseDir, + args->getOption("output"), + args->isSet("genoutput")); + QObject::connect(part->browserExtension(), SIGNAL(openURLRequest(const KURL &, const KParts::URLArgs &)), + regressionTest, SLOT(slotOpenURL(const KURL&, const KParts::URLArgs &))); + QObject::connect(part->browserExtension(), SIGNAL(resizeTopLevelWidget( int, int )), + regressionTest, SLOT(resizeTopLevelWidget( int, int ))); + + regressionTest->m_keepOutput = args->isSet("keep-output"); + regressionTest->m_showGui = args->isSet("show"); + + { + QString failureSnapshot = args->getOption("cmp-failures"); + if (failureSnapshot.isEmpty()) + failureSnapshot = findMostRecentFailureSnapshot(); + if (!failureSnapshot.isEmpty()) + regressionTest->setFailureSnapshotConfig( + new KSimpleConfig(failureSnapshotPrefix + failureSnapshot, true), + failureSnapshot); + } + + if (args->isSet("save-failures")) { + QString failureSaver = args->getOption("save-failures"); + regressionTest->setFailureSnapshotSaver( + new KSimpleConfig(failureSnapshotPrefix + failureSaver, false), + failureSaver); + } + + bool result = false; + QCStringList tests = args->getOptionList("test"); + // merge testcases specified on command line + for (; testcase_index < args->count(); testcase_index++) + tests << args->arg(testcase_index); + if (tests.count() > 0) + for (QValueListConstIterator<QCString> it = tests.begin(); it != tests.end(); ++it) { + result = regressionTest->runTests(*it,true); + if (!result) break; + } + else + result = regressionTest->runTests(); + + if (result) { + if (args->isSet("genoutput")) { + printf("\nOutput generation completed.\n"); + } + else { + printf("\nTests completed.\n"); + printf("Total: %d\n", + regressionTest->m_passes_work+ + regressionTest->m_passes_fail+ + regressionTest->m_failures_work+ + regressionTest->m_failures_fail+ + regressionTest->m_errors); + printf("Passes: %d",regressionTest->m_passes_work); + if ( regressionTest->m_passes_fail ) + printf( " (%d unexpected passes)", regressionTest->m_passes_fail ); + if (regressionTest->m_passes_new) + printf(" (%d new since %s)", regressionTest->m_passes_new, regressionTest->m_failureComp->group().latin1()); + printf( "\n" ); + printf("Failures: %d",regressionTest->m_failures_work); + if ( regressionTest->m_failures_fail ) + printf( " (%d expected failures)", regressionTest->m_failures_fail ); + if ( regressionTest->m_failures_new ) + printf(" (%d new since %s)", regressionTest->m_failures_new, regressionTest->m_failureComp->group().latin1()); + printf( "\n" ); + if ( regressionTest->m_errors ) + printf("Errors: %d\n",regressionTest->m_errors); + + QFile list( regressionTest->m_outputDir + "/links.html" ); + list.open( IO_WriteOnly|IO_Append ); + QString link, cl; + link = QString( "<hr>%1 failures. (%2 expected failures)" ) + .arg(regressionTest->m_failures_work ) + .arg( regressionTest->m_failures_fail ); + if (regressionTest->m_failures_new) + link += QString(" <span style=\"color:red;font-weight:bold\">(%1 new failures since %2)</span>") + .arg(regressionTest->m_failures_new) + .arg(regressionTest->m_failureComp->group()); + if (regressionTest->m_passes_new) + link += QString(" <p style=\"color:green;font-weight:bold\">%1 new passes since %2</p>") + .arg(regressionTest->m_passes_new) + .arg(regressionTest->m_failureComp->group()); + list.writeBlock( link.latin1(), link.length() ); + list.close(); + } + } + + // Only return a 0 exit code if all tests were successful + if (regressionTest->m_failures_work == 0 && regressionTest->m_errors == 0) + rv = 0; + + // cleanup + delete regressionTest; + delete part; + delete toplevel; +// delete fac; + + return rv; +} + +// ------------------------------------------------------------------------- + +RegressionTest *RegressionTest::curr = 0; + +RegressionTest::RegressionTest(KateDocument *part, KConfig *baseConfig, + const QString &baseDir, + const QString &outputDir, bool _genOutput) + : QObject(part) +{ + m_part = part; + m_view = static_cast<KateView *>(m_part->widget()); + m_baseConfig = baseConfig; + m_baseDir = baseDir; + m_baseDir = m_baseDir.replace( "//", "/" ); + if ( m_baseDir.endsWith( "/" ) ) + m_baseDir = m_baseDir.left( m_baseDir.length() - 1 ); + if (outputDir.isEmpty()) + m_outputDir = m_baseDir + "/output"; + else + m_outputDir = outputDir; + createMissingDirs(m_outputDir + "/"); + m_keepOutput = false; + m_genOutput = _genOutput; + m_failureComp = 0; + m_failureSave = 0; + m_showGui = false; + m_passes_work = m_passes_fail = m_passes_new = 0; + m_failures_work = m_failures_fail = m_failures_new = 0; + m_errors = 0; + + ::unlink( QFile::encodeName( m_outputDir + "/links.html" ) ); + QFile f( m_outputDir + "/empty.html" ); + QString s; + f.open( IO_WriteOnly | IO_Truncate ); + s = "<html><body>Follow the white rabbit"; + f.writeBlock( s.latin1(), s.length() ); + f.close(); + f.setName( m_outputDir + "/index.html" ); + f.open( IO_WriteOnly | IO_Truncate ); + s = "<html><frameset cols=150,*><frame src=links.html><frame name=content src=empty.html>"; + f.writeBlock( s.latin1(), s.length() ); + f.close(); + + curr = this; +} + +#include <qobjectlist.h> + +static QStringList readListFile( const QString &filename ) +{ + // Read ignore file for this directory + QString ignoreFilename = filename; + QFileInfo ignoreInfo(ignoreFilename); + QStringList ignoreFiles; + if (ignoreInfo.exists()) { + QFile ignoreFile(ignoreFilename); + if (!ignoreFile.open(IO_ReadOnly)) { + fprintf(stderr,"Can't open %s\n",ignoreFilename.latin1()); + exit(1); + } + QTextStream ignoreStream(&ignoreFile); + QString line; + while (!(line = ignoreStream.readLine()).isNull()) + ignoreFiles.append(line); + ignoreFile.close(); + } + return ignoreFiles; +} + +RegressionTest::~RegressionTest() +{ + // Important! Delete comparison config *first* as saver config + // might point to the same physical file. + delete m_failureComp; + delete m_failureSave; +} + +void RegressionTest::setFailureSnapshotConfig(KConfig *cfg, const QString &sname) +{ + Q_ASSERT(cfg); + m_failureComp = cfg; + m_failureComp->setGroup(sname); +} + +void RegressionTest::setFailureSnapshotSaver(KConfig *cfg, const QString &sname) +{ + Q_ASSERT(cfg); + m_failureSave = cfg; + m_failureSave->setGroup(sname); +} + +QStringList RegressionTest::concatListFiles(const QString &relPath, const QString &filename) +{ + QStringList cmds; + int pos = relPath.findRev('/'); + if (pos >= 0) + cmds += concatListFiles(relPath.left(pos), filename); + cmds += readListFile(m_baseDir + "/tests/" + relPath + "/" + filename); + return cmds; +} + +bool RegressionTest::runTests(QString relPath, bool mustExist, int known_failure) +{ + m_currentOutput = QString::null; + + if (!QFile(m_baseDir + "/tests/"+relPath).exists()) { + fprintf(stderr,"%s: No such file or directory\n",relPath.latin1()); + return false; + } + + QString fullPath = m_baseDir + "/tests/"+relPath; + QFileInfo info(fullPath); + + if (!info.exists() && mustExist) { + fprintf(stderr,"%s: No such file or directory\n",relPath.latin1()); + return false; + } + + if (!info.isReadable() && mustExist) { + fprintf(stderr,"%s: Access denied\n",relPath.latin1()); + return false; + } + + if (info.isDir()) { + QStringList ignoreFiles = readListFile( m_baseDir + "/tests/"+relPath+"/ignore" ); + QStringList failureFiles = readListFile( m_baseDir + "/tests/"+relPath+"/KNOWN_FAILURES" ); + + // Run each test in this directory, recusively + QDir sourceDir(m_baseDir + "/tests/"+relPath); + for (uint fileno = 0; fileno < sourceDir.count(); fileno++) { + QString filename = sourceDir[fileno]; + QString relFilename = relPath.isEmpty() ? filename : relPath+"/"+filename; + + if (filename.startsWith(".") || ignoreFiles.contains(filename) ) + continue; + int failure_type = NoFailure; + if ( failureFiles.contains( filename ) ) + failure_type |= AllFailure; + if ( failureFiles.contains ( filename + "-result" ) ) + failure_type |= ResultFailure; + runTests(relFilename, false, failure_type); + } + } + else if (info.isFile()) { + + QString relativeDir = QFileInfo(relPath).dirPath(); + QString filename = info.fileName(); + m_currentBase = m_baseDir + "/tests/"+relativeDir; + m_currentCategory = relativeDir; + m_currentTest = filename; + m_known_failures = known_failure; + m_outputCustomised = false; + // gather commands + // directory-specific commands + QStringList commands = concatListFiles(relPath, ".kateconfig-commands"); + // testcase-specific commands + commands += readListFile(m_currentBase + "/" + filename + "-commands"); + + rereadConfig(); // reset options to default + if ( filename.endsWith(".txt") ) { +#if 0 + if ( relPath.startsWith( "domts/" ) && !m_runJS ) + return true; + if ( relPath.startsWith( "ecma/" ) && !m_runJS ) + return true; +#endif +// if ( m_runHTML ) + testStaticFile(relPath, commands); + } + else if (mustExist) { + fprintf(stderr,"%s: Not a valid test file (must be .txt)\n",relPath.latin1()); + return false; + } + } else if (mustExist) { + fprintf(stderr,"%s: Not a regular file\n",relPath.latin1()); + return false; + } + + return true; +} + +void RegressionTest::createLink( const QString& test, int failures ) +{ + createMissingDirs( m_outputDir + "/" + test + "-compare.html" ); + + QFile list( m_outputDir + "/links.html" ); + list.open( IO_WriteOnly|IO_Append ); + QString link; + link = QString( "<a href=\"%1\" target=\"content\" title=\"%2\">" ) + .arg( test + "-compare.html" ) + .arg( test ); + link += m_currentTest; + link += "</a> "; + if (failures & NewFailure) + link += "<span style=\"font-weight:bold;color:red\">"; + link += "["; + if ( failures & ResultFailure ) + link += "R"; + link += "]"; + if (failures & NewFailure) + link += "</span>"; + link += "<br>\n"; + list.writeBlock( link.latin1(), link.length() ); + list.close(); +} + +/** returns the path in a way that is relatively reachable from base. + * @param base base directory (must not include trailing slash) + * @param path directory/file to be relatively reached by base + * @return path with all elements replaced by .. and concerning path elements + * to be relatively reachable from base. + */ +static QString makeRelativePath(const QString &base, const QString &path) +{ + QString absBase = QFileInfo(base).absFilePath(); + QString absPath = QFileInfo(path).absFilePath(); +// kdDebug() << "absPath: \"" << absPath << "\"" << endl; +// kdDebug() << "absBase: \"" << absBase << "\"" << endl; + + // walk up to common ancestor directory + int pos = 0; + do { + pos++; + int newpos = absBase.find('/', pos); + if (newpos == -1) newpos = absBase.length(); + QConstString cmpPathComp(absPath.unicode() + pos, newpos - pos); + QConstString cmpBaseComp(absBase.unicode() + pos, newpos - pos); +// kdDebug() << "cmpPathComp: \"" << cmpPathComp.string() << "\"" << endl; +// kdDebug() << "cmpBaseComp: \"" << cmpBaseComp.string() << "\"" << endl; +// kdDebug() << "pos: " << pos << " newpos: " << newpos << endl; + if (cmpPathComp.string() != cmpBaseComp.string()) { pos--; break; } + pos = newpos; + } while (pos < (int)absBase.length() && pos < (int)absPath.length()); + int basepos = pos < (int)absBase.length() ? pos + 1 : pos; + int pathpos = pos < (int)absPath.length() ? pos + 1 : pos; + +// kdDebug() << "basepos " << basepos << " pathpos " << pathpos << endl; + + QString rel; + { + QConstString relBase(absBase.unicode() + basepos, absBase.length() - basepos); + QConstString relPath(absPath.unicode() + pathpos, absPath.length() - pathpos); + // generate as many .. as there are path elements in relBase + if (relBase.string().length() > 0) { + for (int i = relBase.string().contains('/'); i > 0; --i) + rel += "../"; + rel += ".."; + if (relPath.string().length() > 0) rel += "/"; + } + rel += relPath.string(); + } + return rel; +} + +/** processes events for at least \c msec milliseconds */ +static void pause(int msec) +{ + QTime t; + t.start(); + do { + kapp->processEvents(); + } while (t.elapsed() < msec); +} + +void RegressionTest::doFailureReport( const QString& test, int failures ) +{ + if ( failures == NoFailure ) { + ::unlink( QFile::encodeName( m_outputDir + "/" + test + "-compare.html" ) ); + return; + } + + createLink( test, failures ); + + QFile compare( m_outputDir + "/" + test + "-compare.html" ); + + QString testFile = QFileInfo(test).fileName(); + + QString renderDiff; + QString domDiff; + + QString relOutputDir = makeRelativePath(m_baseDir, m_outputDir); + + // are blocking reads possible with KProcess? + char pwd[PATH_MAX]; + (void) getcwd( pwd, PATH_MAX ); + chdir( QFile::encodeName( m_baseDir ) ); + + if ( failures & ResultFailure ) { + domDiff += "<pre>"; + FILE *pipe = popen( QString::fromLatin1( "diff -u baseline/%1-result %3/%2-result" ) + .arg ( test, test, relOutputDir ).latin1(), "r" ); + QTextIStream *is = new QTextIStream( pipe ); + for ( int line = 0; line < 100 && !is->eof(); ++line ) { + QString line = is->readLine(); + line = line.replace( '<', "<" ); + line = line.replace( '>', ">" ); + domDiff += line + "\n"; + } + delete is; + pclose( pipe ); + domDiff += "</pre>"; + } + + chdir( pwd ); + + // create a relative path so that it works via web as well. ugly + QString relpath = makeRelativePath(m_outputDir + "/" + + QFileInfo(test).dirPath(), m_baseDir); + + compare.open( IO_WriteOnly|IO_Truncate ); + QString cl; + cl = QString( "<html><head><title>%1</title>" ).arg( test ); + cl += QString( "<script>\n" + "var pics = new Array();\n" + "pics[0]=new Image();\n" + "pics[0].src = '%1';\n" + "pics[1]=new Image();\n" + "pics[1].src = '%2';\n" + "var doflicker = 1;\n" + "var t = 1;\n" + "var lastb=0;\n" ) + .arg( relpath+"/baseline/"+test+"-dump.png" ) + .arg( testFile+"-dump.png" ); + cl += QString( "function toggleVisible(visible) {\n" + " document.getElementById('render').style.visibility= visible == 'render' ? 'visible' : 'hidden';\n" + " document.getElementById('image').style.visibility= visible == 'image' ? 'visible' : 'hidden';\n" + " document.getElementById('dom').style.visibility= visible == 'dom' ? 'visible' : 'hidden';\n" + "}\n" + "function show() { document.getElementById('image').src = pics[t].src; " + "document.getElementById('image').style.borderColor = t && !doflicker ? 'red' : 'gray';\n" + "toggleVisible('image');\n" + "}" ); + cl += QString ( "function runSlideShow(){\n" + " document.getElementById('image').src = pics[t].src;\n" + " if (doflicker)\n" + " t = 1 - t;\n" + " setTimeout('runSlideShow()', 200);\n" + "}\n" + "function m(b) { if (b == lastb) return; document.getElementById('b'+b).className='buttondown';\n" + " var e = document.getElementById('b'+lastb);\n" + " if(e) e.className='button';\n" + " lastb = b;\n" + "}\n" + "function showRender() { doflicker=0;toggleVisible('render')\n" + "}\n" + "function showDom() { doflicker=0;toggleVisible('dom')\n" + "}\n" + "</script>\n"); + + cl += QString ("<style>\n" + ".buttondown { cursor: pointer; padding: 0px 20px; color: white; background-color: blue; border: inset blue 2px;}\n" + ".button { cursor: pointer; padding: 0px 20px; color: black; background-color: white; border: outset blue 2px;}\n" + ".diff { position: absolute; left: 10px; top: 100px; visibility: hidden; border: 1px black solid; background-color: white; color: black; /* width: 800; height: 600; overflow: scroll; */ }\n" + "</style>\n" ); + + cl += QString( "<body onload=\"m(5); toggleVisible('dom');\"" ); + cl += QString(" text=black bgcolor=gray>\n<h1>%3</h1>\n" ).arg( test ); + if ( renderDiff.length() ) + cl += "<span id='b4' class='button' onclick='showRender();m(4)'>R-DIFF</span> \n"; + if ( domDiff.length() ) + cl += "<span id='b5' class='button' onclick='showDom();m(5);'>D-DIFF</span> \n"; + // The test file always exists - except for checkOutput called from *.js files + if ( QFile::exists( m_baseDir + "/tests/"+ test ) ) + cl += QString( "<a class=button href=\"%1\">HTML</a> " ) + .arg( relpath+"/tests/"+test ); + + cl += QString( "<hr>" + "<img style='border: solid 5px gray' src=\"%1\" id='image'>" ) + .arg( relpath+"/baseline/"+test+"-dump.png" ); + + cl += "<div id='render' class='diff'>" + renderDiff + "</div>"; + cl += "<div id='dom' class='diff'>" + domDiff + "</div>"; + + cl += "</body></html>"; + compare.writeBlock( cl.latin1(), cl.length() ); + compare.close(); +} + +void RegressionTest::testStaticFile(const QString & filename, const QStringList &commands) +{ + qApp->mainWidget()->resize( 800, 600); // restore size + + // Set arguments + KParts::URLArgs args; + if (filename.endsWith(".txt")) args.serviceType = "text/plain"; + m_part->browserExtension()->setURLArgs(args); + // load page + KURL url; + url.setProtocol("file"); + url.setPath(QFileInfo(m_baseDir + "/tests/"+filename).absFilePath()); + m_part->openURL(url); + + // inject commands + for (QStringList::ConstIterator cit = commands.begin(); cit != commands.end(); ++cit) { + QString str = (*cit).stripWhiteSpace(); + if (str.isEmpty() || str.startsWith("#")) continue; + Kate::Command *cmd = KateCmd::self()->queryCommand(str); + if (cmd) { + QString msg; + if (!cmd->exec(m_view, str, msg)) + fprintf(stderr, "ERROR executing command '%s': %s\n", str.latin1(), msg.latin1()); + } + } + + pause(200); + + Q_ASSERT(m_part->config()->configFlags() & KateDocumentConfig::cfDoxygenAutoTyping); + + bool script_error = false; + { + // Execute script + TestJScriptEnv jsenv(m_part); + jsenv.output()->setChangedFlag(&m_outputCustomised); + jsenv.output()->setOutputString(&m_outputString); + script_error = evalJS(jsenv.interpreter(), m_baseDir + "/tests/"+QFileInfo(filename).dirPath()+"/.kateconfig-script", true) + && evalJS(jsenv.interpreter(), m_baseDir + "/tests/"+filename+"-script"); + } + + int back_known_failures = m_known_failures; + + if (!script_error) goto bail_out; + + if (m_showGui) kapp->processEvents(); + + if ( m_genOutput ) { + reportResult(checkOutput(filename+"-result"), "result"); + } else { + int failures = NoFailure; + + // compare with output file + if ( m_known_failures & ResultFailure) + m_known_failures = AllFailure; + bool newfail; + if ( !reportResult( checkOutput(filename+"-result"), "result", &newfail ) ) + failures |= ResultFailure; + if (newfail) + failures |= NewFailure; + + doFailureReport(filename, failures ); + } + +bail_out: + m_known_failures = back_known_failures; + m_part->setModified(false); + m_part->closeURL(); +} + +bool RegressionTest::evalJS(Interpreter &interp, const QString &filename, bool ignore_nonexistent) +{ + QString fullSourceName = filename; + QFile sourceFile(fullSourceName); + + if (!sourceFile.open(IO_ReadOnly)) { + if (!ignore_nonexistent) { + fprintf(stderr,"ERROR reading file %s\n",fullSourceName.latin1()); + m_errors++; + } + return ignore_nonexistent; + } + + QTextStream stream ( &sourceFile ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + QString code = stream.read(); + sourceFile.close(); + + saw_failure = false; + ignore_errors = false; + Completion c = interp.evaluate(UString( code ) ); + + if ( /*report_result &&*/ !ignore_errors) { + if (c.complType() == Throw) { + QString errmsg = c.value().toString(interp.globalExec()).qstring(); + printf( "ERROR: %s (%s)\n",filename.latin1(), errmsg.latin1()); + m_errors++; + return false; + } + } + return true; +} + +class GlobalImp : public ObjectImp { +public: + virtual UString className() const { return "global"; } +}; + +RegressionTest::CheckResult RegressionTest::checkOutput(const QString &againstFilename) +{ + QString absFilename = QFileInfo(m_baseDir + "/baseline/" + againstFilename).absFilePath(); + if ( svnIgnored( absFilename ) ) { + m_known_failures = NoFailure; + return Ignored; + } + + CheckResult result = Success; + + // compare result to existing file + QString outputFilename = QFileInfo(m_outputDir + "/" + againstFilename).absFilePath(); + bool kf = false; + if ( m_known_failures & AllFailure ) + kf = true; + if ( kf ) + outputFilename += "-KF"; + + if ( m_genOutput ) + outputFilename = absFilename; + + // get existing content + QString data; + if (m_outputCustomised) { + data = m_outputString; + } else { + data = m_part->text(); + } + + QFile file(absFilename); + if (file.open(IO_ReadOnly)) { + QTextStream stream ( &file ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + + QString fileData = stream.read(); + + result = ( fileData == data ) ? Success : Failure; + if ( !m_genOutput && result == Success && !m_keepOutput ) { + ::unlink( QFile::encodeName( outputFilename ) ); + return Success; + } + } else if (!m_genOutput) { + fprintf(stderr, "Error reading file %s\n", absFilename.latin1()); + result = Failure; + } + + // generate result file + createMissingDirs( outputFilename ); + QFile file2(outputFilename); + if (!file2.open(IO_WriteOnly)) { + fprintf(stderr,"Error writing to file %s\n",outputFilename.latin1()); + exit(1); + } + + QTextStream stream2(&file2); + stream2.setEncoding( QTextStream::UnicodeUTF8 ); + stream2 << data; + if ( m_genOutput ) + printf("Generated %s\n", outputFilename.latin1()); + + return result; +} + +void RegressionTest::rereadConfig() +{ + m_baseConfig->setGroup("Kate Document Defaults"); + m_part->config()->readConfig(m_baseConfig); + m_baseConfig->setGroup("Kate View Defaults"); + m_view->config()->readConfig(m_baseConfig); +} + +bool RegressionTest::reportResult(CheckResult result, const QString & description, bool *newfail) +{ + if ( result == Ignored ) { + //printf("IGNORED: "); + //printDescription( description ); + return true; // no error + } else + return reportResult( result == Success, description, newfail ); +} + +bool RegressionTest::reportResult(bool passed, const QString & description, bool *newfail) +{ + if (newfail) *newfail = false; + + if (m_genOutput) + return true; + + QString filename(m_currentTest + "-" + description); + if (!m_currentCategory.isEmpty()) + filename = m_currentCategory + "/" + filename; + + const bool oldfailed = m_failureComp && m_failureComp->readNumEntry(filename); + if (passed) { + if ( m_known_failures & AllFailure ) { + printf("PASS (unexpected!)"); + m_passes_fail++; + } else { + printf("PASS"); + m_passes_work++; + } + if (oldfailed) { + printf(" (new)"); + m_passes_new++; + } + if (m_failureSave) + m_failureSave->deleteEntry(filename); + } + else { + if ( m_known_failures & AllFailure ) { + printf("FAIL (known)"); + m_failures_fail++; + passed = true; // we knew about it + } else { + printf("FAIL"); + m_failures_work++; + } + if (!oldfailed && m_failureComp) { + printf(" (new)"); + m_failures_new++; + if (newfail) *newfail = true; + } + if (m_failureSave) + m_failureSave->writeEntry(filename, 1); + } + printf(": "); + + printDescription( description ); + return passed; +} + +void RegressionTest::printDescription(const QString& description) +{ + if (!m_currentCategory.isEmpty()) + printf("%s/", m_currentCategory.latin1()); + + printf("%s", m_currentTest.latin1()); + + if (!description.isEmpty()) { + QString desc = description; + desc.replace( '\n', ' ' ); + printf(" [%s]", desc.latin1()); + } + + printf("\n"); + fflush(stdout); +} + +void RegressionTest::createMissingDirs(const QString & filename) +{ + QFileInfo dif(filename); + QFileInfo dirInfo( dif.dirPath() ); + if (dirInfo.exists()) + return; + + QStringList pathComponents; + QFileInfo parentDir = dirInfo; + pathComponents.prepend(parentDir.absFilePath()); + while (!parentDir.exists()) { + QString parentPath = parentDir.absFilePath(); + int slashPos = parentPath.findRev('/'); + if (slashPos < 0) + break; + parentPath = parentPath.left(slashPos); + pathComponents.prepend(parentPath); + parentDir = QFileInfo(parentPath); + } + for (uint pathno = 1; pathno < pathComponents.count(); pathno++) { + if (!QFileInfo(pathComponents[pathno]).exists() && + !QDir(pathComponents[pathno-1]).mkdir(pathComponents[pathno])) { + fprintf(stderr,"Error creating directory %s\n",pathComponents[pathno].latin1()); + exit(1); + } + } +} + +void RegressionTest::slotOpenURL(const KURL &url, const KParts::URLArgs &args) +{ + m_part->browserExtension()->setURLArgs( args ); + + m_part->openURL(url); +} + +bool RegressionTest::svnIgnored( const QString &filename ) +{ + QFileInfo fi( filename ); + QString ignoreFilename = fi.dirPath() + "/svnignore"; + QFile ignoreFile(ignoreFilename); + if (!ignoreFile.open(IO_ReadOnly)) + return false; + + QTextStream ignoreStream(&ignoreFile); + QString line; + while (!(line = ignoreStream.readLine()).isNull()) { + if ( line == fi.fileName() ) + return true; + } + ignoreFile.close(); + return false; +} + +void RegressionTest::resizeTopLevelWidget( int w, int h ) +{ + qApp->mainWidget()->resize( w, h ); + // Since we're not visible, this doesn't have an immediate effect, QWidget posts the event + QApplication::sendPostedEvents( 0, QEvent::Resize ); +} + +#include "test_regression.moc" + +// kate: indent-width 4 diff --git a/kate/part/test_regression.h b/kate/part/test_regression.h new file mode 100644 index 000000000..bc528b4bc --- /dev/null +++ b/kate/part/test_regression.h @@ -0,0 +1,249 @@ +/** + * This file is part of the KDE project + * + * Copyright (C) 2001,2003 Peter Kelly (pmk@post.com) + * Copyright 2006 Leo Savernik (l.savernik@aon.at) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#ifndef TEST_REGRESSION_H +#define TEST_REGRESSION_H + +#include <katejscript.h> +#include <kateview.h> +#include <kurl.h> +#include <qobject.h> +#include <qstringlist.h> +#include <kjs/ustring.h> +#include <kjs/object.h> +#include <kjs/interpreter.h> + +class KateDocument; +class KateView; +class RegressionTest; +class QTimer; + +namespace KParts { + class URLArgs; +} + +class OutputObject; + +/** + * @internal + * The backbone of Kate's automatic regression tests. + */ +class TestJScriptEnv : public KateJScript +{ + public: + TestJScriptEnv(KateDocument *part); + virtual ~TestJScriptEnv(); + + /** returns the global scope */ + KJS::Object global() const { return *m_global; } + /** returns the script interpreter */ + KJS::Interpreter &interpreter() { return *m_interpreter; } + /** returns the document scope */ + KJS::Object document() const { return *m_document; } + /** returns the view scope */ + KJS::Object view() const { return *m_view; } + /** returns the output object */ + OutputObject *output() const { return m_output; } + + protected: + OutputObject *m_output; +}; + +/** + * @internal + */ +class KateViewObject : public KJS::ObjectImp +{ + public: + KateViewObject(KJS::ExecState *exec, KateView *v, KJS::ObjectImp *fallback); + virtual ~KateViewObject(); + + virtual const KJS::ClassInfo *classInfo() const; + virtual KJS::Value get(KJS::ExecState *exec, const KJS::Identifier &propertyName) const; + + private: + // evil hack I: class layout of katejscript/KateJSView must be duplicated + // here, structurally as well as functionally + KateView *view; + // end evil hack + KJS::ObjectImp *fallback; +}; + +/** + * @internal + */ +class KateViewFunction : public KJS::ObjectImp +{ + public: + KateViewFunction(KJS::ExecState *exec, KateView *v, int _id, int length); + + bool implementsCall() const; + KJS::Value call(KJS::ExecState *exec, KJS::Object &thisObj, const KJS::List &args); + + enum { KeyReturn, Type, Backspace, DeleteWordLeft, KeyDelete, + DeleteWordRight, Transpose, CursorLeft, ShiftCursorLeft, CursorRight, + ShiftCursorRight, WordLeft, ShiftWordLeft, WordRight, ShiftWordRight, + Home, ShiftHome, End, ShiftEnd, Up, ShiftUp, Down, ShiftDown, ScrollUp, + ScrollDown, TopOfView, ShiftTopOfView, BottomOfView, ShiftBottomOfView, + PageUp, ShiftPageUp, PageDown, ShiftPageDown, Top, ShiftTop, Bottom, + ShiftBottom, ToMatchingBracket, ShiftToMatchingBracket }; + private: + KateView *m_view; + int id; +}; + +class OutputFunction; + +/** + * Customizing output to result-files. Writing any output into result files + * inhibits outputting the content of the katepart after script execution, enabling one to check for coordinates and the like. + * @internal + */ +class OutputObject : public KJS::ObjectImp +{ + public: + OutputObject(KJS::ExecState *exec, KateDocument *d, KateView *v); + virtual ~OutputObject(); + + virtual KJS::UString className() const; + + void setChangedFlag(bool *flag) { changed = flag; } + void setOutputString(QString *s) { outstr = s; } + + private: + KateDocument *doc; + KateView *view; + bool *changed; + QString *outstr; + + friend class OutputFunction; +}; + +/** + * Customizing output to result-files. + * @internal + */ +class OutputFunction : public KJS::ObjectImp +{ + public: + OutputFunction(KJS::ExecState *exec, OutputObject *obj, int _id, int length); + + bool implementsCall() const; + virtual KJS::Value call(KJS::ExecState *exec, KJS::Object &thisObj, const KJS::List &args); + + enum { Write, Writeln, WriteCursorPosition, WriteCursorPositionln }; + private: + OutputObject *o; + int id; +}; + +/** + * @internal + */ +class RegressionTest : public QObject +{ + Q_OBJECT +public: + + RegressionTest(KateDocument *part, KConfig *baseConfig, + const QString &baseDir, const QString &outputDir, + bool _genOutput); + ~RegressionTest(); + + enum OutputType { ResultDocument }; + void testStaticFile(const QString& filename, const QStringList &commands); + enum CheckResult { Failure = 0, Success = 1, Ignored = 2 }; + CheckResult checkOutput(const QString& againstFilename); + enum FailureType { NoFailure = 0, AllFailure = 1, ResultFailure = 4, NewFailure = 65536 }; + bool runTests(QString relPath = QString::null, bool mustExist = false, int known_failure = NoFailure); + bool reportResult( bool passed, const QString & description = QString::null, bool *newfailure = 0 ); + bool reportResult(CheckResult result, const QString & description = QString::null, bool *newfailure = 0 ); + void rereadConfig(); + static void createMissingDirs(const QString &path); + + void setFailureSnapshotConfig(KConfig *cfg, const QString &snapshotname); + void setFailureSnapshotSaver(KConfig *cfg, const QString &snapshotname); + + void createLink( const QString& test, int failures ); + void doFailureReport( const QString& test, int failures ); + + KateDocument *m_part; + KateView *m_view; + KConfig *m_baseConfig; + QString m_baseDir; + QString m_outputDir; + bool m_genOutput; + QString m_currentBase; + KConfig *m_failureComp; + KConfig *m_failureSave; + + QString m_currentOutput; + QString m_currentCategory; + QString m_currentTest; + + bool m_keepOutput; + bool m_getOutput; + bool m_showGui; + int m_passes_work; + int m_passes_fail; + int m_passes_new; + int m_failures_work; + int m_failures_fail; + int m_failures_new; + int m_errors; + bool saw_failure; + bool ignore_errors; + int m_known_failures; + bool m_outputCustomised; + QString m_outputString; + + static RegressionTest *curr; + +private: + void printDescription(const QString& description); + + static bool svnIgnored( const QString &filename ); + +private: + /** + * evaluate script given by \c filename within the context of \c interp. + * @param ignore if \c true don't evaluate if script does not exist but + * return true nonetheless. + * @return true if script was valid, false otherwise + */ + bool evalJS( KJS::Interpreter &interp, const QString &filename, bool ignore = false); + /** + * concatenate contents of all list files down to but not including the + * tests directory. + * @param relPath relative path against tests-directory + * @param filename file name of the list files + */ + QStringList concatListFiles(const QString &relPath, const QString &filename); + +private slots: + void slotOpenURL(const KURL &url, const KParts::URLArgs &args); + void resizeTopLevelWidget( int, int ); + +}; + +#endif |