• Skip to content
  • Skip to link menu
KDE 4.1 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

Kate

katesmartmanager.cpp

Go to the documentation of this file.
00001 /* This file is part of the KDE libraries
00002    Copyright (C) 2005 Hamish Rodda <rodda@kde.org>
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License version 2 as published by the Free Software Foundation.
00007 
00008    This library is distributed in the hope that it will be useful,
00009    but WITHOUT ANY WARRANTY; without even the implied warranty of
00010    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00011    Library General Public License for more details.
00012 
00013    You should have received a copy of the GNU Library General Public License
00014    along with this library; see the file COPYING.LIB.  If not, write to
00015    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00016    Boston, MA 02110-1301, USA.
00017 */
00018 
00019 #include "katesmartmanager.h"
00020 
00021 #include "katedocument.h"
00022 #include "katesmartcursor.h"
00023 #include "katesmartrange.h"
00024 
00025 #include <QThread>
00026 #include <QMutexLocker>
00027 
00028 #include <kdebug.h>
00029 
00030 static const int s_defaultGroupSize = 40;
00031 static const int s_minimumGroupSize = 20;
00032 static const int s_maximumGroupSize = 60;
00033 
00034 using namespace KTextEditor;
00035 
00036 KateSmartManager::KateSmartManager(KateDocument* parent)
00037   : QObject(parent)
00038   , m_firstGroup(new KateSmartGroup(0, 0, 0L, 0L))
00039   , m_invalidGroup(new KateSmartGroup(-1, -1, 0L, 0L))
00040   , m_clearing(false)
00041 {
00042   connect(doc()->history(), SIGNAL(editDone(KateEditInfo*)), SLOT(slotTextChanged(KateEditInfo*)));
00043   //connect(doc(), SIGNAL(textChanged(Document*)), SLOT(verifyCorrect()));
00044 }
00045 
00046 KateSmartManager::~KateSmartManager()
00047 {
00048   clear(true);
00049 
00050   KateSmartGroup* smartGroup = m_firstGroup;
00051   while (smartGroup) {
00052     KateSmartGroup* toDelete = smartGroup;
00053     smartGroup = smartGroup->next();
00054     delete toDelete;
00055   }
00056 
00057   delete m_invalidGroup;
00058 }
00059 
00060 KateDocument * KateSmartManager::doc( ) const
00061 {
00062   return static_cast<KateDocument*>(parent());
00063 }
00064 
00065 KateSmartCursor * KateSmartManager::newSmartCursor( const Cursor & position, SmartCursor::InsertBehavior insertBehavior, bool internal )
00066 {
00067   QMutexLocker l(internal ? doc()->smartMutex() : 0);
00068 
00069   KateSmartCursor* c;
00070   if (usingRevision() != -1 && !internal)
00071     c = new KateSmartCursor(translateFromRevision(position), doc(), insertBehavior);
00072   else
00073     c = new KateSmartCursor(position, doc(), insertBehavior);
00074 
00075   if (internal)
00076     c->setInternal();
00077   return c;
00078 }
00079 
00080 KateSmartRange * KateSmartManager::newSmartRange( const Range & range, SmartRange * parent, SmartRange::InsertBehaviors insertBehavior, bool internal )
00081 {
00082   QMutexLocker l(internal ? doc()->smartMutex() : 0);
00083 
00084   KateSmartRange* newRange;
00085 
00086   if (usingRevision() != -1 && !internal)
00087     newRange = new KateSmartRange(translateFromRevision(range), doc(), parent, insertBehavior);
00088   else
00089     newRange = new KateSmartRange(range, doc(), parent, insertBehavior);
00090 
00091   if (internal)
00092     newRange->setInternal();
00093   if (!parent)
00094     rangeLostParent(newRange);
00095   return newRange;
00096 }
00097 
00098 KateSmartRange * KateSmartManager::newSmartRange( KateSmartCursor * start, KateSmartCursor * end, SmartRange * parent, SmartRange::InsertBehaviors insertBehavior, bool internal )
00099 {
00100   QMutexLocker l(internal ? doc()->smartMutex() : 0);
00101 
00102   if (usingRevision() != -1 && !internal) {
00103     KTextEditor::Cursor tempStart = translateFromRevision(*start, (insertBehavior & SmartRange::ExpandLeft) ? SmartCursor::StayOnInsert : SmartCursor::MoveOnInsert);
00104     KTextEditor::Cursor tempEnd = translateFromRevision(*end, (insertBehavior & SmartRange::ExpandRight) ? SmartCursor::MoveOnInsert : SmartCursor::StayOnInsert);
00105     *start = tempStart;
00106     *end = tempEnd;
00107   }
00108 
00109   KateSmartRange* newRange = new KateSmartRange(start, end, parent, insertBehavior);
00110   if (internal)
00111     newRange->setInternal();
00112   if (!parent)
00113     rangeLostParent(newRange);
00114   return newRange;
00115 }
00116 
00117 void KateSmartGroup::addCursor( KateSmartCursor * cursor)
00118 {
00119   Q_ASSERT(!m_feedbackCursors.contains(cursor));
00120   Q_ASSERT(!m_normalCursors.contains(cursor));
00121 
00122   if (cursor->feedbackEnabled())
00123     m_feedbackCursors.insert(cursor);
00124   else
00125     m_normalCursors.insert(cursor);
00126 }
00127 
00128 void KateSmartGroup::changeCursorFeedback( KateSmartCursor * cursor )
00129 {
00130   if (!cursor->feedbackEnabled()) {
00131     Q_ASSERT(!m_feedbackCursors.contains(cursor));
00132     Q_ASSERT(m_normalCursors.contains(cursor));
00133     m_normalCursors.remove(cursor);
00134     m_feedbackCursors.insert(cursor);
00135 
00136   } else {
00137     Q_ASSERT(m_feedbackCursors.contains(cursor));
00138     Q_ASSERT(!m_normalCursors.contains(cursor));
00139     m_feedbackCursors.remove(cursor);
00140     m_normalCursors.insert(cursor);
00141   }
00142 }
00143 
00144 void KateSmartGroup::removeCursor( KateSmartCursor * cursor)
00145 {
00146   if (cursor->feedbackEnabled()) {
00147     Q_ASSERT(m_feedbackCursors.contains(cursor));
00148     Q_ASSERT(!m_normalCursors.contains(cursor));
00149     m_feedbackCursors.remove(cursor);
00150 
00151   } else {
00152     Q_ASSERT(!m_feedbackCursors.contains(cursor));
00153     Q_ASSERT(m_normalCursors.contains(cursor));
00154     m_normalCursors.remove(cursor);
00155   }
00156 }
00157 
00158 void KateSmartGroup::joined( KateSmartCursor * cursor )
00159 {
00160   addCursor(cursor);
00161 }
00162 
00163 void KateSmartGroup::leaving( KateSmartCursor * cursor )
00164 {
00165   removeCursor(cursor);
00166 }
00167 
00168 KateSmartGroup * KateSmartManager::groupForLine( int line ) const
00169 {
00170   // Special case
00171   if (line == -1)
00172     return m_invalidGroup;
00173 
00174   // FIXME maybe this should perform a bit better
00175   KateSmartGroup* smartGroup = m_firstGroup;
00176   while (smartGroup && !smartGroup->containsLine(line))
00177     smartGroup = smartGroup->next();
00178 
00179   // If you hit this assert, it is a fundamental bug in katepart.  A cursor's
00180   // position is being set beyond the end of the document, or (perhaps less
00181   // likely), in this class itself.
00182   //
00183   // Please figure out how to reproduce, and report to rodda@kde.org.
00184   Q_ASSERT(smartGroup);
00185   return smartGroup;
00186 }
00187 
00188 void KateSmartManager::slotTextChanged(KateEditInfo* edit)
00189 {
00190   QMutexLocker lock(doc()->smartMutex());
00191 
00192   KateSmartGroup* firstSmartGroup = groupForLine(edit->oldRange().start().line());
00193   KateSmartGroup* currentGroup = firstSmartGroup;
00194 
00195   // Check to see if we need to split or consolidate
00196   int splitEndLine = edit->translate().line() + firstSmartGroup->endLine();
00197 
00198   if (edit->translate().line() > 0) {
00199     // May need to expand smart groups
00200     KateSmartGroup* endGroup = currentGroup->next();
00201 
00202     int currentCanExpand = endGroup ? s_maximumGroupSize - currentGroup->length() : s_defaultGroupSize - currentGroup->length();
00203     int expanded = 0;
00204 
00205     if (currentCanExpand) {
00206        int expandBy = qMin(edit->translate().line(), currentCanExpand);
00207       // Current group can expand to accommodate the extra lines
00208       currentGroup->setNewEndLine(currentGroup->endLine() + expandBy);
00209 
00210       expanded = expandBy;
00211     }
00212 
00213     if (expanded < edit->translate().line()) {
00214       // Need at least one new group
00215       int newStartLine, newEndLine;
00216 
00217       do {
00218         newStartLine = currentGroup->newEndLine() + 1;
00219         newEndLine = qMin(newStartLine + s_defaultGroupSize - 1, splitEndLine);
00220         currentGroup = new KateSmartGroup(newStartLine, newEndLine, currentGroup, endGroup);
00221 
00222       } while (newEndLine < splitEndLine);
00223     }
00224 
00225 
00226   } else if (edit->translate().line() < 0) {
00227     // Might need to consolitate
00228     // Consolidate groups together while keeping the end result the same
00229     while (currentGroup->next() && currentGroup->length() + edit->translate().line() < s_minimumGroupSize)
00230       currentGroup->merge();
00231 
00232     // Reduce the size of the current group
00233     currentGroup->setNewEndLine(currentGroup->endLine() + edit->translate().line());
00234   }
00235 
00236   // Shift the groups so they have their new start and end lines
00237   if (edit->translate().line())
00238     for (KateSmartGroup* smartGroup = currentGroup->next(); smartGroup; smartGroup = smartGroup->next())
00239       smartGroup->translateShifted(*edit);
00240 
00241   // Translate affected groups
00242   for (KateSmartGroup* smartGroup = firstSmartGroup; smartGroup; smartGroup = smartGroup->next()) {
00243     if (smartGroup->startLine() > edit->oldRange().end().line())
00244       break;
00245 
00246     smartGroup->translateChanged(*edit);
00247   }
00248 
00249   // Cursor feedback
00250   bool groupChanged = true;
00251   for (KateSmartGroup* smartGroup = firstSmartGroup; smartGroup; smartGroup = smartGroup->next()) {
00252     if (groupChanged) {
00253       groupChanged = smartGroup->startLine() <= edit->oldRange().end().line();
00254       // Don't continue iterating if no line translation occurred.
00255       if (!groupChanged && !edit->translate().line())
00256         break;
00257     }
00258 
00259     if (groupChanged)
00260       smartGroup->translatedChanged(*edit);
00261     else
00262       smartGroup->translatedShifted(*edit);
00263   }
00264 
00265   // Range feedback
00266   foreach (KateSmartRange* range, m_topRanges) {
00267     KateSmartRange* mostSpecific = feedbackRange(*edit, range);
00268 
00269     if (!mostSpecific)
00270       mostSpecific = range;
00271     range->feedbackMostSpecific(mostSpecific);
00272   }
00273 
00274   //debugOutput();
00275   //verifyCorrect();
00276 }
00277 
00278 KateSmartRange* KateSmartManager::feedbackRange( const KateEditInfo& edit, KateSmartRange * range )
00279 {
00280   KateSmartRange* mostSpecific = 0L;
00281 
00282   // This range preceeds the edit... no more to do
00283   if (range->end() < edit.start() || range->end() == edit.start() && !range->isEmpty()) {
00284     //kDebug() << "Not feeding back to " << *range << "as edit start" << edit.start();
00285     return mostSpecific;
00286   }
00287 
00288   foreach (SmartRange* child, range->childRanges())
00289     if (!mostSpecific)
00290       mostSpecific = feedbackRange(edit, static_cast<KateSmartRange*>(child));
00291     else
00292       feedbackRange(edit, static_cast<KateSmartRange*>(child));
00293 
00294   //kDebug() << "edit" << edit.oldRange() << edit.newRange() << "last at" << range->kStart().lastPosition() << range->kEnd().lastPosition() << "now" << *range;
00295 
00296   if (range->start() > edit.newRange().end() ||
00297       (range->start() == edit.newRange().end() && range->kStart().lastPosition() == edit.oldRange().end()))
00298   {
00299     // This range is after the edit... has only been shifted
00300     //kDebug() << "Feeding back shifted to " << *range;
00301     range->shifted();
00302 
00303   } else {
00304     // This range is within the edit.
00305     //kDebug() << "Feeding back translated to " << *range;
00306     if (!mostSpecific)
00307       if (range->start() < edit.oldRange().start() && range->end() > edit.oldRange().end())
00308         mostSpecific = range;
00309 
00310     range->translated(edit);
00311   }
00312 
00313   return mostSpecific;
00314 }
00315 
00316 
00317 void KateSmartGroup::translateChanged( const KateEditInfo& edit)
00318 {
00319   //kDebug() << "Was " << edit.oldRange() << " now " << edit.newRange() << " numcursors feedback " << m_feedbackCursors.count() << " normal " << m_normalCursors.count();
00320 
00321   foreach (KateSmartCursor* cursor, m_feedbackCursors)
00322     cursor->translate(edit);
00323 
00324   foreach (KateSmartCursor* cursor, m_normalCursors)
00325     cursor->translate(edit);
00326 }
00327 
00328 void KateSmartGroup::translateShifted(const KateEditInfo& edit)
00329 {
00330   m_newStartLine = m_startLine + edit.translate().line();
00331   m_newEndLine = m_endLine + edit.translate().line();
00332 }
00333 
00334 void KateSmartGroup::translatedChanged(const KateEditInfo& edit)
00335 {
00336   m_startLine = m_newStartLine;
00337   m_endLine = m_newEndLine;
00338 
00339   foreach (KateSmartCursor* cursor, m_feedbackCursors)
00340     cursor->translated(edit);
00341 }
00342 
00343 void KateSmartGroup::translatedShifted(const KateEditInfo& edit)
00344 {
00345   if (m_startLine != m_newStartLine) {
00346     m_startLine = m_newStartLine;
00347     m_endLine = m_newEndLine;
00348   }
00349 
00350   if (edit.translate().line() == 0)
00351     return;
00352 
00353   // Todo: don't need to provide positionChanged to all feedback cursors?
00354   foreach (KateSmartCursor* cursor, m_feedbackCursors)
00355     cursor->shifted();
00356 }
00357 
00358 KateSmartGroup::KateSmartGroup( int startLine, int endLine, KateSmartGroup * previous, KateSmartGroup * next )
00359   : m_startLine(startLine)
00360   , m_newStartLine(startLine)
00361   , m_endLine(endLine)
00362   , m_newEndLine(endLine)
00363   , m_next(next)
00364   , m_previous(previous)
00365 {
00366   if (m_previous)
00367     m_previous->setNext(this);
00368 
00369   if (m_next)
00370     m_next->setPrevious(this);
00371 }
00372 
00373 void KateSmartGroup::merge( )
00374 {
00375   Q_ASSERT(m_next);
00376 
00377   foreach (KateSmartCursor* cursor, next()->feedbackCursors())
00378     cursor->migrate(this);
00379   m_feedbackCursors += next()->feedbackCursors();
00380 
00381   foreach (KateSmartCursor* cursor, next()->normalCursors())
00382     cursor->migrate(this);
00383   m_normalCursors += next()->normalCursors();
00384 
00385   m_newEndLine = m_endLine = next()->endLine();
00386   KateSmartGroup* newNext = next()->next();
00387   delete m_next;
00388   m_next = newNext;
00389   if (m_next)
00390     m_next->setPrevious(this);
00391 }
00392 
00393 const QSet< KateSmartCursor * > & KateSmartGroup::feedbackCursors( ) const
00394 {
00395   return m_feedbackCursors;
00396 }
00397 
00398 const QSet< KateSmartCursor * > & KateSmartGroup::normalCursors( ) const
00399 {
00400   return m_normalCursors;
00401 }
00402 
00403 void KateSmartManager::debugOutput( ) const
00404 {
00405   int groupCount = 1;
00406   KateSmartGroup* currentGroup = m_firstGroup;
00407   while (currentGroup->next()) {
00408     ++groupCount;
00409     currentGroup = currentGroup->next();
00410   }
00411 
00412   kDebug() << "KateSmartManager: SmartGroups " << groupCount << " from " << m_firstGroup->startLine() << " to " << currentGroup->endLine();
00413 
00414   currentGroup = m_firstGroup;
00415   while (currentGroup) {
00416     currentGroup->debugOutput();
00417     currentGroup = currentGroup->next();
00418   }
00419 }
00420 
00421 void KateSmartGroup::debugOutput( ) const
00422 {
00423   kDebug() << " -> KateSmartGroup: from " << startLine() << " to " << endLine() << "; Cursors " << m_normalCursors.count() + m_feedbackCursors.count() << " (" << m_feedbackCursors.count() << " feedback)";
00424 }
00425 
00426 void KateSmartManager::verifyCorrect() const
00427 {
00428   KateSmartGroup* currentGroup = groupForLine(0);
00429   Q_ASSERT(currentGroup);
00430   Q_ASSERT(currentGroup == m_firstGroup);
00431 
00432   forever {
00433     if (!currentGroup->previous())
00434       Q_ASSERT(currentGroup->startLine() == 0);
00435 
00436     foreach (KateSmartCursor* cursor, currentGroup->feedbackCursors()) {
00437       Q_ASSERT(currentGroup->containsLine(cursor->line()));
00438       Q_ASSERT(cursor->smartGroup() == currentGroup);
00439     }
00440 
00441     if (!currentGroup->next())
00442       break;
00443 
00444     Q_ASSERT(currentGroup->endLine() == currentGroup->next()->startLine() - 1);
00445     Q_ASSERT(currentGroup->next()->previous() == currentGroup);
00446 
00447     currentGroup = currentGroup->next();
00448   }
00449 
00450   Q_ASSERT(currentGroup->endLine() == doc()->lines() - 1);
00451 
00452   kDebug() << "Verified correct." << currentGroup->endLine() << doc()->lines() - 1;
00453 }
00454 
00455 void KateSmartManager::rangeGotParent( KateSmartRange * range )
00456 {
00457   Q_ASSERT(m_topRanges.contains(range));
00458   m_topRanges.remove(range);
00459 }
00460 
00461 void KateSmartManager::rangeLostParent( KateSmartRange * range )
00462 {
00463   Q_ASSERT(!m_topRanges.contains(range));
00464   m_topRanges.insert(range);
00465 }
00466 
00467 void KateSmartManager::rangeDeleted( KateSmartRange* range )
00468 {
00469   emit signalRangeDeleted(range);
00470 
00471   if (!range->parentRange())
00472     m_topRanges.remove(range);
00473 }
00474 
00475 void KateSmartManager::unbindSmartRange( SmartRange * range )
00476 {
00477   static_cast<KateSmartRange*>(range)->unbindAndDelete();
00478 }
00479 
00480 void KateSmartManager::deleteCursors(bool includingInternal)
00481 {
00482   m_invalidGroup->deleteCursors(includingInternal);
00483   for (KateSmartGroup* g = m_firstGroup; g; g = g->next())
00484     g->deleteCursors(includingInternal);
00485 }
00486 
00487 void KateSmartGroup::deleteCursors( bool includingInternal )
00488 {
00489   if (includingInternal) {
00490     qDeleteAll(m_feedbackCursors);
00491     m_feedbackCursors.clear();
00492 
00493     qDeleteAll(m_normalCursors);
00494     m_normalCursors.clear();
00495 
00496   } else {
00497     deleteCursorsInternal(m_feedbackCursors);
00498     deleteCursorsInternal(m_normalCursors);
00499   }
00500 }
00501 
00502 void KateSmartGroup::deleteCursorsInternal( QSet< KateSmartCursor * > & set )
00503 {
00504   foreach (KateSmartCursor* c, set.toList()) {
00505     if (!c->range() && !c->isInternal()) {
00506       set.remove(c);
00507       delete c;
00508     }
00509   }
00510 }
00511 
00512 void KateSmartManager::deleteRanges( bool includingInternal )
00513 {
00514   foreach (KateSmartRange* range, m_topRanges.toList()) {
00515     if (includingInternal || !range->isInternal()) {
00516       range->deleteChildRanges();
00517       delete range;
00518 
00519       if (!includingInternal)
00520         m_topRanges.remove(range);
00521     }
00522   }
00523 
00524   if (includingInternal)
00525     m_topRanges.clear();
00526 }
00527 
00528 void KateSmartManager::clear( bool includingInternal )
00529 {
00530   deleteRanges(includingInternal);
00531 
00532   m_clearing = true;
00533   deleteCursors(includingInternal);
00534   m_clearing = false;
00535 }
00536 
00537 void KateSmartManager::useRevision(int revision)
00538 {
00539   if (!m_usingRevision.hasLocalData())
00540     m_usingRevision.setLocalData(new int);
00541 
00542   *m_usingRevision.localData() = revision;
00543 }
00544 
00545 int KateSmartManager::usingRevision() const
00546 {
00547   if (m_usingRevision.hasLocalData())
00548     return *m_usingRevision.localData();
00549 
00550   return -1;
00551 }
00552 
00553 void KateSmartManager::releaseRevision(int revision) const
00554 {
00555   doc()->history()->releaseRevision(revision);
00556 }
00557 
00558 int KateSmartManager::currentRevision() const
00559 {
00560   return doc()->history()->revision();
00561 }
00562 
00563 static void translate(KateEditInfo* edit, Cursor& ret, SmartCursor::InsertBehavior insertBehavior)
00564 {
00565   // NOTE: copied from KateSmartCursor::translate()
00566   // If this cursor is before the edit, no action is required
00567   if (ret < edit->start())
00568     return;
00569 
00570   // If this cursor is on a line affected by the edit
00571   if (edit->oldRange().overlapsLine(ret.line())) {
00572     // If this cursor is at the start of the edit
00573     if (ret == edit->start()) {
00574       // And it doesn't need to move, no action is required
00575       if (insertBehavior == SmartCursor::StayOnInsert)
00576         return;
00577     }
00578 
00579     // Calculate the new position
00580     Cursor newPos;
00581     if (edit->oldRange().contains(ret)) {
00582       if (insertBehavior == SmartCursor::MoveOnInsert)
00583         ret = edit->newRange().end();
00584       else
00585         ret = edit->start();
00586 
00587     } else {
00588       ret += edit->translate();
00589     }
00590 
00591     return;
00592   }
00593 
00594   // just need to adjust line number
00595   ret.setLine(ret.line() + edit->translate().line());
00596 }
00597 
00598 Cursor KateSmartManager::translateFromRevision(const Cursor& cursor, SmartCursor::InsertBehavior insertBehavior) const
00599 {
00600   Cursor ret = cursor;
00601 
00602   foreach (KateEditInfo* edit, doc()->history()->editsBetweenRevisions(usingRevision()))
00603     translate(edit, ret, insertBehavior);
00604 
00605   return ret;
00606 }
00607 
00608 Range KateSmartManager::translateFromRevision(const Range& range, KTextEditor::SmartRange::InsertBehaviors insertBehavior) const
00609 {
00610   Cursor start = range.start(), end = range.end();
00611 
00612   foreach (KateEditInfo* edit, doc()->history()->editsBetweenRevisions(usingRevision())) {
00613     translate(edit, start, insertBehavior & KTextEditor::SmartRange::ExpandLeft ? SmartCursor::StayOnInsert : SmartCursor::MoveOnInsert);
00614     translate(edit, end, insertBehavior & KTextEditor::SmartRange::ExpandRight ? SmartCursor::MoveOnInsert : SmartCursor::StayOnInsert);
00615   }
00616 
00617   return Range(start, end);
00618 }
00619 
00620 #include "katesmartmanager.moc"

Kate

Skip menu "Kate"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • KIO
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.4
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal