Tutorials/Hacking KDE

From ThorstensHome
Revision as of 14:35, 10 January 2009 by WikiSysop (Talk)

Jump to: navigation, search

This article is about my hobby improving the KDE desktop environment (KDE). I list some code here that could help either you or me.

Contents

Build system

I use the build process from http://techbase.kde.org/Getting_Started/Build/KDE4 (I wrote it as well). If you want to use two computers to build KDE on, use a USB disk. On that, there are your sources. You can use the following procedure to be able to exchange your harddisk:

ln -s /mnt/sdb1/home/kde-devel /home/kde-devel

debugging output

Goal ist to remove debugging output caused by kDebug from the programs. Here is how to:

cmake -DCMAKE_BUILD_TYPE=Release .

tools I use

egypt - creates call graphs

egypt creates static call graphs for a given C/C++ program you will have to use

cmake -DCMAKE_CXX_FLAGS=-dr -DCMAKE_C_FLAGS=-dr

then do something like

/root/egypt-1.6/egypt main.cpp.131r.expand > callgraph.dot

then

dot -Tps -o callgraph.ps callgraph.dot

then show your ps file

konqueror callgraph.ps

doxygen

Not as good as egypt for call graphs, but gives you a good api documentation.

sourcenavigator

SourceNaviGatOr allows you to switch from a call to a function to its implementation

krazy

checks for possibly wrong code

icemon

svn co https://svn.kde.org/home/kde/trunk/playground/devtools

Tutorials

parser

See parser.

Kontact plugins

See kontact plugins.

Using a kioslave/write a mini-browser

This tells you how to use a kioslave in KDE 4. In it, we write some kind of a mini-browser as a demo. We use the http kioslave kio_http.

See using a kioslave in KDE 4.

Using the fish kioslave/access remote files

In KDE, you can access remote files via fish://. Here is an example how to load /tmp/fishtest.txt from localhost. You need to

useradd -m fishuser
passwd fishuser 
touch /tmp/fishtest.txt

first.

main.cpp

#include <kapplication.h>
#include <kaboutdata.h>
#include <kcmdlineargs.h>
#include <kdebug.h>
#include <kio/netaccess.h>
#include <KMainWindow>
#include <KTemporaryFile>

int main (int argc, char *argv[])
{
  const QByteArray& ba=QByteArray("test");
  const KLocalizedString name=ki18n("myName");
  KAboutData aboutData( ba, ba, name, ba, name);
  KCmdLineArgs::init( argc, argv, &aboutData );
  KApplication khello;
  KMainWindow* mainwindow=new KMainWindow();
  QWidget* mywidget=new QWidget();
  mainwindow->setCentralWidget(mywidget);
  mainwindow->show();
  kDebug() << "loading fish://fishuser@localhost/tmp/fishtest.txt";
  QString err=QString();
  QString content=QString();
  QString filename="fish://fishuser@localhost/tmp/fishtest.txt";
  KUrl url(filename);
  kDebug() << url.url();
  // load remotely
  {
    KTemporaryFile tmpFile;
    if ( !tmpFile.open() ) err = QString::fromLatin1( "Unable to get temporary file" );
    else
    {
      QString test2;
      QString& test=test2;
      if (!KIO::NetAccess::download( url, test, 0 )) err=QString::fromLatin1("Could not download");
      filename=test;
      kDebug() << "Using file " << filename;
      static QFile file(filename);
      static QTextStream stream(&file);
      if (file.open(QIODevice::ReadOnly))
        while (!stream.atEnd()) content.append(stream.readLine()).append("\n");
      file.close();
    }
  }
  kDebug() << "err=" << err;
  kDebug() << "content=" << content;
}

CMakeLists.txt

PROJECT( tutorial )
FIND_PACKAGE(KDE4 REQUIRED)
INCLUDE_DIRECTORIES( ${KDE4_INCLUDES} . )


SET(tutorialSources main.cpp )

KDE4_ADD_EXECUTABLE(tutorial ${tutorialSources} )

TARGET_LINK_LIBRARIES(tutorial ${KDE4_KDEUI_LIBS} ${KDE4_KPARTS_LIBS} )

Compile, link and run

cmake . && make && ./tutorial

Write a kioslave

See write a kioslave.

dirwatcher

test.cpp

#include "hello.h"
#include <qstring.h>
#include <kapplication.h>
#include <kdebug.h>
#include <kwindowsystem.h>
#include <kaboutdata.h>
#include <kmessagebox.h>
#include <kcmdlineargs.h>

int main(int argc, char *argv[])
{
  const QByteArray qba="test";
  const KLocalizedString kls=ki18n("test");
  KAboutData aboutData( qba, qba, kls, qba, kls, KAboutData::License_GPL, kls, kls, qba, qba);
  KCmdLineArgs::init( argc, argv, &aboutData );
  KApplication khello2;
  khello* dw1=new khello("/tmp/test");
  KGuiItem kgi(QString("Hello"),QString(),QString("this is a tooltip"),QString("this is whatsthis"));
  kDebug() << KWindowSystem::activeWindow();
  khello2.exec();
}

hello.h

#ifndef _KHELLO_H_
#define _KHELLO_H_

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <kmainwindow.h>
#include <kdirwatch.h>

/**
 * @short Application Main Window
 * @author Thorsten Staerk
 * @version 0.1
 */
class khello : public KMainWindow
{
    Q_OBJECT
public:
    KDirWatch dw;
    /** Default Constructor */
    khello(char* args);
    /** Default Destructor */
    virtual ~khello();
public slots:
    void slotdirty();
};
#endif // _KHELLO_H_

hello.cpp

/*
 This program shows us how to set up a kdirwatch, telling us when a file has changed.
 To compile this, create project with kdevelop and add these files.
 */

#include "hello.h"
#include <qlabel.h>
#include <kdebug.h>
#include <kmainwindow.h>
#include <klocale.h>
#include <kmessagebox.h>

khello::khello(char* args)
    : KMainWindow( 0)
{
    // set the shell's ui resource file
    kDebug() << "args = " << args << endl;
    dw.addFile(args);
    kDebug() << "Watching file " << args << endl;
    connect(&dw, SIGNAL(dirty(const QString & ) ),
           SLOT( slotdirty() ) );
}

khello::~khello()
{
}

void khello::slotdirty()
{
  kDebug() << "File has changed" << endl;
}

#include "hello.moc"

compile and run it

moc hello.h>hello.moc && g++ -I/usr/include/Qt -I/home/kde-devel/kde/include -L/home/kde-devel/kde/lib -lkdeui -lkio test.cpp hello.cpp


QTreeWidget

This is a very minimalistic QTreeWidget coding demonstration - you can use it to learn, and as a template.

main.cpp

#include <QTreeWidget>
#include <kapplication.h>
#include <kaboutdata.h>
#include <kcmdlineargs.h>
#include <KMainWindow>

int main (int argc, char *argv[])
{
  const QByteArray& ba=QByteArray("test");
  const KLocalizedString name=ki18n("myName");
  KAboutData aboutData( ba, ba, name, ba, name);
  KCmdLineArgs::init( argc, argv, &aboutData );
  KApplication khello;
  KMainWindow* mainwindow=new KMainWindow();
  QWidget* mywidget=new QWidget();
  QTreeWidget* qw=new QTreeWidget(mywidget);
  qw->setColumnCount(3);
  QStringList columns;
  columns << "first column" << "second column" << "third column";
  QTreeWidgetItem* item=new QTreeWidgetItem(qw,columns);
  qw->addTopLevelItem(item);
  mainwindow->setCentralWidget(mywidget);
  mainwindow->show();
  return khello.exec();
}

CMakeLists.txt

PROJECT( tutorial )
FIND_PACKAGE(KDE4 REQUIRED)
INCLUDE_DIRECTORIES( ${KDE4_INCLUDES} . )


SET(tutorialSources main.cpp )

KDE4_ADD_EXECUTABLE(tutorial ${tutorialSources} )

TARGET_LINK_LIBRARIES(tutorial ${KDE4_KDEUI_LIBS} ${KDE4_KPARTS_LIBS} )

To compile, link and run this program, use:

cmake . && make && ./tutorial

Your own html editor

Here we develop an html editor:

main.cpp

#include <QTextEdit>
#include <QTreeWidget>
#include <kapplication.h>
#include <kaboutdata.h>
#include <kcmdlineargs.h>
#include <KMainWindow>

int main (int argc, char *argv[])
{
  const QByteArray& ba=QByteArray("tutorial");
  const KLocalizedString name=ki18n("tutorial");
  KAboutData aboutData( ba, ba, name, ba, name);
  KCmdLineArgs::init( argc, argv, &aboutData );
  KApplication khello;
  KMainWindow* mainwindow=new KMainWindow();
  QWidget* mywidget=new QWidget();
  QTextEdit* qt=new QTextEdit(mywidget);
  qt->setText("these are <b>fat</b> characters");
  mainwindow->setCentralWidget(mywidget);
  mainwindow->show();
  return khello.exec();
}

CMakeLists.txt

PROJECT( tutorial )
FIND_PACKAGE(KDE4 REQUIRED)
INCLUDE_DIRECTORIES( ${KDE4_INCLUDES} . )

SET(tutorialSources main.cpp )

KDE4_ADD_EXECUTABLE(tutorial ${tutorialSources} )

TARGET_LINK_LIBRARIES(tutorial ${KDE4_KDEUI_LIBS} ${KDE4_KPARTS_LIBS} )

To compile, link and run this program, use:

cmake . && make && ./tutorial

dbus

dbus has a strange and complicated syntax.

kde-devel@kolossus:~/qt-copy> dbus-send --type=method_call --print-reply --dest=org.kde.ktimetracker /KTimeTracker org.kde.ktimetracker.ktimetracker.version
method return sender=:1.64 -> dest=:1.84
   string "4.0.0"


Error msgs and what they mean

Error msgs originating from ASSERT are just a shame. Here is a bit of help what they might want to say to you:

ASSERT: "s->parsed == false" in file /home/kde-devel/kde/src/kdelibs/kdecore/kernel/kcmdlineargs.cpp, line 520

=> You need to call

KUniqueApplication myApp;

before

KCmdLineArgs *args = KCmdLineArgs::parsedArgs();

yes, this makes it impossible to program a switch if you want a konsole application (KUniqueApplication myApp(false,false)) or not.

Could not run kstartupconfig4

When I got this error, I first wondered why it could not be started, but the problem was rather it returned an error. To track this error down, call kstartupconfig4 from command line and look for the return code. For me, it was 3. Looking into kdostartupconfig.cpp showed that this means problem when opening the file startupconfig. I added a debugging output how the file name is before returning 3. It is /root/.kde/share/config/startupconfigkeys. This file did not exist. I did a touch on this file. Then KDE came up.

The misleading error msg is contained in startkde. My bugfix

Find out the active window on your desktop

main.cpp

#include <qstring.h>
#include <kapplication.h>
#include <kdebug.h>
#include <kwindowsystem.h>
#include <kaboutdata.h>
#include <kmessagebox.h>
#include <kcmdlineargs.h>

int main (int argc, char *argv[])
{
  const QByteArray qba="test";
  const KLocalizedString kls=ki18n("test");
  KAboutData aboutData( qba, qba, kls, qba, kls, KAboutData::License_GPL, kls, kls, qba, qba);
  KCmdLineArgs::init( argc, argv, &aboutData );
  KApplication khello;
  KGuiItem kgi(QString("Hello"),QString(),QString("this is a tooltip"),QString("this is whatsthis"));
  kDebug() << KWindowSystem::activeWindow();
}

Compile and run it

g++ -I/home/kde-devel/kde/include \
-I/home/kde-devel/qt-copy/include \
-I/home/kde-devel/qt-copy/include/Qt \
-I/home/kde-devel/qt-copy/include/QtGui \
-L/home/kde-devel/qt-copy/lib \
-L/home/kde-devel/kde/lib \
-lQtCore -lQtGui -lkdeui main.cpp  
./a.out

See also

Find out when a focus change occurs

main.cpp

#include <hello.h>
#include <qstring.h>
#include <kapplication.h>
#include <kdebug.h>
#include <kwindowsystem.h>
#include <kaboutdata.h>
#include <kmessagebox.h>
#include <kcmdlineargs.h>

int main(int argc, char *argv[])
{
  const QByteArray qba="test";
  const KLocalizedString kls=ki18n("test");
  KAboutData aboutData( qba, qba, kls, qba, kls, KAboutData::License_GPL, kls, kls, qba, qba);
  KCmdLineArgs::init( argc, argv, &aboutData );
  KApplication myApp;
  khello* dw1=new khello();
  kDebug() << "Active Window is " << KWindowSystem::activeWindow();
  myApp.exec();
}

hello.h

#ifndef _KHELLO_H_
#define _KHELLO_H_

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <kmainwindow.h>
#include <kdirwatch.h>

/**
 * @short Application Main Window
 * @author Thorsten Staerk
 * @version 0.1
 */
class khello : public KMainWindow
{
    Q_OBJECT
public:
    KDirWatch dw;
    /** Default Constructor */
    khello();
    /** Default Destructor */
    virtual ~khello();
public slots:
    void slotdirty();
};
#endif // _KHELLO_H_

hello.cpp

/*
 This program shows us how to set up a watcher which Window gets the focus.
 Copyright (c) 2007-11-04 by Thorsten Staerk
 */

#include "hello.h"
#include <qlabel.h>
#include <kdebug.h>
#include <kmainwindow.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kwindowsystem.h>

khello::khello()
    : KMainWindow( 0)
{
    kDebug() << "Watching which window gets the focus ";
    KWindowSystem* kw=KWindowSystem::self();
    connect(kw, SIGNAL(activeWindowChanged(WId)), SLOT( slotdirty() ) );
}

khello::~khello()
{
}

void khello::slotdirty()
{
  kdDebug() << "Focus has changed" << endl;
}

#include "hello.moc"

CMakeLists.txt

PROJECT( kde4start )
FIND_PACKAGE(KDE4 REQUIRED)
INCLUDE_DIRECTORIES( ${KDE4_INCLUDES} . )


SET(kde4startSources hello.cpp main.cpp)

KDE4_ADD_EXECUTABLE(kde4start ${kde4startSources} )

TARGET_LINK_LIBRARIES(kde4start ${KDE4_KDEUI_LIBS} ${KDE4_KPARTS_LIBS} )

Compile and run this using

cmake .
make
./kde4start

ToDo

krep

krep should look how many processors it has and adjust its thread count accordingly. Ideas:

ktimetracker

make the kontact plugin selectable. The heart of this is a KCModule that is in every selectable kontact plugin (even in kaddressbook, but not in the plugins path).

bugzillas

kmail

Get an ide

To get an ide for kmail, I use kdevelop: http://techbase.kde.org/index.php?title=Getting_Started/Set_up_KDE_4_for_development#KDevelop

allow addresses ending on .

e-Mail-addresses may not end with a . before the @. However, some people do have them. We should allow it, but warn. Here is a workaround:

--- libemailfunctions/email.cpp (revision 773158)
+++ libemailfunctions/email.cpp (working copy)
@@ -509,6 +509,7 @@
 //-----------------------------------------------------------------------------
 bool KPIM::isValidSimpleEmailAddress( const QString& aStr )
 {
+  return true;
   // If we are passed an empty string bail right away no need to process further
   // and waste resources
   if ( aStr.isEmpty() ) {

http://en.wikipedia.org/wiki/E-mail_address

kmail search

This chapter's intention is to improve the kmail searches a bit.

Search for attachments

Problem: see http://bugs.kde.org/show_bug.cgi?id=84409

svn diff
Index: headeritem.cpp
===================================================================
--- headeritem.cpp      (revision 748768)
+++ headeritem.cpp      (working copy)
@@ -297,6 +297,8 @@
         !headers->paintInfo()->showAttachment &&
         msgBase->attachmentState() == KMMsgHasAttachment )
       pixmaps << *KMHeaders::pixAttachment;
+    kdDebug(5006) << "Setting the attachment pix" << endl;
+    kdDebug(5006) << "For msg " << msgBase->subject() << endl;

     // Only merge the crypto icons in if that is configured.
     if ( headers->paintInfo()->showCryptoIcons ) {

The problem is the status of a KMMessage which is a KMMsgBase. KMMessage->status() = mStatus.

Stalling when searching /

The first problem is that the search stalls when you search the "/" folder in an imap account.


The Stalling happens in searchjob.cpp, function searchCompleteFolder, when it gets the URL imap://user:password@server:143/;SECTION%3DHEADER%20Subject%20%22E%22

Solved by this commit: http://websvn.kde.org/branches/KDE/3.5/kdepim/kmail/searchjob.cpp?r1=645363&r2=745678&sortby=date

the kioslave

http://api.kde.org/4.0-api/kdelibs-apidocs/kio/html/classKIO_1_1Scheduler.html#_details

For kmail, kio_imap4's kdemain and IMAP4Protocol::IMAP4Protocol is started after you enter the password.

Index: imapaccountbase.cpp
===================================================================
--- imapaccountbase.cpp (revision 743935)
+++ imapaccountbase.cpp (working copy)
@@ -286,6 +286,8 @@

 ImapAccountBase::ConnectionState ImapAccountBase::makeConnection()
 {
+  kDebug() << "***********************************************************************************************";
+  kDebug() << "Entering ImapAccountBase::makeConnection()";


kio_imap4

test.cpp

#include <kio/scheduler.h>
#include <kurl.h>
#include <kconfiggroup.h>
#include <kconfig.h>
#include <klocale.h>
#include <kcomponentdata.h>
int main()
{
  KComponentData instance( "kcmkmail_config_misc" );
  KConfigGroup passwords( KGlobal::config(), "Passwords" );
  const KUrl url("imap://dummy:password@localhost/inbox");
  KIO::Scheduler::getConnectedSlave(url,KIO::MetaData());
}
kde-devel@kolossus:~/kio> g++ -I/home/kde-devel/kde/include -L/home/kde-devel/kde/lib -lkio test.cpp

See also kio-imap4 programming

Patch

This has all been resolved, see https://bugs.kde.org/show_bug.cgi?id=153448

Multiple folders search

svn diff kmfoldersearch.cpp
Index: kmfoldersearch.cpp
===================================================================
--- kmfoldersearch.cpp  (revision 744858)
+++ kmfoldersearch.cpp  (working copy)
@@ -173,6 +173,8 @@
         return;
     }

+    // TODO: add multiple folders to be searched here, after gui
+    // extension (BUG 153443)
     mFolders.append( mRoot );
     if ( recursive() )
     {
@@ -259,11 +261,13 @@
             mLastFolder = folder->label();
             folder->open("kmsearch");
             mOpenedFolders.append( folder );
+            kdDebug() << "Opened folder '" << mLastFolder << "' for searching." << endl;
             connect( folder->storage(),
                 SIGNAL( searchResult( KMFolder*, QValueList<Q_UINT32>, const KMSearchPattern*, bool ) ),
                 this,
                 SLOT( slotSearchFolderResult( KMFolder*, QValueList<Q_UINT32>, const KMSearchPattern*, bool ) ) );
             folder->storage()->search( mSearchPattern );
+            kdDebug() << "Searched folder '" << mLastFolder << "'." << endl;
         } else
           --mRemainingFolders;
         mProcessNextBatchTimer->start( 0, true );
@@ -276,7 +280,9 @@
                                        const KMSearchPattern* pattern,
                                        bool complete )
 {
+    kdDebug() << "BEGIN slotSearchFolderResult" << endl;
     if ( pattern != mSearchPattern ) return;
+    kdDebug() << "Doing slotSearchFolderResult" << endl;
     kdDebug(5006) << k_funcinfo << folder->label() << " found " << serNums.count() << endl;
     mLastFolder = folder->label();
     QValueListIterator<Q_UINT32> it;
@@ -579,7 +585,7 @@

     assert(!folder()->name().isEmpty());
     assert(mOpenCount == 0);
-
+    kdDebug() << "tstaerk was here" << endl;
     kdDebug(5006) << "Creating folder " << location() << endl;
     if (access(QFile::encodeName(location()), F_OK) == 0) {
         kdDebug(5006) << "KMFolderSearch::create call to access function failed."


Meetings

Osnabrück 2009

goals:

  • C++ parser
  • menus
  • N810
  • kioslave
  • akonadi

Friday

got to know Bertjan, discussion on Linux Terminal Servers and KDE 4. Got KDE build environment. David wants to kompile on his N810, I want to use this to improve my tutorial

Saturday

we should go for Qt Creator as our C++ parser. Together with Will we verify http://bugs.kde.org/show_bug.cgi?id=147948. Backporting now.

Osnabrück 2008

My Blog

Here's the first blog in my life. KDE PIM meetings are for hackers like a bottle of water in a desert. Finally time to hack, hack, hack and only sporadic meetings. And those meetings about development topics. This meeting took place from friday 2008-02-01 till 2008-02-03. This time I remembered to take vacation for the friday. At 10:00 am, I departed from Alzey and arrived shortly after 14:00 in Osnabrück at Intevation who hosted this meeting (thanks!). Some guys were already there. I finally got to know Thomas who showed me how to use kdevelop for KDE 4. I used this day to get KDE compile on my notebook. We left the office at 1 am.

krep

On Saturday, I gave a presentation about my pet project, simplified debugging, see I showed how you increase your productivity by using a combination of add_trace and krep. My pain points are:

  • with grep, I filter away either too much or too less
  • changing the pattern for grep requires grep's restart
  • grep's restart requires the restart of the application whose output you are sending to grep.

This makes debugging hard.

You use add_trace to change your KDE source code so that every function outputs its name first when called:

QString KarmStorage::saveCalendar()
{
  kDebug(0) << "Entering function" << endl;

Here, the kDebug-line has been added by add_trace. You compile again then and watch the output using krep. This way, you can almost create a call tree. And as soon as I manage to make debugging output as soon as you leave a function, you will actually be able. You would start e.g.

ktimetracker 2>&1 | krep

krep then allows you to shrink or expand your view of ktimetracker's output.

Windows debugging

After my presentation, Jaroslaw gave his presentation about debugging KDE on Windows. There you can use MS Visual C's ide and you have a similar functionality as krep. It was also impressive to see the call stack being displayed while debugging and the performance of the system.

KDE 4.1 discussion

Interesting discussion about our proceeding for KDE 4.1. Good meeting minutes on the mailing list.

Akonadi

Akonadi for insiders. Discussing about the state (that I do not know). I preferred finally improving the KDE code.

debugging output

I asked how you compile your KDE programs so that kDebug messages are not output. The correct answer is use

cmake -DCMAKE_BUILD_TYPE=Release .

Dinner

at the italian

kmail

Discussion with Ingo about kmail bugs. If we manage to get the body structure (documented in some rfc), the body information, we might be able to close bug 80995.

key signing party

My kmail is now secured thanks to Ingo taking me into the web of trust by signing my gpg key. The key server even had my old key that I had signed by c't at CeBit 2001.

HTML editor

Mike recommends me to try QTextEdit, QTextBrowser and QTextDocument for an HTML editor.

David

Hacking session with David on krep. You should replace

foo(QString bar)

by

foo(const QString& bar)

whenever possible for the reasons:

  • const prevents you from accidentially changing bar
  • QString& is in contrast to QString a pass-by-reference, not a pass-by-value. You save the time of copying a QString.
QString blah;

is by default equivalent to

QString blah=QString();

for QString blah; uses the default constructor to construct the object (unless adviced otherwise). This is also valid in a header-file. This is valid for all non-trivial data types, for all data types that have a constructor. So, you still need to initialize an int like this:

int i=0;

And, pay attention, the following will have a NON DEFINED value:

QString* qs;

You need to initialize it:

QString* qs=new QString();

or

QString* qs=0;

Thinking complicated is the cool and the ugly part of C programming (C++ is a dialect of C).

We created a benchmark for krep:

for i in $(seq 1 1 300); do echo $i; done | ./krep

it took 17 seconds (in revision 770163), then we worsened it, then it took 10 seconds (in revision 770188), then 2 seconds (in revision 770205).

Deleting a pointer ? Best set it to 0 afterwards so you know it is useless.

We left the office shortly after 2am.

commitfilter

During the breakfast, we discussed about various things, e.g. the strangest bug reports and mails on mailing lists. There was a discussion that the term "spouse" (e.g. in an address book) is discriminating people who live in relations and cannot marry because e.g. they are gay and live in intolerant countries. So it was proposed to change "spouse" to "partner". I think, so far this is good and understandable. But soon, a guy came up demanding to support polygamy for tolerance's sake. Or, there was a guy stating the letter K would make people commit suicide and demanding from us to change our logo...

What I also learned during breakfast: Did you know that you can easily watch changes in KDE's code base by registering at http://commitfilter.kde.org ? This site will send you a mail when the svn branch (can also be trunk ;) that you select changes. Thanks Thomas for the tip.

kexi

Talked with Jaroslaw about using kexi as database backend for ktimetracker. Also akonadi could be used. I could start with my kexi experiences at http://websvn.kde.org/trunk/koffice/kexi/tests/newapi/. Writing this blog, I discovered, that even german Autobahnen (motorways) are subjects of ENGLISH wikipedia's articles, so, here is one of these important topics, including pictures.

kcal error messages

Discussion with Cornelius and the others about error message handling problems with kcal. As this involves adding of virtual functions, we will not be able to get the fix in before KDE 5. And then, we will have Akonadi.

Past

Bugs worth remembering