My blog has been moved to ariya.ofilabs.com.

Friday, April 20, 2007

Custom toggle action for QDockWidget

Thanks to QDockWidget::toggleViewAction, if you want to create an action (and placed in in the menu, for example) to switch a dock widget on and off, it is very simple indeed. The text of the action will be the title of the dock. If you have more than dock widgets, this can be combined together in a sub menu "Show Docks", just as illustrated below:

When that menu item (and the corresponding action) is checked/unchecked by the user, magically the dock will show/disappear.

But what if you want to create your own toggle action? For example, you want to customize the text of the action to be a bit more descriptive, say "Show Function List". Or perhaps you just want the total control.

Well, you can create your own actions and get this result:

which can be realized by the following code:

  m_actions->showHistory = new QAction( tr("Show Expression &History"), this );
  m_actions->showFunctions = new QAction( tr("Show &Functions List"), this );
  m_actions->showHistory->setToggleAction( true );
  m_actions->showFunctions->setToggleAction( true );
  connect( m_actions->showHistory, SIGNAL( toggled(bool) ), 
    m_historyDock, SLOT( setVisible(bool) ) );
  connect( m_actions->showFunctions, SIGNAL( toggled(bool) ), 
    m_functionsDock, SLOT( setVisible(bool) ) );

So far so good. Everytime you toggle the menu item, the docks can disappear and reappear.

However, it's not completely foolproof. You can, infact, make the dock disappear by closing it manually, i.e. clicking on the X button on the dock title bar. But by doing that, the "checked" status of the action itself is not properly adjusted (i.e. it is still "checked" while the dock is long gone).

With Qt 4.3, the solution is easy: use visibilityChanged signal of QDockWidget:

  connect( m_historyDock, SIGNAL( visibilityChanged(bool) ), 
    m_actions->showHistory, SLOT( setChecked(bool) ) );
  connect( m_functionsDock, SIGNAL( visibilityChanged(bool) ), 
    m_actions->showFunctions, SLOT( setChecked(bool) ) );

If, for whatever reason, you're stucked with Qt 4.2, then you have to find another remedy since visibilityChanged does not exist there. There are many ways to do this.

A particularly complicated but looks-elegant solution is by hijacking trapping the show and hide event of the docks using eventFilter trick. So, hook the event filters during docks construction:

  m_historyDock->installEventFilter( this );
  m_functionsDock->installEventFilter( this );

and do something like this in the window's event filter:

bool MyWindow::eventFilter( QObject* object, QEvent* event )
{
  if( object == m_historyDock )
    if( event->type() == QEvent::Hide || event->type() == QEvent::Show )
      m_actions->showHistory->setChecked( event->type() == QEvent::Show );

  if( object == m_functionsDock )
    if( event->type() == QEvent::Hide || event->type() == QEvent::Show )
      m_actions->showFunctions->setChecked( event->type() == QEvent::Show );

  return false;
}

Looks good? Not really. Because for all this trouble, you can just do the same in these two lines of code (the simple and yet effective solution):

  connect( m_historyDock->toggleViewAction(), SIGNAL( toggled( bool ) ),
    m_actions->showHistory, SLOT( setChecked( bool ) ) );
  connect( m_functionsDock->toggleViewAction(), SIGNAL( toggled( bool ) ),
    m_actions->showFunctions, SLOT( setChecked( bool ) ) );

Any other solution, perhaps?

Thursday, April 19, 2007

on winning in life

Life is a game, and if you aren't in it to win, what the heck are you still doing here?

-- Linus Torvalds

Wednesday, April 18, 2007

Tab bar with RoundedNorth for tabbed dock widgets

Warning: don't try this in real world's application!

Update: I found a trick to reduce the flicker, just read further on.

One advantage of dock widgets (using QDockWidget) is that the docks can be "stacked", either programatically using tabifyDockWidget or when the user explicitly places one dock on top of another. Example is shown below (for future version of SpeedCrunch, more about this in another blog post). There are two dock widgets: History and Functions, and at the moment they are stacked.

However, apparently the tab bar (QTabBar) which is used to choose the dock is always placed at the bottom. Or, using the Qt's terminology, it has the shape of QTabBar::RoundedSouth. Since there are other possibilities for tab bar's shape, e.g. RoundedNorth, would it be possible to make something like this, where the tab is place at the top?

Unfortunately until Qt 4.2 this is not possible yet (issue 146772), although according to issue 145880, "vertical tab bar layout" will be possible in Qt 4.3.

Just for fun, I found a very hackish way to make RoundedNorth for the tab bar (hence, the screenshot above). The trick is to find the tab bar using run-time introspection feature of Qt and then change the geometry manually. If I know in advance that there will be only one tab bar in my main window and there are only two dock widgets, this can be accomplished with a private slot like this:

void MainWindow::hackTabbedDocks()
{
 QDockWidget* topDock = d->historyDock;
 if( topDock->height() == 0)
   topDock = d->functionsDock;

 QList<QTabBar *> allTabs = findChildren<QTabBar *>();
 for(int ii = 0; ii < allTabs.size(); ++ii)
 {
   QTabBar* tab = allTabs[ii];
   if(tab->parentWidget() == this)
   {
     if(tab->geometry().top() > topDock->geometry().top())
     {
       tab->setShape( QTabBar::RoundedNorth );
       int h = tab->geometry().height();
       tab->move(tab->geometry().left(), topDock->geometry().top());
       topDock->move(topDock->geometry().left(), topDock->geometry().top()+h);
     }
     break;
   }
 }
}

I found out, I always need to call this slot twice so that it can work. To simplify, there two other slots which manage it:

void MainWindow::handleTabChange()
{
  QTimer::singleShot(0, this, SLOT(hack1()));
}

void MainWindow::hack1()
{
  hackTabbedDocks();
  QTimer::singleShot(100, this, SLOT(hackTabbedDocks()));
}

And I need to bind any signals which indicate that the tab bar has been relayouted, e.g. when a new tab is selected , when it is resized, etc, to handleTabChange slot above. For illustration purpose, let's just do the first. Of course, this can be done only when tab bar already exists. So, time for another silly slot:

void MainWindow::initHack()
{
  QList<QTabBar *> allTabs = findChildren<QTabBar *>();
  for(int ii = 0; ii < allTabs.size(); ++ii)
  {
    QTabBar* tab = allTabs[ii];
    if(tab->parentWidget() == this)
    {
      connect(tab, SIGNAL(currentChanged(int)), this, SLOT(handleTabChange()));
      break;
    }
  }
  handleTabChange();
}

which will be called from MainWindow's constructor by abusing QTimer once more:

  tabifyDockWidget( d->historyDock, d->functionsDock );
  QTimer::singleShot(0, this, SLOT(initHack()));

That's it!

The big disadvantage of this trick is obvious: flicker occurs everytime you do something with the docks, e.g. changing the tab. It will be quite annoying, but all of this is just a hack anyway. So was it fun? Yes. Useful? No.

(I guess the real "solution" is only waiting for the Trolls to implement it)

UPDATE. Here is the trick to reduce the flicker so that it becomes acceptable (even almost not noticeable). I need another private slot and some magic with setUpdatesEnabled as follows:

void MainWindow::handleTabChange()
{
  setUpdatesEnabled(false);
  QTimer::singleShot(0, this, SLOT(hack1()));
}

void MainWindow::hack1()
{
  hackTabbedDocks();
  QTimer::singleShot(0, this, SLOT(hack2()));
}

void MainWindow::hack2()
{
  hackTabbedDocks();
  setUpdatesEnabled(true);
}

Sunday, April 15, 2007

N800

Thanks to Nokia (and whoever has recommended me), I finally got the once-in-a-lifetime developer-discounted N800. After a little problem with payment and delivery, which fortunately were solved, I can enjoy this cute little gadget.

N800

Using this N800 on and off for some weeks, I can only say that it's a great experience. On the hardware side, everything works perfectly. The 4.1-inches display is sharp, touch screen works without glitch, control buttons are easy to use, my 2 GB SD-card is detected without hassle, FM radio shows no problem, the "hidden" web cam gives good image quality. Speaking of web cam, the direction is a bit strange because it is rather "tilted aways" from your face. When doing a video chat, your partner would see it as if you don't face him directly.

Internet connection with both Bluetooth/GPRS and WiFi works out of the box. For road warrior with e.g. GPRS flat-rate, N800 is better than small-screen smartphone and surely lighter than sub-notebook. The installed Opera browser is good enough for most surfing needs, even Flash is supported, although YouTube video playback will be sluggish. Fortunately, playing typical audio and video files are smooth enough (hardware decoding, I reckon). Text, voice and video chat are all easy to setup and they work right away.

Since N800 is targeted as Internet Tablet, only few other applications are available preinstalled. This is a bit pity, as I guess this device could be a very good PDA. However, installing 3rd-party applications is not so difficult, e.g. browse to http://downloads.maemo.org and use the single-click install button. And there's Pimlico project to bring PIM suite to N800.

For multimedia, the included media player is good enough, but nothing beats Canola (see it in action) if you want to impress your friends, it transforms N800 into a portable FrontRow-style media center. With N800's speaker, Canola definitely boosts the "cool factor" easily.

When finally I can have some more free time (read: after finishing the dissertation), I want to play around with it and probably try to port some of my personal programs to the platform.

crispy catfish

With the risk of being blacklisted by some PlanetKDE readers, here is yet another food post on pecel lele or fried catfish with raw vegetables.

When you live in Java, for less than a dollar you can get this kind of food easily almost everywhere so the thought of cooking yourself catfish (ikan lele) hardly comes into mind (unless you're the seller of course). But in Europe, it would be real hard to find a restaurant that offer this in the menu.

pecel lele

Here is the rough recipe. The fish is basically fried until it becomes crispy. But before, rub the fish with lime juice and a bit of salt, pepper, garlic and wait some time until it absorbs everything. To prepare the dip, use a mix of chili sauce, tomatoes, shallots and shrimp taste. Add soy sauce to taste and then serve with fresh vegetables and warm rice.

Wednesday, April 04, 2007

Google as your assistant

Soon Google technologies would manage you life (almost) completely.

Just recently I had short chat with someone I haven't met for ages. I was doing this through chat feature of Google Mail. Cause we had not so much time to talk, we planned to have another chat session on Friday:

Later on, I checked the chat log again. I wanted to put a reminder in my calendar (using Google Calendar), but then look what was offered to me right there:

Putting privacy issues aside, apparently my chat log is automatically analyzed (no surprise here, messages in Google Mail is scanned to determine the content-targeted ads). Because seems that I and my friend have an appointment to chat again, that's why I was given the link to add it to my calendar.

In another test, I chatted with someone and just typed "how about dinner on friday?", and yes, Google Mail interface again displays the link to add "dinner" to my calendar. Pretty neat, isn't it?

I predict that soon it's even completely normal that, if you plan a lunch with your business partner, perhaps somewhere downtown, Google technologies could place a call, make the reservation, confirm the menu, order the taxi at the right time (complete with printed optimal route for the driver), pull the right business documents, etc etc.

What a digital life.