Neden Qt sizin GUI seçim aracınız
Recently, I found myself in the need of a Thai-English dictionary application. Unlike, for instance, Japanese, Thai is somewhat underrepresented on the Internet, and I had to search for quite a while until I found something that looked as if it would suit my needs: The LEXiTRON dictionary, which essentially consists of two XML files (Thai -> English and English -> Thai) and a Java lookup application, both licensed under a BSDish license. After downloading the data files and the application, I was baffled to see that, whatever I tried, changing locales, fonts, it did not display the Thai characters correctly. I grudgingly concluded that if I wanted this to work, I would have to write my own lookup application.
Now, this developer is not the GUI-kind of developer, the one that puts all his efforts into providing even the most dimwitted user with a pleasant experience. To the contrary, I generally consider a segmentation fault to be the proper reaction to user errors. But in this case, I felt I had no choice but to put on the fluffy fur gloves and write a really nice interface, one that I would not curse at when using it frequently.
I could never get the hang of these weird scripting languages, and C and objects don't go too well together, so the choice was C++, and the standard GUI toolkit for the Linux C++ developer is Qt, or so I was told. (There's a lot of KDE guys hanging around here...) So off I went, downloaded and compiled Qt 4.0, opened a Konqueror window with the Qt documentation in it, and started to hack. I expected working with a GUI toolkit to be an awful pain in the ass, but throughout the development of my little application, whatever problem I encountered appeared to be astonishingly easy to solve using the Qt toolkit.
The dictionary data is encoded in TIS-620, the standard Thai encoding before UTF-8 hit the streets. Here's how to read such a file using Qt:
QFile dict; QTextStream ds; dict.setFileName("telex"); dict.open(QIODevice::ReadOnly); ds.setDevice(&dict); ds.setCodec(QTextCodec::codecForName("TIS-620"));
That's it, now you can use the readLine() method of QTextStream and get a QString for each line in the TIS-620-encoded files.
Qt provides you with all the standard data structures in an easy-to-use form. Here's how to fill an associative array with dictionary entries for quick lookup:
QMultiHash<QString, TedEntry> searchwords; ... TedEntry te; QString searchword; ... searchwords.insert(searchword, te);
And here's how to retrieve a TedEntry (a structure I defined to hold dictionary entries, essentially a number of strings and an ID) from the searchwords hash:
QMultiHash<QString, TedEntry>::const_iterator i; i = searchwords.find(query); TedEntry e = i.value();
You can also iterate over all hash entries to look for a regular expression:
QRegExp queryre(qstring_containing_regex); QMultiHash<QString, TedEntry>::const_iterator i; i = searchwords.begin(); for(; i!=searchwords.end(); i++) { TedEntry e = i.value(); ... if(e.tsearch.contains(queryre) || e.esearch.contains(queryre)) { ... } }
e.tsearch and e.esearch are of type QString. This class is the most convenient thing since shirt ironing machines. Look at this:
html+="<i>"+tr("Syn.")+"</i> "+linkUp(t.esyn)+"<br>";
If you have ever programmed in Commodore BASIC, you will feel right at home. And if you wonder what exactly I am doing with this heap of HTML:
QTextBrowser entry; ... entry->setHtml(html);
That's how to display a (subset of) HTML page in Qt. The perfect (not to mention simple) solution for displaying formatted text. And what if the user clicks on a link?
connect(entry,SIGNAL(anchorClicked(const QUrl&)), this, SLOT(doHyperSearch(const QUrl&)));
This makes sure the method this.doHyperSearch(const QUrl&) is called then. I am using this for cross-referencing dictionary entries.
String processing, not exactly a known strength of either C or C++, is a real strong point of Qt. This splits up a string along word boundaries:
QString text; ... QStringList list = text.split(QRegExp("\\b"));
Put this in your Perl pipe and smoke it.
Session management is easy, too. Here's how to set the geometry of your main window to be the same it was when the user closed the application the last time:
Saving it (in the dialog destructor):
QSettings settings; settings.setValue("geometry",geometry());
Loading it (in the dialog constructor):
QSettings settings; setGeometry(settings.value("geometry",mainLayout->geometry()).toRect());
mainLayout->geometry() is used as a default if there is no setting named "geometry" yet.
Layout managers are a very helpful thing, and, in my opinion, much easier to use than graphical dialog editors. Here's how the layout of the main dialog is pieced together:
QHBoxLayout *topLayout = new QHBoxLayout; topLayout->addWidget(clear); topLayout->addWidget(query); topLayout->addWidget(search); ...
QHBoxLayout *middleLayout = new QHBoxLayout; middleLayout->addWidget(status,2); middleLayout->addWidget(headlist,1);
QHBoxLayout *bottomLayout = new QHBoxLayout; bottomLayout->addWidget(entry,2); bottomLayout->addWidget(results,1);
QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(topLayout); mainLayout->addLayout(middleLayout); mainLayout->addLayout(bottomLayout); setLayout(mainLayout);
First, a number of widgets are glued together horizontally, then these horizontal "bars" are glued together vertically, then this whole thing is set to be the layout of the dialog. That's all. The bottommost widgets, entry and result, are scaled asymmetrically, entry being twice as wide as results.
This is how to implement debugging output:
qDebug() << "URL" << url.path();
This is how you check if one file is newer than another one:
if(QFileInfo("lex.bin").lastModified() < QFileInfo("telex").lastModified()) ...
These are just examples, Qt seems to offer an easy to use solution for next to every problem you are likely to encounter while developing a GUI application, and to many problems not specific to GUI development as well. Qt objects have a consistent and intuitive behavior, and it is very hard to make serious mistakes. In fact, I had nearly all features working before getting my first (and, so far, only) segmentation fault, which turned out to be casued by a very stupid mistake and easy to fix. Performance is usually very good (QListWidget is the only case I have encountered so far for which this is not the case), and the documentation is excellent. I had my application up and running with most features in half a day, I never once had the desire to kill anybody, and I now have a working Thai-English/English-Thai dictionary for my desktop:
Ulih 09:55, 16 Aug 2005 (MDT)