cat /dev/maxilys

A glance in the mind of a KDE/Linux developer to see how ideas turn into code.

2006-09-12

KDE: Coffee break

Time for my coffee break and I really deserve it. The work I did to prevent the window captions from flickering with Serenity window decoration is amazing. (Unashamed auto-congratulation!) ;-)

What caused the flickering was the multiple redraws which could eventually take quite some time, especially with the Zen button style. So the obvious solution was to reduce the numbers of these redraws and to accelerate any that would occur.

What I did is to reduce the drawing of the caption framing --even invisible-- to one single action: drawPixmap(). Of course, I already used a buffered drawing for the caption but the whole titlebar was filled with the background color before the pixmap was shown. That could take a lot of time because the framing needed to be totally redrawn, the caption eventually shortened... And during that fraction of second of calculations all you saw was the background... Hence the flickering!

The first thing to do was to mask the titlebar so that the background never shows over the caption. Easy: setClipRegion(wholeTitlebar - captionRectangle). Then I made each window "carry" a pixmap of its caption framing. If the style or the size of the caption, or else the state active/inactive of the window didn't change since last redraw, I just use this "carried" pixmap as caption framing, otherwise I redraw it. I make a copy of the "carried" pixmap, I draw the caption text over it and I put this modified copy on screen in one move. Whatever the time all the calculations take, you can't see anything but a smooth change.

I grabbed the border of the window and resized it as quickly as possible in every direction to force numerous total redraws of the whole caption. The caption text really slides smoothly over its framing. That's what amazed me.

Hey! Whatever you code is only theory until it is executed by the CPU. Theory is a rather dry matter while the result --especially a visual result-- is what can trigger emotions, like amazement.

The next thing I had to test was multiple and quick changes of the caption text. I launched KMail, replied to whatever email it selected and typed an utterly long subject. The first that came to my mind: "Attention à la marche en descendant du bus et en entrant dans la gare routière" followed by a bunch of random characters and spaces. Then I hold the Backspace down until KMail changes the subject to "(No subject)". Second amazement of the day. No flickering either while typing in or while deleting. And that definitively wasn't the case before I began theorizing. '\x3a\x2d\x44' (Coder's happy face.) ;-)

Another theory regarding Fitts' law "Bigger target, easier target" and the menubar. It's just a theory, I have absolutely no idea about how it would turn out. According to some usability rules, the menu entries in the menubar should be as large as possible and there shouldn't be no useless space in between them.

How to implement this in a style while neither Qt nor KDE are aware of this?

Qt allow a style to reduce the space in between the menu entries, eventually to nothing. Serenity already puts the entries closer than what KDE's default settings are. It's only a matter of a few pixels otherwise the menubar would look like too crammed. Now, imagine that I actually remove the spaces in between the entries. How could I space them another way?

There's nothing Qt allows you to do to put spaces only on each side of the content of a widget. Nothing unless you're ready to see the menu entries grow and grow indefinitely after each redraw. That's what would happen if I tried to resize the entries "on the fly". There would be no way to know if I already enlarged an entry on not. The only way is to enlarge the content, i.e. the text of the entry.

Here is my theory: What if I caught the redraw of a QMenuBar in the event filter and added, well, 2 or 3 white spaces around each of its entries before to let the redraw happen? (To check if an entry already begins with spaces is easy so we wouldn't see the entries grow indefinitely.)

In theory, that should work without taking too much time. A menu seldom contains more than a dozen of entries.

if ( ::qt_cast<QMenuBar*>(obj) && (ev->type() == QEvent::Paint))
{
QMenuBar* menubar = static_cast<QMenuBar*>(obj);
for (uint index = 0; index < menubar->count(); index++)
{
int id = menubar->QMenuData::idAt(index);
QString entry = menubar->text(id);
if (! entry.startsWith(" ") )
{
entry = " " + entry + " ";
menubar->changeItem(id, entry);
}
}
return false;
}

It even compiles.

:-> (Devilish smile...)

And it actually works! Yes, that's just simple. (And only one white space is enough to retrieve the same spacing as before my patch.)

Before:
The menubar without the patch


After:
The menubar after the patch


The usability avantage: You can't "fall" in between the menu entries. Whenever the mouse cursor is somewhere in between the first and the last entry, an entry is always under it. That's better.

I shouldn't say it but I surprize myself sometimes. :-b

Well, the coffee break has been over for a long time, now. I'm gonna find the way not to generate too many intermediate QString's along my patch.

0 Comments:

Post a Comment

<< Home