Display gif only once - qt

I'm using gifs in my app. I'd like to only show my gif once and then make it disappear. But it keeps looping and showing again and again.
Here's what I've tried:
movie = new QMovie(":/in_game/src/countdown.gif");
processLabel = new QLabel();
this->addWidget(processLabel);
processLabel->setStyleSheet("background-color: rgba(0,0,0,0%)");
processLabel->setGeometry(280,250,128,128);
processLabel->setMovie(movie);
int i=0;
if(i<1)
{
movie->start();
i++;
}
else
{
movie->stop();
processLabel->setEnabled(false);
}
Of course in my .h I've created a QMovie and a QLabel... Any ideas on how to only display once ?

You have QMovie::frameCount() method and signal QMovie::frameChanged(). Check your current frame number and stop when current frame become equal QMovie::frameCount()
m_movie = new QMovie(":/gif/tenor.gif");
connect(m_movie, SIGNAL(frameChanged(int)),
this, SLOT(OnFrameChanged(int)));
ui->lblMovie->setMovie(m_movie);
m_movie->start();
And in slot:
void MainWindow::OnFrameChanged(int frame)
{
if (frame == m_movie->frameCount() - 1) {
m_movie->stop();
}
}

Related

Qt: QFormLayout::addRow(QWidget* label, QWidget* field) not adding a new row

I am trying to add a new row to my QFormLayout after I have loaded two QLineEdits from a file, which works, but when I run my code it doesnt add anything, ot atleast anything that I can see. And I am also not able to add any wigets using QLayout::addWidget(QWidget* widget) anymore, which I used to be able to.
Thanks
The code, where it doesnt work:
void Kegelbuch::load(QString path, QString tab) {
//Load json file
Datastream* loadStream = new Datastream;
QJsonObject data = loadStream->loadData(path);
//Declaring all important Variables
QJsonArray keglerFullName;
QJsonArray keglerShortName;
QFormLayout* formLayout = (QFormLayout*)ui.keglerTab->layout();
int defaultRows = 2, width = 155;
//Retriev arrays from file
if (data.contains("KeglerFullName") && data["KeglerFullName"].isArray()) {
keglerFullName = data["KeglerFullName"].toArray();
}
if (data.contains("KeglerShortName") && data["KeglerShortName"].isArray()) {
keglerShortName = data["KeglerShortName"].toArray();
}
//Correctly add QLineEdits to the FormLayout
for (auto names : boost::combine(keglerFullName, keglerShortName)) {
QLineEdit fullNameEdit;
QLineEdit shortNameEdit;
QJsonValue fullNameValue, shortNameValue;
boost::tie(fullNameValue, shortNameValue) = names;
if (fullNameValue.isString()) {
fullNameEdit.setText(fullNameValue.toString());
fullNameEdit.setObjectName("fullName");
fullNameEdit.setMinimumWidth(width);
}
if (shortNameValue.isString()) {
shortNameEdit.setText(shortNameValue.toString());
shortNameEdit.setMaximumWidth(width);
shortNameEdit.setObjectName("shortName");
}
/*
if (keglerFullName.at(1).isString()) {
fullNameEdit->setText(keglerFullName.at(1).toString());
fullNameEdit->setObjectName("fullName");
fullNameEdit->setMinimumWidth(width);
}
if (keglerShortName.at(1).isString()) {
shortNameEdit->setText(keglerShortName.at(1).toString());
shortNameEdit->setMaximumWidth(width);
shortNameEdit->setObjectName("shortName");
}
*/
formLayout->addRow(&fullNameEdit, &shortNameEdit);
}
}

How to limit the number of QCheckboxes checked at the same time?

I'm in the process of creating a QT application which is using multiple (14) qcheckboxes. I need to have a limit (preferably set as a variable that i can change) to the number of checkboxes that can be checked at the same time, is there any way to achieve this cleanly ? Thanks for your time.
There is no simple way of doing this, you have to write your code to do it.
I suppose you have the checkboxes in some parent widget class. So I would create a slot which looks like this.
void SomeParentWidget::onCheckBoxToggled(bool value)
{
// when we unchecked the checkbox,
// we do not need to count the number of checked ones
if (!value)
return;
int total = 0;
int limit = 15; // your "magic" number of maximum checked checkboxes
for (auto chb : allCheckBoxes()) // allCheckBoxes() is some method which returns all the checkboxes in consideration
{
if (chb->isChecked())
{
++total;
if (total > limit)
{
// too many checkboxes checked! uncheck the sender checkbox
// Note: you may want to add some nullptr checks or asserts to the following line for better robustness of your code.
qobject_cast<QCheckBox*>(sender())->setChecked(false);
return;
}
}
}
}
And when creating each of your checkboxes inside some parent widget, connect this slot to their signal:
auto chb = new QCheckBox();
connect(chb, &QCheckBox::toggled, this, &SomeParentWidget::onCheckBoxToggled);
Implementation of allCheckBoxes() is up to you, I do not know how you can retrieve the collection of all your check boxes. Depends on your design.
I found another, even simpler solution. Use this slot.
void SomeParentWidget::onCheckBoxToggled(bool value)
{
static int totalChecked = 0; // static! the value is remembered for next invocation
totalChecked += value ? 1 : -1;
Q_ASSERT(totalChecked >= 0);
int maxChecked = 15; // any number you like
if (value && totalChecked > maxChecked)
{
qobject_cast<QCheckBox*>(sender())->setChecked(false);
}
}
... and connect it to checkboxes' toggled() signal. Note that in order to work correctly, all check boxes must be unchecked at the time when you make the signal-slot connection because this function starts counting from zero (0 is the initial value of the static variable).
You can store all your checkboxes in a map (either in an std::map, an std::unordered_map or an QMap). Your keys will be your checkboxes, and your values will be their states, so something like this:
std::unordered_map<QCheckBox*, bool> m_checkBoxStates;
Here's what your connected to your toggled signal of all your checkboxes look like (keep in mind that all the signals will be connected to the same slot):
void MainWindow::onToggled(bool checked) {
QCheckBox* checkBox = sender(); //the checkbox that has been toggled
m_checkBoxStates[checkBox] = checked;
if (!checked) {
return;
}
const int count = std::count_if(m_checkBoxStates.begin(), m_checkBoxStates.end(),
[](const auto pair) {
return pair.second == true;
});
if (count > maxCount) {
checkBox->setChecked(false);
}
}

JavaFX: Convert Image to Greysacale

I need to do that in my program. I have to do it in two ways:
1.) by my own, with the following code:`
private Image convertToGrayScale(Image image) {
WritableImage result = new WritableImage((int) image.getWidth(), (int)
image.getHeight());
PixelReader preader = image.getPixelReader();
PixelWriter pwriter = result.getPixelWriter();
for (int i = 0; i < result.getWidth(); i++) {
for (int j = 0; j < result.getHeight(); j++) {
Color c = preader.getColor(i, j);
double red = (c.getRed() * 0.299);
double green = (c.getGreen() * 0.587);
double blue = (c.getBlue() * 0.114);
double sum = c.getRed() + c.getBlue() + c.getGreen();
pwriter.setColor(i , j, new Color(sum, sum, sum, 1.0));
}
}
return result;
}
2.) with the help of the openCV library, with the following code (it was copied almost perfectly from their site) :
public WritableImage loadAndConvert() throws Exception {
//Loading the OpenCV core library
System.loadLibrary( Core.NATIVE_LIBRARY_NAME );
String input = "C:/Users/Dan Ivgy/eclipse-workspace/LuckyBride/sample/20180402_170204.jpg";
//Reading the image
Mat src = Imgcodecs.imread(input);
//Creating the empty destination matrix
Mat dst = new Mat();
//Converting the image to gray scale and saving it in the dst matrix
Imgproc.cvtColor(src, dst, Imgproc.COLOR_RGB2GRAY);
// Imgcodecs.imwrite("C:/opencv/GeeksforGeeks.jpg", dst);
//Extracting data from the transformed image (dst)
byte[] data1 = new byte[dst.rows() * dst.cols() * (int)(dst.elemSize())];
dst.get(0, 0, data1);
//Creating Buffered image using the data
BufferedImage bufImage = new BufferedImage(dst.cols(),dst.rows(),
BufferedImage.TYPE_BYTE_GRAY);
//Setting the data elements to the image
bufImage.getRaster().setDataElements(0, 0, dst.cols(), dst.rows(), data1);
//Creating a WritableImage
WritableImage writableImage = SwingFXUtils.toFXImage(bufImage, null);
System.out.println("Converted to Grayscale");
return writableImage;
}
In both cases, the problem was that i have'nt got a "greyscale" output, just somthing different (and before you
asked: yeas, i have tried to to do it on several pictures, not only one)
Here's the input picture and the output picture:
Well, as you can see, this is Not a greyscale! maybe a sunset-scale..
I'd really appraciate any help, Thank you :)
(espcially if there's something faster out there since those solutions rather takes a while to run)
If someone knows, why there is not some built in option in javaFX as there is a lot of sophisticated imageview effects and this one is so simple and so prevalent.
UPDATE:
i found a website that do somthing similar to ehat i did - and somehow i got a different output! i don't get it
here's the website.
and here's the output from my computer:
my output
UPDATE#2: as #matt correctly asked, here's the code that use this method:
ImageIO.write(SwingFXUtils
.fromFXImage
(convertToGrayScale(new Image(getClass().getResource("1_CNc4RxV85YgthtvZh2xO5Q.jpeg").toExternalForm()) ), null), "jpg", file);
the original target was to show the image to rhe user, and the problem was there, so i changed the code to this one which save the image so i could isolate the problem more easly..
Ok guys, after some research, and endless number of attememts i got what i need
to solve the problem ill show my solution here and come to a coclude of my insights about this issue..
first, ill give the the disticntion between "SAVING the file" and "Setting the file in the ImageView.
when we want just to show it in image view, i have'ne expereinced any problem using almost every solution suggested here. the most simple, short and sweet (in my opinion) is the following one:
private Image convertToGrayScale(Image image) {
WritableImage result = new WritableImage((int) image.getWidth(), (int) image.getHeight());
PixelReader preader = image.getPixelReader();
PixelWriter pwriter = result.getPixelWriter();
for (int i = 0; i < result.getWidth(); i++)
for (int j = 0; j < result.getHeight(); j++)
pwriter.setColor(i , j, preader.getColor(i, j).grayscale());
return result;
}
(for convinience, i ignored exception handling)
it works fine when i use it along when i use it along with the following code:
Image img1 = convertToGrayScale(new Image(filepath);
imageView.setImage(img1);
about SAVING this output image, after some research and using #trashgold 's references, and this important one :
i got my solution as the following:
private void saveBadImage(BufferedImage originalImage, File dest) throws IOException
{
// use the following line if you want the first parameter to be a filepath to src image instead of Image itself
//BufferedImage originalImage = ImageIO.read(file);
// jpg needs BufferedImage.TYPE_INT_RGB
// png needs BufferedImage.TYPE_INT_ARGB
// create a blank, RGB, same width and height
BufferedImage newBufferedImage = new BufferedImage(
originalImage.getWidth(),
originalImage.getHeight(),
BufferedImage.TYPE_INT_RGB);
// draw a white background and puts the originalImage on it.
newBufferedImage.createGraphics().drawImage(originalImage,0,0,null);
// save an image
ImageIO.write(newBufferedImage, "jpg", dest);
}
and, i use it with the following code:
Image img1 = convertToGrayScale(new Image(filepath));
BufferedImage img2 = new BufferedImage((int) img1.getWidth(), (int) img1.getHeight(), BufferedImage.TYPE_INT_ARGB);
saveBadImage(img2, file);
and, it works perfectly!
Thank you all guys, and i hope my insights will help to some people
I decided to work in awt, then create a javafx image.
public class App extends Application {
#Override
public void start(Stage stage) {
WritableImage gray = null;
try {
BufferedImage awtImage = ImageIO.read(new URL("https://i.stack.imgur.com/ysIrl.jpg"));
gray = new WritableImage(awtImage.getWidth(), awtImage.getHeight());
BufferedImage img2 = new BufferedImage(awtImage.getWidth(), awtImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
for(int i = 0; i<awtImage.getWidth(); i++){
for(int j = 0; j<awtImage.getHeight(); j++){
int c = awtImage.getRGB(i, j);
int r = (c>>16)&255;
int g = (c>>8)&255;
int b = (c)&255;
int s = (int)(r*0.299 + g*0.587 + b*0.114);
int gr = (255<<24) + (s<<16) + (s<<8) + s;
img2.setRGB(i, j, gr);
}
}
gray = SwingFXUtils.toFXImage(img2, gray);
} catch (IOException e) {
e.printStackTrace();
}
ImageView view = new ImageView(gray);
ScrollPane pane = new ScrollPane(view);
Scene scene = new Scene(new StackPane(pane), 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
Both methods work for me, so I'm not sure if this helps you. I can save the result too.
System.out.println("saving " +
ImageIO.write(img2, "PNG", new File("tested.png")) );
If you're using oracles jdk, I think you can save JPEG. I am using OpenJDK which doesn't seem to be able to write JPEG.

How to pin a tab in Qt

Is it possible to pin a tab with Qt?
I want a tab to always stay in place (index 0) while still able to move other tabs.
So far I tried to listen to QTabBar::tabMoved and revert the move but that's too late. I don't want it even to attempt to move.
Worst case for me would be to be forced to change the mouse handling. Let me know please if there is an other way.
I have never found a nice way to do that. But, I used the fact that you can store raw data in the QTabBar to pin the tabs and undo a move if it was a pinned tab. It's not perfect and I still have some ugly behavior, but I didn't want to use mouse events, neither.
First, create a struct to store the current state of a tab:
struct PinnedTab
{
bool isPinned;
int currentIndex;
};
Q_DECLARE_METATYPE(PinnedTab); // For QVariant
Then, create a custom QTabBar to handle the move and use QTabWidget to replace the current tab bar (you have to do that before inserting the tabs):
class Bar: public QTabBar
{
public:
void pin(int const index)
{
PinnedTab info;
info.isPinned = true;
info.currentIndex = index; // TODO: move the tab to the left and do not use current index
setTabData(index, QVariant::fromValue(info));
}
Bar(QWidget* parent=nullptr): QTabBar(parent)
{}
virtual void tabLayoutChange() override
{
for (int i = 0; i != count(); ++i) // Check if a pinned tab has moved
{
if (tabData(i).isValid())
{
PinnedTab const info = tabData(i).value<PinnedTab>();
if (info.isPinned == true && i != info.currentIndex) {
rollbackLayout();
return;
}
}
}
for (int i = 0; i != count(); ++i)
{
if (tabData(i).isValid())
{
PinnedTab info = tabData(i).value<PinnedTab>();
info.currentIndex = i;
setTabData(i, QVariant::fromValue(info));
}
else
{
PinnedTab info;
info.isPinned = false;
info.currentIndex = i;
setTabData(i, QVariant::fromValue(info));
}
}
}
void rollbackLayout() {
for (int i = 0; i != count(); ++i)
{
if (tabData(i).isValid())
{
PinnedTab const info = tabData(i).value<PinnedTab>();
if (i != info.currentIndex) {
moveTab(i, info.currentIndex);
}
}
}
}
};
tabLayoutChange is called when the layout has changed. So, it will be called when you move a tab.
the rollbackLayout method is used to move each tab to the last position stored in the tab data.
Call pin to pin a tab with the given index.
I simplified my code for more clarity and you may have to redefine some behavior (for now, if you pin a tab, it will keep its current position and it will not handle the insert/remove tabs).

Qt | Creating a dynamic Chart / plot with QGraphicsView, QGraphics Scene | realtime data

I am currently developing a small programm in Qt. To show a plot you can use qwt or qcustomplot or the qpainterevent or QChart. But I am interessted in a solution for a dynamic plot which is writen with the QGraphicsView.
My preferences
-width of my chart should be constant
-realtime plotting
-the first sample should be deleted or overwriten if the end of the chart is reached, so it is a dynamic and fluent chart
My example beneath is able to be dynamic and fluent... but just for the number, which is in my if clause. I do not get why.
The idea is to delete the first lineitem, so I have constantly 99 items. If I delete a item I want to give the next item the position from the item before.
So
x=99 will be x=98 ......x=1 will be x=0;
Do I have a mistake in my idea?
I also have had several ideas, but this is probably the best.
Thanks in advance
Konrad
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
scene = new QGraphicsScene(this);
ui->graphicsView->setScene(scene);
vectorPoint = new QVector<QPoint>;
line = new QVector<QGraphicsLineItem*>;
yDatai = 0;
xDatai = 0;
Grenzenlaufvariable = 0;
timer = new QTimer(this);
timer->start(10);
connect (timer, SIGNAL(timeout()),this,SLOT(newData()));
connect(this,SIGNAL(newPaint()),this,SLOT(paint()));
}
MainWindow::~MainWindow()
{
delete ui;
delete scene;
delete vectorPoint;
delete line;
}
void MainWindow::newData()
{
if (yDatai == 100 || yDatai == -100)
{
Grenzenlaufvariable++;
}
if (Grenzenlaufvariable%2==0)
{
yDatai+=1;
}
else
{
yDatai-=1;
}
xDatai++;
point = {xDatai,yDatai};
vectorPoint->append(point);
if(vectorPoint->size()>1)
{
item = scene->addLine(QLineF(vectorPoint->at(ix-1),vectorPoint->at(ix)));
line->append(item);
}
ix++;
emit newPaint();
}
void MainWindow::paint()
{
if(line->size()==99)
{
scene->removeItem(line->at(0));
line->removeAt(0);
qDebug()<<line->size();
for (int ip= 0;ip <line->size();ip++)
{
oldx = line->at(ip)->x();
line->at(ip)->setX(oldx-1);
qDebug()<<ip;
}
}
}
So far, thats the best answer, pay attention that if you use 100Hz as samplerate, my performance is just stable with 50 samplesInView.
You can decrease the samplerate and increase the samplesInView to have more values in the plot.
Important:
xDatashort is a QVector<double> which includes all the x-values
yDatashort is a QVector<double> which includes all the y-values
both are filled with values in the programm class, this class is emitting the signal to the connection which start the slot drawGraph().
You can also just use an QVector<QPoint> which makes it more easy to handle, but its not what I want in my case.
lineVector is a QVector<QGraphicsLineItem> which inclues all the Lines from the view
xScale is used to extend the plot, yScale as well.
width is the width of the Coordinationsystem
xAxisMark is the pixeldistance between to distance marks
marksVector is a QVector<double> which includes the distance marks of the x Axis, which should be dynamic
iCurrentVectorPoint is a runtimevariable, which helps me to add the lines.
!!This code is good to use for realtime plotting, but it has not the best performance, so if anybody is having ideas to unleash potential, feel free to achieve the best answer :) !!
For further questions just comment and I will try to help you to get a nice handmade plot on your device.
void Plot::drawGraph()
{
if(programm->xDatashort.size()>1)
{
if(lineVector->size()==programm->samplesInView)
{
for (int ip =0;ip<programm->samplesInView;ip++)
{
lineVector->at(ip)->setLine((ip)*xScale,(programm->yDatashort.at(ip))*yScale*(-1),(ip+1)*xScale,(programm->yDatashort.at(ip+1))*yScale*(-1));
}
for (int iy=1 ; iy<(width/xAxisMarks)+1 ; iy++)
{
int oldx = marksVector->at(iy)->x();
oldx-=1;
if(oldx%xAxisMarks==0 || oldx==0)
{
marksVector->at(iy)->setX(oldx+xAxisMarks);
}
else
{
marksVector->at(iy)->setX(oldx);
}
}
}
else
{
item = scene->addLine(QLineF(programm->xDatashort.at(iCurrentVectorPoint-1)*xScale, programm->yDatashort.at(iCurrentVectorPoint-1)*yScale*(-1), programm->xDatashort.at(iCurrentVectorPoint)*xScale, programm->yDatashort.at(iCurrentVectorPoint)*yScale*(-1)));
lineVector->append(item);
}
}
iCurrentVectorPoint++;
}
Update:
Code stable for more than 50 min with 800 samples in view, with 100 Hz samplerate and 20 Hz framerate. Using a Thread for the simulatordata. Feel free to ask me nearly everything about this topic, I worked through it for nearly 2 months :D
void MainWindow::drawChart()
{
//To check the framerate I implemented a framecounter
framerunner++;
ui->Framerate->setText(QString::number(int(framerunner/double(DurationTimer->elapsed()/1000))));
//Using to stay focused on the scene, not neccesary if you define the x values from [startview-endview]
QRect a;
if(Samplevector.size()!=0)
{
a.setRect(Samplevector.at(Samplevector.size()-1).getX()-850,0,900,200);
qDebug()<<Samplevector.at(Samplevector.size()-1).getX();
ui->LinegraphView->setSceneRect(a);
}
//delete everything in the scene and redraw it again
scene->clear();
if(Samplevector.size()>1)
{
for(int i=1;i<Samplevector.size();i++)
scene->addLine(QLineF(Samplevector.at(i-1).getX(),Samplevector.at(i-1).getY(),Samplevector.at(i).getX(),Samplevector.at(i).getY()));
}
}
void MainWindow::start()
{
framerate->start(50);
DurationTimer->start();
hegsimulator->moveToThread(thread);
thread->start();
qDebug()<<"Request "<<this->QObject::thread()->currentThreadId();
}
void MainWindow::stop()
{
framerate->stop();
hegsimulator->stopDevice();
}
void MainWindow::prepareGraph()
{
samplerunner++;
ui->Samplerate->setText(QString::number(int(samplerunner/double(DurationTimer->elapsed()/1000))));
Samplevector.append(hegsimulator->getSample());
if(Samplevector.size()>800)
{
//graphlinevector.first()->hide();
//scene->removeItem(graphlinevector.first());
// graphlinevector.removeFirst();
Samplevector.removeFirst();
}
// if(Samplevector.size()>1)
// {
// item = scene->addLine(QLineF(Samplevector.at(Samplevector.size()-2).getX(),Samplevector.at(Samplevector.size()-2).getY(),Samplevector.at(Samplevector.size()-1).getX(),Samplevector.at(Samplevector.size()-1).getY()));
// graphlinevector.append(item);
// }
}
CAUTION!!
These Solutions are not the fastest ones. Use QPainterPath instead of QLineF, it is like you take a pen, draw a line, put the pen away, and this 1000 times.
Better to safe all QPoints in a QPainterPath and take the pen once to draw. That increases the performance to Realtime Plotting with a 4 min trend and more without problems.

Resources