Fetch text from unnamed QGraphicsTextItem - c++

A friend of mine and I are currently trying to make a game in C++ using Qt. Our current problem is that we need to fetch text from a QGraphicsTextItem on a button mousePressEvent. In the game menu it is possible to choose how many players there are, therefore we've placed a QGraphicsTextItem in a for-loop to make it possible for all the users to type in their names. Because of the for-loop, we don't have names for every single text item object so we can store the names. We've managed to store all the memory addresses to the objects using QMap, but we don't know how to get the text of of this. We don't even know if this is the best way to do it.
GameInfo.h
class GameInfo {
public:
GameInfo();
int players;
QStringList names = (QStringList() // Default player names. This array should be overwritten by the custom names
<< "Player 1"
<< "Player 2"
<< "Player 3"
<< "Player 4"
<< "Player 5"
<< "Player 6"
<< "Player 7");
QMap<int, QGraphicsTextItem**> textBoxMap; // This is where we store all the addresses
};
Game.cpp
QGraphicsRectItem * overviewBox = new QGraphicsRectItem();
overviewBox->setRect(0, 0, 782, 686);
scene->addItem(overviewBox);
int faceNo = 0;
// Create the player selection section
for(int i = 1; i <= players; i++) { // "players" is defined another place in the code, and is an integer between 1 and 6
Container * selContainer = new Container();
selContainer->Selection(i, faceNo);
selContainer->setPos(50, 70 + 110 * (i - 1));
scene->addItem(selContainer);
Container * ovContainer = new Container(overviewBox);
ovContainer->Overview(i, faceNo);
ovContainer->setPos(0, 0 + 110 * (i - 1));
info->textBoxMap.insert(i, &selContainer->textBox->playerText); // This is where we save the addresses
}
Selection.cpp
extern Game * game;
Container::Container(QGraphicsItem * parent): QGraphicsRectItem(parent) {
}
void Container::Selection(int nPlayers, int sPiceNo, QGraphicsItem *parent) {
QString numName = QString::number(nPlayers);
setRect(0, 0, 672, 110);
this->setPen(Qt::NoPen); // Removes border
int posY = this->rect().height() / 2;
QSignalMapper * signalMapper = new QSignalMapper(this);
arrowL = new Arrow(0, posY - 32, 0, this);
piece = new Piece(sPiceNo, 96, posY - 32, 1, 1, this);
arrowR = new Arrow(192, posY - 32, 1, this);
textBox = new TextBox(game->info->names[nPlayers - 1], true, this);
textBox->setPos(288, posY - 32);
lockBtn = new Button("Lock", 96, 32, this);
connect(lockBtn, SIGNAL(clicked()), signalMapper, SLOT(map()));
signalMapper->setMapping(lockBtn, nPlayers);
connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(lock(int)));
lockBtn->setPos(640, posY - 16);
}
void Container::Overview(int ovPlayers, int ovPiceNo, QGraphicsItem * parent) {
// Some code...
}
void Container::lock(int nPlayer) {
qDebug() << game->info->textBoxMap[nPlayer];
qDebug() << game->info->names[nPlayer - 1];
game->info->names[nPlayer - 1] = **game->info->textBoxMap[nPlayer].toPlainText(); // This line causes an error
}
The error that occurs because of the last line looks like this:
error: no match for 'operator=' (operand types are 'QString' and 'QGraphicsTextItem')
game->info->names[nPlayer - 1] = **game->info->textBoxMap[nPlayer];
^
TextBox.cpp
TextBox::TextBox(QString text, bool editable, QGraphicsItem * parent): QGraphicsRectItem(parent) {
this->editable = editable;
// Draw the textbox
setRect(0, 0, 320, 64);
if(!editable) {
this->setPen(Qt::NoPen); // Removes border
}
else if(editable) {
QBrush brush;
brush.setStyle(Qt::SolidPattern);
brush.setColor(QColor(255, 255, 255, 255));
setBrush(brush);
}
// Draw the text
playerText = new QGraphicsTextItem(text, this);
int fontId = QFontDatabase::addApplicationFont(":/fonts/built_titling_bd.ttf");
QString family = QFontDatabase::applicationFontFamilies(fontId).at(0);
QFont built(family, 25);
playerText->setFont(built);
int xPos = 0;
int yPos = rect().height() / 2 - playerText->boundingRect().height() / 2;
playerText->setPos(xPos,yPos);
}
My question is how do i fetch the text from the QGraphicsTextItem?

You should try to learn a bit more about C++ before trying to develop a game imo. (Having public variables is against OO's and C++ paradigm)
But here is what you are looking for:
http://doc.qt.io/qt-5/qgraphicstextitem.html#toPlainText
EDIT:
If you are not able to debug some line of code, I could only recommend to try to seperate your code in order to have a minimum of call in a single line. I haven't try the code bellow, but that how you should try to debug your code:
void Container::lock(int nPlayer)
{
qDebug() << game->info->textBoxMap[nPlayer];
qDebug() << game->info->names[nPlayer - 1];
QGraphicsTextItem **value = game->info->textBoxMap.value(nPlayer, nullptr);
game->info->names[nPlayer - 1] =(*value)->toPlainText();
}

Related

QScrollArea - Resize content widgets by keeping the aspect ratio

I have a layout that looks like this.
Where:
Blue: rectangle it's a ScrollArea
Orange: rectangles are the widgets from that ScrollArea
My code:
#include <QtWidgets>
///////////////////////////////////////////////////////////////////////////////////////
class RoundedPolygon : public QPolygon {
public:
RoundedPolygon() { SetRadius(10); }
void SetRadius(unsigned int iRadius) { m_iRadius = iRadius; }
const QPainterPath &GetPath() {
m_path = QPainterPath();
if (count() < 3) {
qDebug() << "!! Polygon should have at least 3 points !!";
return m_path;
}
QPointF pt1;
QPointF pt2;
for (int i = 0; i < count(); i++) {
pt1 = GetLineStart(i);
if (i == 0)
m_path.moveTo(pt1);
else
m_path.quadTo(at(i), pt1);
pt2 = GetLineEnd(i);
m_path.lineTo(pt2);
}
// close the last corner
pt1 = GetLineStart(0);
m_path.quadTo(at(0), pt1);
return m_path;
}
private:
QPointF GetLineStart(int i) const {
QPointF pt;
QPoint pt1 = at(i);
QPoint pt2 = at((i + 1) % count());
float fRat = m_iRadius / GetDistance(pt1, pt2);
if (fRat > 0.5f)
fRat = 0.5f;
pt.setX((1.0f - fRat) * pt1.x() + fRat * pt2.x());
pt.setY((1.0f - fRat) * pt1.y() + fRat * pt2.y());
return pt;
}
QPointF GetLineEnd(int i) const {
QPointF pt;
QPoint pt1 = at(i);
QPoint pt2 = at((i + 1) % count());
float fRat = m_iRadius / GetDistance(pt1, pt2);
if (fRat > 0.5f)
fRat = 0.5f;
pt.setX(fRat * pt1.x() + (1.0f - fRat) * pt2.x());
pt.setY(fRat * pt1.y() + (1.0f - fRat) * pt2.y());
return pt;
}
float GetDistance(QPoint pt1, QPoint pt2) const {
int fD = (pt1.x() - pt2.x()) * (pt1.x() - pt2.x()) + (pt1.y() - pt2.y()) * (pt1.y() - pt2.y());
return sqrtf(fD);
}
private:
QPainterPath m_path;
unsigned int m_iRadius{};
};
class PolygonButtonWidget : public QWidget {
Q_OBJECT
public:
explicit PolygonButtonWidget(QWidget *parent = nullptr) : QWidget(parent) {}
~PolygonButtonWidget() override = default;
protected:
void resizeEvent(QResizeEvent *event) override {
float ratioW = 8;
float ratioH = 3;
// ui->scrollAreaWidgetContents->setFixedSize(5000, h);
float thisAspectRatio = (float) event->size().width() / event->size().height();
if (thisAspectRatio < ratioW / ratioH) {
float w = event->size().height() * ratioW / ratioH;
float h = event->size().height();
qDebug() << hasHeightForWidth() << " " << w << " " << h;
this->resize(w, h);
if (m_nrButtons != 0) {
this->move((w + 20) * m_nrButtons, this->y());
}
}
QWidget::resizeEvent(event);
}
int m_nrButtons{};
public:
void setMNrButtons(int mNrButtons) {
m_nrButtons = mNrButtons;
}
protected:
void paintEvent(QPaintEvent *event) override {
int offset = 50;
m_polygon.clear();
m_polygon.emplace_back(0, height()); //DOWN-LEFT
m_polygon.emplace_back(width() - offset, height()); //DOWN-RIGHT
m_polygon.emplace_back(width(), 0); //TOP-RIGHT
m_polygon.emplace_back(0 + offset, 0);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
RoundedPolygon poly;
poly.SetRadius(15);
for (QPoint point: m_polygon) {
poly << point;
}
QBrush fillBrush;
fillBrush.setColor(Qt::darkBlue);
fillBrush.setStyle(Qt::SolidPattern);
QPainterPath path;
path.addPath(poly.GetPath());
painter.fillPath(path, fillBrush);
}
void mousePressEvent(QMouseEvent *event) override {
auto cursorPos = mapFromGlobal(QCursor::pos());
qDebug() << "X: " << cursorPos.x() << " Y: " << cursorPos.y();
inside(cursorPos, m_polygon);
qDebug() << "Pressed";
}
private:
std::vector<QPoint> m_polygon;
bool inside(QPoint point, std::vector<QPoint> polygon) {
auto x = point.x();
auto y = point.y();
auto inside = false;
auto i = 0;
auto j = polygon.size() - 1;
while (i < polygon.size()) {
auto xi = polygon[i].x();
auto yi = polygon[i].y();
auto xj = polygon[j].x();
auto yj = polygon[j].y();
auto intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
j = i++;
}
qDebug() << inside;
return inside;
}
};
///////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget root;
QHBoxLayout layout{&root};
for (int i = 0; i < 10; ++i) {
auto p = new PolygonButtonWidget();
p->setMinimumSize(100, 100);
p->setMNrButtons(i);
layout.addWidget(p);
}
root.setStyleSheet("background-color: rgb(19,19,19);");
QScrollArea view;
view.setWidget(&root);
view.show();
app.exec();
}
#include "main.moc"
The problem arises when I'm trying to resize the window. In the moment of resizing, I want my widgets to keep their aspect ratio. But that's not going to happen.
I have scroll list of widgets which is looking like this (if it's expended on X way too much)
If I will scale it on Y-axis it's going to look like this.
After I've changed the resizeEvent now it's going to look something like this
or like this
How can I fix this? For some reason, some of my widgets are going to disappear, what should be my approach in order to fix this issue?
The problem is caused by the assumption that there's any mechanism that will automatically resize the widgets for you. There isn't. A QScrollArea acts as a layout barrier and any layouts inside of it are isolated from its size, and thus from any resize events.
You must resize the container widget (the one with blue outline on your diagram) yourself anytime the scroll area changes size, and you need first to prepare a test case for the widgets such that their size changes are properly managed when placed in the layout of your choice, and said layout is resized.
Finally, the pet peeve of mine: It's unlikely that you actually need the QMainWindow for anything. It's just a silly Qt Creator template. But unless you want an MDI interface and docking, you shouldn't be using the QMainWindow - and especially not when making a self-contained example. All you need here is QScrollArea as a top-level widget. That's literally all. Any QWidget can be a top-level window!
For future submissions, please provide all the code needed in a single main.cpp file that begins with #include <QtWidgets> and ends with #include "main.moc". You won't need any other includes for Qt classes, and you can write class definitions Java-style, with all the methods defined within the class declaration itself. This provides for short code - after all, a SO question isn't an Enterprise project. It's supposed to be minimal, and that really means that anything not necessary must be removed. No need for header files, multiple includes, nor other fluff - i.e. use Qt containers instead of C++ STL so that you don't need more includes etc.
Your example should look roughly as follows:
#include <QtWidgets>
class PolygonButtonWidget : public QAbstractButton {
Q_OBJECT
/* without seeing the code here, your question is unanswerable */
};
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
QWidget root;
QHBoxLayout layout{&root};
PolygonButtonWidget buttons[10];
for (auto &button : buttons)
layout.addWidget(&button);
QScrollArea view;
view.setWidget(&root);
view.show();
app.exec();
view.takeWidget();
}
#include "main.moc"
Without such an example, your question is hard to answer, since:
How can we debug it? Debugging means using a debugger. If your code cannot be immediately compiled, then it's quite unlikely that someone will bother debugging it, and debugging by inspection is often error-prone.
How can we provide a tested answer if we'd have to first write the entire "test case" for it?
How can we know what's inside your button widget? The behavior of that widget does affect the ultimate solution.
It'd also help if you described a few use cases that you'd expect to work. That is, mock up (with a drawing) the state of the widgets before and after the view is resized, so that we can easily see what it is that you expect to happen. A lot of it is very easy to miss when explaining your needs in words. Use cases are a lingua franca of software specifications. If you don't use them, it's highly likely that you yourself don't know what behavior you expect in all cases.

QGroupBox's child restricts shrink the form

I have a multiple screen video player, and I just want to keep 16:9 ratio. There is a qgroupbox as a container of a qwidget which plays video in it. I also use qgroupbox to show selected frame by painting border to green. I can't do this on qwidget because rendered video overlaps that. When I have done with resize, I emit a signal with mouseup event to be able to informed about the resize operation completed. Then I calculate new bounds for qwidget to keep 16:9 ratio and apply this values for qwidget. Here is the image to show you how my app looks like:
And here is the code that I use to resize qwidgets:
void playBack::OnWindowResized()
{
float ratio = 16.0f / 9.0f;
float w = playBackplayer_contList.at(0)->size().width(); //qgroupbox's width
float h = playBackplayer_contList.at(0)->size().height();//qgroupbox's height
float currentRatio = w / h;
float newW = 0;
float newH = 0;
if (currentRatio > ratio)
{
newH = h;
newW = h*ratio;
}
else if (currentRatio < ratio)
{
newW = w;
newH = w / ratio;
}
qDebug() << "NEW WIDGET SIZE: " << (int)newW << " x " << (int)newH;
for (int i = 0; i < playBackplayer_widgtList.count(); i++)
{
playBackplayer_widgtList.at(i)->setMinimumSize(newW, newH);
//playBackplayer_widgtList.at(i)->resize(newW, newH);
}
}
This code works perfectly when I enlarge form, but When I want to shrink, It doesn't allow me to do that. Because I set a minimum value for qwidgets. If I don't use setMinimumSize, use resize(w,h) instead, than orientation problems occur. And here is a example for this issue:
This code below shows ctor and this is where I set the layout:
playBack::playBack()
{
playback_player_1_widget = new QWidget;
playback_player_2_widget = new QWidget;
playback_player_3_widget = new QWidget;
playback_player_4_widget = new QWidget;
playback_player_1_widget_cont = new QGroupBox;
playback_player_2_widget_cont = new QGroupBox;
playback_player_3_widget_cont = new QGroupBox;
playback_player_4_widget_cont = new QGroupBox;
playBackplayer_widgtList.append(playback_player_1_widget);
playBackplayer_widgtList.append(playback_player_2_widget);
playBackplayer_widgtList.append(playback_player_3_widget);
playBackplayer_widgtList.append(playback_player_4_widget);
playBackplayer_contList.append(playback_player_1_widget_cont);
playBackplayer_contList.append(playback_player_2_widget_cont);
playBackplayer_contList.append(playback_player_3_widget_cont);
playBackplayer_contList.append(playback_player_4_widget_cont);
int rowcnt = 0;
int colcnt = 0;
for (int i = 0; i < 4; i++)
{
playBackplayer_contList.at(i)->setStyleSheet(QString("border:1px solid #000;background-color:#000;"));
playBackplayer_widgtList.at(i)->setStyleSheet(QString("background-color:#f00;"));
QGridLayout* layout = new QGridLayout;
layout->setRowStretch(0, 1);
layout->setColumnStretch(0, 1);
layout->setRowStretch(2, 1);
layout->setColumnStretch(2, 1);
playBackplayer_widgtList.at(i)->setMinimumWidth(100);
playBackplayer_widgtList.at(i)->setMinimumHeight(100);
playBackplayer_widgtList.at(i)->setMaximumWidth(10000);
playBackplayer_widgtList.at(i)->setMaximumHeight(10000);
layout->addWidget(playBackplayer_widgtList.at(i),1,1);
layout->setMargin(0);
layout->setSpacing(0);
playBackplayer_contList.at(i)->setLayout(layout);
mainLayout->addWidget(playBackplayer_contList.at(i), colcnt, rowcnt);
rowcnt++;
if (rowcnt % 2 == 0)
{
rowcnt = 0;
colcnt++;
}
playBackplayer_widgtList.at(i)->setAcceptDrops(true);
}
}
I have tried various things, I have tried to set size 0 for qwidget before resize, (in mousedownevent) that didn't work, I have tried deleting layout for qgroupbox, after resize happens, create new layout and set it for groupbox, that didn't work, I have tried layout()->adjustSize(), update(), repaint(), all that stuff didn't work. What am I missing? I need helps from you. Any help would be appreciated. Thank you in advance.
Do away with the grid layout inside the container group boxes. Instead, align and resize the video widget with setGeometry
Here is a simple subclass of QGroupBox I made that keeps your desired ratio and always stays in the center:
class RatioGroupBox : public QGroupBox{
Q_OBJECT
public:
RatioGroupBox(QWidget *parent = nullptr) : QGroupBox (parent){
setFlat(true);
setStyleSheet("border:1px solid #000;background-color:#000;");
setMinimumSize(100, 100);
setMaximumSize(10000, 10000);
ratio = 16.0f/9.0f;
ratioWidget = new QWidget(this);
ratioWidget->setStyleSheet("background: #f00;");
ratioWidget->setAcceptDrops(true);
}
protected:
void resizeEvent(QResizeEvent *){//or you can use your own resize slot
float w = width();
float h = height();
float currentRatio = w/h;
float newW(0);
float newH(0);
if (currentRatio > ratio){
newH = h;
newW = h*ratio;
}
else if (currentRatio < ratio){
newW = w;
newH = w / ratio;
}
ratioWidget->setGeometry((w-newW)/2, (h-newH)/2, newW, newH);
}
private:
QWidget *ratioWidget;
float ratio;
};
Your entire ctor will become something like:
playBack::playBack()
{
for(int r=0; r<2; r++){
for(int c=0; c<2; c++){
RatioGroupBox* playback_player_cont = new RatioGroupBox;
mainLayout->addWidget(playback_player_cont, c, r);
playBackplayer_contList.append(playback_player_cont);
}
}
}
You can of course access your video widgets by exposing ratioWidget any way you like. Either by making it public or creating a getter function.

Draw waveform from raw data using QAudioProbe

For some strange reason QAudioRecorder::audioInputs() returns twice as much devices that I actually have
They're seem to be duplicated but not really - looks like they giving different samples, because when I'm trying to play recorded audio from first two devices it sounds twice as fast, when second two devices sounds normally.
Heres my code:
#include "MicrophoneWidget.h"
#include <QLayout>
#include <sndfile.h>
MicrophoneWidget::MicrophoneWidget(QWidget *parent) : QWidget(parent)
{
QAudioEncoderSettings settings;
settings.setCodec("audio/PCM");
settings.setQuality(QMultimedia::HighQuality);
settings.setChannelCount(1);
recorder = new QAudioRecorder(this);
recorder->setEncodingSettings(settings);
button = new QPushButton();
button->setCheckable(true);
devicesBox = new QComboBox();
connect(devicesBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onDeviceChanged(int)));
for(const QString& device : recorder->audioInputs()) devicesBox->addItem(device, QVariant(device));
label = new QLabel();
connect(button, SIGNAL(toggled(bool)), this, SLOT(onButtonToggled(bool)));
QVBoxLayout* layout = new QVBoxLayout();
layout->addWidget(devicesBox);
layout->addWidget(button);
layout->addWidget(label);
setLayout(layout);
probe = new QAudioProbe();
probe->setSource(recorder);
connect(probe, SIGNAL(audioBufferProbed(QAudioBuffer)), this, SLOT(onAudioBufferProbed(QAudioBuffer)));
}
void MicrophoneWidget::resizeEvent(QResizeEvent*)
{
pixmap = QPixmap(label->size());
}
void MicrophoneWidget::onAudioBufferProbed(QAudioBuffer buffer)
{
qDebug() << buffer.byteCount() / buffer.sampleCount();
const qint32 *data = buffer.constData<qint32>();
pixmap.fill(Qt::transparent);
painter.begin(&pixmap);
int count = buffer.sampleCount() / 2;
float xScale = (float)label->width() / count;
float center = (float)label->height() / 2;
for(int i = 0; i < count; i++) samples.push_back(data[i]);
for(int i = 1; i < count; i++)
{
painter.drawLine(
(i - 1) * xScale,
center + ((float)(data[i-1]) / INT_MAX * center),
i * xScale,
center + ((float)(data[i]) / INT_MAX * center)
);
}
painter.end();
label->setPixmap(pixmap);
}
void MicrophoneWidget::onButtonToggled(bool toggled)
{
if(toggled)
{
samples.clear();
recorder->record();
}
else
{
recorder->stop();
SF_INFO sndFileInfo;
sndFileInfo.channels = 1;
sndFileInfo.samplerate = 44100;
sndFileInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_32;
QString filePath = "customWAV-" + QString::number(QDateTime::currentMSecsSinceEpoch()) + ".wav";
SNDFILE* sndFile = sf_open(filePath.toStdString().c_str(), SFM_WRITE, &sndFileInfo);
if(sndFile != nullptr)
{
sf_count_t count = sf_write_int(sndFile, samples.data(), samples.size());
qDebug() << "Written " << count << " items; " << (samples.size() / sndFileInfo.samplerate) << " seconds";
}
sf_close(sndFile);
}
}
void MicrophoneWidget::onDeviceChanged(int index)
{
recorder->stop();
recorder->setAudioInput(devicesBox->itemData(index).toString());
if(button->isChecked())recorder->record();
}
So, how should I parse the raw data to draw correct waveform?
Firs of all check that the buffer hast exactly the sample type that you expect, to do it, check the QAudioFormat sampleType function. There are 3 alternatives:
QAudioFormat::SignedInt,
QAudioFormat::UnSignedInt,
QAudioFormat::Float
This should help you to decide the correct cast for the given samples. In my case, as the different Qt examples, I use:
const qint16 *data = buffer.data<qint16>();
And them you can normalise it easily using this function:
qreal getPeakValue(const QAudioFormat& format)
{
// Note: Only the most common sample formats are supported
if (!format.isValid())
return qreal(0);
if (format.codec() != "audio/pcm")
return qreal(0);
switch (format.sampleType()) {
case QAudioFormat::Unknown:
break;
case QAudioFormat::Float:
if (format.sampleSize() != 32) // other sample formats are not supported
return qreal(0);
return qreal(1.00003);
case QAudioFormat::SignedInt:
if (format.sampleSize() == 32)
#ifdef Q_OS_WIN
return qreal(INT_MAX);
#endif
#ifdef Q_OS_UNIX
return qreal(SHRT_MAX);
#endif
if (format.sampleSize() == 16)
return qreal(SHRT_MAX);
if (format.sampleSize() == 8)
return qreal(CHAR_MAX);
break;
case QAudioFormat::UnSignedInt:
if (format.sampleSize() == 32)
return qreal(UINT_MAX);
if (format.sampleSize() == 16)
return qreal(USHRT_MAX);
if (format.sampleSize() == 8)
return qreal(UCHAR_MAX);
break;
}
return qreal(0);
}
Now you should iterate over the vector and divide by the peak that the functions returns, this will give a range of samples from [-1, 1], so save it in a QVector array to plot it.
To plot it, you have different alternatives, Qt introduce his own QtCharts module but you can still use QCustomPlot or Qwt. An example with QCustomPlot:
QCPGraph myPlot = ui->chart->addGraph();
myPlot->setData(xAxys.data(), recorded.data()); // init an X vector from 0 to the size of Y or whatever you want
ui->chart->yAxis->setRange(QCPRange(-1,1)); // set the range
ui->chart->replot();
You can find a complete example in the Qt examples or check my little GitHub project, LogoSpeech Studio, you will find complete example of how to plot the wave form, spectrogram, pitch and different properties of a signal.

QtCharts - Background, foreground display

I want to display a QGraphicsRectItem in my QChartView. But the rectangle is displayed behind the lines series in the chart.
I've tried to do a setZValue(10), for example, on my QGraphicsRectItem and setZValue(0) on my QChart but it is still displayed behind.
Obviously I want the informations in the rectangle to be displayed in front of the series of the chart.
Constructor
StatisticsChartView::StatisticsChartView(QWidget *parent, QChart *chart)
: QChartView(chart, parent)
{
/* Create new chart */
_chart = new QChart();
chart = _chart;
_chart->setAnimationOptions(QChart::AllAnimations);
/* Default granularity */
m_iGranularity = DEFAULT_GRANULARITY;
/* Creating ellipse item which will display a circle when the mouse goes over the series */
m_ellipse = new QGraphicsEllipseItem(_chart);
penEllipse.setColor(QColor(0, 0, 0));
penBorder.setWidth(1);
m_ellipse->setPen(penEllipse);
/* Creating text item which will display the x and y value of the mouse position */
m_coordX = new QGraphicsSimpleTextItem(_chart);
m_coordY = new QGraphicsSimpleTextItem(_chart);
penBorder.setColor(QColor(0, 0, 0));
penBorder.setWidth(1);
m_coordX->setPen(penBorder);
m_coordY->setPen(penBorder);
m_rectHovered = new QGraphicsRectItem(_chart);
m_rectHovered->setBrush(QBrush(Qt::yellow));
m_coordHoveredX = new QGraphicsSimpleTextItem(m_rectHovered);
m_coordHoveredY = new QGraphicsSimpleTextItem(m_rectHovered);
penBorder.setColor(QColor(0, 0, 0));
penBorder.setWidth(1);
m_coordHoveredX->setPen(penBorder);
m_coordHoveredY->setPen(penBorder);
m_lineItemX = new QGraphicsLineItem(_chart);
m_lineItemY = new QGraphicsLineItem(_chart);
penLine.setColor(QColor(0, 0, 0));
penLine.setStyle(Qt::DotLine);
m_lineItemX->setPen(penLine);
m_lineItemY->setPen(penLine);
/* Enable zooming in the rectangle drawn with the left click of the mouse, zoom out with right click */
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
mousePressed = 0;
seriesHovered = false;
setMouseTracking(true);
_chart->setAcceptHoverEvents(true);
_chart->setZValue(50);
m_ellipse->setZValue(10); //so it is displayed over the series
m_coordHoveredX->setZValue(20); //so it is displayed over others
setRenderHint(QPainter::Antialiasing);
setChart(_chart);
}
Creation of series
void StatisticsChartView::drawCurve(bool bDrawScale)
{
int w = WIDTH;
int h = HEIGHT;
/* Creating series */
QLineSeries *lineFalse = new QLineSeries();
QLineSeries *lineAutomatic = new QLineSeries();
QLineSeries *lineOk = new QLineSeries();
QLineSeries *lineFalsePositive = new QLineSeries();
QLineSeries *lineManualTreatement = new QLineSeries();
QLineSeries *lineFalseNegative = new QLineSeries();
QList<QLineSeries*> lineSeriesList;
lineSeriesList << lineFalse << lineAutomatic << lineOk << lineFalsePositive << lineManualTreatement << lineFalseNegative;
QList<QString> nameSeriesList;
nameSeriesList << "False" << "Automatic" << "Ok" << "FalsePositive" << "ManualTreatement" << "FalseNegative";
QList<QVector<GraphPoint>> graphPointList;
graphPointList << gpFalse << gpDetected << gpOk << gpDetectedNotOk << gpManualTreatement << gpFalseNegative;
double graphX = 100.0 / (m_iGranularity);
bool pointsVisible = true;
for (int n = 0; n < lineSeriesList.count(); ++n)
{
/* Adding points to line series */
for (int i = 0; i < m_iGranularity + 1; ++i)
{
lineSeriesList[n]->append(i * graphX, (float)(graphPointList[n][i]).fValue * 100);
lineSeriesList[n]->setPointsVisible(pointsVisible);
lineSeriesList[n]->setName(nameSeriesList[n]);
}
}
_chart->legend()->setVisible(true);
_chart->legend()->setAlignment(Qt::AlignBottom);
/* Setting axis X and Y */
axisX = new QValueAxis();
axisY = new QValueAxis();
axisX->setRange(0, 100);
axisY->setRange(0, 100);
/* Adding line series to the chart and attaching them to the same axis */
for (int j = 0; j < lineSeriesList.count(); ++j)
{
_chart->addSeries(lineSeriesList[j]);
_chart->setAxisX(axisX, lineSeriesList[j]);
_chart->setAxisY(axisY, lineSeriesList[j]);
connect(lineSeriesList[j], SIGNAL(hovered(QPointF, bool)), this, SLOT(onSeriesHovered(QPointF, bool)));
}
_chart->resize(w, h);
return;
}
Drawing rectangle on chart
void StatisticsChartView::onSeriesHovered(QPointF point, bool state)
{
seriesHovered = state;
/* Updating the size of the rectangle */
if (mousePressed == 0 && seriesHovered == true)
{
/* x and y position on the graph */
qreal x = _chart->mapToPosition(point).x();
qreal y = _chart->mapToPosition(point).y();
/* x and y value on the graph from 0 to 100 for ou graph */
qreal xVal = point.x();
qreal yVal = point.y();
qreal maxX = axisX->max();
qreal minX = axisX->min();
qreal maxY = axisY->max();
qreal minY = axisY->min();
/* We don't want to display value outside of the axis range */
if (xVal <= maxX && xVal >= minX && yVal <= maxY && yVal >= minY)
{
m_coordHoveredX->setVisible(true);
m_coordHoveredY->setVisible(true);
m_rectHovered->setVisible(true);
m_ellipse->setVisible(true);
m_rectHovered->setRect(x - 31, y - 31, 30, 30);
qreal rectX = m_rectHovered->rect().x();
qreal rectY = m_rectHovered->rect().y();
qreal rectW = m_rectHovered->rect().width();
qreal rectH = m_rectHovered->rect().height();
/* We're setting the labels and nicely adjusting to chart axis labels (adjusting so the dot lines are centered on the label) */
m_coordHoveredX->setPos(rectX + rectW / 4 - 3, rectY + 1);
m_coordHoveredY->setPos(rectX + rectW / 4 - 3, rectY + rectH / 2 + 1);
/* Setting value to displayed with four digit max, float, 1 decimal */
m_coordHoveredX->setText(QString("%1").arg(xVal, 4, 'f', 1, '0'));
m_coordHoveredY->setText(QString("%1").arg(yVal, 4, 'f', 1, '0'));
m_ellipse->setRect(QRectF::QRectF(x, y, 10, 10));
m_ellipse->setPos(x, y);
m_ellipse->setBrush(QBrush(Qt::red));
}
else
{
/* We're not displaying information if out of the chart */
m_coordHoveredX->setVisible(false);
m_coordHoveredY->setVisible(false);
m_rectHovered->setVisible(false);
m_ellipse->setVisible(false);
}
}
else
{
/* We're not displaying information if series aren't hovered */
m_coordHoveredX->setVisible(false);
m_coordHoveredY->setVisible(false);
m_rectHovered->setVisible(false);
m_ellipse->setVisible(false);
}
}
You should try using a series especially for your rectangle.
Setting it as the last series, on your chart, to be above the other lines. And adding a legend or a callout for the text.

Correctly implementing a custom QWidget in Qt

I'm completely new to Qt/GUI programming and I'm trying to create the UI for a simple Tic Tac Toe game. I created two custom QWidget classes. My main window class (GameWindow) extends the base QWidget, and my other class XOSpace extends QFrame. XOSpace serves two purposes: dividing up the board into spaces (each one will have a HLine or VLine shape), and being a starting point for drawing X's and O's in the correct spot on the board (as soon as I can figure out how to use Qt painters and paint events). My problem is that when I add the XOSpaces to GameWindow they don't display. But when I added QFrame objects from the base class as a test, they displayed fine. How do I extend QFrame (or any widget class), and still make sure it will function the same as the base classes in Qt? Are there any functions I need to reimplement? Anything else?
class XOSpace : public QFrame {
Q_OBJECT
private:
XO xo ; //enum representing whether this space holds an X, O, or blank
public:
explicit XOSpace(QWidget *parent = 0) ;
explicit XOSpace(QWidget *parent, int size, QFrame::Shape) ;
~XOSpace();
void setXO(XO) ;
};
XOSpace::XOSpace(QWidget *parent) : QFrame(parent) {
this->xo = XO::blank ;
this->setGeometry(QRect());
this->setFrameShape(QFrame::HLine) ;
this->setFrameShadow(QFrame::Sunken) ;
this->setMinimumWidth(96) ;
this->setLineWidth(1) ;
this->show();
}
XOSpace::XOSpace(QWidget* parent, int size, QFrame::Shape shape) : QFrame(parent) {
this->xo = XO::blank ;
this->setGeometry(QRect());
this->setFrameShape(shape);
this->setFrameShadow(QFrame::Sunken);
this->setMinimumWidth(size) ;
this->setLineWidth(1) ;
this->show() ;
}
QSize XOSpace::sizeHint() const {
return this->size();
}
void XOSpace::setXO(XO xo) {
this->xo = xo ;
}
XOSpace::~XOSpace() {
;
}
namespace Ui {
class GameWindow;
}
class GameWindow : public QWidget {
Q_OBJECT
private:
Ui::GameWindow *ui ;
//these don't display:
vector<XOSpace*>* hSpaces ;
//these do:
QFrame* vLineOne ;
/* declare 7 more
like this */
public:
explicit GameWindow(QWidget *parent = 0);
~GameWindow();
friend class XOSpace ;
};
GameWindow::GameWindow(QWidget *parent) : QWidget(parent),
ui(new Ui::GameWindow) {
ui->setupUi(this);
this->setWindowTitle("Hello world!");
QGridLayout *mainLayout = new QGridLayout() ;
mainLayout->setColumnMinimumWidth(0, 25);
mainLayout->setColumnMinimumWidth(6, 25);
this->setLayout(mainLayout) ;
QPushButton* button = new QPushButton("Play") ;
button->setFixedWidth(100);
mainLayout->addWidget(button, 2, 3) ;
QGridLayout* secondaryLayout = new QGridLayout() ;
mainLayout->addLayout(secondaryLayout, 1, 1, 1, 5);
QGroupBox* gBox = new QGroupBox() ;
secondaryLayout->addWidget(gBox, 0, 0);
QGridLayout* boardLayout = new QGridLayout() ;
gBox->setLayout(boardLayout);
hSpaces = new vector<XOSpace*>() ;
vLineOne = new QFrame() ;
vLineOne->setGeometry(QRect());
vLineOne->setFrameShape(QFrame::VLine);
vLineOne->setFrameShadow(QFrame::Sunken);
vLineOne->setMinimumHeight(96) ;
/*repeat for vLines 2-4
*/
vLineFive = new QFrame() ;
vLineFive->setGeometry(QRect());
vLineFive->setFrameShape(QFrame::VLine);
vLineFive->setFrameShadow(QFrame::Sunken);
vLineFive->setMinimumHeight(48);
/*repeat for vLines 6-8
*/
for(vector<XOSpace*>::size_type i = 0 ; i < 6 ; i++) {
hSpaces->push_back(new XOSpace(this, 96,
QFrame::HLine));
}
//horizontal spaces: (don’t display properly)
boardLayout->addWidget(hSpaces->at(0), 0, 0,
Qt::AlignBottom);
boardLayout->addWidget(hSpaces->at(1), 0, 2,
Qt::AlignBottom);
boardLayout->addWidget(hSpaces->at(2), 0, 4,
Qt::AlignBottom);
boardLayout->addWidget(hSpaces->at(3), 3, 0, Qt::AlignTop);
boardLayout->addWidget(hSpaces->at(4), 3, 2, Qt::AlignTop);
boardLayout->addWidget(hSpaces->at(5), 3, 4, Qt::AlignTop);
//vertical spaces: (display OK)
boardLayout->addWidget(vLineOne, 0, 1) ;
boardLayout->addWidget(vLineFive, 1, 1) ;
boardLayout->addWidget(vLineTwo, 0, 3) ;
boardLayout->addWidget(vLineSix, 1, 3) ;
boardLayout->addWidget(vLineThree, 2, 1) ;
boardLayout->addWidget(vLineSeven, 3, 1) ;
boardLayout->addWidget(vLineFour, 2, 3) ;
boardLayout->addWidget(vLineEight, 3, 3) ;
mainLayout->setRowStretch(0, 1);
//set rows and columns stretch
mainLayout->setVerticalSpacing(0) ;
//set spacing etc.
}
GameWindow::~GameWindow() {
delete ui;
if (hSpaces != nullptr) {
for(vector<XOSpace*>::size_type i = 0 ; i < hSpaces->size() ; i++) {
if (hSpaces->at(i) != nullptr) {
delete hSpaces->at(i) ;
}
}
delete hSpaces ;
}
}
The XOSpaces are supposed to be drawn as horizontal line segments that will make up the two horizontal lines on a tic tac toe board. Here's what my application looks like now:
Here is a lot of little pieces of advice. The first GUI or two that you make in Qt will probably be pretty hard to do. Using layouts, like you have started doing will help quite a bit.
So here are the first couple recommendations I would give:
Don't call show in your xospace constructors. Adding them to a layout, makes it the parent's job to show it. So in your main when you call gameWindow->show(); it handles all the showing of nested elements.
QLabel is a subclass of QFrame. Change the elements in question to be of type QLabel and then add in setText("X") to their constructor or somewhere to make sure you can see your elements.
If you aren't using a the UI form, I would leave it out.
Based on your game layout, this is what your variables look like:
// vL1 vL2
// hs0 vL6 hs1 vL5 hs2
// vL3 vL4
// hs3 vL7 hs4 vL8 hs5
// ??? ???
Instead of relying on the size of these vertical and horizontal lines for your stretching and size constraints, why not use the elements that will sit on the board, like in spots marked with an X below:
// X vL1 X vL2 X
// hs0 vL6 hs1 vL5 hs2
// X vL3 X vL4 X
// hs3 vL7 hs4 vL8 hs5
// X ??? X ??? X
This would allow you to take two and span them across the grid.
boardLayout->addWidget(vLines.at(0), 0, 1, 5, 1) ;
boardLayout->addWidget(vLines.at(1), 0, 3, 5, 1) ;
mainLayout->setRowStretch(1, 1);
Then like I hint above, you could use a QList or a QVector to remove so much copy and paste code.
vLines.append(new QFrame);
vLines.append(new QFrame);
foreach(QFrame * f, vLines)
{
//f->setGeometry(QRect());
f->setFrameShape(QFrame::VLine);
f->setFrameShadow(QFrame::Sunken);
f->setMinimumHeight(96);
}
Also setGeometry() is useful if you aren't using layouts. And setting the geometry to QRect(), is probably equivalent to the default constructor it has.
And when you start putting object trees together, you don't have to worry as much about how they clean up:
http://qt-project.org/doc/qt-4.8/objecttrees.html
EDIT:
Here is how I would layout the board:
QGridLayout * board = new QGridLayout();
for(int r = 0; r < 5; r++)
{
for(int c = 0; c < 5; c++)
{
if(r % 2 == 1)
{
if(c % 2 == 1)
{
// is an intersection
// leave it blank?
// or add a box?
}
else
{
// is a horizontal line
QFrame * f = new QFrame();
f->setFrameShape(QFrame::HLine);
f->setFrameShadow(QFrame::Sunken);
f->setMinimumWidth(96);
board->addWidget(f,r, c);
}
}
else
{
if(c % 2 == 1)
{
// is a vertical line
QFrame * f = new QFrame();
f->setFrameShape(QFrame::VLine);
f->setFrameShadow(QFrame::Sunken);
f->setMinimumHeight(96);
board->addWidget(f, r, c);
}
else
{
// is an XO location
board->addWidget(new QLabel(), r, c, Qt::AlignCenter);
}
}
}
}
setLayout(board);
Then if you just let QGridLayout manage your item's location and access, you do something like this:
void GameWindow::setXO(QString val, int r, int c)
{
// upper left xo location is 0,0
// lower right xo location is 2,2
// we map to skip the frame locations
if(r > 2 || r < 0 || c < 0 || c > 2)
{
qDebug() << "Error in setXO" << r << c;
return;
}
QLabel * xo = qobject_cast<QLabel*>(board->itemAtPosition(r*2, c*2)->widget());
if(xo != 0)
{
xo->setText(val);
}
}
Hope that helps.