Skip to content

Week 3: Dynamically Disabling Empty Menus

Friday, 13 February 2026  |  Tanish Kumar

Going into Week 3, my next task was clear. I had successfully stopped the Lokalize menubar from jumping around, but it left me with top-level menus that were clickable but completely empty depending on which tab you were using. My goal was to figure out how to dynamically grey out those useless menu headers, like the "Sync" menu in the Project Overview, so users wouldn't be tricked into clicking them.

I started by checking the Qt documentation and a very helpful discussion on the Qt forums to see how to properly handle this. The forum thread highlighted a crucial distinction in the Qt API: using setVisible(false) completely hides a menu, while setEnabled(false) keeps it visible but greys it out. This was an important detail. If i had used setVisible(), the menus would have vanished completely, bringing back the jumping ui bug I had just fixed in Week 1. I needed to use setEnabled() to keep the menu structure frozen in place while making the empty headers unclickable.

To implement this, I hooked directly into the application's tab-switching logic. I wrote a new function that gets triggered every single time a user changes tabs. It grabs the main menubar and loops through every top-level action. For each menu it finds, it checks if the menu has any currently visible actions. If the answer is no, it disables that specific menu header using action->setEnabled(false), turning the menu grey so the user knows it cannot be clicked.

void LokalizeMainWindow::updateMenuAvailability()
{
    QMenuBar *bar = menuBar();
    if (!bar)
        return;

    // Refresh top-level menu state based on currently available actions.
    for (QAction *action : bar->actions()) {
        QMenu *menu = action->menu();
        if (!menu)
            continue;

        // Disable top-level menu when it has no visible actions.
        action->setEnabled(menuHasVisibleAction(menu));
    }
}

bool LokalizeMainWindow::menuHasVisibleAction(const QMenu *menu) const
{
    for (QAction *action : menu->actions()) {
        if (action->isSeparator() || !action->isVisible())
            continue;

        if (QMenu *subMenu = action->menu()) {
            // Check submenu actions too.
            if (menuHasVisibleAction(subMenu))
                return true;
            continue;
        }
        return true;
    }
    return false;
}

The tricky part was writing the helper function to accurately figure out if a menu was actually "empty." It wasn't enough to just check if the menu contained items. Inside the helper, I loop through the menu's actions. If an item is just a ui line (a separator) or is already hidden, the code skips it. I also had to account for nested submenus, so if the action is a submenu, the function calls itself recursively to check inside it. The implementation is completely generic-meaning if anyone adds new menus or actions to Lokalize in the future, this code will handle them automatically.

After the fix: In the Translation Memory tab, the Edit, Go, and Sync menus dynamically grey out. This adapts instantly to whichever tab is active (for example, switching to the Project Overview will only grey out the Sync menu)
After the fix: In the Translation Memory tab, the Edit, Go, and Sync menus dynamically grey out. This adapts instantly to whichever tab is active (for example, switching to the Project Overview will only grey out the Sync menu)

I opened the merge request, and i'm happy to say it was quickly merged into master! The Lokalize menus now update smoothly based on the context of the active tab. They stay exactly where they belong, and empty menus disable themselves as expected. With this menubar issue resolved:)