Geometry information of QRubberBand giving SIGSEGV - qt

I am designing a Qt application using Qt Creator. As a part of the application, I need to be able to get position, height and width information of QRubberBand before I hide() it. For that purpose I tried to use the following logic, which is given in the documentation of QRubberBand:
void Widget::mousePressEvent(QMouseEvent *event)
{
origin = event->pos();
if (!rubberBand)
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
rubberBand->setGeometry(QRect(origin, QSize()));
rubberBand->show();
}
void Widget::mouseMoveEvent(QMouseEvent *event)
{
rubberBand->setGeometry(QRect(origin, event->pos()).normalized());
}
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
rubberBand->hide();
// determine selection, for example using QRect::intersects()
// and QRect::contains().
}
And defined rubberBand as below in the private section of the header file:
QRubberBand *rubberBand;
After doing that it works well. To go to the next step I defined the following integers as well (private section of the header):
int rubX;
int rubY;
int rubWidth;
int rubHeight;
And I tried to get geometry information before hiding rubberBand in mouseReleaseEvent similar to the following:
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
rubX = rubberBand->x();
rubY = rubberBand->y();
rubHeight = rubberBand->height();
rubWidth = rubberBand->width();
rubberBand->hide();
}
When I added those codes, the program runs, but when I try drawing the rubberBand, the program crashes giving SIGSEGV.
So here are my questions:
Why this is happening?
Is it possible to accomplish my goal by slightly editing the code?
What should I do to get what I want?
I know that I have done a foolish mistake, but I have not found it yet. Do not hesitate to comment if you want to get more information about the question.

From the code and comments you appear to be assuming that the rubberBand member will automatically be initialized to nullptr. However, it's actually uninitialized making the following...
if (!rubberBand)
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
result in undefined behaviour.
Change your Widget constructor to include an explicit initialization...
Widget::Widget ()
: rubberBand(nullptr)
{
...
}
Better still, just make rubberBand a non-pointer member...
QRubberBand rubberBand;
and initialize it in the constructor with...
Widget::Widget ()
: rubberBand(QRubberBand::Rectangle, this)
{
...
}

Related

Qt - Not correctly adding widgets to QHBoxLayout after clearing the layout

I have a QHBoxLayout in which I added some widgets. I need to be able to refresh the layout dynamically so I use this to clear the layout :
void ClearLayout(QLayout* layout)
{
if (!layout)
return;
QLayoutItem* item;
while ((item = layout->takeAt(0)) != nullptr)
{
delete item->widget();
ClearLayout(item->layout());
}
}
This indeed removes all widgets and layouts. After this layout->isEmpty() returns true and layout->count() returns 0.
However, when I try to add new widgets (same type of other previously added but new instance) It does not work !
AddWidget()
{
// DeviceWidget inherits QWidget
DeviceWidget* deviceWidget = new DeviceWidget;
deviceWidget->setFixedSize(150, 200);
connect(deviceWidget->GetSignalObject(), &DeviceObject::Selected, this,
&DeviceLayout::SelectedDevice);
layout->addWidget(deviceWidget, 0, Qt::AlignCenter);
}
This is the same function used previously to add the widgets to the layout and worked the first time at Construction:
MainLayout(QWidget* parent) : QHBoxLayout(parent)
{
layout = new QHBoxLayout;
addLayout(layout);
uint32 nb = GetDeviceNumber(); // returns 2
for (uint32 i = 0; i < deviceNb; ++i)
AddDeviceWidget();
}
After trying to add 2 widgets I have layout->isEmpty() returns true and layout->count() returns 2 so I'm confused …
thanks for any help provided :)
EDIT:
The problem seems to be comming from my DeviceWidget class since trying to add a simple QLabel to the cleared layout worked. Here's the DeviceWidget Constructor:
DeviceWidget::DeviceWidget(QWidget* parent) : QWidget(parent)
{
QVBoxLayout* vLayout = new QVBoxLayout;
QLabel* deviceIcon = new QLabel("DeviceIcon", this);
deviceIcon->setFixedSize(128, 128);
deviceIcon->setPixmap(QPixmap::fromImage(QImage("Resources/Icons/device.png")));
deviceIcon->setObjectName("DeviceIcon");
// StatusWidget inherits QWidget
// Just override paintEvent to display a colored filled disk
m_status = new StatusWidget(20, Status::Close, this);
m_status->setObjectName("DeviceStatus");
vLayout->addWidget(deviceIcon, 0, Qt::AlignCenter);
vLayout->addWidget(m_status, 0, Qt::AlignCenter);
// DeviceObjct inherits from QObject an add a signal
m_object = new DeviceObject(size());
// Function clearing the stylesheet background-color
Clear();
setLayout(vLayout);
installEventFilter(this);
setObjectName(QString("DeviceWidget"));
}
Commenting installEventFilter(this) make it work so I think I need to add an event filter to make it work but I don't know which one
As said in the Edit, The problem is coming from DeviceWidget added in the layout that override eventFilter. There is probably a way to add a case in eventFilter to make it work but in my case it was best either (1) or (2):
1. Remove eventFilter from class DeviceWidget and put it in class DeviceObject: m_object is present to emit a signal according to event:
DeviceObject.h:
DeviceObject(QObject* parent);
bool eventFilter(QObject* obj, QEvent* event) override;
signals:
void Select(uint32 i);
Then in class DeviceWidget still call installEventFilter but with m_object as parameter: installEventFilter(m_object);
For the other event (Enter/Leave) I overrode void enterEvent(QEvent* event) and void leaveEvent(QEvent* event) for class DeviceWidget. That's what lead me to the second option that seems better.
2. Completely remove eventFilter and installEventFilter as it is only used to emit a signal when widget is clicked and do things when cursor hovers the widget. Instead override enterEvent and leaveEvent for class DeviceWidget like said before for hover event.
Then in class DeviceObjecy override void mousePressEvent(QMouseEvent*) for clicked event.

Override checkable QGroupBox toggle behaviour

I've subclassed the QGroupBox class, with the checkable property enabled. I'm trying to override the behaviour of the toggle/checked events.
Here's the code:
class SideWidgetGroupBox: public QGroupBox
{
Q_OBJECT
public:
SideWidgetGroupBox(QWidget* parent = 0): QGroupBox(parent)
{
this->setCheckable(true);
connect(this, SIGNAL(toggled(bool)), this, SLOT(my_toggled(bool)));
}
private slots:
void my_toggled (bool on)
{
std::cout << "my toggled method" <<std::endl;
}
};
So far so good, my slot gets executed. However the groupboxs' contents also get enabled/disabled. Is there a way to prevent that? Or do I have to manually reset the original enabled/disabled state?
Is there a way to prevent enabling/disabling of a content?
Yes, but this way is not easy, because there is no QCheckBox there. What looks like a check box is an area of QGroupBox. And all events are processed by QGroupBox:
1. Override event method and prevent processing of QEvent::KeyRelease and QEvent::MouseRelease events by the base class.
bool SideWidgetGroupBox::event(QEvent *e)
{
switch (e->type()) {
case QEvent::KeyRelease:
case QEvent::MouseButtonRelease:
myHandler(e);
return true;
}
return QGroupBox::event(e);
}
2. In myHandler check whether space pressed or the mouse clicked on the checkbox. Store checkBox value and do what you need. Use this code to check what is under cursor:
QStyleOptionGroupBox box;
initStyleOption(&box);
QStyle::SubControl released = style()->hitTestComplexControl(QStyle::CC_GroupBox, &box,
event->pos(), this);
bool toggle = released == QStyle::SC_GroupBoxLabel || released == QStyle::SC_GroupBoxCheckBox;
if (toggle)
{
m_state = !m_state;
update();
}
3. Add method initStyleOption and set state to the state of the checkBox (you should store it by yourself):
void SideWidgetGroupBox::initStyleOption(QStyleOptionGroupBox *option) const
{
QGroupBox::initStyleOption(option);
QStyle::State flagToSet = m_state ? QStyle::State_On : QStyle::State_Off;
QStyle::State flagToRemove = m_state ? QStyle::State_Off : QStyle::State_On;
option->state |= flagToSet;
option->state &= ~flagToRemove;
option->state &= ~QStyle::State_Sunken;
}
4.Method initStyleOption in QGroupBox is not virtual that is why you need to reimplement paintEvent also:
void paintEvent(QPaintEvent *)
{
QStylePainter paint(this);
QStyleOptionGroupBox option;
initStyleOption(&option);
paint.drawComplexControl(QStyle::CC_GroupBox, option);
}
do I have to manually reset the original enabled/disabled state?
You can't do this with setEnabled because it checks current checked state and prevents enabling of children. Although you can call setEnabled for children directly using this->findChildren<QWidget*>
Suggestion
You can use ways described above or remove standard checkBox and(or) label and put your own QCheckBox over the group (without layout, of course) and use it as you want. If you group can be moved you will need to move the check box also.

Checking that value of ui elements has been changed

Is there any way to check that the ui elements(line edit,combo box,etc.) of the dialog has been changed.
What I want is to show a message to the user if he changes the value of any single ui element, saying that details have been partially filled.
What i can do is use connect for each ui element & based on the value changed of each element i am setting a boolean flag & at the time of close event i am checking that boolean flag.
But Its quite complicate to check it for each widget.
Is there any easier way.
Code that I am using for single ui element is,
connect(ui->leAge,SIGNAL(textChanged(QString)),this,SLOT(functChanged())); //In Constructor
void DemoDialog::functChanged() //Will be called if value of line edit (ui->leAge) is changed
{
flag=true;
}
void DemoDialog::closeEvent(QCloseEvent *event)
{
if (flag) {
if (QMessageBox::warning(this,"Close","Do you want to close?",QMessageBox::Yes|QMessageBox::No)==QMessageBox::Yes) {
this->close();
}
}
You can't reimplement closeEvent to prevent closing a window. The close() call that you do is either redundant or an error (infinite recursion), since a closeEvent method call is just a way of being notified that a closing is imminent. At that point it's too late to do anything about it.
Keep in mind the following:
Closing a dialog usually is equivalent to canceling the dialog. Only clicking OK should accept the changes.
When a user wants to close a dialog, you don't have to ask them about it. They initiated the action. But:
It is proper to ask a user about dialog closure if there are changes have not been accepted - on platforms other than OS X.
So, you have to do several things:
Reimplement the void event(QEvent*) method. This allows you to reject the close event.
Offer Apply/Reset/Cancel buttons.
Your flag approach can be automated. You can find all the controls of the dialog box and set the connections automatically. Repeat the statement below for every type of control - this gets tedious rather quickly:
foreach(QTextEdit* w, findChildren<QTextEdit*>())
connect(w, SIGNAL(textChanged(QString)), SLOT(functChanged()));
You can leverage the meta property system. Most controls have a user property - that's the property that holds the primary value of the control (like text, selected item, etc). You can scan all of the widget children, and connect the property change notification signal of the user property to your flag:
QMetaMethod slot = metaObject().method(
metaObject().indexOfSlot("functChanged()"));
foreach (QWidget* w, findChildren<QWidget*>()) {
QMetaObject mo = w->metaObject();
if (!mo.userProperty().isValid() || !mo.userProperty().hasNotifySignal())
continue;
connect(w, mo.notifySignal(), this, slot);
}
Each widget is a QObject. QObjects can have properties, and one of the properties can be declared to be the user property. Most editable widget controls have such a property, and it denotes the user input (text, numerical value, selected index of the item, etc.). Usually such properties also have change notification signals. So all you do is get the QMetaMethod denoting the notification signal, and connect it to your function that sets the flag.
To determine the changed fields, you don't necessarily need a flag. In many dialog boxes, it makes sense to have a data structure that represent the data in the dialog. You can then have a get and set method that retrieves the data from the dialog, or sets it on the dialog. To check for changed data, simply compare the original data to current data:
struct UserData {
QString name;
int age;
UserData(const QString & name_, int age_) :
name(name_), age(age_) {}
UserData() {}
};
class DialogBase : public QDialog {
QDialogButtonBox m_box;
protected:
QDialogButtonBox & buttonBox() { return m_box; }
virtual void isAccepted() {}
virtual void isApplied() {}
virtual void isReset() {}
virtual void isRejected() {}
public:
DialogBase(QWidget * parent = 0) : QDialog(parent) {
m_box.addButton(QDialogButtonBox::Apply);
m_box.addButton(QDialogButtonBox::Reset);
m_box.addButton(QDialogButtonBox::Cancel);
m_box.addButton(QDialogButtonBox::Ok);
connect(&m_box, SIGNAL(accepted()), SLOT(accept()));
connect(&m_box, SIGNAL(rejected()), SLOT(reject()));
connect(this, &QDialog::accepted, []{ isAccepted(); });
connect(this, &QDialog::rejected, []{ isRejected(); });
connect(&buttonBox(), &QDialogButtonBox::clicked, [this](QAbstractButton* btn){
if (m_box.buttonRole(btn) == QDialogButtonBox::ApplyRole)
isApplied();
else if (m_box.buttonRole(btn) == QDialogButtonBox::ResetRole)
isReset();
});
}
}
class UserDialog : public DialogBase {
QFormLayout m_layout;
QLineEdit m_name;
QSpinBox m_age;
UserData m_initialData;
public:
UserDialog(QWidget * parent = 0) : QDialog(parent), m_layout(this) {
m_layout.addRow("Name", &m_name);
m_layout.addRow("Age", &m_age);
m_age.setRange(0, 200);
m_layout.addRow(&buttonBox());
}
/// Used by external objects to be notified that the settings
/// have changed and should be immediately put in effect.
/// This signal is emitted when the data was changed.
Q_SIGNAL void applied(UserData const &);
UserData get() const {
return UserData(
m_name.text(), m_age.value());
}
void set(const UserData & data) {
m_name.setText(data.name);
m_age.setValue(data.age);
}
void setInitial(const UserData & data) { m_initialData = data; }
bool isModified() const { return get() == m_initialData; }
protected:
void isAccepted() Q_DECL_OVERRIDE { emit applied(get()); }
void isApplied() Q_DECL_OVERRIDE { emit applied(get()); }
void isReset() Q_DECL_OVERRIDE { set(m_initialData); }
};
If you're only checking whether the input fields are filled when the Dialog closes, you don't need the flags you can only check if there is any input.
If you are filling the input fields programatically at some points but are also only interested in the change when the dialog closes, you can also check in the close function whether the current input is equal to the one you set earlier.
From the code you posted, I can't really see what you need the flags for.

How to make a Qt dialog read-only?

How to make a QT dialog read-only? Any general way to implement it easily? For example
(1) set all its containing widgets disable. (how to implement it?)
(2) Intercept edit events like key pressed, mouse pressed but how not to intercept the one to close the dialog?
I think this feature should be very helpful.
Disabling the widgets can be done similar to the following:
void myDialog::disableWidgets()
{
QList<QWidget *> widgets = this->findChildren<QWidget *>();
foreach(QWidget* widget, widgets)
{
widget->setEnabled(false);
}
}
To intercept events, QDialog includes the function installEventFilter(QObject*).
This allows you to use a separate object to receive all events passed to the dialog. You can then choose to handle the event in the object, or pass it on to the dialog itself by calling the base class QObject::eventFilter
class MyEventHandler : public QObject
{
Q_OBJECT
protected:
bool MyEventHandler::eventFilter(QObject *obj, QEvent *event)
{
// handle key press events
if (event->type() == QEvent::KeyPress)
{
// Do something
// ...
return true; // event handled by the class
}
else
{ // ignore this event and pass it to the dialog as usual
return QObject::eventFilter(obj, event);
}
}
return false;
};
QDialog* dlg = new QDialog;
MyEventHandler evtHandler = new MyEventHandler;
dlg->installEventFilter(evtHandler);
Read-only is a strange term to apply to a dialog. Disabling all widgets as above does the trick. If you only wanted to make the input part of a QInputDialog read-only (while leaving scrollbars, buttons, etc. enabled), you could adapt that code as below:
QInputDialog dialog(this);
dialog.setOptions(QInputDialog::UsePlainTextEditForTextInput);
dialog.setWindowTitle("Title");
dialog.setLabelText("Label");
dialog.setTextValue("1\n2\n3\n");
QList<QWidget *> widgets = dialog.findChildren<QWidget *>();
foreach(QWidget* widget, widgets) {
if (strcmp(widget->metaObject()->className(),"QPlainTextEdit")==0) {
QPlainTextEdit *t = static_cast<QPlainTextEdit*>(widget);
t->setReadOnly(true);
}
}
dialog.exec();

Respond to application-wide "hotkey" in Qt

I've got a simple Qt app, and I just want to respond to the F12 key, regardless of which widget has focus.
Is there some easy signal or something I can hook in to?
I want to use the F12 key to toggle the main window fullscreen on/off.
I haven't tried, but here is what I would do :
Create a QShortcut and make sure its context (with setContext()) is Qt::ApplicationShortcut.
shortcut = new QShortcut(QKeySequence(Qt::Key_F12), parent);
shortcut->setContext(Qt::ApplicationShortcut);
Then you just need to connect a slot to the QShortcut::activated() signal.
If you have a "central widget" which all of the other widgets are children of, then you can simply set that as the widget argument for QShortcut.
(Python, qt5)
self.centralwidget = QtWidgets.QWidget(MainWindow)
QtWidgets.QShortcut(QtGui.QKeySequence("F12"), self.centralwidget, self.goFullScreen)
I added this as an answer because the shortcut context flag: Qt.ApplicationShortcut did not work for me.
Setting the shortcut context to Qt::ApplicationShortcut has a serious flaw. It will not work in modal dialogs. So if you want a trully real pan-application-wide shortcut, then you need to override application's notify() method. The alternative is to install event filter for the application object but that I suspect would be slower and requires slightly more code. With notify() it is very simple:
class MyApplication : public QApplication
{
// TODO: constructor etc.
protected:
bool MyApplication::notify(QObject *receiver, QEvent *event) override
{
if (event->type() == QEvent::KeyPress)
{
auto keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_F12 && keyEvent->modifiers() == Qt::NoModifiers)
{
// TODO: do what you need to do
return true;
}
}
return QApplication::notify(receiver, event);
}
}

Resources