Qt QSpinBox with a set of predefined values - qt

I have a QSpinBox which should only accept a set of discrete values (let's say 2, 5, 10). I can setMinimum(2) and setMaximum(10), but I cannot setSingleStep because I have a step of 3 and one of 5.
Is there a different widget which I can use, but which has the same UI as the QSpinBox?
If not, what should I overwrite to achieve the desired effect?

Use QSpinBox::stepsBy() to handle the values.
For example:
class Spinbox: public QSpinBox
{
public:
Spinbox(): QSpinBox()
{
acceptedValues << 0 << 3 << 5 << 10; // We want only 0, 3, 5, and 10
setRange(acceptedValues.first(), acceptedValues.last());
}
virtual void stepBy(int steps) override
{
int const index = std::max(0, (acceptedValues.indexOf(value()) + steps) % acceptedValues.length()); // Bounds the index between 0 and length
setValue(acceptedValues.value(index));
}
private:
QList<int> acceptedValues;
};

Related

How to create a Matrix like QWidget?

How to create a QTableWidget with some cells filled using uniform sized widgets and the unused cells filled with the size of widgets?The resulting table should contain a row/column as blank even if no widget is present.
Since the QTableWidget apparently has to contain something in each cell, and the default is an editable text box, it seem the solution would be to insert a non-editable dummy item into the blank cells. Or have a custom widget which can hide its contents, which is what I've done in the example below.
You specifically asked about fixed size... there's really a number of ways to go about this, depending on the exact needs. You can set fixed sizes on the whole table by using the horizontal and vertical headers and making them non-resizable. Another way is to call QTableView::setColumnWidth() / setRowHeight() for each row/column (while adding/item or whenever).
Here's an example using the former method (setting size on headers), and two ways to determine the actual size -- either a fixed size before building the table, or using a lookup on the custom widget item to determine a size.
UPDATE: Previous version used a dummy QTableWidgetItem to fill in the blank areas, but I think this is better if one needs to use a custom widget anyway. See edit history for previous version.
// main.cpp
#include <QtWidgets>
// A custom table widget item which contains a progress bar. The bar is
// hidden if the value is set to < 0.
class ProgressBarTwItem : public QWidget, public QTableWidgetItem
{
Q_OBJECT
public:
ProgressBarTwItem(QWidget *parent = nullptr, int value = -1) :
QWidget(parent),
QTableWidgetItem(QTableWidgetItem::UserType)
{
QHBoxLayout *l = new QHBoxLayout(this);
l->setContentsMargins(0,0,0,0);
m_pb = new QProgressBar(this);
// don't let PB size dictate our size
m_pb->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
m_pb->setAlignment(Qt::AlignCenter);
m_pb->setMaximum(44);
l->addWidget(m_pb);
setValue(value);
}
QSize sizeHint() const override { return QSize(75, 25); }
public slots:
void setValue(int value = -1) const
{
m_pb->setVisible(value > -1);
if (value > -1)
m_pb->setValue(value);
}
private:
QProgressBar *m_pb;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
a.setStyle("Fusion");
QDialog d;
d.setLayout(new QVBoxLayout);
QTableWidget *tw = new QTableWidget(5, 4, &d);
d.layout()->addWidget(tw);
// set to `true` to find the largest size from added custom widgets,
// otherwise use preset size
const bool useWidgetSize = true;
// This will be the fixed cell size, either preset or determined from custom widget.
// If looking up from widgets, this becomes the minimum size
// (eg. in case widgets didn't return valid sizes).
QSize cellSize = (useWidgetSize ? QSize(10, 10) : QSize(50, 15));
for (int r=0; r < tw->rowCount(); ++r) {
for (int c=0; c < tw->columnCount(); ++c) {
// make some widgets hidden and others display a progress value
const int val = (!((r*c) % (1+c)) ? -1 : (c+1 + r*10));
ProgressBarTwItem *item = new ProgressBarTwItem(tw, val);
tw->setCellWidget(r, c, item);
// tw->setItem(r, c, item); // needed? widget is shown anyway... docs not clear.
// Check the size.
if (useWidgetSize)
cellSize = cellSize.expandedTo(item->sizeHint());
}
}
// set fixed sizes for columns and rows on the horizontal and vertical headers
// respectively (this works even if they are hidden)
tw->horizontalHeader()->setDefaultSectionSize(cellSize.width());
tw->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);
tw->verticalHeader()->setDefaultSectionSize(cellSize.height());
tw->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
return d.exec();
}
#include "main.moc"

How display multiple values in QLabel in Qcreator

I am developing calculator using Qt SDK I want to get values from the calculator buttons in to one Qlabel. I have designed GUI using design view I tried to get values using following code.
void MainWindow::on_three_button_clicked(){
ui->label->setText("3");
}
but then Only I can get one button value at a time
please help me on this...
You can have one QString variable to store current pressed value. Here I am considering your class variable defined as QString myEnteredNum. Then you can use it as below code:
void MainWindow::on_three_button_clicked(){
myEnteredNum.append("3"); // This method you can add in each number slot
ui->label->setText(myEnteredNum);
}
I hope this helps you.
There are two general approaches to this. You can append the digits directly to a number, and bind the label to the number. The m_first label below is updated that way. You can also append the digits directly to the label. The m_second label is updated so.
#include <QtWidgets>
class Window : public QWidget {
Q_OBJECT
Q_PROPERTY(double number MEMBER m_number WRITE setNumber NOTIFY numberChanged)
double m_number;
QGridLayout m_grid;
QLabel m_first, m_second;
QPushButton m_digits[10], m_clear;
public:
Window(QWidget * parent = 0) : QWidget(parent),
m_grid(this), m_clear("C") {
m_grid.addWidget(&m_first, 0, 0, 1, 3);
m_grid.addWidget(&m_second, 1, 0, 1, 3);
m_grid.addWidget(&m_clear, 5, 2);
for (int i = 0; i < 9; ++i) {
m_digits[i].setText(QString::number(i));
if (i > 0) m_grid.addWidget(m_digits + i, 2 + (i-1)/3, (i-1)%3);
else m_grid.addWidget(m_digits + i, 5, 1);
QObject::connect(m_digits + i, &QPushButton::clicked, [i, this]{
// Add the digit to the number
setNumber(m_number * 10.0 + i);
// Add the digit to the label
m_second.setText(m_second.text().append(QString::number(i)));
});
}
QObject::connect(&m_clear, &QPushButton::clicked, [this]{
setNumber(0);
m_second.setText("0");
});
QObject::connect(this, &Window::numberChanged,
&m_second, &QLabel::setNum);
emit m_clear.clicked(); // initialize everything
}
Q_SIGNAL void numberChanged(double);
void setNumber(double n) { m_number = n; emit numberChanged(n); }
};
int main(int argc, char ** argv) {
QApplication app(argc, argv);
Window window;
window.show();
return app.exec();
}
#include "main.moc"
Finally, a calculator is stateful and, to avoid common mistakes, you could use a QStateMachine to make this fact explicit, and to adjust the behavior of the calculator depending on which state it's in.

How to get the row/column location of a widget in a QGridLayout?

I made an own GridPushButton class to store the buttons position in gridlayout. The parent is QPushButton. I have a problem with asking it's x and y coordinates in window (like x:654, y:768). I thought it will be inherited from base class, but it doesn't. Now i have two options:
Use the original QPushButton class and somehow get its position in gridlayout (like x:0, y:1 if it's in the first row and second column) or
Use my GridPushButton and somehow get the x and y coordinate in window.
class GridPushButton : public QPushButton
{
Q_OBJECT
public:
GridPushButton(int coordX, int coordY, QWidget *parent = 0);
int coordinateX() { return _coordX; }
int coordinateY() { return _coordY; }
protected:
int _coordX;
int _coordY;
};
This is my class. I tried to make a new private variable and give it the QPushButton::x(), but doesn't work. Any idea to get the x and y coordinate from parent?
Or any idea to get the QPushButtons position in GridLayout?
There are two notions of coordinates that you're mixing up. There is the position within the parent widget. That's available via QWidget::x(), QWidget::y() and QWidget::pos() methods. You don't need to implement anything here: it already works.
Then there's the notion of the row and column within the grid layout. This can be obtained without a need for any subclassing. The grid layout knows where its widgets are, you can simply ask it for the row/column location of any widget.
#include <QtWidgets>
struct Pos { int row = -1, col = -1; };
Pos gridPosition(QWidget * widget) {
if (! widget->parentWidget()) return {};
auto layout = qobject_cast<QGridLayout*>(widget->parentWidget()->layout());
if (! layout) return {};
int index = layout->indexOf(widget);
Q_ASSERT(index >= 0);
int _;
Pos pos;
layout->getItemPosition(index, &pos.row, &pos.col, &_, &_);
return pos;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
QGridLayout l(&w);
QLabel gridPos;
l.addWidget(&gridPos, 0, 0, 1, 4);
for (int i = 1; i < 4; ++ i)
for (int j = 0; j < 3; ++ j) {
auto b = new QPushButton(QString("%1,%2").arg(i).arg(j));
l.addWidget(b, i, j);
QObject::connect(b, &QPushButton::clicked, [&gridPos, b]{
auto p = gridPosition(b);
gridPos.setText(QString("Grid Pos: %1,%2")
.arg(p.row).arg(p.col));
});
}
w.show();
return a.exec();
}

use a created vector as range for QDoubleSpinBox and QSlider

I've created a vector v with 128 elements from -3000 to 3000 and I want to associate it to a QDoubleSpinBox and a QSlider, because dividing 6000 to 128 and setting the QDoubleSpinBox we have always the round problem. So can we set the range and the stepsize both to a QDoubleSpinBox and QSlider with a vector like this?
std::vector<double> v(128);
for (int i = 0; i < 128; ++i)
{
v[i] = -3000.0 + 6000.0 * (i) / 127.0;
}
QSlider only operates with int steps, so you'd just need to do the calculation yourself:
#include <QtGui>
#include <cmath>
class MyWidget : public QWidget {
Q_OBJECT
public:
MyWidget() : QWidget() {
slider_ = new QSlider;
slider_->setRange(0, 127);
connect(slider_, SIGNAL(valueChanged(int)), SLOT(ChangeSpinBox(int)));
box_ = new QDoubleSpinBox;
box_->setRange(-3000.0, 3000.0);
connect(box_, SIGNAL(valueChanged(double)), SLOT(ChangeSlider(double)));
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(slider_);
layout->addWidget(box_);
setLayout(layout);
}
private slots:
void ChangeSpinBox(int sliderValue) {
if (convertSpinBoxValueToSlider(box_->value()) != sliderValue) {
box_->setValue((6000.0 * sliderValue / 127.0) - 3000.0);
}
}
void ChangeSlider(double spinBoxValue) {
slider_->setValue(convertSpinBoxValueToSlider(spinBoxValue));
}
private:
QSlider *slider_;
QDoubleSpinBox *box_;
static int convertSpinBoxValueToSlider(double value) {
return qRound((value + 3000.0) * 127.0 / 6000.0);
}
};
int main(int argc, char **argv) {
QApplication app(argc, argv);
MyWidget w;
w.show();
return app.exec();
}
#include "main.moc"
Or am I not understanding your question?
First of all, you have a bug in your vector computation. If you want to divide a range to X parts, then your vector must be of size X+1 not X. Example: you want to split range 0-10 to 5 parts. How many items you need? Your code suggests 5. But the answer is [0,2,4,6,8,10] = 6. So have vector 129 and do the division by 128.
So you would like to have a step size of 6000/128 = 48.875 exactly. You can do that with QDoubleSpinBox
QDoubleSpinBox::setRange(-3000.0,3000.0);
QDoubleSpinBox::setSingleStep(48.875);
but not with QSlider which takes only integer. You can avoid the rounding error by multiplying your range by 1000.
QSlider::setRange(-3000000,3000000);
QSlider::setSingleStep(48875);
For setting the selected result to the spinbox, you need to divide by 1000 again of course.

Need QGraphicsScene signal or event for _after_ change

I use QGraphicsScene of the Qt framework. Inside the scene I have some QGraphicsItems which the user can select and move.
I would like to have an info label where the current x and y coordinate of the currently moved selection (can consist of many items) is displayed.
I have tried with the signal changed of QGraphicsScene. But it is fired before the x() and y() property of the items is set to the new values. So the labels always show the second-to-last coordinates. If one moves the mouse slowly, the display is not very wrong. But with fast moves and sudden stops, the labels are wrong. I need a signal that is fired after the scene hast changed.
I have also tried to override the itemChange method of QGraphicsItem. But it is the same. It is fired before the change. (The new coordinates are inside the parameters of this method, but I need the new coordinates of all selected items at once)
I have also tried to override the mouseMove events of QGraphicsScene and of QGraphicsView but they, too, are before the new coordinates are set.
I did a test: I used a oneshot timer so that the labels are updated 100 ms after the signals. Then everything works fine. But a timer is no solution for me.
What can I do?
Make all items un-moveable and handle everything by my own?
QGraphicsItem::itemChange() is the correct approach, you were probably just checking the wrong flag. Something like this should work fine:
QVariant::myGraphicsItem( GraphicsItemChange change, const QVariant &value )
{
if( change == QGraphicsItem::ItemPositionHasChanged )
{
// ...
}
}
Note the use of QGraphicsItem::ItemPositionHasChanged rather than QGraphicsItem::ItemPositionChange, the former is called after the position changes rather than before.
The solution is to combine various things that you're already doing. Instrument itemChange, looking for and count the items with updated geometry. Once you've counted as many items as there are in the current selection, fire off a signal that will have everything ready for updating your status. Make sure you've set the QGraphicsItem::ItemSendsGeometryChanges flag on all your items!
This code was edited to remove the lag inherent in using a zero-timer approach. Below is a sscce that demonstrates it.
You create circles of random radius by clicking in the window. The selection is toggled with Ctrl-click or ⌘-click. When you move the items, a centroid diamond follows the centroid of the selected group. This gives a visual confirmation that the code does indeed work. When the selection is empty, the centroid is not displayed.
I've gratuitously added code to show how to leverage Qt's property system so that the items can be generic and leverage the notifier property of a scene if it has one. In its absence, the items simply don't notify, and that's it.
// https://github.com/KubaO/stackoverflown/tree/master/questions/scenemod-11232425
#include <QtWidgets>
const char kNotifier[] = "notifier";
class Notifier : public QObject
{
Q_OBJECT
int m_count = {};
public:
int count() const { return m_count; }
void inc() { m_count ++; }
void notify() { m_count = {}; emit notification(); }
Q_SIGNAL void notification();
};
typedef QPointer<Notifier> NotifierPointer;
Q_DECLARE_METATYPE(NotifierPointer)
template <typename T> class NotifyingItem : public T
{
protected:
QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override {
QVariant v;
if (change == T::ItemPositionHasChanged &&
this->scene() &&
(v=this->scene()->property(kNotifier)).isValid())
{
auto notifier = v.value<NotifierPointer>();
notifier->inc();
if (notifier->count() >= this->scene()->selectedItems().count()) {
notifier->notify();
}
}
return T::itemChange(change, value);
}
};
// Note that all you need to make Circle a notifying item is to derive from
// NotifyingItem<basetype>.
class Circle : public NotifyingItem<QGraphicsEllipseItem>
{
QBrush m_brush;
public:
Circle(const QPointF & c) : m_brush(Qt::lightGray) {
const qreal r = 10.0 + (50.0*qrand())/RAND_MAX;
setRect({-r, -r, 2.0*r, 2.0*r});
setPos(c);
setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable |
QGraphicsItem::ItemSendsGeometryChanges);
setPen({Qt::red});
setBrush(m_brush);
}
};
class View : public QGraphicsView
{
Q_OBJECT
QGraphicsScene scene;
QGraphicsSimpleTextItem text;
QGraphicsRectItem centroid{-5, -5, 10, 10};
Notifier notifier;
int deltaCounter = {};
public:
explicit View(QWidget *parent = {});
protected:
Q_SLOT void gotUpdates();
void mousePressEvent(QMouseEvent *event) override;
};
View::View(QWidget *parent) : QGraphicsView(parent)
{
centroid.hide();
centroid.setRotation(45.0);
centroid.setPen({Qt::blue});
centroid.setZValue(2);
scene.addItem(&centroid);
text.setPos(5, 470);
text.setZValue(1);
scene.addItem(&text);
setRenderHint(QPainter::Antialiasing);
setScene(&scene);
setSceneRect(0,0,500,500);
scene.setProperty(kNotifier, QVariant::fromValue(NotifierPointer(&notifier)));
connect(&notifier, &Notifier::notification, this, &View::gotUpdates);
connect(&scene, &QGraphicsScene::selectionChanged, &notifier, &Notifier::notification);
}
void View::gotUpdates()
{
if (scene.selectedItems().isEmpty()) {
centroid.hide();
return;
}
centroid.show();
QPointF centroid;
qreal area = {};
for (auto item : scene.selectedItems()) {
const QRectF r = item->boundingRect();
const qreal a = r.width() * r.height();
centroid += item->pos() * a;
area += a;
}
if (area > 0) centroid /= area;
auto st = QStringLiteral("delta #%1 with %2 items, centroid at %3, %4")
.arg(deltaCounter++).arg(scene.selectedItems().count())
.arg(centroid.x(), 0, 'f', 1).arg(centroid.y(), 0, 'f', 1);
this->centroid.setPos(centroid);
text.setText(st);
}
void View::mousePressEvent(QMouseEvent *event)
{
const auto center = mapToScene(event->pos());
if (! scene.itemAt(center, {})) scene.addItem(new Circle{center});
QGraphicsView::mousePressEvent(event);
}
int main(int argc, char *argv[])
{
QApplication app{argc, argv};
View v;
v.show();
return app.exec();
}
#include "main.moc"

Resources