Thursday, December 27, 2007

Exporting History from Konqueror

In my Konqueror settings I have had it retain my history sense August 2006. This has resulted in a history file that contains over eighteen thousand entries. As you can guess Konqueror has some trouble with a history of this size. A dataset of this size is a good way to test any history managers memory usage and performance. I was hoping to be able to export the data with dcop, but unfortunately the only function available is allUrls which doesn't make that much sense because the dates are just as important as the urls. For the curious to see your history urls (replacing 11489 with your Konqueror pid) do the following:

dcop konqueror-11489 KonqHistoryManager allURLs

Reaching a dead end with dcop I created a little application that uses libkonq to extract the history.
#include kapplication.h
#include kcmdlineargs.h
#include konq_historymgr.h

int main(int argc, char **argv)
{
    KCmdLineArgs::init(argc, argv, "konqueror", "konqueror", "I do stuff", "0.1");
    KApplication application(argc, argv);
    KonqHistoryManager manager(0, "manager");
    KonqHistoryList list = manager.entries();
    for (uint i = 0; i < list.count(); ++i) {
        printf("%s\n%s\n%s\n",
                list.at(i)->url.url().latin1(),
                list.at(i)->title.latin1(),
                list.at(i)->lastVisited.toString().latin1());
    }
    return 0;
}

Already this data has proved useful and helped me reduce my memory footprint by half and catch my performance bottlenecks so it can handle 20 entries or 20,000 just as fast.

Saturday, December 22, 2007

Valgrind callgrind tools Part 3: Code coverage

In the Valgrind manual they list some tools you could write for Valgrind. One of the suggestions is to write a coverage tool. So a few months ago I sat down and did exactly that. It was a nice little tool and I happily posted it to the Valgrind developer mailing list only to discover that I was not the first person to do this nor was any of our solutions that good in the end. The easy part of the project is obtaining a recored of what was executed. In fact Cachegrind does a fantastic job of that. The only thing my tool did (that I thought was new) was automatically collect data on all the jumps which discovered after I wrote my tool was already present in the callgrind tool if you pass in the argument "--collect-jumps=yes". So me and several others have all written bad versions of callgrind and the cg_annotate script. Code coverage with Valgrind is made up of two problems. The first is obtaining the code execution and the second is applying this over the source for the program. The first is easy by using "callgrind --collect-jump=yes". The second part isn't as easy a new problem for every language, but at least for C++ using Roberto's parser makes this task a lot easier.

Here is an older output for a test that I wrote for QColumnView that shows some missing coverage:

valgrind --tool=callgrind --collect-jumps=yes ./tst_qcolumnview

./callgrind_coverage ~/dev/qt/tests/auto/qcolumnview/callgrind.out.16089 ~/dev/qt/src/gui/itemviews/qcolumnview.cpp

Function coverage - Has each function in the program been executed?
Not executed: 612 void QColumnViewPrivate::_q_clicked(const QModelIndex &index)
Coverage: 39/40 97%

Condition coverage - Has each evaluation point (such as a true/false decision) been executed?
443 for (int i = 0; i < ranges; ++i) {
842 for (; (i < list.count() && i < d->columns.count()); ++i) {
212 if (d->columns.isEmpty() || dx == 0)
976 if (!model() || !selectionModel())
976 if (!model() || !selectionModel())
229 if (!index.isValid() || d->columns.isEmpty())
229 if (!index.isValid() || d->columns.isEmpty())
272 if (leftEdge > -horizontalOffset()
336 switch (cursorAction) {
338 if (current.parent().isValid() && current.parent() != rootIndex())
338 if (current.parent().isValid() && current.parent() != rootIndex())
557 for (int i = columns.size() - 1; i >= 0; --i) {
549 while (currentColumn == -1 && parentIndex.isValid()) {
572 if (currentColumn == -1 && parent.isValid())
577 if (build && columns.size() > currentColumn + 1) {
580 && !columns.at(currentColumn + 1)->rootIndex().isValid());
689 if (!columns.isEmpty() && columns.last()->isHidden())
689 if (!columns.isEmpty() && columns.last()->isHidden())
695 if (show && view->isHidden())
394 if (horizontalLength < viewportSize.width() && q->horizontalScrollBar()->value() == 0) {
813 if (!columns.isEmpty() && columns.last() == previewColumn)
1024 if (!model || columns.isEmpty())
1035 if (x != view->x() || viewportHeight != view->height())
1042 if (x != view->x() || viewportHeight != view->height())
368 if (diff < 0 && horizontalScrollBar()->isVisible()
877 if (currentParent == previous.parent()
877 if (currentParent == previous.parent()
879 for (int i = 0; i < d->columns.size(); ++i) {
893 for (int i = 0; i < d->columns.size(); ++i) {
512 if (!found && columns.at(i)->cornerWidget() == grip) {
Coverage: 566/598 377%

Statement coverage - Has each line of the source code been executed?
Coverage: 475/523 90%
-------------
Key:
c == compiled
e == executed
r == return
j == jump
s == skipped
- == comment/whitespace/not compiled
-------------
94 [j] f: }
273 [c]: && rightEdge <= ( -horizontalOffset() + viewport()->size().width())) {
286 [c]: newScrollbarValue = rightEdge + horizontalOffset();
336 [j] f: switch (cursorAction) {
369 [c]: && horizontalScrollBar()->value() == horizontalScrollBar()->maximum()) {
579 [c]: bool viewingChild = (!model->hasChildren(parent)
612 [c]: void QColumnViewPrivate::_q_clicked(const QModelIndex &index)
614 [c]: Q_Q(QColumnView);
615 [c]: QModelIndex parent = index.parent();
616 [c]: QAbstractItemView *columnClicked = 0;
617 [c]: for (int column = 0; column < columns.count(); ++column) {
618 [c]: if (columns.at(column)->rootIndex() == parent) {
619 [c]: columnClicked = columns[column];
623 [c]: if (q->selectionModel() && columnClicked) {
624 [c]: QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::Current;
625 [c]: if (columnClicked->selectionModel()->isSelected(index))
626 [c]: flags |= QItemSelectionModel::Select;
627 [c]: q->selectionModel()->setCurrentIndex(index, flags);
690 [c]: columns.last()->setVisible(true);
726 [c]: model()->fetchMore(index);
824 [c]: previewColumn->setMinimumWidth(qMax(previewColumn->verticalScrollBar()->width(),
861 [rc]: return list;
878 [c]: && model()->hasChildren(current) && model()->hasChildren(previous)) {
939 [c]: QItemSelectionModel *replacementSelectionModel =
941 [c]: replacementSelectionModel->setCurrentIndex(
943 [c]: replacementSelectionModel->select(
993 [c]: QModelIndex br = model()->index(model()->rowCount(parent) - 1,
994 [c]: model()->columnCount(parent) - 1,
1081 [c]: opt.rect = QRect(option.rect.x() + option.rect.width() - width, option.rect.y(),


Note that this tool is a bit of a hack and is a work in progress so you will no doubt find issues. But it is good enough to be useful so I figured it would be time to publish:

The source for callgrind_coverage can be found in my callgrind tools git repository.

Monday, December 10, 2007

Valgrind callgrind tools Part 2: accessing information inside a callgrind files

From Valgrind's callgrind manual:

Callgrind is a Valgrind tool for profiling programs. The collected data consists of the number of instructions executed on a run, their relationship to source lines, and call relationship among functions together with call counts. Optionally, a cache simulator (similar to cachegrind) can produce further information about the memory access behavior of the application.

The result is that the files that are produces contain a plethora of data just waiting to be mined. I have written a tool called callgrind_info to extract information out of a callgrind file.

When you want to watch a function for performance regressions callgrind excels at getting results that are similar one run to the next. Extracting the cost of a function in a nightly script would be a very useful thing to do to automatically determine if you are having performance regressions:
./callgrind_info callgrind.out.5511 -cost 'tst_QListView::cursorMove()'
158034263

The matching functionality for the "-cost" is the "-function" which will output every function that was used in some specific file:
./callgrind_info callgrind.out.5511 -functions tst_qlistview.cpp                              
tst_QListView::tst_QListView()
tst_QListView::~tst_QListView()
tst_QListView::cleanup()
tst_QListView::cursorMove()
...

callgrind_info also lets you extract a list of the different specification types.

If you want to know every object/library that was loaded during the run of the program:
./callgrind_info callgrind.out.5511 -spec ob
/lib/tls/i686/cmov/libm-2.6.1.so
/usr/lib/libstdc++.so.6.0.9
/home/ben/dev/qt/tests/auto/qlistview/tst_qlistview
/lib/tls/i686/cmov/libnss_nis-2.6.1.so
/lib/tls/i686/cmov/libc-2.6.1.so
...

Every file:
./callgrind_info callgrind.out.5511 -spec fl | more
???
qbasicatomic.h
qdatetime.h
qwidget.h
qlist.h
qabstractitemmodel.h
qsize.h
...

Every function:
./callgrind_info callgrind.out.5511 -spec fn | more
0x00003460
floor
QBasicAtomicInt::operator int() const
QBasicAtomicInt::operator!=(int) const
QBasicAtomicInt::operator!() const
QBasicAtomicInt::operator==(int) const
QBasicAtomicInt::operator=(int)
QTime::QTime()
QWidget::show()
QWidget::resize(int, int)
QWidget::height() const
...


The callgrind info tool just touches on some of the data that can be extracted, but already it has proved to be very useful.

The source for callgrind_info can be found in my callgrind tools git repository.

Sunday, December 09, 2007

Valgrind callgrind tools Part 1: decompressing callgrind files

If you have ever used the Vallgrind tool callgrind you will find that it generates a file "callgrind.out.pid". Looking inside of this file you will find a compressed version of the execution of the program. Here is a snippet from a callgrind file I generated last night:
fn=(43726) tst_QListView::indexAt()
463 4
+2 4
cfn=(24288)
calls=1 148
* 3399
+1 1
+1 1
+2 4
cob=(26)
cfi=(265)
cfn=(21860)
calls=1 150
* 320655
+1 5
...

After reading the callgrind format specification you shouldn't have too much trouble being able to read it if you really want to. By default the callgrind files are compressed which make it harder to read then it has to be. So if you get a compressed callgrind file that you want to read I have written a tool to decompress the file into a more reasonable format. Here is the same code as before, but this time decompressed:
fn=tst_QListView::indexAt()
463 4
465 4
cfn=QtTestModel::QtTestModel(QObject*)
calls=1 148
465 3399
466 1
467 1
469 4
cob=/home/ben/dev/qt/lib/libQtGui.so.4.4.0
cfi=qlistview.cpp
cfn=QListView::QListView(QWidget*)
calls=1 150
469 320655
470 5
...

If you are going to be either extracting information out of a callgrind file or writing your own tool that generates a callgrind files like my QScript profiler having a decompresser comes in handy.

The source for callgrind decompress can be found in my callgrind tools git repository.

Wednesday, November 28, 2007

Limiting the runtime of a program

I have some programs that run in a cron job at night and occasionally they hang and don't exit which means that anything scheduled after them doesn't run and the next day I have processes hanging around. To solve that I made a twelve line program that uses features in QProcess to automatically kill a process that is taking longer then five minutes. Here is the slightly bigger version that lets you specify the timeout if you want and has a help.
timelimit -l 60000 ./reallyLongrunningapp

#include QtCore
int main(int argc, char **argv)
{
if (argc < 2) {
QTextStream out(stderr);
out << "Usage: timelimit -l 1000 command [args]" << endl;
return -1;
}

QProcess process;
QStringList arguments;
for (int i = 2; i < argc; ++i)
arguments.append(argv[i]);
QString command = argv[1];
int timeLimit = 1000 * 60 * 5;
if (argc > 3 && command == "-l") {
bool ok;
int userLimit = arguments.value(0).toInt(&ok);
if (ok) {
timeLimit = userLimit;
command = argv[3];
arguments.pop_front();
arguments.pop_front();
}
}
process.setProcessChannelMode(QProcess::ForwardedChannels);
process.start(command, arguments);
process.waitForFinished(timelimit);
return process.exitCode();
}

Friday, November 09, 2007

QtScript profiling

Working on some QtScript code a little bit ago I wanted to profile the code. There didn't exists a profiler to I wrote one. It uses QScriptEngineAgent which is part of Qt 4.4.

qscriptprofiler runs with script files as arguments that are each executed in the order they are passed. When the script is finished it will generate a callgrind file that can be loaded in KCachegrind. At this time qscriptprofiler only uses gettimeofday() to determine how long something takes so there is the possibility that it isn't accurate if your system is under heavy load. Improving it to not use the system clock is left as an exercise to the user (it is good enough for what I have been using it for). The profiler can be used in other projects and a pri file is included.

You can get the source for qscriptprofiler on github.

Thursday, November 08, 2007

Auto test stub generator

When writing auto tests for Qt code one of the more annoying parts of the task is getting the initial test file up and running. The basic makeup of the test files are about the same so I wrote a little tool that helps by automatically creating stub code for the class you are writing a test for. Then it is just a task of going through the file filling it in with the actual tests.

Some features include:
- Creates a stub tests for each non private function in the class.
- Creates a subclass for the class to expose any protected functions for testing.
- Creates a basic sanity test that just calls each function.
- Creates a _data() function for each test.
- Populates the _data() functions with columns for each argument and the return value.
- Adds QFETCH stub code that matches the generated _data function as a place to start from.
- Adds the four init and cleanup functions with documentation so you don't have to look up what does what.
- Adds signal spys to each tests if the class contains any signals.

The source code can be found: QAutoTestGenerator

Here is an example class:

class Example : QObject
{
Q_OBJECT
signals:
void done();
public:
void launch(int value);
protected:
bool crashed();
};

And the generated stub:

#include <QtTest/QtTest>
#include <example.h>

class tst_Example : public QObject
{
Q_OBJECT

public slots:
void initTestCase();
void cleanupTestCase();
void init();
void cleanup();

private slots:
void example_data();
void example();
void launch_data();
void launch();
void crashed_data();
void crashed();
void done_data();
void done();
};

// Subclass that exposes the protected functions.
class SubExample : public Example
{
public:
bool call_crashed()
{ return SubExample::crashed(); }

void call_done()
{ return SubExample::done(); }
};

// This will be called before the first test function is executed.
// It is only called once.
void tst_Example::initTestCase()
{
}

// This will be called after the last test function is executed.
// It is only called once.
void tst_Example::cleanupTestCase()
{
}

// This will be called before each test function is executed.
void tst_Example::init()
{
}

// This will be called after every test function.
void tst_Example::cleanup()
{
}

void tst_Example::example_data()
{
}

void tst_Example::example()
{
SubExample example;
/*
example.isDone();
example.launch();
example.call_crashed();
example.call_done();
*/
QVERIFY(false); // remove after test is implemented
}

void tst_Example::launch_data()
{
QTest::addColumn<int>("value");
QTest::newRow("null") << 0;
}

// public void launch(int value)
void tst_Example::launch()
{
/*
QFETCH(int, value);

SubExample example;

QSignalSpy spy0(&example, SIGNAL(done()));
QCOMPARE(spy0.count(), 0);
example.launch();
*/
QVERIFY(false); // remove after test is implemented
}

void tst_Example::crashed_data()
{
/*
QTest::addColumn<bool>("crashed");
QTest::newRow("null") << 0;
*/
}

// protected bool crashed()
void tst_Example::crashed()
{
/*
QFETCH(bool, crashed);

SubExample example;

QSignalSpy spy0(&example, SIGNAL(done()));
QCOMPARE(spy0.count(), 0);
example.call_crashed();
*/
QVERIFY(false); // remove after test is implemented
}

void tst_Example::done_data()
{
QTest::addColumn<int>("foo");
QTest::newRow("null") << 0;
}

// protected void done()
void tst_Example::done()
{
/*
QFETCH(int, foo);

SubExample example;

QSignalSpy spy0(&example, SIGNAL(done()));
QCOMPARE(spy0.count(), 0);
example.call_done();
*/
QVERIFY(false); // remove after test is implemented
}

QTEST_MAIN(tst_Example)
#include "tst_example.moc"

Monday, October 29, 2007

Quickies

I have a number of presents for you all.

First off is a little file manager that I wrote several months ago. It is called Ambit and is pretty much a weekend clone of OS X's Finder.
http://github.com/icefox/ambit/tree/master

Robero's wrote a kick ass C++ parser and code model. With it you can quickly and easily parse C++ and walk it sources to be able to do all sorts of cool things. Older versions of Roberto's parser can be found in various places in KDE, but they don't compile with just QtCore (the one in KDevelop require kate!) so I pulled this one out of the Qt Jambi sources and bounbled it up, added an example and put it up online where others can easily get at it to make their own tools such as those I talked about in making testing enjoyable with parsers.
http://github.com/icefox/rpp/tree/master

I have begun learning Lisp (or more exactly Common Lisp). Rather then just trying to make something and read tutorials as I need them I am slowly going through some Lisp books I own, working through the problems and exercises. I started a blog with the notes as I learn Lisp. http://takentheredpill.blogspot.com/

Wednesday, October 17, 2007

Why working for Trolltech kicks ass

  1. An office with a door and window!
    Do you find open air office cubes help you get in the zone for programming? Neither do I. Many people have written why offices are more productive then cubes so it is surprising that places like Google still use cubes. What is better then an office with a door? How about a window the size of the wall with a nice view. Not a view of another building twenty feet away, but hills, trees, and the occasional bird sitting on the ledge. At Trolltech there are two developers in every office (photo was taken during a move when there was only one desk in it).

  2. Smart developers
    Walking down the hall you will find many smart developers. They aren't just smart, they like what they do and many work on open source projects such as KDE and Webkit. How about putting Qt on the dreambox, hosting KDE hackathons on Sunday's, contributing to Git and much more. We love what we do and it shows.

  3. Release source code
    Go work for Google and there is a good chance you will never get to release your source code and perhaps never even be allowed to talk about it! Qt is released under the GPL license. Qtopia is released under the GPL. QScript, Qt-jambi, etc all GPL. We put up nightly snapshots for both our stable branch and main branch and host a matching git repository with them. We encourage releasing the source to our projects under a open source license. We put the source for our projects, neat ideas, and components up on labs.trolltech.com before they go in Qt so you can check them out. As a developer when you hack on code in Qt it doesn't end up in an internal black hole or locked in a binary, but is available for you to look at years later and reminisce or show off*.

    *Of course that also means that everyone else reads your code too so customers that catch your bugs will often send in useful patches so remember to write full test coverage.

  4. Company trips
    In the fall the developers head off for a cabin trip. Those in the Berlin office drive up and bring some excellent German beer and food. We rent some cabins for a weekend and have a lot of fun. In the spring the entire company heads off for a fantastic ski trip. Trips often include many other events such as bike rides, hikes, snow man contests and on one occasion a sleigh ride.

  5. labs.trolltech.com is a place where we can blog about what we are working on, put up code, projects and ideas. We get input on API and features and users get to mess around with what is coming down the pipeline and get solid information about where Qt and Trolltech is headed. As a developer you can talk about what you are working on, you can show of API and get feedback to make your product better.

  6. Creative Fridays
    You have probably heard of Googles 20% rule. At Trolltech we have creative Friday where you get to work on a creative project. Many of these end up labs.trolltech.com so even if they don't end up succeeding or not put in Qt they will still be on labs. Got an idea or itch to make something better that is outside of your day to day project then this is a perfect fit and many excellent improvements and features in Qt have come about because of this.

Monday, October 08, 2007

What I learned from Qt that made me a better programmer in C++

Reginald Braithwaite says he would love to hear stories about how programmers learned from X that makes me a better programmer in Y. So here are a few quick thoughts on how the Qt library made me a better C++ programmer.

1) The API matters, a lot


In a way Trolltech is in the business of selling good API's. By making their API consistent across Qt, it is easier as developers to understand how the class works and one can often guess correctly what an API will be without having to look it up. You wont find functions that take six bool arguments or function names that are overly vague when a better one will do. Each new function gets multiple API reviews and before every release all new functions and classes get a final review. Check out this excellent article on designing C++ APIs for some good examples.

Every time you add a new class or function you are introducing new API. Even if that API is only for yourself it is something that you will have to use and later read. On personal projects taking two minutes to step back and cleanup the API will payoff in the long run. Your code will be more consistent and when someone else comes along to check it out they will not only be happier with the cleaner API, but have an easier time getting up to speed and more likely to contribute back.

2) How to hide your private data (or working around one of c++'s flaws)


When building libaries in C++ there are a lot of rules you have to follow to make sure that you do not break binary compatibility.

The first and best step you can take is to make sure that your API is good (see #1). This goes a long way in fixing design issues before you can't anymore. Walk though use cases and make sure that the class provides what is needed. Hopefully most of the time you should be able to get away with just adding a new function or two, but the core of the class will be stable.

But behind the public API there are tricks you can used to hide all of the internals of the class in a way that lets you change them at will without breaking binary compatibility. One of them is the private class:

class FooPrivate;
class Foo {
public:
Foo();
private:
FooPrivate *d;
};


A technique that is used in Qt and KDE each class has a private class (with its own header). Data, implementation and private functions are moved into the private object. This provides a number of very positive benefits.
- You can add internal virtual functions, change function names, and generally do whatever you want.
- The implemenation can be completly changed without worrying about breaking binary compatibility.
- Users of the class don't need to include all the headers for private objects, reducing compile time.

Saturday, August 18, 2007

Seven Tips for giving Google Edu presentations (or any tech video that is going up online)

I have been subscribed to the Google Edu Video RSS for about a year now. I love how one day there is a presentation about how to best defend against a cougar attack and the next day a presentation about LLVM 2.0 It reminds me of when I was at CSH and every week there was a presentation or two. On Wednesday "Compression algorithms" and Friday "How to make a potato gun". The fact that anyone could give (or request) a presentation at any time to a small audience led to a wide range of interesting topics. Maybe you didn't know anything about the topic, but you always learned something.

After watching many Google Edu videos I have put together things to do when giving your presentation.

  1. Don't thank Google!
    The video tape is only 60 minutes long and wasting two minutes of it saying thank you is a waste of time. Yes you use Google and yes you find it useful, we all do. You don't need to say it.

  2. Don't give your life history
    If it takes eight minutes to get to the start of your real presentation then you have done something wrong. Usually there is someone who introduces you in thirty seconds or less. You do not need to give us your resume. If you are on Google edu we will give you the benifit of the doubt that you probably know what you are talking about.

  3. Repeat questions
    Any time someone asks a question you need to repeat it. If you do not repeat the question then that whole part of the presentation is dead to those who are watching it online. For some presentations that are largely QA it means that you will only ever reach the ten people in the room and not the other ten thousand that are trying to watch it online.

  4. A basic Intro is more then helpful, it is required
    When giving a technical talk, spend one minutes explaining what it is that you are doing. Example are good too. Jumping straight into the meat without anything will loose a lot of people who will just click to the next video. We will probably be able to understand a lot of what you are talking about if you just give us some place to start from.

  5. Don't poll the audience
    The physical audience size for some of these presentations ranges from 10 to 50. You should not ask for a raise of hands about anything, because there are 50,000 other people who will view it online.

  6. Don't give out secrets
    Just because only eight people turned up to see the presentation doesn't mean that you can say "just between you and me..." and then give away your company plans [cough]Adobe dude[cough]. Remember that the physical audience is nothing compared to the number of people that will watch it online.

  7. Don't feel bad because only 20 people show up
    At the start of the Surviving in the wilderness video that is linked above the guys is disappointed because only twenty people show up, but little did he know that the video would be watched twenty thousand times. How is that for an audience?

Monday, August 06, 2007

Javascript Genetic Algorithm

The other evening I was looking to play around with QScript. I have done a handful of JavaScript and know that there are a number of very interesting features that the language possesses. I have also been looking to hack on a Genetic Algorithm project. Putting the two together I made a Genetic Algorithm example written in JavaScript. It was quite a lot of fun to make and is quite a small amount of code in the end. I included an example that tries to find the shortest path between a set of points (the travelling salesman problem). You are free to type in any problem you want and it will attempt to solve it with the genetic algorithm. After it runs it will generate a graph showing the progress as its gets a better and better solution (using the canvas tag, sorry it doesn't work in Konq 3.x). And it will paint the final solution that it finds to the problem. Although it ended up being a little website rather then a Qt app it was fun learning more JavaScript and more about GA's. If you are interested in learning about GA's I have a number of links including some to Google books at the bottom.

Sunday, March 18, 2007

Anigma Game

Originally called "Puzz-le", Anigma was a very well done puzzle game for the Sharp Zaurus. When blocks of the same color are moved next to each other they disappear. The goal is to eliminate all the blocks on each level. Beside colored blocks there are other various objects the player can interact with. This includes elevators, disappearing trap-blocks, fire pits and more.

Released under the BSD license I ported it from Qt 2 to Qt 4. The original source is available on Walter Rawdanik's website. After porting the code some parts were upgraded to take advantage of new features in Qt4, but overall the code retains the original design. If you enjoy this game please send Walter Rawdanik an e-mail thanking him for licensing Puzz-le under the BSD.

The source for Anigma is under the BSD license. I recommend using Qt 4.2.3 with the game, but if you use 4.2.2 everything plays fine just the text at the top of the screen shows up very dark due to a Qt painting bug.

Download Anigma on the Anigma project page.






Thursday, March 15, 2007

Git and hooks

Update: See my more recent blog post Git hooks for a more in depth look at Git hooks.

The past few weeks I have started learning Git and this past week started using it for one of my own little project I have been hacking on. It has been a fun experience and once I figured out the basics it went pretty smooth. Here are a few tips that would have been nice to know before I started:

- Don't bother using Cogito, but just use Git directly. Git has matured a lot sense it was first introduced and removing another layer helps to make it less confusing.

- There are a handful of docs all over, but Git introduction/tutorial is the best intro I have seen.

- The equivalent of "svn revert file.cpp" is "git checkout HEAD file.cpp"

- Unlike svn Git wont automatically know what you want to commit, you have to add the file first.

- Git bisect is a handy little thing.

- repo.or.cz provides a nice easy place to host mirrors or push locations.

The one thing I haven't figured out how to do is go back and edit older commit messages which you can do in svn.

But the coolest thing about Git has to be the hook functionality. CVS, Subversion, Perforce, and well all revision control systems have hooks, but they are all located on the server side, there are no hooks for the client. With Git because your depot is on the client suddenly any hooks that you add are all run locally. To add a script to Git you place it in .git/hooks/ and modify the appropriate hook such as pre-commit to utilize it. Any time I have ever seen hooks being used before now was for integrating with other project services. KDE has some hooks so you can say that the commit solves a bug and it will automatically e-mail that change to bugs.kde.org. Subversion comes with a script to e-mail the change to someone or presumably a mailing-list. KDE has one that is more advanced, but still follows the basic idea of summarizing the change and letting someone/something know. From time to time in open source projects I have heard developers discuss the idea that the hook system can be used to make sure that no commits break the build by compiling the project with the patch first, but invariably it is never implemented because usually the build would take longer then the average time between commits, or the server is on a slow machine, or that the server for security reasons isn't setup for package building, or that the server doesn't have space, or sometimes devs want to commit broken code. Another common request is that any commit go through a code style tool. At least in KDE this would never fly because the KDE repository covers many project with different coding styles just for starters. Suffice it to say any hook that is put in place has to go through the community and overall very inflexible.

With the repository on my local machine I was quick to take advantage of that fact and added a handful of scripts to my pre-commit to:

- Check that the code in the patch conforms to the code style:
"astyle --indent=spaces=4 --brackets=linux --indent-labels --pad=oper --one-line=keep-statements --convert-tabs --indent-preprocessor "
- Check that the project compiles (In theory it was compiled before committing so this should be quick)
- Check that all the autotests affected by the patch pass

My development machine is a fast machine, no one else will be wanting to commit to the server, and I know my box is properly setup for building the repository. The above hooks usually are very quick and I think whatever delay it causes is worth it in the long run. The personalization of the hooks is what makes it more powerful then the hooks on a server though. For me if the source is already built I am not going to force it to distclean and rebuild while for someone else this might be required. Beyond that I even made a hook that checks what time it was and if it was after 3am then it tells me to go to bed and review the commit in the morning. A hook like that would never get into the KDE repository, but it is something I can have locally. Making quick modification to hooks are not something you would do with them on a CVS server. Hooks in revision control systems generally have been a very inflexible and an underutilized idea.

Dozens more ideas for hooks can be found on englishbreakfastnetwork.org. Spell check docbooks, validate xml files, check for binary incompatibility, etc. Because most of the time patches are small most tests wouldn't even do anything because they wouldn't be affected (i.e. if no xml file have changed there is nothing to validate).

Once I had it setup and running looking at Subversion is seems unfortunate that there isn't an official way to add client side hooks there too.

Some more links:
Hooks in Git Documentation
Hooks in Subversion Documentation

Wednesday, February 28, 2007

QColumnView

With 4.3 features wrapping there is a new itemview class called QColumnView. It provides a way of displaying a tree model. Each column is another level in the tree with the far left being the top of the tree and the far right side being a node. It is known for quick and easy keyboard navigation and a final preview area. Most commonly this view is known for its usage in OS X and in particularly in Finder. Below is a screenshot of using the view with the dir model and a simple file preview widget.



This widget isn't just for the finder though. I have found the same ui pattern in a lot of places (mostly on OS X). On extreme example was a date scheduling application. The first column let you select the month, the second you selected the day and the third the time. But rather then using lists of strings it showed calendars and clocks. I have seen a lot of applications who only need a few columns such as iTunes with their genre/artist/album columns. There are other applications who have only had two columns, the model was just a list (which was the first column) and when you clicked on an item the second column showed a detailed control of the item.

From the UI Patters and Techniques page on Cascading Lists:

"Use When the user needs to navigate a hierarchy that isn't very deep, but might have many items on each level. An [QTreeView] would work, but the user would be scrolling up and down a lot to see all the items, and they wouldn't get a good overview of the items at higher levels in the hierarchy. ... By spreading the hierarchy out across several scrolled lists, you show more of it at once. It's that simple. Visibility is good when you're dealing with complex information structures. Also, laying the items out in lists organizes them nicely -- a user can more easily keep track of what level they're dealing with than they could with an outline format, since the hierarchy levels are in nice predictable fixed-position lists."

And of course QTimeLine is used to provide smooth animations when scrolling between two columns. I snagged an animated gif, but be warned it came out a lot choppier then in real life.




The most intersting part of QColumnView is the last column. An empty space by default it can be replaced with a large widget that provides controls or previews. With such a large area to use quite a lot of information about the currently selected item can be shown. Unlike a tree where a separate area is needed or with icon where there is a constant battle to keep the items small, but have then be big to convey the information (often being just a small thumbnail). I look forward to seeing how it is used.

Tuesday, February 27, 2007

Qt QAbstractItemModel ModelTest


With labs.trolltech.com going live this week I was able to release a little tool that provides a way to check for common errors in implementations of QAbstractItemModel.

Never really finding anywhere in Qt where this would belong, labs is a perfect way to get this to developers. Although quite small it has been very usefull in finding issues, a lot faster then me sitting down and reading through code. It is especially usefull when writing tree based models because it will check all of the edge cases. On many occasions when KDE developers have asked me questions about problems they are having with their models, after running their code with the model test I was able to resolve the problem within minutes.

You can find the source on its labs page here:
http://labs.trolltech.com/page/Projects/Itemview/Modeltest

Saturday, February 24, 2007

PixelTool


Included with Qt 4.2 you will find a useful cross platform application you might not know about, called pixel tool. Pixeltool is a desktop magnifier and as you move your mouse around the screen it will show the magnified contents in its window. Hitting the space bar freezes the application so it will stop updating. The application source is located in /tools/pixeltool/, and is built by default so the binary is placed in bin/ like assistant and designer. Below is a screenshot selecting the plus in the tree menu in KMail.

If you right click on the window there are a handful of features you can tweak such as the grid color, grid size and zooming. I am not too sure if any distributions package this tool (Debian's qt4-dev-tools package doesn't), but it would be cool if they did.

Monday, January 15, 2007

Home For Christmas

For christmas I flew back to Boston to be with relatives. Having lived in America my entire life up until recently it was an interesting experience and I was surprised by the differences in American culture that I noticed upon returning.

After not owning a car the last year and a half I was amusing at how often people would drive when they could walk. My favorite experience the past few weeks was when I went out to lunch with some friends. We pulled out into the road, which was the main road for the town and the traffic on the road was almost bumper to bumper, stop light to stop light. Five minutes later about a mile up the road we pulled into the restaurant. It was like in Office space where the guy with the cane is walking faster then the cars. The best part was that the car I rode in was a hybrid and during the middle of lunch we briefly talked about the price of gas at the pump.

Jen and I walked around town, but still it was surprising how often we had to convince people that we really could walk a mile to the store and didn't need a ride. Someone once told me that the streets of Europe are not as handicap accessible as in America, but at least in Arlington that wasn't true at all. Lots of sidewalks don't go down to the road on corners, trees have uprooted chunks all over the place, many side streets simply didn't have sidewalks, and some side roads had no sidewalks and barriers on both side (so you couldn't even walk in the dirt) highly discouraging anyone from walking that way. One day it was sixty degrees or so outside and we were happy to see other adults outside until we realized that every single one was in some sort of workout clothes and running or "quick" walking. As far as I can tell walking in America is only for exercising, not for actually getting somewhere.

Although I knew that there was less adds on TV back home in Norway I was surprised just how many there actually where. Every few minutes, another set started for your enjoyment. I watched the evening news a few times and was annoyed at just how poor the news is here. The things that they report that are not important or the lack of actual news in a five minute span was amazing.

Part of the fun of visiting was eating all of the food I missed, I wonder if Norwegians who live in the America miss brown cheese? Every other day we went to a different old favorite restaurant with friends. The food was fine, but I think in my head I had been looking forward to it for so long that they couldn't match up to my expectations. One in particular I was looking forward to was Mountain Dew. I it different tasting over in Europe, but of course by the time I came back here it doesn't taste as good as I remember.

What would going to the US be without visiting a mall and the local megastores. Apple stores sure seem to be doing well and I couldn't help but noticing every time I was in one there were more girls then guys in them. As some of you know I enjoy transformers so it was fun going to the toy stores where after such a long time they were all new to me and I couldn't help but pick up one or two. Similar to the malls we went to the local grocery store. The amount of variety and over abundance of "stuff" at places like super stop in shop is amusing. Having become accustom to shopping in stores who's total size was only the size of the produce section at stop and shop I really had to question if getting to choose between two hundred tooth brushes had any benefits.

During this whole time the closest I got to programming was getting a new MacBook. I have to say I am very happy with it. The speed jump from my old 667Mhz powerbook is amazing and I can actually do real work on it (rather then sshing somewhere else). I like the built in iSight and set it up to take a picture every time it wakes up from sleep after a set amount of time which has presented me with a range of amusing photos, such as the one on the left. Touching on some of the software I must give credit where credit is due, iMovie/iDVD is slick, really slick. I video taped Christmas and with just two hours work pulled it off the camera, did basic editing, titles, transitions, and slapped it on a DVD with photos also taken during the video taping.

One task I got done while here was closing my savings account with Bank Of America and opening one with ING direct. Besides having a horrible website, Bank of America's savings accounts have a dirt floor low interest rate at .5% last I checked verses ING's 4.5%. If you are interested in opening an ING direct account send me and e-mail and I can send you a referral e-mail which will give you $25 just for opening the account. A nice easy way to get a better return on your savings and a little bonus.

Popular Posts