Correctly implementing a custom QWidget in Qt - c++

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.

Related

Qt5 QPushButton's can't be clicked (?!)

I have an inherited widget game_widget in which I declared 9 QPushButton's that are stored in an array via a method init_ui and a layout widget on which the buttons are supposed to be placed. There is also init_ui function that is called in the constructor. Here are the main elements of the class:
class game_widget : public QWidget
{
Q_OBJECT
public:
// The layout widget for the buttons
QWidget* gridLayoutWidget = new QWidget(this);
QPushButton** fields; // Fields list
QPushButton* field1 = new QPushButton(gridLayoutWidget);
...
QPushButton* field9 = new QPushButton(gridLayoutWidget);
...
private:
void init_ui();
};
Here is init_ui:
void game_widget::init_ui()
{
fields = new QPushButton* [9]; // Fields list
fields[0] = field1;
...
fields[8] = field9;
...
// Preparing layout for the buttons
gridLayoutWidget->setGeometry(QRect(10, 10, 531, 531));
QGridLayout* grid_layout = new QGridLayout(gridLayoutWidget);
// Adding each field to the layout
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
{
fields[i * 3 + j]->setMaximumSize(QSize(170, 170));
fields[i * 3 + j]->setMinimumSize(QSize(170, 170));
grid_layout->addWidget(fields[i * 3 + j], i, j);
}
}
Now the thing is that those buttons are not even clickable - not to mention that hovering over them doesn't do anything with them as well, there is no animation. Nothing else about them was changed, so their behavior should be normal, but it isn't. If You have the slightest idea what might be going on, please help.
You are creating 9 extra QPushButtons in void game_widget::init_ui(), try the following:
void game_widget::init_ui()
{
QVector <QPushButton*> fields; // Fields list
fields[0] << field1;
...
fields[8] << field9;
...
// Preparing layout for the buttons
gridLayoutWidget->setGeometry(QRect(10, 10, 531, 531));
QGridLayout* grid_layout = new QGridLayout(gridLayoutWidget);
// Adding each field to the layout
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
{
fields[i * 3 + j]->setMaximumSize(QSize(170, 170));
fields[i * 3 + j]->setMinimumSize(QSize(170, 170));
grid_layout->addWidget(fields[i * 3 + j], i, j);
}
}

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.

Fetch text from unnamed QGraphicsTextItem

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();
}

Updating QGridLayouts

Hi guys I'm coding game for my studies and I've big problems with that (My leg was injured and I couldn't go to lessons).
My job is to do simple Battleships game in c++, qt.
I'm in point where logic code is done, but gui is a big mess.
Here's code for gui .cpp file:
#include <QtWidgets>
#include "dialog.h"
Dialog::Dialog()
{
createGraczBox();
createKomputerBox();
createOdpowiedz();
QGridLayout *mainLayout = new QGridLayout;
mainLayout->addWidget(graczBox , 0 , 0 );
mainLayout->addWidget(komputerBox , 0 , 1 );
mainLayout->addWidget(Odpowiedz , 0 , 2 );
setLayout(mainLayout);
setFixedSize(800,400);
setWindowTitle(tr("Battleships!"));
}
void Dialog::createGraczBox()
{
graczBox = new QGroupBox(tr("Gracz"));
QGridLayout *layout = new QGridLayout;
for (int j = 0; j < NumGridRows; ++j) {
labels[j] = new QLabel(tr("%0").arg(j+1));
layout->addWidget(labels[j], 0 , j + 1 , Qt::AlignLeft);
}
for (int i = 0; i < NumGridRows; ++i) {
labels[i] = new QLabel(tr("%0").arg(i + 1));
layout->addWidget(labels[i], i + 1, 0);
}
for(int g = 1;g<10;++g)
{
layout->setColumnStretch(g,1);
}
graczBox->setLayout(layout);
}
void Dialog::createKomputerBox()
{
komputerBox = new QGroupBox(tr("Komputer"));
QGridLayout *layout = new QGridLayout;
for (int j = 0; j < NumGridRows; ++j) {
labels[j] = new QLabel(tr("%0").arg(j+1));
layout->addWidget(labels[j], 0 , j + 1 );
}
for (int i = 0; i < NumGridRows; ++i) {
labels[i] = new QLabel(tr("%0").arg(i + 1));
layout->addWidget(labels[i], i + 1, 0);
}
for(int g = 1;g<10;++g)
{
layout->setColumnStretch(g,1);
}
komputerBox->setLayout(layout);
}
void Dialog::createOdpowiedz()
{
Odpowiedz = new QGroupBox(tr("Komendy"));
QFormLayout *layout = new QFormLayout;
xLabel = new QLabel;
QPushButton *zmienna_x_przycisk = new QPushButton(tr("X"));
connect(zmienna_x_przycisk, SIGNAL(clicked()), this, SLOT(setx()));
yLabel = new QLabel;
QPushButton *zmienna_y_przycisk = new QPushButton(tr("Y"));
connect(zmienna_y_przycisk, SIGNAL(clicked()), this, SLOT(sety()));
xLabel->setText(tr("Aktualne X: %1").arg(zmienna_x));
yLabel->setText(tr("Aktualne Y: %1").arg(zmienna_y));
layout->addRow(xLabel);
layout->addRow(zmienna_x_przycisk);
layout->addRow(yLabel);
layout->addRow(zmienna_y_przycisk);
Odpowiedz->setLayout(layout);
}
void Dialog::setx()
{
bool ok_x;
x = QInputDialog::getInt(this, tr("Podaj X:"),
tr(""), 1, 1, 10, 1, &ok_x);
if (ok_x)
x=zmienna_x;
}
void Dialog::sety()
{
bool ok_y;
y = QInputDialog::getInt(this, tr("Podaj Y:"),
tr(""), 1, 1, 10, 1, &ok_y);
if (ok_y)
y=zmienna_y;
}
They way it should work:
I'm choosing x and y by clicking on it.
Choosing numbers in new window.
They should appear in "Aktualne X:/Y:".
When I've x and y, click ok button (he's not there by now).
Computer checking numbers marking it in space Komputer / Gracz.
Reset x and y to 0.
Show text "You missed. Computer missed."
Go on till one'll win.
But I don't know how to make my layout updating itself by other actions. I can't make dowhile work here.
You need to use signals and slots here. Create "OK" button and connect it to a function, that will be used to handle your x and y variables.

How to add lines numbers to QTextEdit?

I am writing a Visual Basic IDE, and I need to add lines numbers to QTextEdit and highlight current line. I have found this tutorial, but it is written in Java and I write my project in C++.
I know that Qt tutorial recommends using QPlainTextEdit for text editor implementations, and that the question (except as mentioned in the title), is more general than dealing (absolutely) with a QTextEdit widget, but I succeeded in implementing the behaviour (line numbers + current line number highlight), and I think this might be helpful for some people (like me) who really want to keep going with the Rich Text widget, and want to share my implementation (which is far from perfect - quite fast coded...).
LineNumberArea.h : (Same as "QPlainTextEdit" tutorial)
class LineNumberArea : public QWidget
{
Q_OBJECT
public:
LineNumberArea(QTextEdit *editor);
QSize sizeHint() const;
protected:
void paintEvent(QPaintEvent *event);
private:
QTextEdit *codeEditor;
};
LineNumberArea.cpp : (Same as "QPlainTextEdit" tutorial)
LineNumberArea::LineNumberArea(QTextEdit *editor) : QWidget(editor) {
codeEditor = editor;
}
QSize LineNumberArea::sizeHint() const {
return QSize(((QTextEditHighlighter *)codeEditor)->lineNumberAreaWidth(), 0);
}
void LineNumberArea::paintEvent(QPaintEvent *event) {
((QTextEditHighlighter *)codeEditor)->lineNumberAreaPaintEvent(event);
}
>> qtextedithighlighter.h :
class QTextEditHighlighter : public QTextEdit
{
Q_OBJECT
public:
explicit QTextEditHighlighter(QWidget *parent = 0);
int getFirstVisibleBlockId();
void lineNumberAreaPaintEvent(QPaintEvent *event);
int lineNumberAreaWidth();
signals:
public slots:
void resizeEvent(QResizeEvent *e);
private slots:
void updateLineNumberAreaWidth(int newBlockCount);
void updateLineNumberArea(QRectF /*rect_f*/);
void updateLineNumberArea(int /*slider_pos*/);
void updateLineNumberArea();
private:
QWidget *lineNumberArea;
};
>> qtextedithighlighter.cpp :
#include "qtextedithighlighter.h"
QTextEditHighlighter::QTextEditHighlighter(QWidget *parent) :
QTextEdit(parent)
{
// Line numbers
lineNumberArea = new LineNumberArea(this);
///
connect(this->document(), SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int)));
connect(this->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updateLineNumberArea/*_2*/(int)));
connect(this, SIGNAL(textChanged()), this, SLOT(updateLineNumberArea()));
connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateLineNumberArea()));
///
updateLineNumberAreaWidth(0);
}
int QTextEditHighlighter::lineNumberAreaWidth()
{
int digits = 1;
int max = qMax(1, this->document()->blockCount());
while (max >= 10) {
max /= 10;
++digits;
}
int space = 13 + fontMetrics().width(QLatin1Char('9')) * (digits);
return space;
}
void QTextEditHighlighter::updateLineNumberAreaWidth(int /* newBlockCount */)
{
setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
}
void QTextEditHighlighter::updateLineNumberArea(QRectF /*rect_f*/)
{
QTextEditHighlighter::updateLineNumberArea();
}
void QTextEditHighlighter::updateLineNumberArea(int /*slider_pos*/)
{
QTextEditHighlighter::updateLineNumberArea();
}
void QTextEditHighlighter::updateLineNumberArea()
{
/*
* When the signal is emitted, the sliderPosition has been adjusted according to the action,
* but the value has not yet been propagated (meaning the valueChanged() signal was not yet emitted),
* and the visual display has not been updated. In slots connected to this signal you can thus safely
* adjust any action by calling setSliderPosition() yourself, based on both the action and the
* slider's value.
*/
// Make sure the sliderPosition triggers one last time the valueChanged() signal with the actual value !!!!
this->verticalScrollBar()->setSliderPosition(this->verticalScrollBar()->sliderPosition());
// Since "QTextEdit" does not have an "updateRequest(...)" signal, we chose
// to grab the imformations from "sliderPosition()" and "contentsRect()".
// See the necessary connections used (Class constructor implementation part).
QRect rect = this->contentsRect();
lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
updateLineNumberAreaWidth(0);
//----------
int dy = this->verticalScrollBar()->sliderPosition();
if (dy > -1) {
lineNumberArea->scroll(0, dy);
}
// Addjust slider to alway see the number of the currently being edited line...
int first_block_id = getFirstVisibleBlockId();
if (first_block_id == 0 || this->textCursor().block().blockNumber() == first_block_id-1)
this->verticalScrollBar()->setSliderPosition(dy-this->document()->documentMargin());
// // Snap to first line (TODO...)
// if (first_block_id > 0)
// {
// int slider_pos = this->verticalScrollBar()->sliderPosition();
// int prev_block_height = (int) this->document()->documentLayout()->blockBoundingRect(this->document()->findBlockByNumber(first_block_id-1)).height();
// if (dy <= this->document()->documentMargin() + prev_block_height)
// this->verticalScrollBar()->setSliderPosition(slider_pos - (this->document()->documentMargin() + prev_block_height));
// }
}
void QTextEditHighlighter::resizeEvent(QResizeEvent *e)
{
QTextEdit::resizeEvent(e);
QRect cr = this->contentsRect();
lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
}
int QTextEditHighlighter::getFirstVisibleBlockId()
{
// Detect the first block for which bounding rect - once translated
// in absolute coordinated - is contained by the editor's text area
// Costly way of doing but since "blockBoundingGeometry(...)" doesn't
// exists for "QTextEdit"...
QTextCursor curs = QTextCursor(this->document());
curs.movePosition(QTextCursor::Start);
for(int i=0; i < this->document()->blockCount(); ++i)
{
QTextBlock block = curs.block();
QRect r1 = this->viewport()->geometry();
QRect r2 = this->document()->documentLayout()->blockBoundingRect(block).translated(
this->viewport()->geometry().x(), this->viewport()->geometry().y() - (
this->verticalScrollBar()->sliderPosition()
) ).toRect();
if (r1.contains(r2, true)) { return i; }
curs.movePosition(QTextCursor::NextBlock);
}
return 0;
}
void QTextEditHighlighter::lineNumberAreaPaintEvent(QPaintEvent *event)
{
this->verticalScrollBar()->setSliderPosition(this->verticalScrollBar()->sliderPosition());
QPainter painter(lineNumberArea);
painter.fillRect(event->rect(), Qt::lightGray);
int blockNumber = this->getFirstVisibleBlockId();
QTextBlock block = this->document()->findBlockByNumber(blockNumber);
QTextBlock prev_block = (blockNumber > 0) ? this->document()->findBlockByNumber(blockNumber-1) : block;
int translate_y = (blockNumber > 0) ? -this->verticalScrollBar()->sliderPosition() : 0;
int top = this->viewport()->geometry().top();
// Adjust text position according to the previous "non entirely visible" block
// if applicable. Also takes in consideration the document's margin offset.
int additional_margin;
if (blockNumber == 0)
// Simply adjust to document's margin
additional_margin = (int) this->document()->documentMargin() -1 - this->verticalScrollBar()->sliderPosition();
else
// Getting the height of the visible part of the previous "non entirely visible" block
additional_margin = (int) this->document()->documentLayout()->blockBoundingRect(prev_block)
.translated(0, translate_y).intersect(this->viewport()->geometry()).height();
// Shift the starting point
top += additional_margin;
int bottom = top + (int) this->document()->documentLayout()->blockBoundingRect(block).height();
QColor col_1(90, 255, 30); // Current line (custom green)
QColor col_0(120, 120, 120); // Other lines (custom darkgrey)
// Draw the numbers (displaying the current line number in green)
while (block.isValid() && top <= event->rect().bottom()) {
if (block.isVisible() && bottom >= event->rect().top()) {
QString number = QString::number(blockNumber + 1);
painter.setPen(QColor(120, 120, 120));
painter.setPen((this->textCursor().blockNumber() == blockNumber) ? col_1 : col_0);
painter.drawText(-5, top,
lineNumberArea->width(), fontMetrics().height(),
Qt::AlignRight, number);
}
block = block.next();
top = bottom;
bottom = top + (int) this->document()->documentLayout()->blockBoundingRect(block).height();
++blockNumber;
}
}
Hope this can help...
Here's the equivalent tutorial in C++:
Qt4: http://doc.qt.io/qt-4.8/qt-widgets-codeeditor-example.html
Qt5: http://doc.qt.io/qt-5/qtwidgets-widgets-codeeditor-example.html
I was looking for a line numbers painting solution for QTextEdit (not QPlainTextEdit), and I found the previous answer with sample code for QTextEdit is useful, but when we set custom line height in QTextEdit's associated SyntaxHighligher, it doesn't work reliably.
To fix that problem, I figured out a simpler way to determine the y coordinate of each block rect by using this code:
// Here is the key to obtain the y coordinate of the block start
QTextCursor blockCursor(block);
QRect blockCursorRect = this->cursorRect(blockCursor);
And then we can draw line number of each block via:
painter.drawText(-5, blockCursorRect.y() /* + a little offset to align */,
m_lineNumberArea->width(), fixedLineHeight,
Qt::AlignRight, number);
This seems much simpler and more reliable than calculating the block y coordinate by adding previous block height up.
Hope it helps for someone who is looking for similar solutions.
Here is an easy example to determine the text positions for drawing line numbers for the QTextEdit derived classes.
code_browser::code_browser(QWidget *parent): QTextBrowser(parent)
, m_line_number_area(new LineNumberArea(this))
, m_show_line_numbers(false)
{
connect(document(), SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int)));
connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(vertical_scroll_value(int)));
connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int)));
connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));
updateLineNumberAreaWidth(blockCount());
highlightCurrentLine();
}
void code_browser::vertical_scroll_value(int value)
{
Q_EMIT updateRequest(contentsRect(), value);
}
void code_browser::lineNumberAreaPaintEvent(QPaintEvent *event)
{
QPainter painter(m_line_number_area);
painter.fillRect(event->rect(), Qt::lightGray);
int top = 0;
QTextBlock block = firstVisibleBlock(top);
int blockNumber = block.blockNumber();
QRectF block_rect = blockBoundingRect(block);
int bottom = top + qRound(block_rect.height());
while (block.isValid() && top <= event->rect().bottom())
{
if (block.isVisible() && bottom >= event->rect().top())
{
QString number = QString::number(blockNumber + 1);
painter.setPen(Qt::black);
painter.drawText(0, top, m_line_number_area->width(), fontMetrics().height(), Qt::AlignRight, number);
}
block = block.next();
top = bottom;
block_rect = blockBoundingRect(block);
bottom = top + qRound(block_rect.height());
++blockNumber;
}
}
int code_browser::blockCount() const
{
return document()->blockCount();
}
QTextBlock code_browser::firstVisibleBlock(int& diff)
{
QPointF content_offset = contentOffset();
for (QTextBlock block = document()->begin(); block.isValid(); block = block.next())
{
if (block.isVisible())
{
QRectF block_rect = blockBoundingRect(block);
if (block_rect.top() >= content_offset.y())
{
diff = block_rect.top() - content_offset.y();
return block;
}
}
}
diff = -1;
return document()->begin();
}
QRectF code_browser::blockBoundingRect(const QTextBlock &block) const
{
QAbstractTextDocumentLayout *layout = document()->documentLayout();
return layout->blockBoundingRect(block);
}
QPointF code_browser::contentOffset() const
{
return QPointF(horizontalScrollBar()->value(), verticalScrollBar()->value());
}
Python's adaptation:
QTextEditHighlighter.py
from PySide6.QtCore import QRectF, QRect, Qt
from PySide6.QtGui import QResizeEvent, QTextCursor, QPaintEvent, QPainter, QColor
from PySide6.QtWidgets import QTextEdit, QApplication
from LineNumberArea import LineNumberArea
class QTextEditHighlighter(QTextEdit):
def __init__(self):
# Line numbers
QTextEdit.__init__(self)
self.lineNumberArea = LineNumberArea(self)
self.document().blockCountChanged.connect(self.updateLineNumberAreaWidth)
self.verticalScrollBar().valueChanged.connect(self.updateLineNumberArea)
self.textChanged.connect(self.updateLineNumberArea)
self.cursorPositionChanged.connect(self.updateLineNumberArea)
self.updateLineNumberAreaWidth(0)
def lineNumberAreaWidth(self):
digits = 1
m = max(1, self.document().blockCount())
while m >= 10:
m /= 10
digits += 1
space = 13 + self.fontMetrics().horizontalAdvance('9') * digits
return space
def updateLineNumberAreaWidth(self, newBlockCount: int):
self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0)
def updateLineNumberAreaRect(self, rect_f: QRectF):
self.updateLineNumberArea()
def updateLineNumberAreaInt(self, slider_pos: int):
self.updateLineNumberArea()
def updateLineNumberArea(self):
"""
When the signal is emitted, the sliderPosition has been adjusted according to the action,
but the value has not yet been propagated (meaning the valueChanged() signal was not yet emitted),
and the visual display has not been updated. In slots connected to self signal you can thus safely
adjust any action by calling setSliderPosition() yourself, based on both the action and the
slider's value.
"""
# Make sure the sliderPosition triggers one last time the valueChanged() signal with the actual value !!!!
self.verticalScrollBar().setSliderPosition(self.verticalScrollBar().sliderPosition())
# Since "QTextEdit" does not have an "updateRequest(...)" signal, we chose
# to grab the imformations from "sliderPosition()" and "contentsRect()".
# See the necessary connections used (Class constructor implementation part).
rect = self.contentsRect()
self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height())
self.updateLineNumberAreaWidth(0)
dy = self.verticalScrollBar().sliderPosition()
if dy > -1:
self.lineNumberArea.scroll(0, dy)
# Addjust slider to alway see the number of the currently being edited line...
first_block_id = self.getFirstVisibleBlockId()
if first_block_id == 0 or self.textCursor().block().blockNumber() == first_block_id-1:
self.verticalScrollBar().setSliderPosition(dy-self.document().documentMargin())
# # Snap to first line (TODO...)
# if first_block_id > 0:
# slider_pos = self.verticalScrollBar().sliderPosition()
# prev_block_height = (int) self.document().documentLayout().blockBoundingRect(self.document().findBlockByNumber(first_block_id-1)).height()
# if (dy <= self.document().documentMargin() + prev_block_height)
# self.verticalScrollBar().setSliderPosition(slider_pos - (self.document().documentMargin() + prev_block_height))
def resizeEvent(self, event: QResizeEvent):
QTextEdit.resizeEvent(self, event)
cr = self.contentsRect()
self.lineNumberArea.setGeometry(QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height()))
def getFirstVisibleBlockId(self) -> int:
# Detect the first block for which bounding rect - once translated
# in absolute coordinated - is contained by the editor's text area
# Costly way of doing but since "blockBoundingGeometry(...)" doesn't
# exists for "QTextEdit"...
curs = QTextCursor(self.document())
curs.movePosition(QTextCursor.Start)
for i in range(self.document().blockCount()):
block = curs.block()
r1 = self.viewport().geometry()
r2 = self.document().documentLayout().blockBoundingRect(block).translated(
self.viewport().geometry().x(), self.viewport().geometry().y() - (
self.verticalScrollBar().sliderPosition()
)).toRect()
if r1.contains(r2, True):
return i
curs.movePosition(QTextCursor.NextBlock)
return 0
def lineNumberAreaPaintEvent(self, event: QPaintEvent):
self.verticalScrollBar().setSliderPosition(self.verticalScrollBar().sliderPosition())
painter = QPainter(self.lineNumberArea)
painter.fillRect(event.rect(), Qt.lightGray)
blockNumber = self.getFirstVisibleBlockId()
block = self.document().findBlockByNumber(blockNumber)
if blockNumber > 0:
prev_block = self.document().findBlockByNumber(blockNumber - 1)
else:
prev_block = block
if blockNumber > 0:
translate_y = -self.verticalScrollBar().sliderPosition()
else:
translate_y = 0
top = self.viewport().geometry().top()
# Adjust text position according to the previous "non entirely visible" block
# if applicable. Also takes in consideration the document's margin offset.
if blockNumber == 0:
# Simply adjust to document's margin
additional_margin = self.document().documentMargin() -1 - self.verticalScrollBar().sliderPosition()
else:
# Getting the height of the visible part of the previous "non entirely visible" block
additional_margin = self.document().documentLayout().blockBoundingRect(prev_block) \
.translated(0, translate_y).intersect(self.viewport().geometry()).height()
# Shift the starting point
top += additional_margin
bottom = top + int(self.document().documentLayout().blockBoundingRect(block).height())
col_1 = QColor(90, 255, 30) # Current line (custom green)
col_0 = QColor(120, 120, 120) # Other lines (custom darkgrey)
# Draw the numbers (displaying the current line number in green)
while block.isValid() and top <= event.rect().bottom():
if block.isVisible() and bottom >= event.rect().top():
number = f"{blockNumber + 1}"
painter.setPen(QColor(120, 120, 120))
if self.textCursor().blockNumber() == blockNumber:
painter.setPen(col_1)
else:
painter.setPen(col_0)
painter.drawText(-5, top,
self.lineNumberArea.width(), self.fontMetrics().height(),
Qt.AlignRight, number)
block = block.next()
top = bottom
bottom = top + int(self.document().documentLayout().blockBoundingRect(block).height())
blockNumber += 1
if __name__ == '__main__':
app = QApplication([])
w = QTextEditHighlighter()
w.show()
app.exec()
LineNumberArea.py
from PySide6.QtCore import QSize
from PySide6.QtWidgets import QWidget
class LineNumberArea(QWidget):
def __init__(self, editor):
QWidget.__init__(self, editor)
self.codeEditor = editor
def sizeHint(self) -> QSize:
return QSize(self.codeEditor.lineNumberAreaWidth(), 0)
def paintEvent(self, event):
self.codeEditor.lineNumberAreaPaintEvent(event)