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);
}