QtToolBar with underlined shortcut key in button text - c++

I have a simple Qt toolbar with text only button Action:
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
{
QToolBar* toolBar = new QToolBar(this);
QAction* action = toolBar->addAction("&Action");
QObject::connect(action, SIGNAL(triggered()), this, SLOT(onAction()));
action->setShortcut(QKeySequence("ctrl+a"));
addToolBar(toolBar);
}
I would like to have A in Action underlined to reflect its role as a shortcut key. How to accomplish that?

Standard QAction widget (it is a QToolButton actually) uses stripped version of its text for display: "&Menu Option..." becomes "Menu Option".
You can create a custom QAction widget which does not use stripped text by subclassing QWidgetAction:
MyAction::MyAction(QObject *parent) :
QWidgetAction(parent)
{
}
QWidget* MyAction::createWidget(QWidget *parent)
{
QToolButton *tb = new QToolButton(parent);
tb->setDefaultAction(this);
tb->setText(this->text());// override text stripping
tb->setFocusPolicy(Qt::NoFocus);
return tb;
}
In your MainWindow constructor use it as follows:
MainWindow(QWidget* parent=0) : QMainWindow(parent)
{
QToolBar* toolBar = new QToolBar(this);
MyAction* action = new MyAction();
action->setText("&Action");
action->setShortcut(QKeySequence(tr("ctrl+a","Action")));
toolBar->addAction(action);
QObject::connect(action, SIGNAL(triggered()), this, SLOT(onAction()));
addToolBar(toolBar);
}
Appearence of underline shortcut letters depends on your application style.
Here is an example of a custom style that will force shortcut underline display:
class MyStyle : public QProxyStyle
{
public:
MyStyle();
int styleHint(StyleHint hint,
const QStyleOption *option,
const QWidget *widget,
QStyleHintReturn *returnData) const;
};
int MyStyle::styleHint(QStyle::StyleHint hint,
const QStyleOption *option,
const QWidget *widget,
QStyleHintReturn *returnData) const
{
if (hint == QStyle::SH_UnderlineShortcut)
{
return 1;
}
return QProxyStyle::styleHint(hint, option, widget, returnData);
}
Then you should set that style to your application:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
a.setStyle(new MyStyle);
Widget w;
w.show();
return a.exec();
}

Related

QPalette propagation to QDialog not working

I have set a dark theme on my QWidget. In that widget I call a QDialog. The dialog appears in default palette which is light.
This is my custom dialog (Which in this case is for choosing colors for palette! Twisted right?) .cpp file:
#include "custompalettedialog.h"
CustomPaletteDialog::CustomPaletteDialog(QPalette palette, QWidget *parent) : QDialog(parent),
m_palette(palette)
{
QVBoxLayout* mainLayout = new QVBoxLayout(this);
mainLayout->setSpacing(0);
mainLayout->setMargin(0);
QFormLayout* mainFormLayout = new QFormLayout;
mainFormLayout->setHorizontalSpacing(0);
mainFormLayout->setVerticalSpacing(0);
mainLayout->addLayout(mainFormLayout);
for (int var = 0; var < QPalette::ColorRole::NColorRoles; ++var) {
QPushButton* bandButton = new QPushButton;
bandButton->setFlat(true);
bandButton->setAutoFillBackground(true);
QPalette pal = bandButton->palette();
pal.setColor(QPalette::Button, m_palette.color(static_cast<QPalette::ColorRole>(var)));
bandButton->setPalette(pal);
connect(bandButton, &QPushButton::clicked, this, [this, bandButton, var]() {
QColor color = QColorDialog::getColor();
if (color.isValid()) {
m_palette.setColor(static_cast<QPalette::ColorRole>(var), color);
QPalette pal = bandButton->palette();
pal.setColor(QPalette::Button, color);
bandButton->setPalette(pal);
}
});
mainFormLayout->addRow(QVariant::fromValue(static_cast<QPalette::ColorRole>(var)).toString(), bandButton);
}
QPushButton* doneButton = new QPushButton("Done");
connect(doneButton, &QPushButton::clicked, this, &QDialog::accept);
mainLayout->addWidget(doneButton);
}
QPalette CustomPaletteDialog::getPalette()
{
return m_palette;
}
QPalette CustomPaletteDialog::getPalette(bool* ok, const QPalette palette, QWidget *parent, const QString title)
{
CustomPaletteDialog* dialog = new CustomPaletteDialog(palette, parent);
dialog->setFont(parent->font());
dialog->setModal(true);
dialog->setWindowTitle(title);
dialog->exec();
QPalette pal = dialog->getPalette();
*ok = dialog->result();
dialog->deleteLater();
return pal;
}
and the .h file:
#ifndef CUSTOMPALETTEDIALOG_H
#define CUSTOMPALETTEDIALOG_H
#include <QDialog>
#include <QFormLayout>
#include <QPushButton>
#include <QLineEdit>
#include <QColorDialog>
#include <QComboBox>
class CustomPaletteDialog : public QDialog
{
Q_OBJECT
public:
explicit CustomPaletteDialog(QPalette palette, QWidget *parent = nullptr);
static QPalette getPalette(bool* ok, const QPalette palette, QWidget *parent = nullptr, const QString title = QString());
QPalette getPalette();
private:
QPalette m_palette;
};
#endif // CUSTOMPALETTEDIALOG_H
I call the static method getPalette from a parent widget.
I have tried adding this:
dialog->setPalette(parent->palette());
before dialog->setFont(parent->font()); in that static method and not working. My question is how to propagate QPalette from parent QWidget to child QDialog?
This is how I used it:
QPushButton* paletteAddButton = new QPushButton("Add...");
mainGroupBoxLayout->addWidget(paletteAddButton);
connect(paletteAddButton, &QPushButton::clicked, this, [this, customThemeItemModel]() {
bool ok = false;
QPalette palette = CustomPaletteDialog::getPalette(&ok, this->palette(), this, "Chosse UI Palette");
if (ok) {
QStandardItem* item = new QStandardItem("newPalette");
item->setToolTip("Double Click to Change Name, Right Click for More");
item->setData(palette);
customThemeItemModel->appendRow(item);
saveThemes(customThemeItemModel);
}
});
I have a list of themes, user can add custom themes to that list.

Qt: propagate mouse clicks through a transparent widget

I'm trying to create a widget which, when shown, it will intercept any mouse clicks, process them, but then forward the click to the widget that was under the mouse when it was clicked. I've created a class that represents this widget; all it does for now is capture mouse release events, hide itself and post the event to the widget the click was intended for using QApplication::postEvent:
class Overlay : public QWidget
{
Q_OBJECT
public:
Overlay(QWidget* parent)
: QWidget(parent){}
protected:
void mouseReleaseEvent(QMouseEvent* event)
{
hide();
auto child = parentWidget()->childAt(event->pos()); // get the child widget the event was intended for
auto e = new QMouseEvent(*event);
if(child) QCoreApplication::postEvent(child, e); // why doesn't this have any effect?
event->accept();
}
void paintEvent(QPaintEvent* event)
{
QPainter p(this);
p.fillRect(rect(), QColor(0,0,0,150));
}
};
Unfortunately, the call to postEvent doesn't seem to have any effect at all. Here is an example where I'm setting up some child widgets; clicking on any widget will print something out:
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr)
{
m_overlay = new Overlay(this);
m_overlay->hide();
auto button = new QPushButton("Add overlay");
auto button2 = new QPushButton("PushButton");
auto list = new QListWidget();
list->addItems(QStringList() << "item1" << "item2" << "item3");
connect(list, &QListWidget::itemClicked, [](const QListWidgetItem* item)
{
qDebug() << item->text();
});
connect(button2, &QPushButton::clicked, []()
{
qDebug() << "Button clicked";
});
connect(button, &QPushButton::clicked, [this]()
{
m_overlay->setGeometry(rect());
m_overlay->raise();
m_overlay->show();
});
auto layout = new QVBoxLayout(this);
layout->addWidget(list);
layout->addWidget(button);
layout->addWidget(button2);
}
public:
~Widget(){}
private:
Overlay* m_overlay;
};
I've confirmed that clicks on the overlay are captured and the overlay is hidden as intended but the event is not propagated to the underlying widget. Using QApplication::sendEvent doesn't work either. What am I missing?
Using Qt 5.15.1 on MacOS 11.2.3. Thanks

Qt dialog not showing its widgets

I would like to create custom QT dialog (non-modal). Problem with my implementation is that it shows only dialog window with title, and no widgets that I've added to it.
Code below (I've ommited most of it, added just dialog and main window parts).
MainWindow.h
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
/// some other stuff
private:
std::unique_ptr<ui::DialogAddUpdateItem> addItemDialog;
/// some other stuff
}
MainWindow.cpp
/// some stuff
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/// some stuff
addItemButton = new QPushButton(tr("Add item"));
QObject::connect(addItemButton, &QPushButton::pressed, this, &MainWindow::openAddItemDialog);
navLay->addWidget(addItemButton);
addItemDialog = make_unique<ui::DialogAddUpdateItem>(this);
/// some stuff
}
void MainWindow::openAddItemDialog() {
addItemDialog->show();
//addItemDialog->raise(); does not work with or without those functions
//addItemDialog->activateWindow();
//QApplication::processEvents();
}
DialogAddUpdateItem.h
namespace ui {
class DialogAddUpdateItem : public QDialog
{
Q_OBJECT
public:
DialogAddUpdateItem(QWidget *parent = nullptr);
private:
QPushButton *buttonAcc, *buttonRevert, *buttonCancel;
QGroupBox *centralWidget, *buttonsWidget;
QLabel *labelName, *labelDescription;
QLineEdit *textName;
QPlainTextEdit *textDescription;
}
}
DialogAddUpdateItem.cpp
namespace ui {
DialogAddUpdateItem::DialogAddUpdateItem(QWidget *parent) : QDialog(parent)
{
if (!item) {
setWindowTitle(tr("New object"));
}
centralWidget = new QGroupBox;
QHBoxLayout *itemLay = new QHBoxLayout;
centralWidget->setLayout(itemLay);
labelName = new QLabel(tr("Name"));
itemLay->addWidget(labelName);
textName = new QLineEdit;
if (item) {
textName->setText(QString::fromStdString(item->getName()));
}
itemLay->addWidget(textName);
labelDescription = new QLabel(tr("Description"));
if (item) {
textDescription = new QPlainTextEdit(QString::fromStdString(item->getDescription()));
} else {
textDescription = new QPlainTextEdit;
}
itemLay->addWidget(textDescription);
buttonAcc = new QPushButton(tr("Save"));
buttonAcc->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
QObject::connect(buttonAcc, &QPushButton::clicked, this, &DialogAddUpdateItem::acceptItem);
buttonRevert = new QPushButton(tr("Revert"));
buttonRevert->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
QObject::connect(buttonRevert, &QPushButton::clicked, this, &DialogAddUpdateItem::revertItem);
buttonCancel = new QPushButton(tr("Cancel"));
buttonCancel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
QObject::connect(buttonCancel, &QPushButton::clicked, this, &DialogAddUpdateItem::cancelItem);
buttonsWidget = new QGroupBox;
itemLay->addWidget(buttonsWidget);
QVBoxLayout *buttonsLay = new QVBoxLayout;
buttonsLay->addWidget(buttonAcc);
buttonsLay->addWidget(buttonRevert);
buttonsLay->addWidget(buttonCancel);
}
}
In DialogAddUpdateItem.cpp the centralWidget has no parent, therefore it is not bound to anything and therefore not displayed. You should modify this:
centralWidget = new QGroupBox;
into this:
centralWidget = new QGroupBox(this);
Now the DialogAddUpdateItem will be the parent of the centralWidget and it should display it.
Also it seems like You are leaving some other widgets without a parent - it might cause trouble. For example the QLineEdit textName has this issue.

QScintilla: How to add a user context menu to textEdit? (C++)

I'm struggeling with telling a QScitilla textEdit that is the main widget of my MainWindow app to accept showing a personalized context menu on right-clicking the mouse.
What works fine if I use a standard Qt5 textEdit fails if used with the QScintilla alternative. I tried it with defining a user menu from some actions:
void MainWindow::contextMenuEvent(QContextMenuEvent *event)
{
QMenu menu(this);
menu.addAction(cutAct);
menu.addAction(copyAct);
menu.addAction(pasteAct);
menu.exec(event->globalPos());
}
#endif // QT_NO_CONTEXTMENU
reacting on QContextMenuEvent, but the menu only shows up when I right-click an element of the MainWindow instead of the QScintilla textEdit. When I do within the textEdit, only the standard cut/copy/paste menu is shown.
How to implement that for QScintilla textEdit?
There are two methods:
Method 1: set Qt::CustomContextMenu for context menu policy of QScintilla text edit :
textEdit->setContextMenuPolicy( Qt::CustomContextMenu );
connect(textEdit, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(ShowContextMenu(const QPoint &)));
}
void MainWindow::ShowContextMenu(const QPoint &pos)
{
QMenu contextMenu(tr("Context menu"), this);
QAction action1("Action 1", this);
connect(&action1, &QAction::triggered, this, []{
qDebug() << "On action 1 click !!!";
});
contextMenu.addAction(&action1);
contextMenu.exec(mapToGlobal(pos));
}
Method 2: Define a subclass of QScintilla then redefine the override function contextMenuEvent :
class MyQsciScintilla : public QsciScintilla
{
Q_OBJECT
public:
explicit MyQsciScintilla(QWidget *parent = nullptr);
void contextMenuEvent(QContextMenuEvent *event);
//....
};
void MyQsciScintilla::contextMenuEvent(QContextMenuEvent *event)
{
QMenu *menu = createStandardContextMenu();
menu->addAction(tr("My Menu Item"));
//...
menu->exec(event->globalPos());
delete menu;
}

Nested QDialog automatically presses a QPushButton inside of it

#include <QApplication>
#include <QDebug>
#include <QDialog>
#include <QPushButton>
#include <QTreeWidget>
#include <QVBoxLayout>
int main(int argc, char** argv)
{
QApplication app(argc, argv);
QDialog dialog;
QVBoxLayout layout(&dialog);
QTreeWidget treeWidget;
treeWidget.insertTopLevelItem(0, new QTreeWidgetItem(&treeWidget));
QObject::connect(&treeWidget, &QTreeWidget::activated, [&treeWidget]() {
auto secondDialog = new QDialog(&treeWidget);
auto layout = new QVBoxLayout(secondDialog);
auto button = new QPushButton();
QObject::connect(button, &QPushButton::clicked, []() {
qDebug() << "button clicked";
});
layout->addWidget(button);
secondDialog->show();
});
layout.addWidget(&treeWidget);
dialog.show();
return app.exec();
}
When I activate a QTreeWidget's item by pressing Enter, nested dialog is created and it immediately presses button inside of it. How do I get rid of it?
I solved this by overriding keyPressEvent of the QTreeView that was inside the dialog:
class EnterEatingTreeView : public BaseTreeView
{
public:
explicit EnterEatingTreeView(QWidget* parent = nullptr)
: BaseTreeView(parent)
{
}
protected:
void keyPressEvent(QKeyEvent* event) override
{
BaseTreeView::keyPressEvent(event);
switch (event->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
event->accept();
}
}
};
This way pressing Enter key inside QTreeView doesn't passes to its parent.