Conway's Game of Life - C++ and Qt - c++

I've done all of the layouts and have most of the code written even. But, I'm stuck in two places.
1) I'm not quite sure how to set up the timer. Am I using it correctly in the gridwindow class? And, am I used the timer functions/signals/slots correctly with the other gridwindow functions.
2) In GridWindow's timerFired() function, I'm having trouble checking/creating the vector-vectors. I wrote out in the comments in that function exactly what I am trying to do.
Any help would be much appreciated.
main.cpp
// Main file for running the grid window application.
#include <QApplication>
#include "gridwindow.h"
//#include "timerwindow.h"
#include <stdexcept>
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
void Welcome(); // Welcome Function - Prints upon running program; outputs program name, student name/id, class section.
void Rules(); // Rules Function: Prints the rules for Conway's Game of Life.
using namespace std;
// A simple main method to create the window class and then pop it up on the screen.
int main(int argc, char *argv[])
{
Welcome(); // Calls Welcome function to print student/assignment info.
Rules(); // Prints Conway's Game Rules.
QApplication app(argc, argv); // Creates the overall windowed application.
int rows = 25, cols = 35; //The number of rows & columns in the game grid.
GridWindow widget(NULL,rows,cols); // Creates the actual window (for the grid).
widget.show(); // Shows the window on the screen.
return app.exec(); // Goes into visual loop; starts executing GUI.
}
// Welcome Function: Prints my name/id, my class number, the assignment, and the program name.
void Welcome()
{
cout << endl;
cout << "-------------------------------------------------------------------------------------------------" << endl;
cout << "Name/ID - Gabe Audick #7681539807" << endl;
cout << "Class/Assignment - CSCI-102 Disccusion 29915: Homework Assignment #4" << endl;
cout << "-------------------------------------------------------------------------------------------------" << endl << endl;
}
// Rules Function: Prints the rules for Conway's Game of Life.
void Rules()
{
cout << "Welcome to Conway's Game of Life." << endl;
cout << "Game Rules:" << endl;
cout << "\t 1) Any living cell with fewer than two living neighbours dies, as if caused by underpopulation." << endl;
cout << "\t 2) Any live cell with more than three live neighbours dies, as if by overcrowding." << endl;
cout << "\t 3) Any live cell with two or three live neighbours lives on to the next generation." << endl;
cout << "\t 4) Any dead cell with exactly three live neighbours becomes a live cell." << endl << endl;
cout << "Enjoy." << endl << endl;
}
gridcell.h
// A header file for a class representing a single cell in a grid of cells.
#ifndef GRIDCELL_H_
#define GRIDCELL_H_
#include <QPalette>
#include <QColor>
#include <QPushButton>
#include <Qt>
#include <QWidget>
#include <QFrame>
#include <QHBoxLayout>
#include <iostream>
// An enum representing the two different states a cell can have.
enum CellType
{
DEAD, // DEAD = Dead Cell. --> Color = White.
LIVE // LIVE = Living Cell. ---> Color = White.
};
/*
Class: GridCell.
A class representing a single cell in a grid. Each cell is implemented
as a QT QFrame that contains a single QPushButton. The button is sized
so that it takes up the entire frame. Each cell also keeps track of what
type of cell it is based on the CellType enum.
*/
class GridCell : public QFrame
{
Q_OBJECT // Macro allowing us to have signals & slots on this object.
private:
QPushButton* button; // The button inside the cell that gives its clickability.
CellType type; // The type of cell (DEAD or LIVE.)
public slots:
void handleClick(); // Callback for handling a click on the current cell.
void setType(CellType type); // Cell type mutator. Calls the "redrawCell" function.
signals:
void typeChanged(CellType type); // Signal to notify listeners when the cell type has changed.
public:
GridCell(QWidget *parent = NULL); // Constructor for creating a cell. Takes parent widget or default parent to NULL.
virtual ~GridCell(); // Destructor.
void redrawCell(); // Redraws cell: Sets new type/color.
CellType getType() const; //Simple getter for the cell type.
private:
Qt::GlobalColor getColorForCellType(); // Helper method. Returns color that cell should be based from its value.
};
#endif
gridcell.cpp
#include <iostream>
#include "gridcell.h"
#include "utility.h"
using namespace std;
// Constructor: Creates a grid cell.
GridCell::GridCell(QWidget *parent)
: QFrame(parent)
{
this->type = DEAD; // Default: Cell is DEAD (white).
setFrameStyle(QFrame::Box); // Set the frame style. This is what gives each box its black border.
this->button = new QPushButton(this); //Creates button that fills entirety of each grid cell.
this->button->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); // Expands button to fill space.
this->button->setMinimumSize(19,19); //width,height // Min height and width of button.
QHBoxLayout *layout = new QHBoxLayout(); //Creates a simple layout to hold our button and add the button to it.
layout->addWidget(this->button);
setLayout(layout);
layout->setStretchFactor(this->button,1); // Lets the buttons expand all the way to the edges of the current frame with no space leftover
layout->setContentsMargins(0,0,0,0);
layout->setSpacing(0);
connect(this->button,SIGNAL(clicked()),this,SLOT(handleClick())); // Connects clicked signal with handleClick slot.
redrawCell(); // Calls function to redraw (set new type for) the cell.
}
// Basic destructor.
GridCell::~GridCell()
{
delete this->button;
}
// Accessor for the cell type.
CellType GridCell::getType() const
{
return(this->type);
}
// Mutator for the cell type. Also has the side effect of causing the cell to be redrawn on the GUI.
void GridCell::setType(CellType type)
{
this->type = type;
redrawCell();
}
// Handler slot for button clicks. This method is called whenever the user clicks on this cell in the grid.
void GridCell::handleClick()
{ // When clicked on...
if(this->type == DEAD) // If type is DEAD (white), change to LIVE (black).
type = LIVE;
else
type = DEAD; // If type is LIVE (black), change to DEAD (white).
setType(type); // Sets new type (color). setType Calls redrawCell() to recolor.
}
// Method to check cell type and return the color of that type.
Qt::GlobalColor GridCell::getColorForCellType()
{
switch(this->type)
{
default:
case DEAD:
return Qt::white;
case LIVE:
return Qt::black;
}
}
// Helper method. Forces current cell to be redrawn on the GUI. Called whenever the setType method is invoked.
void GridCell::redrawCell()
{
Qt::GlobalColor gc = getColorForCellType(); //Find out what color this cell should be.
this->button->setPalette(QPalette(gc,gc)); //Force the button in the cell to be the proper color.
this->button->setAutoFillBackground(true);
this->button->setFlat(true); //Force QT to NOT draw the borders on the button
}
gridwindow.h
// A header file for a QT window that holds a grid of cells.
#ifndef GRIDWINDOW_H_
#define GRIDWINDOW_H_
#include <vector>
#include <QWidget>
#include <QTimer>
#include <QGridLayout>
#include <QLabel>
#include <QApplication>
#include "gridcell.h"
/*
class GridWindow:
This is the class representing the whole window that comes up when this program runs.
It contains a header section with a title, a middle section of MxN cells and a bottom section with buttons.
*/
class GridWindow : public QWidget
{
Q_OBJECT // Macro to allow this object to have signals & slots.
private:
std::vector<std::vector<GridCell*> > cells; // A 2D vector containing pointers to all the cells in the grid.
QLabel *title; // A pointer to the Title text on the window.
QTimer *timer; // Creates timer object.
public slots:
void handleClear(); // Handler function for clicking the Clear button.
void handleStart(); // Handler function for clicking the Start button.
void handlePause(); // Handler function for clicking the Pause button.
void timerFired(); // Method called whenever timer fires.
public:
GridWindow(QWidget *parent = NULL,int rows=3,int cols=3); // Constructor.
virtual ~GridWindow(); // Destructor.
std::vector<std::vector<GridCell*> >& getCells(); // Accessor for the array of grid cells.
private:
QHBoxLayout* setupHeader(); // Helper function to construct the GUI header.
QGridLayout* setupGrid(int rows,int cols); // Helper function to constructor the GUI's grid.
QHBoxLayout* setupButtonRow(); // Helper function to setup the row of buttons at the bottom.
};
#endif
gridwindow.cpp
#include <iostream>
#include "gridwindow.h"
using namespace std;
// Constructor for window. It constructs the three portions of the GUI and lays them out vertically.
GridWindow::GridWindow(QWidget *parent,int rows,int cols)
: QWidget(parent)
{
QHBoxLayout *header = setupHeader(); // Setup the title at the top.
QGridLayout *grid = setupGrid(rows,cols); // Setup the grid of colored cells in the middle.
QHBoxLayout *buttonRow = setupButtonRow(); // Setup the row of buttons across the bottom.
QVBoxLayout *layout = new QVBoxLayout(); // Puts everything together.
layout->addLayout(header);
layout->addLayout(grid);
layout->addLayout(buttonRow);
setLayout(layout);
}
// Destructor.
GridWindow::~GridWindow()
{
delete title;
}
// Builds header section of the GUI.
QHBoxLayout* GridWindow::setupHeader()
{
QHBoxLayout *header = new QHBoxLayout(); // Creates horizontal box.
header->setAlignment(Qt::AlignHCenter);
this->title = new QLabel("CONWAY'S GAME OF LIFE",this); // Creates big, bold, centered label (title): "Conway's Game of Life."
this->title->setAlignment(Qt::AlignHCenter);
this->title->setFont(QFont("Arial", 32, QFont::Bold));
header->addWidget(this->title); // Adds widget to layout.
return header; // Returns header to grid window.
}
// Builds the grid of cells. This method populates the grid's 2D array of GridCells with MxN cells.
QGridLayout* GridWindow::setupGrid(int rows,int cols)
{
QGridLayout *grid = new QGridLayout(); // Creates grid layout.
grid->setHorizontalSpacing(0); // No empty spaces. Cells should be contiguous.
grid->setVerticalSpacing(0);
grid->setSpacing(0);
grid->setAlignment(Qt::AlignHCenter);
for(int i=0; i < rows; i++) //Each row is a vector of grid cells.
{
std::vector<GridCell*> row; // Creates new vector for current row.
cells.push_back(row);
for(int j=0; j < cols; j++)
{
GridCell *cell = new GridCell(); // Creates and adds new cell to row.
cells.at(i).push_back(cell);
grid->addWidget(cell,i,j); // Adds to cell to grid layout. Column expands vertically.
grid->setColumnStretch(j,1);
}
grid->setRowStretch(i,1); // Sets row expansion horizontally.
}
return grid; // Returns grid.
}
// Builds footer section of the GUI.
QHBoxLayout* GridWindow::setupButtonRow()
{
QHBoxLayout *buttonRow = new QHBoxLayout(); // Creates horizontal box for buttons.
buttonRow->setAlignment(Qt::AlignHCenter);
// Clear Button - Clears cell; sets them all to DEAD/white.
QPushButton *clearButton = new QPushButton("CLEAR");
clearButton->setFixedSize(100,25);
connect(clearButton, SIGNAL(clicked()), this, SLOT(handleClear()));
buttonRow->addWidget(clearButton);
// Start Button - Starts game when user clicks. Or, resumes game after being paused.
QPushButton *startButton = new QPushButton("START/RESUME");
startButton->setFixedSize(100,25);
connect(startButton, SIGNAL(clicked()), this, SLOT(handleStart()));
buttonRow->addWidget(startButton);
// Pause Button - Pauses simulation of game.
QPushButton *pauseButton = new QPushButton("PAUSE");
pauseButton->setFixedSize(100,25);
connect(pauseButton, SIGNAL(clicked()), this, SLOT(handlePause()));
buttonRow->addWidget(pauseButton);
// Quit Button - Exits program.
QPushButton *quitButton = new QPushButton("EXIT");
quitButton->setFixedSize(100,25);
connect(quitButton, SIGNAL(clicked()), qApp, SLOT(quit()));
buttonRow->addWidget(quitButton);
return buttonRow; // Returns bottom of layout.
}
/*
SLOT method for handling clicks on the "clear" button.
Receives "clicked" signals on the "Clear" button and sets all cells to DEAD.
*/
void GridWindow::handleClear()
{
for(unsigned int row=0; row < cells.size(); row++) // Loops through current rows' cells.
{
for(unsigned int col=0; col < cells[row].size(); col++)
{
GridCell *cell = cells[row][col]; // Grab the current cell & set its value to dead.
cell->setType(DEAD);
}
}
}
/*
SLOT method for handling clicks on the "start" button.
Receives "clicked" signals on the "start" button and begins game simulation.
*/
void GridWindow::handleStart()
{
this->timer = new QTimer(this); // Creates new timer.
connect(this->timer, SIGNAL(timeout()), this, SLOT(timerFired())); // Connect "timerFired" method class to the "timeout" signal fired by the timer.
this->timer->start(500); // Timer to fire every 500 milliseconds.
}
/*
SLOT method for handling clicks on the "pause" button.
Receives "clicked" signals on the "pause" button and stops the game simulation.
*/
void GridWindow::handlePause()
{
this->timer->stop(); // Stops the timer.
delete this->timer; // Deletes timer.
}
// Accessor method - Gets the 2D vector of grid cells.
std::vector<std::vector<GridCell*> >& GridWindow::getCells()
{
return this->cells;
}
void GridWindow::timerFired()
{
// I'm not sure how to write this code.
// I want to take the original vector-vector, and also make a new, empty vector-vector of the same size.
// I would then go through the code below with the original vector, and apply the rules to the new vector-vector.
// Finally, I would make the new vector-vecotr the original vector-vector. (That would be one step in the simulation.)
cout << cells[1][2];
/*
for (unsigned int m = 0; m < original.size(); m++)
{
for (unsigned int n = 0; n < original.at(m).size(); n++)
{
unsigned int neighbors = 0; //Begin counting number of neighbors.
if (original[m-1][n-1].getType() == LIVE) // If a cell next to [i][j] is LIVE, add one to the neighbor count.
neighbors += 1;
if (original[m-1][n].getType() == LIVE)
neighbors += 1;
if (original[m-1][n+1].getType() == LIVE)
neighbors += 1;
if (original[m][n-1].getType() == LIVE)
neighbors += 1;
if (original[m][n+1].getType() == LIVE)
neighbors += 1;
if (original[m+1][n-1].getType() == LIVE)
neighbors += 1;
if (original[m+1][n].getType() == LIVE)
neighbors += 1;
if (original[m+1][n+1].getType() == LIVE)
neighbors += 1;
if (original[m][n].getType() == LIVE && neighbors < 2) // Apply game rules to cells: Create new, updated grid with the roundtwo vector.
roundtwo[m][n].setType(LIVE);
else if (original[m][n].getType() == LIVE && neighbors > 3)
roundtwo[m][n].setType(DEAD);
else if (original[m][n].getType() == LIVE && (neighbors == 2 || neighbors == 3))
roundtwo[m][n].setType(LIVE);
else if (original[m][n].getType() == DEAD && neighbors == 3)
roundtwo[m][n].setType(LIVE);
}
}*/
}

It looks like the timer is set up correctly to me.
For the timerFired() function, I would not create a temporary matrix of GridCell objects, but just a temporary matrix of flags that indicate whether the cell is live or not. These flags are really all that changes in your function, so just temporarily store the new flags, then set them on the original grid cells to save all of the extra memory and allocation time required for creating a temporary matrix of cells. Here is an example:
//Store flags that represent whether the new grid cells will be live or not
vector< vector<bool> > is_live(cells.size());
for(int m=0; m<cells.size(); m++)
{
is_live.at(m).resize(cells.at(m).size());
for(int n=0; n<cells.at(m).size(); n++)
{
//count neighbors
unsigned int neighbors = 0;
for(int i=-1; i<=1; i++)
{
for(int j=-1; j<=1; j++)
{
neighbors += static_cast<int>(
cells[m+i][n+j]->getType() == LIVE);
}
}
//we counted the current cell when counting the neighbors so
//subtract it back off if needed.
neighbors -= static_cast<int>(cells[m][n]->getType() == LIVE);
//Set the type to the original value
is_live[m][n] = cells[m][n]->getType() == LIVE;
//change it based on the neighbor count.
//Some of your logic around here seemed repetitive so I
//did it differently. You may want to change it back
//if you had a specific purpose for the way you did it
is_live[m][n] = (is_live[m][n] && neighbors <= 3) ||
(!is_live[m][n] && neighbors == 3);
}
}
//Set the cell types based on the is_live flags.
for(int m=0; m<cells.size(); m++)
{
for(int n=0; n<cells.at(m).size(); n++)
cells[m][n]->setType(is_live[m][n] ? LIVE : DEAD);
}
Note: I did not compile or test this so there are no guarantees.

Related

How to change one QBarSet bar/element color in qt?

I connected to the hovered signal of QBarSet to a slot, which will change the QBarSet color when mouse hovers on the bar set and reset the color when mouse leaves.
The code snippet looks like this:
void BarChart::hoverTest(bool status, int index)
{
if(status == true) {
set->setColor(Qt::red); //changes to bar set color to red mouse when hovers on bar set
}
else {
set->setColor(QColor(52, 152, 219)); //reset the color when mouse leaves
}
}
And those are the pics before hovering and when hovers:
As you can see, if I hover on the bar set, all this bar set bars(elements) color changed to red. But I want to hover on a specific bar(element) of the bar set, and that bar(element) changes its color, and the rest of them stay the same.
Is there a way to achieve this?
I was little bit searching and trying to make it work and it can be changed by casting QGraphicsItem to QGraphicsRectItem.
It is similar as in previous answer:
QObject::connect(set0, &QBarSet::hovered, [&w](bool status, int /*index*/){
QPoint p = w.mapFromGlobal(QCursor::pos());
if(status){
QGraphicsRectItem *rect = qgraphicsitem_cast<QGraphicsRectItem *>(w.itemAt(p));
rect->brush().setColor(Qt::red);
rect->update();
}
else{
rect->brush().setColor(Qt::blue); //or change it to default colour
rect->update();
}
});
Additionally, it is possible use index of QBarSet::hovered but that take lot of work and it is not possible do it directly. In my case I created method to find all bar graphic objects in chart and sort them by x position so the indices in QObject::connect corresponds to sorted list.
So at first, we need to find all bars in a chart and cast the into QGraphicsRectItem and sort them.
void sortGraphicItems( std::vector<std::pair<float,QGraphicsRectItem*> > &item_list){
for(int i = 0; i<this->items().size();i++){
if(w->items().at(i)->flags().testFlag(QGraphicsItem::ItemIsSelectable)){ //This selects all selectable items
QGraphicsRectItem *it = qgraphicsitem_cast<QGraphicsRectItem *>(this->items().at(i));
if (!it) //if the graphic object is not type of QGraphicsRectItem
continue;
item_list.push_back( std::make_pair(it->rect().x(), it) );
}
}
std::sort(item_list.begin(),item_list.end());
}
and then just do the same but use index of QBarset.
QObject::connect(set0, &QBarSet::hovered, [&w](bool status, int ind){
if(status){
std::vector<std::pair<float,QGraphicsRectItem*> > item_list;
sortGraphicItems(item_list);
QGraphicsRectItem *rect = item_list.at(ind).second;
//change colour of rect
}
else{
//change rect colour back
}
It is not currently possible to change the color of the column individually, so I will show a workaround. This consists of placing a new item on top of the hovered item as shown below:
#include <QApplication>
#include <QtCharts>
QT_CHARTS_USE_NAMESPACE
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QChartView w;
QBarSet *set0 = new QBarSet("bar1");
*set0 << 1 << 4 << 3 << 7 << 2 << 5 << 1 << 3 << 3 << 2 << 1 << 6 << 7 << 5;
QBarSeries *series = new QBarSeries;
series->append(set0);
QChart *chart= new QChart;
w.setChart(chart);
chart->addSeries(series);
w.show();
QGraphicsRectItem hoverItem;
hoverItem.setBrush(QBrush(Qt::red));
hoverItem.setPen(Qt::NoPen);
QObject::connect(set0, &QBarSet::hovered, [&w, &hoverItem](bool status, int /*index*/){
QPoint p = w.mapFromGlobal(QCursor::pos());
if(status){
QGraphicsItem *it = w.itemAt(p);
hoverItem.setParentItem(it);
hoverItem.setRect(it->boundingRect());
hoverItem.show();
}
else{
hoverItem.setParentItem(nullptr);
hoverItem.hide();
}
});
return a.exec();
}

How to automatically increase/decrease text size in label in Qt

I have a Qt application where I have a textedit and a label. When a user presses the button, the textedit text should be displayed on label. For the label I have set few properties like word wrap is enabled and horizontal and vertically it is aligned center. Below is the screenshot :
Now I have to automatically adjust the size of the text in label so that if someone enters a large string, then it should fit inside the label, that means size of text should decrease. And if the text string is small, then size should increase automatically to fill up the complete label. Currently if I am typing it the large string, it looks like something:
As you can see, in the above image, text is moving out of the label. It should remain inside the label.
How to detect in application if the text is moving out of the label height & width. Then how to reduce the text size. I want the size to automatically increase if the string is small and decrease it string is large to fill up the complete label. Is there any class or something provided in QT. Any help or example please. Thanks.
EDIT: With the below code I am able to reduce the size of text to fit inside the label width but not able to make the text multi line.
QString string = ui->textEdit->toPlainText(); //Getting data from textEdit
ui->label->setAlignment(Qt::AlignCenter); //Aligning label text to center
QFont f("Arial",50); //Setting the default font size to 50
QFontMetrics fm(f);
ui->label->setFont(f); //Setting the font to the label
int width = fm.width(string); //Getting the width of the string
int size;
while(width >= 870) //870 is the max width of label
{
size = ui->label->font().pointSize()-1; //Reduce font size by 1
QFont newFont("Arial",size);
QFontMetrics nfm(newFont);
ui->label->setFont(newFont); //Set the new font with new size
width = nfm.width(string); //Get the new width
}
ui->label->setText(string);
You (S. Andrew) solved it a little bit different like I proposed (just a statement but not critics). You did the word wrapping by yourself.
I wrote a minimal complete application to check how the Qt internal word wrapping can be used for your problem:
// standard C++ header:
#include <iostream>
#include <string>
// Qt header:
#include <QApplication>
#include <QBoxLayout>
#include <QFrame>
#include <QGroupBox>
#include <QLabel>
#include <QLineEdit>
#include <QMainWindow>
#include <QStyle>
using namespace std;
class Label: public QLabel {
public:
void layout();
QRect documentRect(); // borrowed from QLabelPrivate
protected:
virtual void resizeEvent(QResizeEvent *pQEvent);
};
QRect Label::documentRect()
{
QRect rect = contentsRect();
int m = margin(); rect.adjust(m, m, -m, -m);
layoutDirection();
const int align
= QStyle::visualAlignment(layoutDirection(), QLabel::alignment());
int i = indent();
if (i < 0 && frameWidth()) { // no indent, but we do have a frame
m = fontMetrics().width(QLatin1Char('x')) / 2 - m;
}
if (m > 0) {
if (align & Qt::AlignLeft) rect.setLeft(rect.left() + m);
if (align & Qt::AlignRight) rect.setRight(rect.right() - m);
if (align & Qt::AlignTop) rect.setTop(rect.top() + m);
if (align & Qt::AlignBottom) rect.setBottom(rect.bottom() - m);
}
return rect;
}
void Label::layout()
{
// get initial settings
QString text = this->text();
QRect rectLbl = documentRect(); // wrong: contentsRect();
QFont font = this->font();
int size = font.pointSize();
QFontMetrics fontMetrics(font);
QRect rect = fontMetrics.boundingRect(rectLbl,
Qt::TextWordWrap, text);
// decide whether to increase or decrease
int step = rect.height() > rectLbl.height() ? -1 : 1;
// iterate until text fits best into rectangle of label
for (;;) {
font.setPointSize(size + step);
QFontMetrics fontMetrics(font);
rect = fontMetrics.boundingRect(rectLbl,
Qt::TextWordWrap, text);
if (size <= 1) {
cout << "Font cannot be made smaller!" << endl;
break;
}
if (step < 0) {
size += step;
if (rect.height() < rectLbl.height()) break;
} else {
if (rect.height() > rectLbl.height()) break;
size += step;
}
}
// apply result of iteration
font.setPointSize(size);
setFont(font);
}
void Label::resizeEvent(QResizeEvent *pQEvent)
{
QLabel::resizeEvent(pQEvent);
layout();
}
int main(int argc, char **argv)
{
cout << QT_VERSION_STR << endl;
// main application
#undef qApp // undef macro qApp out of the way
QApplication qApp(argc, argv);
// setup GUI
QMainWindow qWin;
QGroupBox qGBox;
QVBoxLayout qBox;
Label qLbl;
qLbl.setFrameStyle(Label::Box);
qLbl.setFrameShadow(Label::Sunken);
qLbl.setWordWrap(true);
qBox.addWidget(&qLbl, 1);
QLineEdit qTxt;
qBox.addWidget(&qTxt, 0);
qGBox.setLayout(&qBox);
qWin.setCentralWidget(&qGBox);
qWin.show();
// install signal handlers
QObject::connect(&qTxt, &QLineEdit::editingFinished,
[&qTxt, &qLbl]() {
QString text = qTxt.text();
qLbl.setText(text);
qLbl.layout();
});
return qApp.exec();
}
Compiled and tested with VS2013 / Qt 5.6 on Windows 10 (64 bit):
When playing around with this test application, I recognized that the text fits not everytimes perfectly into the QLabel. I tried to improve the code exchanging QRect rectLbl = rect(); with QRect rectLbl = contentsRect();. This made it better but still not perfect. It seems there is some finetuning necessary (where the development starts to become effort). (See update at end of text.)
Actually, it would not be necessary to derive QLabel. In my first implementation, layout() was a function with QLabel& and const QString& as parameters.
After I got the font size management working, I intended to consider resize events also. Googling a little bit, I found the solution to apply event filters. However, event filters are called before the event is processed but I need after. Finally, I decided to inherit QLabel and to overload QLabel::resizeEvent() to keep things simple.
Btw. I noticed it is even not necessary to set
height eventually to a very large value
as I suggested in a comment earlier. It seems that QFontMetrics::boundingRect(const QRect &rect, int flags, ...) increases the height automa[gt]ically to keep required width when Qt::TextWordWrap is enabled.
Update:
#annacarolina encouraged me to investigate a little bit deeper into this issue that font size is sometimes choosen to large. Some debugging in Label::layout() uncovered that sometimes computed rect looked like unwrapped text where visual output was wrapped. This made me suspiciuous about correctness of the rectLbl. Thus, I started in qlabel.cpp on woboq.org but actually the Qt forum QLabel: Resize font to contentsRect provided the final hint which leaded me to QLabelPrivate::documentRect() (actually again on woboq.org where I already had looked for enlightment). Thus, I added a method Label::documentRect() to my class. This makes results much better (although I'm not fully convinced about "perfect").
In the following code, I am making a logic where I am first getting all the words in the string. Then I am appending the words in QList<QString> data and checking if the width of the appended words is smaller than then width of the label. If the width goes above the width of label then I break it using \n. So in this way I made a list of the sub strings whose total width is around the width of the label and saved it in the List. Then I am calculating the width of sub strings stored in the list and then decreasing its font size till its total width is less than width of the label. After this I am displaying it on the label.
QList<QString> data;
CountWords Word;
ui->label->clear();
QString string = ui->textEdit->toPlainText(); //Getting data from textEdit
QStringList count = Word.GetWords(string); //Here I get the list of words in string
ui->label->setAlignment(Qt::AlignCenter); //Aligning label text to center
QFont f("Arial",50); //Setting the default font size to 50
QFontMetrics fm(f);
ui->label->setFont(f); //Setting the font to the label
int size,fontSize;
QString temp = ui->label->text();
int last = count.size();
//Saving the words in QList
for(int i=0;i<count.size();i++)
{
temp.append(count[i]+" ");
size = fm.width(temp);
if(size > 870)
{
temp.append("\n");
data << temp;
//data.append(temp);
temp.clear();
}
if((last-1)==i)
{
subString.append("\n");
data << subString;
subString.clear();
}
}
//decreasing the font size
QList<int> wide;
for(int i=0;i<data.size();i++)
{
wide << fm.width(data[i]);
while(wide[i] >= 870)
{
fontSize = ui->label->font().pointSize() - 1;
QFont newFont("Arial",fontSize);
QFontMetrics nfm(newFont);
ui->label->setFont(newFont);
wide[i] = 0;
wide[i] = nfm.width(data[i]);
}
}
//Finally displaying it on label
QString labelData;
for(int i=0;i<data.size();i++)
{
labelData = ui->label->text();
labelData.append(data[i]);
ui->label->setText(labelData);
}
After struggling with this issue, I create DynamicFontSizeLabel and DynamicFontSizePushButton widgets. Hope it helps.
https://github.com/jonaias/DynamicFontSizeWidgets/
Thanks Scheff for some inspiration.

Add a tiled base map to a GraphicsScene

I try to create a tiled base map by using Qt GUI:
I have two classes: one to set my tile images that inherits from QGraphicPixmapItem, one to load my map from a text file that is a 2D array composed of the numbers 1, 0 and 2, and add it to a scene:
But my program quits unexpectedly and I do not know where is the break:
my tile class:
class mapTile: public QGraphicsPixmapItem
{
public:
enum Tile {DOOR, GRASS, FIRE};
mapTile(): QGraphicsPixmapItem(),currentTile(GRASS){
syncBitmap();
}
Tile getTile() const {return currentTile;}
void setTile(Tile newTile){
if(currentTile!= newTile){
currentTile=newTile;
syncBitmap();
}
}
private:
void syncBitmap() {//Set my image to my tile
switch(currentTile) {
case DOOR:
image->setPixmap(QPixmap(":/mpa/castledoors.png"));
case GRASS:
image->setPixmap(QPixmap(":/map/grass3_blur.jpg"));
case FIRE:
image->setPixmap(QPixmap(":/map/feu1/png"));
}
}
Tile currentTile;
QGraphicsPixmapItem *image;
};
my class map:
Map.h:
class Map
{
public:
static const int TILE_SIZE=20; //value of the tile in pixels!!!->20X20=a tile
void paintMap(QGraphicsScene *scene);
Map();
private:
static const int WIDTH= 13;// width of the grid cell
static const int HEIGHT= 9;//height of the grid cell
//my grid cell map !!!!
int array[WIDTH][HEIGHT];
mapTile *tile; //my tile
};
And Map.cpp
/*Create a default Map*/
Map::Map()
{
QFile myfile("path to my file");
if (!myfile.open(QIODevice::ReadOnly | QIODevice::Text))
{
QMessageBox::information(0, "error", myfile.errorString());
}
QTextStream in(&myfile);
QString line ;
QStringList fields;
int i=0;
int j=0;
while (!in.atEnd()) {
//Fill my array with my list--> convert Qtring into Int
for (i=0; i<HEIGHT; i++ ) {
line = in.readLine();
fields = line.split(" ");
for(j=0;j<WIDTH;j++)
{
//cout<<fields[j].toInt();
array[i][j] = fields[j].toInt();
}
}
}
}
//Paint my map with my tile
void Map::paintMap(QGraphicsScene *scene){
int i=0, j=0;
tile= new mapTile();
for (i=0; i<HEIGHT; i++){
for(j=0; j<WIDTH; j++){
switch(array[i][j]){
case 0:
tile->setTile(mapTile::GRASS);
tile->setPos(i,j);
scene->addItem(tile);
j+=TILE_SIZE;
case 1:
tile->setTile(mapTile::FIRE);
tile->setPos(i,j);
scene->addItem(tile);
j+=TILE_SIZE;
case 2:
tile->setTile(mapTile::DOOR);
tile->setPos(i,j);
scene->addItem(tile);
j+=TILE_SIZE;
}
i+=TILE_SIZE;//
}
}
}
and finally my mainwindow (I directly give the file.cpp):
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
myMap= new Map;
scene= new QGraphicsScene(this);
myMap->paintMap(scene);
ui->graphicsView->setScene(scene);
}
MainWindow::~MainWindow()
{
delete ui;
}
Sorry guys for the long post but I am stuck!
Any ideas on what I missed?
Well, there are at least five problems in your code:
You can't share the same myTile instance when adding them to the scene. You need to create a new one each time you're adding a tile to the scene. At the moment you're creating only one instance at the start of Map::paintMap.
Your myTile class inherits from QGraphicsPixmapItem, however it also has a QGraphicsPixmapItem *image; member which it leaves uninitialized (so it's a roaming pointer), but then it uses it in image->setPixmap(QPixmap(":/mpa/castledoors.png")); which will crash. Instead, you'll want to just call setPixmap(QPixmap(":/mpa/castledoors.png")) (calling the function of your superclass).
In the above item you may have noticed that you misspelled "map" as "mpa", though this will not be the reason of your crash.
In mapTile::syncPixmap as well as in Map::paintMap, you forgot the break; statements in the switch. Without those, all tiles would appear like fire tiles.
You're adding your tile size to the i and j iterators, as if they were the pixel coordinates, but at the same time you're using them as array indexes. You need to either use separate variables for the pixel coordinates or multiply i and j by the TILE_SIZE when calling setPos instead.
Good luck!
I have finally found solutions to my problems:
I added a scene to my function paintMap for adding each item to my scene and created a QGraphicScene* getScene() that returns the scene then I just call it my mainWindow by just writing
ui->graphicsView->setScene(myMap->getScene())
Moreover in my mapTile class I don't use anymore the function setTile(). I removed it, then changed my QGraphicPixmapItem *image by a QPixmap image and in my function syncbitmap() I did
this->setPixmap(image.scaled(TILE_SIZE,TILE_SIZE));at the end of my switch! Why? Because my tile is already a QGraphicsPixmapItem (inheritance) so I just have to set a pixmap to it! And I put it at the end of the switch because if I set the image before, the image will never change because it will be already set! And finally in the constructor I removed currentTile(GRASS), added a Tile variable in my constructor as an argument and just put currentTile= name of your variable in the constructor and then called the syncBitmap function. So in paintMap() you just have to do:
tile= new mapTile(mapTile::GRASS);
tile->setPos(j*TILE_SIZE,i*TILE_SIZE);
scene->addItem(tile);
in each case! VoilĂ !
I hope that will help a newbie like me, at least to understand the logic behind! I am not sure that is the best way to do but this way works for me! :)

Painting in graphicsview

I am using the graphics view to paint the graphicsitem in it. Earlier when I clicked the button the respective item was painted only once, to again paint the same entity I had topush the button again. To overcome this I constructed the signal to allow to add the entities multiple times without having the need to push the button again. But when I using vector to store the points.It does not append, limiting its capacity to 2 only. Following is my output and the code
circle.cpp
void circle::mousePressEvent(QGraphicsSceneMouseEvent *e)
{
if(e->button()==Qt::LeftButton) {
if(mFirstClick){
x1 = e->pos().x();
y1 = e->pos().y();
mFirstClick = false;
mSecondClick = true;
}
else if(!mFirstClick && mSecondClick){
x2 = e->pos().x();
y2 = e->pos().y();
mPaintFlag = true;
mSecondClick = false;
update();
emit DrawFinished();
_store.set_point(e->pos());
store_point.push_back(_store);
qDebug() << _store.getValue();
qDebug() << "Size of vector =" << store_point.size() << "and" << store_point.capacity();
update();
}
}
mainwindow.cpp
void MainWindow::drawCircle(){
item2 = new circle;
scene->addItem(item2);
qDebug() << "Circle Created";
connect(item2, SIGNAL(DrawFinished()), this, SLOT(drawCircle()));
}
output
Circle Created
QPointF(60, 87)
Size of vector = 1 and 1
Circle Created
QPointF(77, 221)
Size of vector = 2 and 2
QPointF(333, 57)
Size of vector = 1 and 1
When I remove the signal DrawFinished(), the points store perfectly but the item gets painted only once. I need to pushthe button again:(. Following is the output after removing the signal.
QPointF(74, 80)
Size of vector = 1 and 1
QPointF(118, 165)
Size of vector = 2 and 2
QPointF(335, 97)
Size of vector = 3 and 4
What needs to be done to perfectly store the points as well as allow repainting. Please do help me to sort out all this.
Well, not sure if this would answer your request but a comment is too small to write what i want to tell you.
I don't really get what is the purpose of your signal DrawFinished(). Even if it's obvious thanks to the name, I don't think you need it.
If I sum up what you really want, you have a QGraphicView where you want to draw some shapes. Next to it, you have at least one (let's say 3) buttons to select which shapes you want to draw (Circle, Triangle, Rectangle).
Lets say you want to draw some circles, you click on the CircleButton, and then, click on the QGraphicView.
To my mind, I would create something like this:
Two classes, MainWindow and View, view which inherits from QGraphicView. Your three buttons are defined with Qt designer in your MainWindow class. So When you click on a button, you can emit a signal to notify the View.
In the View class you could have one vector for each shapes.
MainWindow.h
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
signals:
void drawCircle();
void drawRectangle();
void drawTriangle();
private:
Ui::MainWindow *ui;
View view;
private slots:
void slotOnCircleButton();
void slotOnRectangleButton();
void slotOnTriangleButton();
};
MainWindow.cpp
[...]
void MainWindow::slotOnCircleButton()
{
emit(drawCircle());
}
[...]
View.h
class View : public QGraphicsView
{
Q_OBJECT
public:
explicit View(QWidget *parent = 0);
enum DrawingMode
{
UNDEFINED,
CIRCLE,
TRIANGLE,
RECTANGLE
}
signals:
public slots:
void slotOnDrawCircle();
void slotOnDrawRectangle();
void slotOnDrawTriangle();
private:
DrawingMode mode;
QVector<QPointF> vectorCircle;
QVector<QPointF> vectorTriangle;
QVector<QPointF> vectorRectangle;
};
View.cpp
[...]
void View::slotOnDrawCircle()
{
this->mode = CIRCLE;
}
[...]
void View::mousePressEvent(QGraphicsSceneMouseEvent *e)
{
if(e->button()==Qt::LeftButton && this->mode != UNDEFINED)
{
qreal x1 = e->pos().x();
qreal y1 = e->pos().y();
if(this->mode == CIRCLE)
{
this->vectorCircle.append(e->pos());
}
else if(...)
[...]
// updatePainting();
}
}
When updating the view, you just have to travel throw your 3 vectors and drawing circle, rectangle or triangle.
This way you don't have such a spaghetti code, it's quite clear.
I didn't run the code so there is probable some minor mistakes, don't forget to make your connections and your initializations.

How to access a variable from another class in Qt?

I am trying to implement in Qt a main window which has 2 widgets: one area where I draw some points and one list box where I write all the points with their respective coordinates. And I would like to implement the function "delete point" of a button on the main window, i.e. when I press the button then the point selected from the list box should disappear from my area where I am drawing. So I was thinking of doing this with signals/slots, but when I try to gain access to my list of points from my drawing area it just doesn't find any containing data. This is my code until now:
paintwidget.cpp (my main window):
PaintWidget::PaintWidget(QWidget parent) :
QWidget(parent),
ui(new Ui::PaintWidget)
{
area = new RenderArea(this);
ui->setupUi(this);
connect(ui->displayWidget, SIGNAL(listUpdated(QList)), ui->pointsListWidget,
SLOT(onListUpdated(QList*)));
connect(ui->deletePoints, SIGNAL(clicked()), this, SLOT(deleteItem()));
}
void PaintWidget::deleteItem()
{
area->deletePoint(ui->pointsListWidget->currentItem());
}
renderarea.cpp (my drawing area):
void RenderArea::mousePressEvent(QMouseEvent *e)
{
point = e->pos();
updateList(point);
this->update();
}
void RenderArea::updateList(const QPoint& p)
{
list.append(p);
if (list.count()>1)
lineAdded(p);
emit listUpdated(&list);
}
void RenderArea::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
painter.setPen(QPen(Qt::black,2));
for (int i = 0; i < list.size(); ++i)
painter.drawPoint(list[i]);
if (list.size()>1)
for(int j = 0; j < list.size()-1; ++j)
painter.drawLine(list[j], list[j+1]);
}
void RenderArea::deletePoint(QListWidgetItem *item)
{
bool ok1;
bool ok2;
int index = item->text().indexOf(",");
int x = item->text().left(index).toInt(&ok1, 10);
int y = item->text().mid(index + 1).toInt(&ok2, 10);
for (int i = 0; i < list.size(); ++i)
//find the point with x and y as coordinates and delete it
}
listbox.cpp:
void ListBox::onListUpdated(QList *list)
{
clear();
for (int i = 0; i < list->size(); ++i)
addItem(new QListWidgetItem(QString::number(list->at(i).x()) + ", " +
QString::number(list->at(i).y())));
}
The list from the render area is a QList of QPoints. The problem is that in the FOR-loop the size of the list is 0 so I cannot see any of the points that it should contain. I think that I am failing to initialize it somewhere but I am not sure where.
The points are drawn with QPainter so when I delete the point from the list is there any possibility to delete them from my drawing area also?
I'm suspecting you've got two RenderArea widgets hanging around for some reason.
You're connecting ui->displayWidget's signal, but acting on the area widget for the delete.
Shouldn't you be calling ui->displayWidget->deletePoint or connecting area's signal?
As for the repaint, you should call the widget's update() method to have it repaint itself.