GUI freeze when inserting javascript into a WebView in Qt - c++

I've got a small chat application that uses a QTcpSocket to communication with my server.
My server sends JSON commands to my Qt chat client which i parse inside of the readyRead(). When a receive the command newUser the below method is executed. For some reason the below method causes my application to freeze on some machines. Meaning it yields the GUI to become unresponsive. The user then has to minimize and then maximize the application for it to become responsive.
Any ideas what could be causing this?
void MainWindow::addUser(QString channelID, QString visitorName) {
int exists = 0;
if (onlineUsers.contains(channelID)) {
exists = 1;
}
// Add the user to our QWebView
ui->allVisitorsWeb->page()->mainFrame()->evaluateJavaScript("$('#allUsers tr:last').after('<tr id=" + channelID + "><td>" + visitorName + "</td></tr>'); undefined");
// Add to active chats QStringList
onlineUsers << channelID;
// Display notification
QSettings settings("MyApp", "App");
settings.beginGroup("aSettings");
if (settings.value("visitorNotification").toString() == "true") {
//displayNotification(visitorName, "Has entered", channelID);
}
settings.endGroup();
// Added to fix freeze
QCoreApplication::processEvents();
}

Related

Why does QDialog::exec() blocks QTcpSocket work

I make a Bomberman (or DynaBlast) game clone with multiplayer. Server game and client game communicate through messages using QTcpSocket. Typical workflow for playing network game is following:
Server player execs NetworkGame dialog, among other things this dialog creates NetworkGame object
Client player execs ClientGame dialog, among other things this dialog creates ClientGame object
Client chooses IP of server and clicks "Connect"
Server accepts connections, server and clients are now able to send messages each other
Client sends "ready"-message, server start game
When game over, the game object emits GameStatusChanged(GameStatus newStatus) signal. This signal connected to MainWindow, which execs GameOverDialog. If player chooses "Play again" at GameOverDialog, MainWindow execs NetworkGame or ClientGame dialogs again and we are at the first points.
So, after first game is over, second exec of ClientGameDialog blocks QTcpSocket work in the way it cann't read data or emit QTcpSocket::readyRead signal (I don't know which one point exactly). ClientGameDialog's GUI is responsive, it can send messages to server, but it cann't read messages. At the same time NetworkGame and NetworkGameDialog work properly - they are able to send and receive messages. I checked all my classes several times and don't see any significant difference.
I think full code is to huge to post it here, so I gave UML a try. This is a chart for most important classes. Green arrow designates Qt's child-parent relations, starting at a child QObject it points to a parent.
When Socket class receives new message through QTcpSocket interface, it emits messageReceived(const Message& message) signal; other classes can connect to this signal via slots and handle messages. I don't see what Client, ServerWorker, Server classes can do with event loop, the just help to process raw data from QTcpSocket and deliver messages to other classes, particulary to Game and Dialog classes.
Here is some code (I have some code duplications, I leave them until better times). Creating game:
// Server game
void MainWindow::startNetworkGame() // User clicked "Start network game" button
{
const auto& player = mainMenuWidget_->selectedPlayer();
gameDialogs_ = createGameDialogs(this, GameType::Server, player);
auto answer = gameDialogs_.creationDialog->exec();
if (answer == QDialog::Accepted) {
auto initializationData = gameDialogs_.creationDialog->initializationData();
initializeGame(initializationData);
startGame(initializationData);
}
}
// Client game
void MainWindow::connectToServer() // User clicked "Connect to server" button
{
const auto& player = mainMenuWidget_->selectedPlayer();
gameDialogs_ = createGameDialogs(this, GameType::Client, player);
auto answer = gameDialogs_.creationDialog->exec(); // At first time it works fine
if (answer == QDialog::Accepted) {
auto initializationData = gameDialogs_.creationDialog->initializationData();
initializeGame(initializationData);
startGame(initializationData);
}
}
Next snippet is code for processing GameStatusChanged signal when game was over:
void MainWindow::gameStatusChanged(GameStatus newStatus)
{
if (newStatus == GameStatus::GameOver) {
auto* gameOverDialog = gameDialogs_.gameOverDialog;
gameOverDialog->setGameResult(gameData_.game->gameResult());
auto gameOverDialogAnswer = gameOverDialog->exec();
if (gameOverDialogAnswer == QDialog::Accepted) {
gameDialogs_.creationDialog->reset();
auto answer = gameDialogs_.creationDialog->exec(); // At this point client cann't receive messages, but server can.
if (answer == QDialog::Accepted) {
auto initializationData = gameDialogs_.creationDialog->initializationData();
initializeGame(initializationData);
startGame(initializationData);
} else {
showMainMenu();
}
} else {
showMainMenu();
}
}
}
I suspect that ClientGameDialog's event loop (is it indeed has it's own event loop) doesn't processes QTcpSocket's events. I tried to replace exec() with open methods for client dialog:
void MainWindow::gameStatusChanged(GameStatus newStatus)
{
if (newStatus == GameStatus::GameOver) {
auto* gameOverDialog = gameDialogs_.gameOverDialog;
gameOverDialog->setGameResult(gameData_.game->gameResult());
auto gameOverDialogAnswer = gameOverDialog->exec();
if (gameOverDialogAnswer == QDialog::Accepted) {
gameDialogs_.creationDialog->reset();
auto d = qobject_cast<ClientGameDialog*>(gameDialogs_.creationDialog);
if (d) {
gameDialogs_.creationDialog->open();
} else {
auto answer = gameDialogs_.creationDialog->exec();
if (answer == QDialog::Accepted) {
auto initializationData = gameDialogs_.creationDialog->initializationData();
initializeGame(initializationData);
startGame(initializationData);
} else {
showMainMenu();
}
}
} else {
showMainMenu();
}
}
}
It works, but I want to find where was the problem. Maybe someone can prompt, where to search a solution. Main questions are: where the difference between Server and Client code flows and why Client code works fine at the first time and breaks at the second.
This is because QDialog::exec is a synchronous blocking operation that will stop the event loop of the main thread and start a new event loop for this dialog. This most of the time is not a problem unless you are doing some continuous work on the main thread such as processing QTCPSocket
Instead of using QDialog::exec use QDialog::open which is asynchronous and does not start a new event loop, you can simply connect signals from the dialog to read the results once the user will accept/close the dialog.
If you require blocking dialogs then you also can simply offload QTcpSocket to another thread and do whole processing asynchronously and only emit required updates to the main GUI thread.

QT - slot preemption / interrupt

I'm trying to write an app for testing ST board via serial port and I'm currently facing the following issue. (The code below is just a simplification of a problem.)
Widget::Widget(QWidget *parent)
: QWidget(parent)
, m_button(new QPushButton(this))
, m_timer(new QTimer(this))
{
m_timer->setSingleShot(true);
connect(m_button, &QPushButton::released, this, &Widget::RunTest);
connect(m_timer, &QTimer::timeout, this, &Widget::OnTimeout);
}
void Widget::RunTest()
{
qDebug() << "Start test";
m_timer->start(1000);
while (m_timeout != true);
qDebug() << "Start end";
}
void Widget::OnTimeout()
{
qDebug() << "Timeout";
m_timeout = true;
}
I want to have a seprate class for gathering and running tests. The tests are triggered by clicking on a button. Some tests will have to send data via serial port and wait for the reply. I would like to be able to implement a timeout feature (if board doesn't reply then finish test with failure). However I the app is waiting for the m_timeout flag indefinitely. So my question is: Is there any signal/slot mechanism similar to interrupt preemption? If no how sush problems are solved in Qt? Shall I create seprate QTimer object and run it in separate thread?

Sleep inside QTConcurrent run method

I'm using QtConcurrent::run to execute some functions in background and not hang the GUI thread. In one function, I read logs from local SQlite database and send them to server by TCP socket.
Now I want to delay the execution after each log so the server has time to save it (TCP response is read in different thread). I'm stuck with Qt4.8 due to implementation limitations (many embeded devices - no chance to upgrade QT on them) and I can't use QThread::sleep(2) because it is protected in 4.8.
Is it possible to somehow pause the execution of thread inside QtConcurrent::run method or should I redesign it to implement my own class inheriting QThread?
void MainWindow::ReportFinishedHUs(bool asyncWork)
{
if(asyncWork == false)
{
QMutexLocker locker(&localDBmutex);
QList<QSqlRecord> HUsToReport = localDB->getHUsForBook();
qDebug() << "HUs to report" << HUsToReport.count();
if(!HUsToReport.isEmpty())
{
Cls_log::WriteDebugLog("HUs to report: " + QString::number(HUsToReport.count()));
foreach (QSqlRecord record, HUsToReport)
{
int _hu = record.indexOf("hu");
int _logTime = record.indexOf("logTime");
QString logTimeString = record.value(_logTime).toString();
QString hu = record.value(_hu).toString();
qDebug() << hu << logTimeString;
// creating message here ...
qDebug() << message;
emit sig_SendTCPMessage(message);
// this is where I need to wait for 2 seconds
QThread::sleep(2);
}
}
}
else
{
QtConcurrent::run(this, &MainWindow::ReportFinishedHUs, false);
}
}
EDIT:
Solved by usleep(2000000) which I somehow discarded for being platform specific... but hey, half of my aplication is platform specific and I only use it in embeded device with constant OS.
Keeping the question open if anyone can suggest more elegand solution using Qt methods. I like to get inspired.

Serial connection does not work correctly with QPushbuttons

void CSerialController::sl_executeCommand(const QString s_command,QString&s_result){
QMutexLocker locker(mpo_mutex);
KT_Error t_error = ERROR_NONE;
QByteArray o_serialOutput;
QString s_serialAnswer = "";
char c_serialLetter = ' ';
// Port is already open
mpo_serial->clearBuffer(); // Flush and ReadAll
t_error = mpo_serial->sendStr(s_command); //
NO_SUCCESS(t_error)
{
Q_EMIT sg_throwError(t_error);
}
SUCCESS(t_error)
{
while ((t_error == ERROR_NONE) && (c_serialLetter != '\n')) // Reads Serial till newline is found
{
t_error = mpo_serial->getChar(c_serialLetter); // -> checks BytesAvailable -> if no bytes available -> waitForReadyRead(1) <- This is in a Loop while given time is over(Timeouttime)
o_serialOutput.append(c_serialLetter);
}
}
NO_SUCCESS(t_error)
{
Q_EMIT sg_throwError(t_error);
}
SUCCESS(t_error)
{
s_serialAnswer = o_serialOutput;
s_serialAnswer.remove("\r\n");
}
s_result = s_serialAnswer; }
Im working with C++ and Qt 5.5 and i cant get the serial connection to work correctly. Im using signals to connect a QPushButton with a slot in the CSerialController class which calls this function in the same class. I tried this function with a endless while-loop and it works correctly all the time. Its just not working when i use the buttons. First time i use a button it works correctly but the second time i press a button which calls this slot, the serialport only sometimes returns a value. (Writing works just not reading) I even tried changing the Thread of this class but that didnt help aswell. If you want i can post the sendStr and getChar functions aswell but like i said they work without a problem(allready used in a lot of projects)
UPDATE: I tried using the Windows API for the serial connection = problem is gone. Seems like a problem with QSerialPort usage on an old CPU (I7-870)

Bukkit - Send data back to people connected through RCON

I have a NodeJS / SocketIO app which connects to my Minecraft server through RCON protocol and it works perfectly, keep the connection open and listens for any kind of data which is retrieved.
Example, if I type a command which isn't available, it will respond with a message.
Now I'm trying to whenever any player on the Minecraft server chats, my Bukkit plugin will take that message and send it through to any connected on RCON.
This is part of my Bukkit plugin which fires when the player chats.
#EventHandler
public void onPlayerChat( AsyncPlayerChatEvent e ) {
Bukkit.getLogger().info("Test 1");
this.getLogger().info("Test 2");
Bukkit.getServer().getConsoleSender().sendMessage("Test 3");
this.getServer().getConsoleSender().sendMessage("Test 4");
}
The messages are recorded in the server log, though I do not get anything back through the rcon protocol.
RCON used by minecraft does not have a mechanism to send messages back after the initial packets. If you need a mechanism like this, you need to invent your own protocol, or make a command that returns the last chat log if it's executed.
To make this command, we can simple make a Queue<String> where we are putting our messages (like chat) into.
final Queue<String> messages = new ArrayDeque<>(64);
public void insertMessage(String message) {
synchronized (messages) {
messages.add(message);
if (messages.size() == 64) // full
messages.remove();
}
}
We can then hook up this method to our chat event:
#EventHandler(priority=EventPriority.MONITOR,ignoreCancelled=true)
public void onChat(AsyncPlayerChatEvent evt) {
insertMessage("[CHAT] " + evt.getPlayer() + " : " + evt.getMessage());
}
Then we only need to make a command to read out our messages list, this is easy to make:
private String getMessage() {
synchronized (messages) {
return messages.poll();
}
}
#Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
if(!cmd.testPermissions()) {
return true;
}
String message;
while((message = getMessage()) != null) {
sender.sendMessage(message);
}
}
This allows you to get the server log from rcon by just typing in a command or making your client application so that it executes this command every second or so.