Monday, May 10, 2010

Qt Creator 2.0 Beta First Impressions

Last week Nokia released a beta of Qt Creator 2.0 (and Qt 4.7 FWIW) and I have finally managed to spend some time playing with it today. Here's a quick list of likes and dislikes in no particular order.

Likes
  • The new Options dialog is much easier to use IMHO. It doesn't add much to the product but there's nothing wrong with a bit of polish and attention to detail.
  • Support for Mercurial (Hg). I have been using this today without any problems.
  • Revamped Projects view. I'm not a massive fan of anything that leads to a dependency on a .pro.user file but the new Projects page is much easier to use than previous releases.
  • The new Build/Target button is pretty smart and really useful when you have multiple projects loaded. Ctrl+T pops it up for those of us who are keyboard-centric.
  • You can now right-click on a project file and open it using the 'System Editor' (i.e. the application registered for the selected file extension) which I will be using a lot.
  • You can finally set the default projects directory.
Dislikes
  • On Windows, Ctrl+W no longer closes the current file - this has now been changed to the 'standard' Ctrl+F4 keystroke instead. I found this annoying and can hopefully restore Ctrl+W using the keyboard mappings editor.
  • The Output view has been removed. I am in two minds as to whether this was a good idea.
  • You cannot make a project active by right-clicking on it - you have to use the new Build/Run Target Selector.
  • I cannot get the Mercurial commit keystroke to work (Alt+H, Alt+C). Alt+H opens the Help menu. I must be missing something here.
  • When you are editing a .ui file, the new Design view is enabled. If you switch back to the Edit view, the XML .ui file is displayed in read-only mode. I'm not sure if this is desirable.
  • Shadow builds are enabled by default which confused me for a little while ("Where the hell's my bloody executable?"). I'm actually on the fence about shadow builds - I'm only doing desktop development so aren't sure if they are of much use to me. Plus I can't find a really good overview of the advantages of using them.
Overall I do prefer it to 1.3 and as I've come to expect from Nokia, it's rock solid.

I'll post a more detailed review when the final version ships (which I'm betting won't be long.)

Code to Generate a Version Number Header (2)

I have recently switched to using Mercurial (Hg) as my version control system of choice so have updated my little tool used to generate a version number header file that other projects can include. I have included both the local and global Hg revision numbers plus I've added an inline function to return the version number as a string. As before, the version number includes the major, minor and daily build numbers. I display the global Hg revision number in my About dialogs in the same way as Qt Creator does (the global Hg revision number is a string).

A typical version header now looks like this:

#ifndef VERSION_H
#define VERSION_H

namespace Version
{
const int MAJOR = 1;
const int MINOR = 6;
const int LOCAL_REVISION = 46;
const int BUILD = 16130;

inline const char* versionString()
{
return "1.6.46.16130";
}

inline const char* globalRevision()
{
return "81a3662fbc71";
}
}

#endif // VERSION_H

The code that produces the header looks like this (apologies for the formatting - the online source code formatter isn't working today):

#include <iostream>
#include <QProcess>
#include <QStringList>
#include <QFile>
#include <QTextStream>
#include <QDate>
#include <QTime>
#include <QFileInfo>
#include <QTemporaryFile>
#include <QtGlobal>
#include <cstdlib>

namespace
{
int getBuildNumber()
{
const QDate today(QDate::currentDate());
return ((today.year() - 1994) * 1000) + today.dayOfYear();
}

int getLocalHgRevision()
{
int revision = 0;
QProcess process;
process.start("hg", QStringList() << "id" << "-n");
if (process.waitForStarted() && process.waitForReadyRead())
{
revision = atoi(process.readAll().constData());
process.waitForFinished();
}
return revision;
}

QString getGlobalHgRevision()
{
QString revision = "";
QProcess process;
process.start("hg", QStringList() << "id" << "-i");
if (process.waitForStarted() && process.waitForReadyRead())
{
revision = QString(process.readAll().constData()).trimmed();
revision.remove('+');
process.waitForFinished();
}
return revision;
}

QByteArray readFile(const QString& fileName)
{
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly))
{
return QByteArray();
}
return file.readAll();
}

int writeFile(const QString& fileName, const int major, const int minor, const int localRevision,
const int buildNumber, const QString& globalRevision)
{
// Create a temp file containing the version info and
// only replace the existing one if they are different
QTemporaryFile tempFile;
if (tempFile.open())
{
QTextStream out(&tempFile);
out << "#ifndef VERSION_H\r\n";
out << "#define VERSION_H\r\n\r\n";
out << "namespace Version\r\n";
out << "{\r\n";
out << "\tconst int MAJOR = " << major << ";\r\n";
out << "\tconst int MINOR = " << minor << ";\r\n";
out << "\tconst int LOCAL_REVISION = " << localRevision << ";\r\n";
out << "\tconst int BUILD = " << buildNumber << ";\r\n\r\n";
out << "\tinline const char* versionString()\r\n";
out << "\t{\r\n";
out << "\t\treturn \"" << major << '.' << minor << '.' << localRevision << '.' << buildNumber << "\";\r\n";
out << "\t}\r\n\r\n";
out << "\tinline const char* globalRevision()\r\n";
out << "\t{\r\n";
out << "\t\treturn \"" << globalRevision << "\";\r\n";
out << "\t}\r\n";
out << "}\r\n\r\n";
out << "#endif // VERSION_H\r\n";

const QString tempFileName = tempFile.fileName();
tempFile.close();

if (!QFile::exists(fileName) || readFile(fileName) != readFile(tempFileName))
{
QFile::remove(fileName);
QFile::copy(tempFileName, fileName);
}

return 0;
}
else
{
std::cout << "Error creating temporary file!" << std::endl;
return 1;
}
}
}

int main(int argc, char *argv[])
{
if (argc != 4)
{
std::cout << "Usage: version major minor filename" << std::endl;
return 1;
}

const int major = atoi(argv[1]);
const int minor = atoi(argv[2]);
const int localRevision = getLocalHgRevision();
const QString globalRevision = getGlobalHgRevision();
const int buildNumber = getBuildNumber();

std::cout << major << '.' << minor << '.' << localRevision << '.' << buildNumber;
std::cout << ' ' << qPrintable(globalRevision) << std::endl;

return writeFile(argv[3], major, minor, localRevision, buildNumber, globalRevision);
}

Sunday, May 9, 2010

Hidden Features of Qt

OK, the title of this post is a bit of a misnomer as the Qt documentation is so good you'd be hard pushed to find something that is truly hidden ... but I came across a great list on stackoverflow and I thought I'd share the best bits here.
  • forever
    This is a macro that expands to for (;;). Very useful. You're already using foreach
    already right?
  • qChecksum
    This returns a CRC-16 checksum of a char* buffer.
  • qCompress / qUncompress
    These functions perform zlib compression/uncompression of a QByteArray. You can also specify the compression level.
  • qFuzzyCompare
    Use this to safely compare float/double values and avoid the dreaded floating point comparison trap.
  • qVersion
    Get the runtime version of Qt being used.
  • qPrintable
    Converts a QString to a const char* instead of writing QString.toLocal8Bit().constData(). Very useful - I could of done with this the other day when outputting some debug strings using...
  • qDebug
    Write to the debug window, in std::iostream style, e.g.:

    qDebug() << "String contents: " << qPrintable(myString);

  • QObject::deleteLater()
    This is used to force an object to delete itself (delete this) when a signal occurs. I have used this to delete a QTcpSocket object automatically when the socket is disconnected.
There are lots more if you follow the link.

Wednesday, April 28, 2010

Sample Multiple Unit Test Project

Long time no post...

Anyway, someone asked me if I could upload a working test project that shows my AutoTest header in use, so click here to download test.zip which includes two sample test classes and a main.cpp that shows two ways to start the tests running.

Open the test.pro file using Qt Creator, build it and let it rip and you should see output that looks something like this:

********* Start testing of Test2 *********
Config: Using QTest library 4.6.2, Qt 4.6.2
PASS : Test2::initTestCase()
PASS : Test2::test1()
FAIL! : Test2::test2() Compared strings are not the same
Actual ("Hello"): Hello
Expected ("World!"): World!
test2.cpp(14) : failure location
PASS : Test2::cleanupTestCase()
Totals: 3 passed, 1 failed, 0 skipped
********* Finished testing of Test2 *********
********* Start testing of Test1 *********
Config: Using QTest library 4.6.2, Qt 4.6.2
PASS : Test1::initTestCase()
PASS : Test1::test1()
FAIL! : Test1::test2() '1 == 0' returned FALSE. ()
test1.cpp(14) : failure location
PASS : Test1::cleanupTestCase()
Totals: 3 passed, 1 failed, 0 skipped
********* Finished testing of Test1 *********


I hope you find this useful - it has certainly made my life easier when using QTestLib.

Thursday, January 14, 2010

Code to Generate a Version Number Header

I've had a couple of requests to post the code I use to generate a version number header, so here it is. Create a Qt console-based application using the code below and pass it the major and minor version numbers and the name of the header file to create on the command-line, e.g.:

version.exe 1 2 ver.h

The code will create a header called ver.h using a major version number of 1 and a minor version number of 2. I include the Subversion revision number and a special build number that represents today's date, so the final version number might be something like:

1.2.2345.16001

Here is the version.cpp file:

#include <iostream> 
#include <QProcess>
#include <QStringList>
#include <QFile>
#include <QTextStream>
#include <QDate>
#include <QTime>
#include <QFileInfo>
#include <QTemporaryFile>
#include <cstdlib>

using namespace std;

static int getBuildNumber()
{
const QDate today(QDate::currentDate());
return ((today.year() - 1994) * 1000) + today.dayOfYear();
}

static int getSubversionRevision()
{
int revision = 0;
QProcess process;
process.start("svnversion", QStringList() << "." << "--no-newline");
if (process.waitForStarted() && process.waitForReadyRead())
{
const QString str(process.readAll().constData());
const int pos = str.indexOf(':');
if (pos != -1)
{
revision = atoi(str.mid(pos + 1).toAscii().constData());
}
else
{
revision = atoi(str.toAscii().constData());
}
process.waitForFinished();
}
return revision;
}

static QByteArray readFile(const QString& fileName)
{
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly))
{
return QByteArray();
}
return file.readAll();
}

static int writeFile(const QString& fileName, const int major, const int minor, const int revision, const int build)
{
// Create a temp file containing the version info and
// only replace the existing one if they are different
QTemporaryFile tempFile;
if (tempFile.open())
{
QTextStream out(&tempFile);
out << "#ifndef VERSION_H\r\n";
out << "#define VERSION_H\r\n\r\n";
out << "namespace Version\r\n";
out << "{\r\n";
out << "\tstatic const int MAJOR = " << major << ";\r\n";
out << "\tstatic const int MINOR = " << minor << ";\r\n";
out << "\tstatic const int REVISION = " << revision << ";\r\n";
out << "\tstatic const int BUILD = " << build << ";\r\n";
out << "}\r\n\r\n";
out << "#endif // VERSION_H\r\n";

const QString tempFileName = tempFile.fileName();
tempFile.close();

if (!QFile::exists(fileName) || readFile(fileName) != readFile(tempFileName))
{
QFile::remove(fileName);
QFile::copy(tempFileName, fileName);
}

return 0;
}
else
{
cout << "Error creating temporary file!" << endl;
return 1;
}
}

int main(int argc, char *argv[])
{
if (argc != 4)
{
cout << "Usage: version major minor filename" << endl;
return 1;
}

const int major = atoi(argv[1]);
const int minor = atoi(argv[2]);
const int revision = getSubversionRevision();
const int build = getBuildNumber();

cout << major << '.' << minor << '.' << revision << '.' << build << endl;

return writeFile(argv[3], major, minor, revision, build);
}