Hi Pl45m4,
toolbar seems to be a crazy environment. I shouted the 'F'-word several times. Had lot of crashes, but finally I think I got it.
Not perfect, but a solution, I can live with ;)
Every posibility, I thought I should work with is blocked/prohibited. Either function not virtual, or no function used for access ...
QWidgetAction is the only posibility to get individual widgets into a toolbar. But that widget will be reduced to a stupid painter subject. The factory action is still used as an action (well - I thought it would be just a factory action, but it is not. Stil a complete QAction!).
After I got that point, it was straightforward to implement -
MultiStateButton - declaration:
class MultiStateToolButton : public QToolButton
{
Q_OBJECT
public:
MultiStateToolButton(ValueModel* vm, const QIcon& s0Icon, const QIcon& s1Icon, const QIcon& s2Icon, QWidget* parent = nullptr);
protected slots:
void stateChanged(const QVariant& state);
private:
ValueModel* model;
const QIcon& s0Icon;
const QIcon& s1Icon;
const QIcon& s2Icon;
};
I implemented it for 3 States, but it could be adapted to any number of states.
ValueModel is used to hold the buttons state - as in my application the buttons state can be changed from outside of my application ...
model could be a Qt-property as well.
MultiStateButton - definition:
MultiStateToolButton::MultiStateToolButton(ValueModel* vm, const QIcon& s0Icon, const QIcon& s1Icon, const QIcon& s2Icon, QWidget* parent)
: QToolButton(parent)
, model(vm)
, s0Icon(s0Icon)
, s1Icon(s1Icon)
, s2Icon(s2Icon) {
setIcon(this->s0Icon);
connect(model, &ValueModel::valueChanged, this, &MultiStateToolButton::stateChanged);
}
void MultiStateToolButton::stateChanged(const QVariant& state) {
qDebug() << "MultiStateButton::stateChanged ... " << state;
switch (state.toInt()) {
case 2: setIcon(s1Icon); break;
case 3: setIcon(s2Icon); break;
default: setIcon(s0Icon); break;
}
}
The hardest point for me to get rid of was, that the toolbutton works fine with const references to icons, but the QWidgetAction will crash with icons as references.
As I could not use one QIcon with multiple pixmaps, I decided to use one QIcon for each picture.
So the subclass of QWidgetAction is straight forward:
class MultiStateAction : public QWidgetAction
{
Q_OBJECT
public:
MultiStateAction(ValueModel* vm, const QIcon& s0Icon, const QIcon& s1Icon, const QIcon& s2Icon, QWidget* parent = nullptr);
QWidget* createWidget(QWidget* parent) override;
private:
ValueModel* model;
QIcon s0Icon;
QIcon s1Icon;
QIcon s2Icon;
};
I started with const references in the widget action, as I thought, I only have to store the parameters for widget creation later on ...
MultiStateAction::MultiStateAction(ValueModel* vm, const QIcon& s0Icon, const QIcon& s1Icon, const QIcon& s2Icon, QWidget *parent)
: QWidgetAction(parent)
, model(vm)
, s0Icon(s0Icon)
, s1Icon(s1Icon)
, s2Icon(s2Icon) {
setIcon(s0Icon);
setText("MultiStateFactoryAction");
}
QWidget *MultiStateAction::createWidget(QWidget *parent) {
QToolButton* tb = new MultiStateToolButton(model, s0Icon, s1Icon, s2Icon, parent);
tb->setDefaultAction(this);
return tb;
}
QToolButton::setDefaultAction() is the most important step. Without that, the button does not emit a triggered signal. With that function call, button and action emit a triggered signal.
Creation of toolbar (in mainwindow or similar) looks like:
Window::Window()
: model(new ValueModel("state", 0))
, tb(new QToolBar)
, a1(new QAction("A"))
, a2(new QAction("B"))
, a3(new QAction("C"))
, a4(new QAction("D"))
, msa(new MultiStateAction(model
, QIcon(":/res/doc.png")
, QIcon(":/res/hand.png")
, QIcon(":/res/ok.png"))) {
setLayout(new QVBoxLayout);
setupActions();
connectActions();
}
void Window::setupActions() {
tb->setIconSize(QSize(100, 100));
tb->addAction(a1);
tb->addAction(a2);
tb->addAction(a3);
tb->addAction(msa);
tb->addAction(a4);
layout()->addWidget(tb);
}
First I had no call of setDefaultAction used and got no signal from the multistatebutton. So I tried several ways to get the button-press-event ...
As said - button state can be changed outside of my app, so the plain actions simulate external status change ...
void Window::connectActions() {
connect(a1, &QAction::triggered, this, [=]() {
qDebug() << "A";
model->setValue(0);
});
connect(a2, &QAction::triggered, this, [=]() {
qDebug() << "B";
model->setValue(1);
});
connect(a3, &QAction::triggered, this, [=]() {
qDebug() << "C";
model->setValue(2);
});
connect(a4, &QAction::triggered, this, [=]() {
qDebug() << "D";
model->setValue(3);
});
connect(msa, &QAction::triggered, this, [=]() {
qDebug() << " - MultiStateAction triggered ... !";
});
QToolButton* multiStateButton = static_cast<QToolButton*>(tb->widgetForAction(msa));
if (multiStateButton) {
connect(multiStateButton, &QToolButton::triggered, this, [=]() {
qDebug() << "MultiStateToolButton clicked ...";
});
}
else {
throw QString("OUPS - toolbar has no MultiStateToolButton button!");
}
}
@Pl45m4 thank you very much for your help!