QT & VTK Interaction Style - c++

I am using VTK as qvtkwidget to display 3D objects in QT.
I know I can change its interaction style by making my own modified interaction style.
I wanna rotate and move an object by right click and holding left click respectively.
And tried to make the modified interaction style class in header file, but there are some difficulties using functions declared in other classes in the modified interaction style. This should be simple.
Does anyone know about this?
boxWidget.at(boxWidget.count()-1)->SetInput(reader->GetOutput());
boxWidget.at(boxWidget.count()-1)->SetScalingEnabled(0); //turn this on to be able to resize the model
boxWidget.at(boxWidget.count()-1)->SetRotationEnabled(0);
boxWidget.at(boxWidget.count()-1)->SetInteractor(this->ui->qvtkWidget->GetInteractor());
boxWidget.at(boxWidget.count()-1)->GetInteractor()->SetInteractorStyle(MyStyle::New()); /
BoxWidget is the object I am trying to apply 'MyStyle'
And the following is the MyStyle class
class MyStyle : public vtkInteractorStyleTrackballCamera , public MainWindow
{
private:
unsigned int NumberOfClicks;
int PreviousPosition[2];
int ResetPixelDistance;
public:
static MyStyle *New();
vtkTypeMacro(MyStyle, vtkInteractorStyleTrackballCamera);
PeterStyle() : NumberOfClicks(0) , ResetPixelDistance(5)
{
this->PreviousPosition[0] = 0;
this->PreviousPosition[1] = 0;
}
virtual void OnLeftButtonDown()
{
qDebug() << "My Style Mouse Left Clicked";
//std::cout << "Pressed left mouse button." << std::endl;
this->NumberOfClicks++;
//std::cout << "NumberOfClicks = " << this->NumberOfClicks << std::endl;
int pickPosition[2];
this->GetInteractor()->GetEventPosition(pickPosition);
int xdist = pickPosition[0] - this->PreviousPosition[0];
int ydist = pickPosition[1] - this->PreviousPosition[1];
this->PreviousPosition[0] = pickPosition[0];
this->PreviousPosition[1] = pickPosition[1];
int moveDistance = (int)sqrt((double)(xdist*xdist + ydist*ydist));
// Reset numClicks - If mouse moved further than resetPixelDistance
if(moveDistance > this->ResetPixelDistance)
{
this->NumberOfClicks = 1;
}
if(this->NumberOfClicks == 2)
{
vtkSmartPointer<vtkCamera> camera =
vtkSmartPointer<vtkCamera>::New();
camera->SetPosition(140.0, 155.0, 590.0);
camera->SetFocalPoint(140.0, 155.0, 0.0);
camera->SetClippingRange(590.0, 600.0);
this->GetCurrentRenderer()->SetActiveCamera(camera);
qDebug() << "Double clicked.";
this->NumberOfClicks = 0;
}
// forward events
vtkInteractorStyleTrackballCamera::OnLeftButtonDown();
}
I don't know what to change to move objects with leftclick.

If you're looking only to move objects with left buttons, you don't need to subclass your own interaction style, vtk already implements vtkInteractorStyleTrackballActor style that allows controlling actors by mouse, without interacting with camera. You can then override it to do whatever you want.

Related

Qt5 C++ resize font based on label width

I have written what I think is a pretty good font resize algorithm that I use on labels in a QGridLayout. The algorithm works rather well on its own. However, the first time I run the algorithm, the layout hasn't been painted yet, and as such, the grid widgets which own the labels haven't been sized to the layout, which means my width() call is wrong, though I have assigned the label text prior to the resize (which doesn't matter). My question is, what is the best way to know the bounding rectangles have been created to fit the layout so my calculation is on the actual label width?
Note, this is Qt5.7, so I know that fm.width() is obsolete, but that's what's available in Raspbian.
My app is reading Sonos metadata and showing it on a 7" RPi display. I'm resizing the metadata that is too long to fit in the layout grids. So, I have a QStackedLayout with a set of layouts to show (clock when Sonos isn't playing, metadata when it is), and I frequently poll the a local Sonos server to find out if there is metadata to show. This works well. What doesn't happen is that on the first time the Sonos metadata layout is shown, the QGridLayout hasn't actually been laid out yet (I believe), so the labels are too big. At some point after the first time I fill in the metadata, the grid layout gets "shown" and the labels are then the correct size. The problem is, by then, it's all done setting metadata and the labels look funny in some cases.
int pointSize = FontSize::Default;
for (auto label : m_labels) {
QFont f = label->font();
if (label->accessibleName() == "title")
pointSize = FontSize::Title;
QFontMetrics fm(f);
if (fm.width(label->text()) > label->width()) {
float factor = (float)label->width() / (float)fm.width(label->text());
if (factor <= .6) {
factor = .6;
}
f.setPointSizeF((f.pointSize() * factor) * .9);
qDebug() << __FUNCTION__ << ": label width:" << label->width();
qDebug() << __FUNCTION__ << ": font width:" << fm.width(label->text());
qDebug() << __FUNCTION__ << ": Calculated font scaling factor to be" << (float)(factor * .9);
qDebug() << __FUNCTION__ << ": Reset font size for text\"" << label->text() << "\" to" << f.pointSize();
}
else
f.setPointSize(pointSize);
label->setFont(f);
}
On an 800x480 display, this results in the first event label width being wrong
calculateLabelFontSize : label width: 640
calculateLabelFontSize : font width: 2051
The label width the next time it's called would end up being correct with a width of 584. However, because I don't always call it a second time, and because sometimes, the Sonos metadata hiccups and causes the display to revert, it may always be wrong, or it may just be right the first time no matter what.
I've considered trying to overload my gui app paintEvent(QPaintEvent *event), but the QPaintEvent class doesn't give me a way to determine which widget called for the event, so I can't just ignore the event until I want it. At least, not that I have determined, so if that's possible, please let me know.
I've tried overloading showEvent, but that doesn't run except when the frame is first shown, so no help there.
I've considered subclassing QGridLayout to simply overload the paintEvent() and use that, but I do have an issue with that solution. There is a progress bar in this layout, which means that the paintEvent is going to fire every half second as I get metadata from the Sonos server. Since I don't know if the paintEvent is firing for the time update, or for the text being set, I get a lot of resize attempts.
I've also simply run the algorithm a second time on the next event, but it's a really weird looking graphical hiccup when the fonts realign, and I don't like it.
Last up, I may just subclass QLabel, simply to overload paintEvent, and use that custom label just for the labels that would be resizable. However, is the label in the layout painted before or after the container it lives in inside the layout? I'll be testing this today I think, but I'm not convinced it would work.
I figure this has a much easier solution though. I just can't figure it out.
I borrowed code from https://github.com/jonaias/DynamicFontSizeWidgets which provided the resize algorithm. It's nice, but iterative. I have been tinkering with a math based solution to calculate instead of just trying to fit by resizing until it works. However, that solution still doesn't work well enough to use. This isn't slow, and does what is implied. It resizes the font until it fits.
This all is done in paintEvent() because using resizeEvent() may result in either an infinite resize loop or the layout may resize itself and suddenly everything looks shifted/squished/stretched. Doing it in paintEvent means it's only called once every time you update the text, but not otherwise.
I may come back to this to try to find a math based solution. I still think this isn't the correct way, but I found something that worked well enough for now.
NewLabel.h
#ifndef NEWLABEL_H
#define NEWLABEL_H
#include <QtCore/QtCore>
#include <QtWidgets/QtWidgets>
#include <cmath>
class NewLabel : public QLabel
{
Q_OBJECT
public:
explicit NewLabel(const QString &text, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
explicit NewLabel(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
virtual ~NewLabel() {}
void setDefaultPointSize(int p)
{
m_defaultPointSize = static_cast<float>(p);
}
protected:
void paintEvent(QPaintEvent *e) override;
private:
float getWidgetMaximumFontSize(QWidget *widget, QString text);
float m_defaultPointSize;
};
#endif
NewLabel.cpp
#define FONT_PRECISION (0.5)
NewLabel::NewLabel(const QString &text, QWidget *parent, Qt::WindowFlags f) : QLabel(text, parent, f)
{
m_defaultPointSize = 12;
}
NewLabel::NewLabel(QWidget *parent, Qt::WindowFlags f) :
QLabel(parent, f)
{
m_defaultPointSize = 12;
}
void NewLabel::paintEvent(QPaintEvent *e)
{
QFont newFont = font();
float fontSize = getWidgetMaximumFontSize(this, this->text());
if (fontSize < m_defaultPointSize) {
newFont.setPointSizeF(fontSize);
setFont(newFont);
}
QLabel::paintEvent(e);
}
float NewLabel::getWidgetMaximumFontSize(QWidget *widget, QString text)
{
QFont font = widget->font();
const QRect widgetRect = widget->contentsRect();
const float widgetWidth = widgetRect.width();
const float widgetHeight = widgetRect.height();
QRectF newFontSizeRect;
float currentSize = font.pointSizeF();
float step = currentSize/2.0;
/* If too small, increase step */
if (step<=FONT_PRECISION){
step = FONT_PRECISION*4.0;
}
float lastTestedSize = currentSize;
float currentHeight = 0;
float currentWidth = 0;
if (text==""){
return currentSize;
}
/* Only stop when step is small enough and new size is smaller than QWidget */
while(step>FONT_PRECISION || (currentHeight > widgetHeight) || (currentWidth > widgetWidth)){
/* Keep last tested value */
lastTestedSize = currentSize;
/* Test label with its font */
font.setPointSizeF(currentSize);
/* Use font metrics to test */
QFontMetricsF fm(font);
/* Check if widget is QLabel */
QLabel *label = qobject_cast<QLabel*>(widget);
if (label) {
newFontSizeRect = fm.boundingRect(widgetRect, (label->wordWrap()?Qt::TextWordWrap:0) | label->alignment(), text);
}
else{
newFontSizeRect = fm.boundingRect(widgetRect, 0, text);
}
currentHeight = newFontSizeRect.height();
currentWidth = newFontSizeRect.width();
/* If new font size is too big, decrease it */
if ((currentHeight > widgetHeight) || (currentWidth > widgetWidth)){
//qDebug() << "-- contentsRect()" << label->contentsRect() << "rect"<< label->rect() << " newFontSizeRect" << newFontSizeRect << "Tight" << text << currentSize;
currentSize -=step;
/* if step is small enough, keep it constant, so it converge to biggest font size */
if (step>FONT_PRECISION){
step/=2.0;
}
/* Do not allow negative size */
if (currentSize<=0){
break;
}
}
/* If new font size is smaller than maximum possible size, increase it */
else{
//qDebug() << "++ contentsRect()" << label->contentsRect() << "rect"<< label->rect() << " newFontSizeRect" << newFontSizeRect << "Tight" << text << currentSize;
currentSize +=step;
}
}
return lastTestedSize;
}

Cocos2dx v.3.0 Portrait Mode

Hey I am developing a Cocos2dx application with XCode and iPhone 5c and I am looking to change the coordinate system to portrait.
I looked at these directions http://www.cocos2d-x.org/wiki/Device_Orientation. According to the direction, you need to change a couple of methods in the RootViewController so that before Cocos does its magic, iOS has already handled rotating the container.
Now the applicable methods of my RootViewController.mm look as follows. It builds and runs.
#ifdef __IPHONE_6_0
- (NSUInteger) supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}
#endif
- (BOOL) shouldAutorotate {
return YES;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return UIInterfaceOrientationIsPortrait ( interfaceOrientation );
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
auto glview = cocos2d::Director::getInstance()->getOpenGLView();
if (glview)
{
CCEAGLView *eaglview = (__bridge CCEAGLView *)glview->getEAGLView();
if (eaglview)
{
CGSize s = CGSizeMake([eaglview getWidth], [eaglview getHeight]);
cocos2d::Application::getInstance()->applicationScreenSizeChanged((int) s.width, (int) s.height);
}
}
}
#end
The code for the init() method of my level looks like this
bool Level1::init()
{
//////////////////////////////
// 1. super init first
if ( !Scene::init() )
{
return false;
}
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
for(int i= 0; i < MAX_TOUCHES; ++i) {
labelTouchLocations[i] = Label::createWithSystemFont("", "Arial", 42);
labelTouchLocations[i]->setVisible(false);
this->addChild(labelTouchLocations[i]);
}
auto eventListener = EventListenerTouchAllAtOnce::create();
// Create an eventListener to handle multiple touches, using a lambda, cause baby, it's C++11
eventListener->onTouchesBegan = [=](const std::vector<Touch*>&touches, Event* event){
// Clear all visible touches just in case there are less fingers touching than last time
std::for_each(labelTouchLocations,labelTouchLocations+MAX_TOUCHES,[](Label* touchLabel){
touchLabel->setVisible(false);
});
// For each touch in the touches vector, set a Label to display at it's location and make it visible
for(int i = 0; i < touches.size(); ++i){
labelTouchLocations[i]->setPosition(touches[i]->getLocation());
labelTouchLocations[i]->setVisible(true);
labelTouchLocations[i]->setString("Touched");
std::cout << "(" << touches[i]->getLocation().x << "," << touches[i]->getLocation().y << ")" << std::endl;
}
};
_eventDispatcher->addEventListenerWithSceneGraphPriority(eventListener, this);
return true;
}
When I touch the point at the lowest left part of the iPhone, the x coordinate of the touch point I print out is about 150. The y coordinate is 0, as expected. Why is the x coordinate not zero? Is there something I am missing in the creation of the scene? I believe I made all the changes that the Cocos documentation requires. Are their docs out of date?
The coordinate system does not start at (0,0) at the bottom left as would be suggested by the documentation. However, there are simple methods that can be used to get the origin and size of the container that are included in the default scene.
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();

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.

Qt Resizing a QMainWindow with a Splitter

I have a QMainWindow with:
Two widgets in a horizontal splitter. "m_liner" is on the right side
Both widgets have a minimum size of say, 300 pixels.
A checkbox to hide/show the right-side widget m_liner.
I want the overall QMainWindow to expand when showing the widget, and shrink when hiding. The code below does this except:
If both widgets are shown, the minimum window size is 600 pixels.
Shrink the window to this smallest size.
Uncheck the box to hide the right-side widget.
Program hides the right-side widget.
Program calls this->resize(300, height);
The window ends up being 600 pixels wide (the minimum size with both widgets visible), instead of around 300 (the minimum size with only the left widget).
Later, I can resize the window down to 300 pixels with the mouse or another button. But it won't resize to 300 in the checkbox event, even if I call resize several times.
Does anyone have an idea how to solve this?
Critical bit of code follows, I have a full project available if you need it:
void MainWindow::on_checkBox_stateChanged(int val)
{
std::cout << "-------------------- Checkbox clicked " << val << std::endl;
bool visible = val;
QWidget * m_liner = ui->textEdit_2;
QSplitter * m_splitter = ui->splitter;
int linerWidth = m_liner->width();
if (linerWidth <= 0) linerWidth = m_lastLinerWidth;
if (linerWidth <= 0) linerWidth = m_liner->sizeHint().width();
// Account for the splitter handle
linerWidth += m_splitter->handleWidth() - 4;
std::cout << "Frame width starts at " << this->width() << std::endl;
std::cout << "Right Panel width is " << m_liner->width() << std::endl;
// this->setUpdatesEnabled(false);
if (visible && !m_liner->isVisible())
{
// Expand the window to include the Right Panel
int w = this->width() + linerWidth;
m_liner->setVisible(true);
QList<int> sizes = m_splitter->sizes();
if (sizes[1] == 0)
{
sizes[1] = linerWidth;
m_splitter->setSizes(sizes);
}
this->resize(w, this->height());
}
else if (!visible && m_liner->isVisible())
{
// Shrink the window to exclude the Right Panel
int w = this->width() - linerWidth;
std::cout << "Shrinking to " << w << std::endl;
m_lastLinerWidth = m_liner->width();
m_liner->setVisible(false);
m_splitter->setStretchFactor(1, 0);
this->resize(w, this->height());
m_splitter->resize(w, this->height());
this->update();
this->resize(w, this->height());
}
else
{
// Toggle the visibility of the liner
m_liner->setVisible(visible);
}
this->setUpdatesEnabled(true);
std::cout << "Frame width of " << this->width() << std::endl;
}
Sounds to me like there are some internal Qt events that need to get propagated before it recognizes that you can resize the main window. If this is the case then I can think of two potential solutions:
Use a queued single shot timer to call the code that resizes your window down to 300px:
m_liner->hide();
QTimer::singleShot( 0, this, SLOT(resizeTo300px()) );
or, after you hide your widget you can try a call to processEvents() (this function has potentially dangerous side effects, so use with caution):
m_liner->hide();
QApplication::processEvents();
resize( w, height() );
Another potential solution would be to set the horizontal size policy of your widget to ignored when hiding it:
m_liner->hide();
m_liner->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Preferred );
resize( w, height() );
When showing your widget again, you'd need to adjust the size policy again.

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

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.