1
\$\begingroup\$

I have a Qt tree view which has a custom tree header. The tree has a so called mode, by default there are two. During each of the modes a different tree is shown. The mode switcher is presented as a context menu of the tree header.

Having a context menu on tree header isn't that hard. But in my case I need to make a tree header such that the user of my code should be able to add new modes are their corresponding tree view pointers, and an action in context menu to switch to the new mode.

Below is my code for the tree header. Please let me know if you have any comments.

class TreeHeader : public QHeaderView
{
public:
    TreeHeader(QWidget *parent = nullptr) noexcept;

    QAction *getModeAction(const QString &sActionTitle) const;

    void addModeAction(QAction *action, eTreeMode mode, QAction *before = nullptr);

protected:
    /// Override the context menu event
    void contextMenuEvent(QContextMenuEvent *event) override;

    /// The context menu
    QMenu *m_pContextMenu = nullptr;

private:
    /// Setups connections and necessary data for the given action
    void setupAction(QAction *action, eTreeMode eMode) const;

}; // class TreeHeader

TreeHeader::TreeHeader(QWidget *parent /* = nullptr */) noexcept
    : QHeaderView(Qt::Horizontal, parent)
    , m_pContextMenu(new QMenu(this))
{
    std::vector<std::pair<eTreeMode, QString>> mapModeToName;
    mapModeToName.emplace_back(eTreeMode::Mode1, QString("By %1").arg(mode1));
    mapModeToName.emplace_back(eTreeMode::Mode2, QString("By %1").arg(mode2));

    // create the default actions
    for (const auto &pair : mapModeToName)
    {
        // add the action to the menu and setup necessary properties to it
        setupAction(m_pContextMenu->addAction(pair.second), pair.first);
    }

    setTextElideMode(Qt::ElideRight);
    setStretchLastSection(true);
}

QAction *TreeHeader::getModeAction(const QString &sActionTitle) const
{
    for (const auto pAction : m_pContextMenu->actions())
    {
        if (pAction->text() == sActionTitle)
            return pAction;
    }
    return nullptr;
}

void TreeHeader::addModeAction(QAction *action, eTreeMode mode, QAction *before /* = nullptr */)
{
    if (!action) return;

    // add the action at appropriate position
    if (before)
        m_pContextMenu->insertAction(before, action);
    else
        m_pContextMenu->addAction(action);

    // setup necessary properties to the action
    setupAction(action, mode);
}

void TreeHeader::contextMenuEvent(QContextMenuEvent *event)
{
    if (auto pTree = static_cast<TreeView *>(parentWidget()))
    {
        // make sure we have a populated tree
        if (!(pTree->getTreeModel() && pTree->getTreeModel()->hasChildren())) return;

        // show the context menu only for the first column which specifies the grouping mode
        if (logicalIndexAt(event->pos()) == 0)
        {
            // get the current mode
            auto eMode = pTree->getMode();

            // set correct checked states of the actions
            for (const auto pAction : m_pContextMenu->actions())
                pAction->setChecked(eMode == static_cast<eTreeMode>(pAction->data().toInt()));

            // and show the menu
            m_pContextMenu->exec(event->globalPos());
        }
    }
}

void TreeHeader::setupAction(QAction *action, eTreeMode eMode) const
{
    // make the action checkable and check it, if necessary
    action->setCheckable(true);

    // set the action's checked state and make necessary connections
    if (auto pTree = dynamic_cast<TreeView *>(parentWidget()))
    {
        action->setChecked(pTree->getMode() == eMode);

        // set up a connection to process to mode change
        if (auto pView = dynamic_cast<TreeViewContainer *>(pTree->parentWidget()))
        {
            // setup a connection to process mode change
            connect(action, &QAction::triggered, pView, &TreeViewContainer::onModeChanged);
        }
    }

    // set the mode as data
    action->setData(eMode);
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.