Slot/signal dynamics regarding QTextEdit - qt

I'm fairly new at C++/Qt, and I only have some web development experience.
I'm testing a few things with Qt for learning purposes and I'm failing miserably at it. I'm pretty sure it's because the whole slot/signal thing hasn't settled in yet, so I was hoping someone could make it clearer for me.
So, I have this main program UI where I have placed a QTextEdit widget. Now I'm trying to reproduce one of those "there are changes to the document, better save it!"-warnings, and it's being displayed everytime someone tries to create a new document.
I'm trying to test for changes in the textEdit widget when the "New Document" option is triggered. I keep getting these compile errors and I don't even know what they mean! What would be the correct test condition? How can I refer to the textEdit, since it's being called somewhere else?
I'm trying something like this:
void Notepad::on_actionNew_triggered()
{
//not getting the test condition right!
if(................................) {
QMessageBox msgBox;
msgBox.setText("Warning!");
msgBox.setInformativeText("Changes were applied to this document.");
msgBox.setStandardButtons(QMessageBox::Discard | QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Cancel);
int ret = msgBox.exec();
switch (ret) {
case QMessageBox::Discard:
// Don't Save was clicked
ui->textEdit->clear();
break;
case QMessageBox::Cancel:
msgBox.close();
break;
default:
// should never be reached
break;
}
}else{ui->textEdit->clear();}
}
I have tried searching some information about this, and I bet most of you might actually think this is pretty obvious, but I'm having real trouble understanding how to get around this.

So, you've got a few things in play here. One is when you're clicking New you need to perform a check to see if there's a document already existing that hasn't been saved, you're not far off by thinking of signal and slots for it.
So when you press New you need to send a signal to your document, if you've created a custom class this is easy as you can add a new slot dirtyDocument (for example!) which can relay the documents status back, for example by emitting another signal containing a bool flag and deal with it inside another slot. Or, in your on_actionNew_triggered slot you could ask the document object which is currently open whether it's got unsaved changes by passing a reference to the object, or maintaining a class reference to it (that's where you have something like Document *doc; in your notepad.h file).
If you're getting compile issues copy any information that doesn't have something specific to your code and paste it into Google. Odds are someone will have asked the same question, most probably on SO itself.

So, I was trying to solve this and I decided to make a slot returning a bool value whenever the textEdit has suffered changes. As I figured out, I thought of using this as a test whenever the "New action" was triggered. So, after the test, I set the bool value back to false and it's working fine:
void Notepad::on_actionNew_triggered()
{
if(Notepad::on_textEdit_textChanged()) {
~Notepad::on_textEdit_textChanged();
QMessageBox msgBox;
msgBox.setText("Warning!");
msgBox.setInformativeText("Changes were applied to this document.");
msgBox.setStandardButtons(QMessageBox::Discard | QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Cancel);
int ret = msgBox.exec();
switch (ret) {
case QMessageBox::Discard:
// Don't Save was clicked
ui->textEdit->clear();
break;
case QMessageBox::Cancel:
msgBox.close();
break;
default:
// should never be reached
break;
}
}else{ui->textEdit->clear();}
}
bool Notepad::on_textEdit_textChanged()
{
return true;
}

Related

Why is there no static QDir::makepath()?

I know, that to create a new path in Qt from a given absolute path, you use QDir::makepath() as dir.makepath(path), as it is suggested in this question. I do not have any trouble in using it and it works fine. My question is directed, as to why the developers would not provide a static function to call in a way like QDir::makepath("/Users/me/somepath/");. Needing to create a new QDir instance seems unnecessary to me.
I can only think of two possible reasons:
1. The developers were "lazy" or did not have time so they did not add one as it is not absolutely necessary.
2. The instance of QDir on which mkpath(path) is called, will be set to path as well, so it would be convenient for further usage - but I can not seem to find any hints that this is the actual behaviour within the docs.
I know I repeat myself, but again, I do not need help as of how to do it, but I am much interested as of why one has to do it that way.
Thanks for any reason I might have missed.
Let's have a look at the code of said method:
bool QDir::mkdir(const QString &dirName) const
{
const QDirPrivate* d = d_ptr.constData();
if (dirName.isEmpty()) {
qWarning("QDir::mkdir: Empty or null file name");
return false;
}
QString fn = filePath(dirName);
if (d->fileEngine.isNull())
return QFileSystemEngine::createDirectory(QFileSystemEntry(fn), false);
return d->fileEngine->mkdir(fn, false);
}
Source: http://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/io/qdir.cpp#n1381
As we can see, a static version would be simple to implement:
bool QDir::mkdir(const QString &dirName) const
{
if (dirName.isEmpty()) {
qWarning("QDir::mkdir: Empty or null file name");
return false;
}
return QFileSystemEngine::createDirectory(QFileSystemEntry(dirName), false);
}
(see also http://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/io/qdir.cpp#n681)
First, the non-static method comes with a few advantages. Obviously there is something to using the object's existing file engine. But also, I would imagine the use-case of creating several directories under a specific directory (that the QDir already points to).
So why not provide both?
Verdict (tl/dr): I think the reason is simple code hygiene. When you use the API, the difference between QDir::makepath(path); and QDir().makepath(path); is slim. The performance hit of creating the object is also negligible, as you would reuse the same object if you happen to perform the operation more often. But on the side of the code maintainers, it is arguably much more convenient (less work and less error prone) to not maintain two versions of the same method.

Reliably showing a "please wait" dialog while doing a lengthy blocking operation in the main Qt event loop

I've got a Qt app that needs to call an expensive non-Qt function (e.g. to unzip a ~200MB zip file), and since I'm calling that function from the main/GUI thread, the Qt GUI freezes up until the operation completes (i.e. sometimes for 5-10 seconds).
I know that one way to avoid that problem would be to call the expensive function from a separate thread, but since there isn't much the user can do until the unzip completes anyway, that seems like overkill. I can't add processEvents() calls into the expensive function itself, since that function is part of a non-Qt-aware codebase and I don't want to add a Qt dependency to it.
The only thing I want to change, then, is to have a little "Please wait" type message appear during the time that the GUI is blocked, so that the user doesn't think that his mouse click was ignored.
I currently do that like this:
BusySplashWidget * splash = new BusySplashWidget("Please wait…", this);
splash->show();
qApp->processEvents(); // make sure that the splash is actually visible at this point?
ReadGiantZipFile(); // this can take a long time to return
delete splash;
This works 95% of the time, but occasionally the splash widget doesn't appear, or it appears only as a grey rectangle and the "Please wait" text is not visible.
My question is, is there some other call besides qApp->processEvents() that I should also do to guarantee that the splash widget becomes fully visible before the lengthy operation commences? (I suppose I could call qApp->processEvents() over and over again for 100mS, or something, to convince Qt that I'm really serious about this, but I'd like to avoid voodoo-based programming if possible ;))
In case it matters, here is how I implemented my BusySplashWidget constructor:
BusySplashWidget :: BusySplashWidget(const QString & t, QWidget * parent) : QSplashScreen(parent)
{
const int margin = 5;
QFontMetrics fm = fontMetrics();
QRect r(0,0,margin+fm.width(t)+margin, margin+fm.ascent()+fm.descent()+1+margin);
QPixmap pm(r.width(), r.height());
pm.fill(white);
// these braces ensure that ~QPainter() executes before setPixmap()
{
QPainter p(&pm);
p.setPen(black);
p.drawText(r, Qt::AlignCenter, t);
p.drawRect(QRect(0,0,r.width()-1,r.height()-1));
}
setPixmap(pm);
}
Moving to another thread is the correct way to go but for simple operations, there's a much less complicated way to accomplish this without the pain of managing threads.
BusySplashWidget splash("Please wait…", this);
QFutureWatcher<void> watcher;
connect(&watcher, SIGNAL(finished()), &splash, SLOT(quit()));
QFuture<void> future = QtConcurrent::run(ReadGiantZipFile);
watcher.setFuture(future);
splash.exec(); // use exec() instead of show() to open the dialog modally
See the documentation about the QtConcurrent framework for more information.

How does memory management work on close modal dialog

If I invoke the dialog this way:
void foo()
{
QMessageBox* dlg = new QMessageBox( QMessageBox::Critical, "Error", "Unknown Error" );
dlg->exec();
}
Will the memory be freed after user have closed the dialog?
Please, point me to the appropriate doc, because I can’t find one.
There's no single piece of documentation.
The QMessageBox is a QObject. It can be owned by a parent widget. It can also be deleted by the event loop when you call deleteLater.
Any top-level QWidget can have the Qt::WA_DeleteOnClose attribute set - it will self-delete when it gets closed.
In your case, since you're calling the (discouraged) blocking exec() method, you don't need to allocate the dialog on the heap. It could be an automatic variable:
QMessageBox dlg(QMessageBox::Critical, "Error", "Unknown Error");
dlg.exec();
Ideally, though, you should show the dialog and have it set up to delete itself upon closure:
QScopedPointer<QMessageBox> dlg(new QMessageBox(QMessageBox::Critical, "Error", "Unknown Error"));
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg.take()->show();
The smart pointer is used to prevent leaks in case the constructor were to fail, or if you changed the code later and there was a code path that missed show(). If the dialog is not shown, it would not ever be deleted - here, the smart pointer takes care of it. Either the dialog is shown, and deletes itself, or it is deleted by the pointer.
Not by itself but you can connect finished to deleteLater and it will. Although creating a concrete instance on the stack like Matthew suggested is better
In your case it will leak the memory associated with dlg.
You want to change your code to be:
void foo()
{
// Stack allocation is faster than heap allocation.
// It also communicates the lifetime of the object better.
QMessageBox dlg( QMessageBox::Critical, "Error", "Unknown Error" );
// This will block until the user closes the message.
int result = dlg.exec();
} // dlg will be release upon losing scope here.
See QMessageBox::exec

Force update of QTListWidget to add/remove elements

I'm attempting to wrap a GUI around an existing management console app. The main function is to search for network devices, which is given a timeout and is essentially a blocking call until the timeout has expired (using sleep to do the blocking). In this example the call is this->Manager->Search(...).
My issue is that I want the QListWidget to display "Searching..." while the search is taking place, and then update with the results at the completion of the search. My on-click code for the Search button is as follows:
void ManagerGUI::on_searchButton_clicked()
{
ui->IPList->clear();
new QListWidgetItem(tr("Searching..."), ui->IPList);
ui->IPList->repaint();
this->Manager->Search(static_cast<unsigned int>(this->ui->searchTime->value()*1000.0));
ui->IPList->clear();
if(this->Manager->GetNumInList() != 0)
this->displayFoundInList(this->Manager->GetFoundList());
else
new QListWidgetItem(tr("No Eyes Found"), ui->IPList);
ui->IPList->repaint();
}
When I hit the button, the QListWidget IPList does not update until after the timeout has taken place (and I assume until after this callback has terminated). Does anyone have any suggestions? I was under the impression that calling ui->IPList->repaint() would cause an immediate redraw of the list.
Additional info:
QT Version 5.1.0 32-Bit
Compiled using VS2012
Running on Win7 Pro 64-bit (but to be ported to OSX and Linux, so nothing win-specific please)
1) You don't need to call repaint directly.
2) You should do your search asynchronously. It is big topic - you should learn basics of Qt first.
Start with signals and slots and then learn about QThread or QtConcurrent. Then implement a class that will do searching and send necessary signals: first signal on search start, second signal - on search stop. Then connect slots to this signals and work with your list view insine this slots.
Problem is that your "Search manager" blocks Qt's event loop. Thats why listview does not repainted.
You need a signal slot system because your search is blocking. Ideally you should do the search in a new thread. However you can cheat with a processEvents()
void ManagerGUI::on_searchButton_clicked()
{
ui->IPList->clear();
new QListWidgetItem(tr("Searching..."), ui->IPList);
emit signalStartSearch();
}
void ManageGUI::slotStartSearch()
{
// Process any outstanding events (such as repainting)
QCoreApplication::processEvents();
this->Manager->Search(static_cast<unsigned int>(this->ui->searchTime->value()*1000.0));
emit signalSearchCompleted();
}
void ManagerGUI::slotSeachCompleted()
{
ui->IPList->clear();
if(this->Manager->GetNumInList() != 0) {
ui->IPList->setUpdatesEnabled(false);
this->displayFoundInList(this->Manager->GetFoundList());
ui->IPList->setUpdatesEnabled(true);
} else {
new QListWidgetItem(tr("No Eyes Found"), ui->IPList);
}
}
Ideally you would want the Manager->Search to emit the signal and then use QtConcurrent::run to do the search in another thread.

Is there any system(application) variable in Qt which keeps track of whether the application is being used first time

I wan’t to display a message, only on the first usage of application, i.e. a piece of code should be executed only once, when application is first time invoked. I tried using QSettings, but couldn’t work out any way.
if(firstTime)
{
//do something!
}
Just check QSettings for some value at launch. If it doesn't exist, then its a first launch. After that, set the variable so it will be found each subsequent load.
In PyQt4 it would look like this (I am sure you can translate to C++):
settings = QSettings("foo.plist", QSettings.NativeFormat)
if not settings.contains('hasLaunched'):
# this is our first time! Weee
# no matter what, set the value for the future
settings.setValue('hasLaunched', 1)
instead of "hasLaunched" you may use "lastLaunchedVersion" and store there version number. This way you may easily do some vesion specific steps on each upgrade if they are required and also detect first launch.
In cpp:
// Setup the QSettings variable
QSettings settings("Organization", "ApplicationName", this);
// Check if the value for "firsttime" is set,
// and add default value to true
if(settings.value("firsttime", true).toBool())
QMessageBox::information(this, "Hello", "This is our first time");
// Set the value to false, because from now on its not the first time
settings.setValue("firsttime", false);

Resources