create ms word-like context menu



  • i want to create a custom context menu where i'll have something like this (the "paste options" and the icon below).
    alt text

    i tried using QWidgetAction:

    QMenu *contextMenu = new QMenu(this);
    
    QWidgetAction *widget = new QWidgetAction(contextMenu);
    widget->setIcon(QIcon(":/icon_path"));
    widget->setText(QString("Paste Options:"));
    
    contextMenu->insertAction(beforeActionPtr, widget);
    

    but it doesn't actually do what i want.

    i thought i could make a QActionGroup and add the action to the group, and then set the action group as widget's default widget via:

    widget->setDefaultWidget(actionGroup);
    

    but QActionGroup is derived from QObject so it wasn't a good idea.

    how can i do this?


  • Moderators

    @user4592357
    Almost right way. You are just using it wrong.

    QVBoxLayout* layout = new QVBoxLayout;
         layout->setContentMargins( 20, 2, 2, 2 );
         layout->addWidget( ... );  // add your (sub-)options there
    
    QWidget* w = new QWidget( contextMenu );
    w->setLayout( layout );
    
    QWidgetAction* wa = new QWIdgetAction( contextMenu );
        wa->setDefaultWidget( w );
    contextMenu->addAction( wa );
    

    Also you need to make sure once a sub-action (widget) is triggered the menu is closed. Either by calling contextMenu->close() or by calling wa->trigger()

    What exactly triggers your sub-action depends on the type you insert.



  • i have another question. how can i add an icon to the left of the menu. in qt docs, it says:
    This property holds the action's icon.
    In toolbars, the icon is used as the tool button icon; in menus, it is displayed to the left of the menu text. There is no default icon.

    so here's my code:

    auto button1 = new QToolButton;
    button1 ->setIcon(QIcon(":/icon1"))
    
    auto button1 = new QToolButton;
    button1 ->setIcon(QIcon(":/icon2"))
    
    // add a Paste Options label and the tool buttons to a grid layout
    auto widget = new QWidget;
    auto layout = new QGridLayout(widget);
    layout->setContentsMargins(0, 0, 0, 3);
    layout->setColumnMinimumWidth(0, 37);
    
    auto pasteOptionslabel = new QLabel(QObject::tr("Paste Options:"), contextMenu);
    pasteOptionslabel->setStyleSheet("background-color: #D5D5D5;");
    
    layout->addWidget(pasteOptionslabel, 0, 1, 1, 3);
    layout->addWidget(button1, 1, 1);
    layout->addWidget(button2, 1, 2);
    layout->setColumnStretch(5, 255);
    
    // add a widget action to the context menu
    auto pasteOptionsWidget = new QWidgetAction(contextMenu);
    pasteOptionsWidget->setIcon(QIcon(":/icon1"));
    pasteOptionsWidget->setDefaultWidget(widget);
    

    it doesn't add an icon (i suppose it's because i already have a widget added?). so how can i add an icon?

    and another question is, i wanna add a background to label, but the background shows only behind text. how can i do that?

    thanks


  • Moderators

    @user4592357 said in create ms word-like context menu:

    it doesn't add an icon (i suppose it's because i already have a widget added?). so how can i add an icon?

    the widget action stretches over the full menu width. So there is no icon shown. You can place the icon inside your widget though.

    and another question is, i wanna add a background to label, but the background shows only behind text. how can i do that?

    i don't quite understand this? Isn't a background not supposed to show behind the content?? o.O



  • i've figured out the part to add bg to the label, the problem is that in context menu, the items are a little bit indented and i need the background include that indent space too, as in the picture in first post.

    and i've tried to pasteOptionsWidget->setIconVisibleInMenu(true); but still no luck. so how can i add icon inside widget?


  • Moderators

    To simplify the submenu you can use a toolbar. As for the label item, if you're gonna use this pattern often you might invest in a custom widget that paints it.

    Here's the code for such menu:

        QMenu menu(this);
        menu.addAction(QIcon(":/cut.png"), tr("Cut"));
        menu.addAction(QIcon(":/copy.png"), tr("Copy"));
    
        auto paste_section_act = new QWidgetAction(&menu);
        paste_section_act->setDefaultWidget(new SectionWidget(QIcon(":/paste.png"), tr("Paste Options:"), &menu));
    
        auto paste_toolbar = new QToolBar();
        paste_toolbar->setIconSize(QSize(26,26));
        auto paste_toolbar_act = new QWidgetAction(&menu);
        paste_toolbar_act->setDefaultWidget(paste_toolbar);
    
        auto spacer = new QWidget();
        spacer->setMinimumWidth(20);
        paste_toolbar->addWidget(spacer);
        paste_toolbar->addAction(QIcon(":/paste.png"), QString());
        paste_toolbar->addAction(QIcon(":/paste.png"), QString());
        paste_toolbar->addAction(QIcon(":/paste.png"), QString());
    
        menu.addAction(paste_section_act);
        menu.addAction(paste_toolbar_act);
    
        connect(paste_toolbar, &QToolBar::actionTriggered, &menu, &QMenu::close);
    
        menu.exec();
    

    And here's a code for a custom section item:

    class SectionWidget : public QWidget
    {
    public:
        SectionWidget(const QIcon& icon, const QString& text, QWidget* parent = nullptr)
            : QWidget(parent), icon(icon), text(text) {}
    protected:
        void paintEvent(QPaintEvent* event) override
        {
            QPainter p(this);
    
            QStyleOptionMenuItem opt;
            opt.initFrom(this);
            opt.rect = rect();
            opt.icon = icon;
            opt.text = text;
            opt.font.setBold(true);
    
            const int icon_size = style()->pixelMetric(QStyle::PM_SmallIconSize, &opt, this);
    
            p.fillRect(rect().adjusted(icon_size + 8, 0, -1, 0), palette().color(QPalette::Base).darker(110));
            style()->drawControl(QStyle::CE_MenuItem, &opt, &p, this);
        }
        QSize minimumSizeHint() const override
        {
            QRect text_rect = fontMetrics().boundingRect(QRect(), Qt::TextSingleLine | Qt::TextShowMnemonic, text);
            QStyleOptionMenuItem opt;
            opt.initFrom(this);
            const int icon_size = style()->pixelMetric(QStyle::PM_SmallIconSize, &opt, this);
            const int some_margin = 6;
            QSize cs;
            cs.setWidth(some_margin + icon_size + text_rect.width());
            cs.setHeight(some_margin + qMax(icon_size, text_rect.height()));
            return style()->sizeFromContents(QStyle::CT_MenuItem, &opt, cs, this);
        }
    private:
        QIcon icon;
        QString text;
    };
    

    This is the result I got:
    office_menu



  • thanks a lot! for some reason it works a little differently for me so i'm trying to correct some things. for me, the toolbar actually shows the outline (that button effect). how can i get rid of that?

    and for the text inside widget isn't aligned in center (vertically). i've tried setting flags (Qt::AlignCenter, Qt::AlignJustify) for textRect but that didn't work


  • Moderators

    for me the bottom padding a larger than top padding

    That could be due to the icon size you used i.e. if it's larger than the calculated text height (notice I'm using max of these two values).



  • okay got it. and the toolbar i added has some weird outline (border?) around it and it gives button effect to the toolbar. how can i remove it?



  • @Chris-Kawa

    there's another thing. when the context menu is shown and when i mouse over the items from top to bottom, when i get to the this custom section, the item above it (i.e. copy) has the focus. how do i change this?


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.