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.