Skip to main content

Ralsina.Me — Roberto Alsina's website

Indeed screw all gui builders... for java!

Read­ing DZone I ran in­to an in­ter­est­ing post ti­tled Screw all GUI builders which ad­vo­cates drop­ping all GUI builders and in­stead cod­ing your UI by hand.

I al­ways ad­vo­cate us­ing Qt De­sign­er in­stead of cod­ing by hand, so I want­ed to see, even it's talk­ing about Java, I thought, "why don't I feel that way"?

My con­clu­sion? I don't see it be­cause in PyQt we are just lucky be­cause our tools don't suck quite as much.

It gives sev­er­al ar­gu­ments:

  • You don't know how ex­ac­t­­ly the gen­er­at­ed code work­s. You don't need to. You start not to care and GUI ap­­pli­­ca­­tion de­vel­op­­ment be­­comes a process of draw­ing and adding sim­­ple event han­dlers here and there.

To that, my re­ac­tion was yes, in­deed I don't care, as long as it work­s, which it has 99.99% of the time. When it did­n't, I did un­der­stand the gen­er­at­ed code, though.

The rea­son for this will be more ob­vi­ous once you see the code in both cas­es, I think.

  • Most GUI builders force you to use sin­­gle class for sin­­gle win­­dow, so gen­er­at­ed class­es tend to have thou­sands of lines of code.

Hm­m­m... I re­al­ly don't know what this means in con­tex­t, but that's just my ja­va ig­no­rance. OTO­H, yes, one class per win­dow, but al­most nev­er thou­sands of lines of code.

For ex­am­ple, the UI for uRSSus main win­dow is quite com­plex, and it is on­ly 530 lines of code. Here's how it look­s, so you see it's not a triv­ial win­dow:

urssus23 * Most GUI builders don't want you to modify the generated code. And if you do, they either break or rewrite your code.

In­deed De­sign­er's code is not meant to be mod­i­fied. That's what in­her­i­tance is there for. Over­load what­ev­er you want changed. Maybe this is eas­i­er in PyQt be­cause Python is more dy­nam­ic than Java? Not sure.

  • GUI builders force you to use an IDE, most­­ly one you start­ed cod­ing with. So if you start with Net­Bean­s, you most like­­ly be forced to stay with it for the whole pro­jec­t.

Just not true for De­sign­er. I can see how that would suck, though.

  • The gen­er­at­ed code is far from be­ing op­ti­­mal. It's not re­­size-friend­­ly, not dy­­nam­ic enough, it has many hard-­­cod­ed val­ues, refac­­tor­ing is most like­­ly im­­pos­si­ble, be­­cause builder would not al­low that.

De­sign­er does gen­er­ate re­size-friend­ly di­alogs if you use it cor­rect­ly. The hard-­cod­ed val­ues are run­time-ed­itable, refac­tor­ing is pret­ty sim­ple (take what­ev­er you wan­t, cre­ate a wid­get with it?)

And since I want­ed to com­pare ap­ples to ap­ples...

Here is his di­alog, done via PyQt and De­sign­er:


No, I did­n't both­er in­sert­ing text in the di­a­log so it loks the same ;-)

The "close" but­ton clos­es the win­dow, the URL opens in your sys­tem brows­er.

The text wid­get ac­tu­al­ly sup­ports a sub­set of HTM­L, so there is a valid HTML doc­u­ment there, in­stead of just plain tex­t.

Al­so, an­oth­er thing is ... De­sign­er's (or rather pyuic's) gen­er­at­ed code is straightor­ward stuff.

Here's the code, which I think is rough­ly equiv­a­lent to his, on­ly it's just 123 LOC, in­stead of 286 (his gen­er­at­ed ver­sion) or 279 (his hand-­made ver­sion). And I did­n't delete the com­ments.

If I were do­ing this for re­al, all wid­gets would have de­scrip­tive names in­stead of Push­But­ton1 and what­ev­er.

Al­so, it's i18n-ready, un­like the Ja­va ver­sion­s, un­less I missed some­thing.

You can al­so get hawkscope.ui from this site to play with, it's done with De­sign­er from Qt 4.4.

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'hawkscope.ui'
# Created: Fri Feb 13 22:39:47 2009
#      by: PyQt4 UI code generator 4.4.4
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.resize(444, 357)
        self.verticalLayout_2 = QtGui.QVBoxLayout(Dialog)
        self.horizontalLayout_2 = QtGui.QHBoxLayout()
        self.label_2 = QtGui.QLabel(Dialog)
        self.verticalLayout = QtGui.QVBoxLayout()
        self.label_3 = QtGui.QLabel(Dialog)
        font = QtGui.QFont()
        self.label_4 = QtGui.QLabel(Dialog)
        self.gridLayout = QtGui.QGridLayout()
        self.label_5 = QtGui.QLabel(Dialog)
        font = QtGui.QFont()
        self.gridLayout.addWidget(self.label_5, 0, 0, 1, 1)
        self.label_10 = QtGui.QLabel(Dialog)
        self.gridLayout.addWidget(self.label_10, 0, 1, 1, 1)
        self.label_9 = QtGui.QLabel(Dialog)
        font = QtGui.QFont()
        self.gridLayout.addWidget(self.label_9, 1, 0, 1, 1)
        self.label_8 = QtGui.QLabel(Dialog)
        self.gridLayout.addWidget(self.label_8, 1, 1, 1, 1)
        self.label_7 = QtGui.QLabel(Dialog)
        font = QtGui.QFont()
        self.gridLayout.addWidget(self.label_7, 2, 0, 1, 1)
        self.label_6 = QtGui.QLabel(Dialog)
        self.gridLayout.addWidget(self.label_6, 2, 1, 1, 1)
        self.label = QtGui.QLabel(Dialog)
        font = QtGui.QFont()
        self.textBrowser = QtGui.QTextBrowser(Dialog)
        self.horizontalLayout = QtGui.QHBoxLayout()
        spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
        self.pushButton = QtGui.QPushButton(Dialog)
        self.pushButton_2 = QtGui.QPushButton(Dialog)

        QtCore.QObject.connect(self.pushButton_2, QtCore.SIGNAL("clicked()"), Dialog.accept)

    def retranslateUi(self, Dialog):
        Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "Dialog", None, QtGui.QApplication.UnicodeUTF8))
        self.label_3.setText(QtGui.QApplication.translate("Dialog", "Hawkscope", None, QtGui.QApplication.UnicodeUTF8))
        self.label_4.setText(QtGui.QApplication.translate("Dialog", "Access anything with single click!", None, QtGui.QApplication.UnicodeUTF8))
        self.label_5.setText(QtGui.QApplication.translate("Dialog", "Version:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_10.setText(QtGui.QApplication.translate("Dialog", "0.4.1", None, QtGui.QApplication.UnicodeUTF8))
        self.label_9.setText(QtGui.QApplication.translate("Dialog", "Released:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_8.setText(QtGui.QApplication.translate("Dialog", "2009-02-06", None, QtGui.QApplication.UnicodeUTF8))
        self.label_7.setText(QtGui.QApplication.translate("Dialog", "Homepage:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_6.setText(QtGui.QApplication.translate("Dialog", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'Droid Sans\'; font-size:8pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><a href=\"\"><span style=\" text-decoration: underline; color:#3c7dbe;\"></span></a></p></body></html>", None, QtGui.QApplication.UnicodeUTF8))
        self.label.setText(QtGui.QApplication.translate("Dialog", "Environment", None, QtGui.QApplication.UnicodeUTF8))
        self.textBrowser.setHtml(QtGui.QApplication.translate("Dialog", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'Droid Sans\'; font-size:8pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Text goes here</p>\n"
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"></p></body></html>", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButton.setText(QtGui.QApplication.translate("Dialog", "C&opy To Clipboard", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButton.setShortcut(QtGui.QApplication.translate("Dialog", "Alt+O", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButton_2.setText(QtGui.QApplication.translate("Dialog", "&Close", None, QtGui.QApplication.UnicodeUTF8))

Counting unread items is HARD

I mean, if google can get it wrong, I have an ex­cuse, right?


See how at the same time you see:

  • At the left: "All items (8)"

  • At the top-­­cen­ter: "8 new item­s"

  • In the tree: 8 items

  • In the list: 14 new items

Each of those num­bers can get out of sync with each oth­er if you do some­thing the "wrong" way.

But what the heck, it seems as of r678 urssus does it right. The list of un­read posts even up­dates when you have it open adding the new posts in the right places with­out dis­turb­ing you, and keep­ing the right num­bers ev­ery­where, AFAIC­S!


So here, in uRSSUs:

  • 13 un­read ar­ti­­cles in the "Un­read Ar­ti­­cles" item

  • 13 in "All Feed­s"

  • 13 adding each fold­er

  • 13 in the ar­ti­­cle list

Sad­ly, this is all post-0.2.11 re­lease.

uRSSus 0.2.11 is out!

Since I did some neat cod­ing on it yes­ter­day and to­day, I de­cid­ed it was a good mo­ment to re­lease uRSSus 0.2.11 in­to the wild.

It does­n't look much dif­fer­ent from 0.2.9 but it works much, much bet­ter.

Gone are the crazy dis­ap­pear­ing/du­pli­cate feeds when drag­ging/­drop­ping in the feed tree.

Gone is the bro­ken up­date sched­uler, and now feeds up­date smooth­ly.

Gone are the in­con­sis­tent dis­plays (like All feeds <> Un­read item­s)

Gone is the CPU guz­zling

Gone seems to be the DB lock­ing and IO churn­ing.

Sad­ly, gone is the DB schema mi­gra­tion be­cause I can't get miruku to work with cur­rent alche­my/mi­grate/Elixir. Since I don't re­mem­ber if the DB schema changed since 0.2.9 this may be bad ... or not. Back­up, friend­s. Ex­port to OPML and back. What­ev­er, this is al­pha stuff ;-)

So, head to the uRSSus home­page and take a look. Maybe you will like it!

Urssus update: 12/2/2009

Yes, af­ter months of do­ing noth­ing, I hve made some changes in uRSSus, my RSS ag­gre­ga­tor.

Ba­si­cal­ly, I re­moved the Qt MVC tree for feeds and re­placed it with a old, re­li­able item-based tree.

And now, mag­i­cal­ly you can drop feeds in­to fold­er­s, sort feeds by un­read coun­t, and it all seems to work.

I am prob­a­bly the on­ly uRSSus us­er in the world, so I am not both­er­ing with a re­lease un­til I kick the beast in­to shape with at least some nice fea­tures, but it's some­thing.

Brightness Reef (Uplift Storm Trilogy, #1)

Cover for Brightness Reef (Uplift Storm Trilogy, #1)


It starts as a hard read, but it works like a snow­bal­l, things get faster and stronger, and fun, and then it all ex­plodes in the fi­nal pages, and you re­al­ize this is about 1/3rd of the book, since it's one of those trilo­gies that are more like a hu­mon­gous book that ran in­to a cleaver.

I start­ed the next vol­ume im­me­di­ate­ly.

Contents © 2000-2024 Roberto Alsina