Porting to the Qt C++ Framework :: Part 3

In this part of our series we’ll dive in and start implementing our logic from the command line application to the GUI version.

The goal during this step is to make exclusive use of the Qt frameworks built-in functions and methods for handling these tasks, as the end result must be a cross-platform compatible app.

We’re already getting a valid directory entry from part 2, so the first step in this part is to figure out how we iterate and query files from our base folder.

As setup for this task, in the first app (the command line one) we defined a few globals:

  1. // path data
  2. string basePath = argv[1];
  3. string imagePath = basePath + "/images";
  4. string storyPath = basePath + "/stories";

We’ll keep that basic structure, but of course implement them as member private variables. It should be noted that in the new version the ‘base’ directory value comes from our Worker constructor with:

  1. Worker *wk = new Worker(processPathQLineEdit->text());

In the worker constructor we then:

  1. // create path items
  2. basePath = path;
  3. imagePath = path += "/images";
  4. xmlPath += "/stories";

Same thing as the original version really, though one thing I had to check on is if using / will be a problem for Windows. *Turns out the answer is no.*

With the setup tasks done notice that in the GUI we have two buttons: The first allows us to select the base folder, the second kicks off the Process Images task. Notice how we do this: in cleaner.enc__cppqt our clicked() SIGNAL of the processButton button is assigned to the processAction slot, which in turn, after creating our Worker instance object, calls cleanImages(). And so we’re getting an images directory via the GUI–so far so good!

This brings us to the next big question question: how do we open and iterate over directories?

Iterator Over Directories

As you may recall the first task of our program is to remove all files with a _bw_ in the name from the /images sub-folder. It looks like we have: QDirIterator and QDir to help us perform this duty, with the possibly of others as well. The former seems to be for general access, the latter for iterating–we want to iterate.

Basic QDirIterator Implementation

  1. QDirIterator it(this->imagePath, QDirIterator::Subdirectories);
  2.  while(it.hasNext()){
  3.  QString fl = it.fileName();
  4.  if(fl != "." && fl != ".." && fl != "./"){
  5.  statusMessage += it.next() += "\n";
  6.  } else {
  7.  it.next();
  8.  }
  9. }

Pretty strait forward really. Iterator over the directory, making sure to skip the path items.

This takes care of the iteration, now we need to find and remove files that have the _bw_.

In the old code we used:

  1. while((dir = readdir(d)) != NULL){
  2.  name = dir->d_name;
  3.  found = name.rfind("_bw_");
  4.  if(found != -1){
  5.  path = imagePath;
  6.  location = imagePath + "/" + name;
  7.  cout << location << endl;
  8.  int t = remove(location.c_str());
  9.  if(t != -1){
  10.  counter++;
  11.  }
  12. }

I should step back to mention the first step was to get the file name. In the first attempt we relied on a struct returned by dirent to store file info for checking, now we have a nice simple fileName() method to call. I’m not going to lie, the Qt way feels much cleaner than the command line version. dirent was tricky to work with at first without having a debugger, this is no longer an issue in Qt. With that out of the way, what of finding the text “_bw_”?

I suspected, and was proven right, that QString would have some type of find function. Sure enough:

  1. fl.contains("_bw_")

Beautiful! I can’t help but show my gratitude over such an elegant and natural thing. Without any documentation look up I was able to basically guess and implement my search function in one line. Yes std::string had the same thing with .find(), but this was a nice treat regardless.

Right, so now how do we delete files?

In the first version we had to use a separate variable (location) and a member function .c_str() to pass to the delete(char*) call. This is fine, but a touch verbose. It would be nice to encapsulate this a bit more.

Delete File Implementation

It turns out that we can use the same remove() call, though it wants a char*, we’ve got a QString. As was the goal anyway, we want to use native solutions for cross-platform consistency. I did some digging and found QFile. QFile has a member function for removing files: .remove(). Even better though, QDirIterator has a member function filePath() to return the full path to the file (not just file name). Of course I may have missed similar functionality in the first go (such as using QDir), but now we save ourselves the hassle of rebuilding the file path from scratch. The final code is:

  1. QString Worker::cleanImages()
  2. {
  3.     this->statusMessage = "Starting Image Clean…\n";
  4.  
  5.     int ct = 0;
  6.     bool t = false;
  7.  
  8.     // loop through directory…
  9.     QDirIterator it(this->imagePath, QDirIterator::Subdirectories);
  10.     while(it.hasNext()){
  11.         QString fl = it.fileName();
  12.         if(fl.contains("_bw_")){
  13.             // remove
  14.             QFile f (it.filePath());
  15.             t = f.remove();
  16.             if(t){
  17.                 ct++;
  18.                 statusMessage += fl += "\n";
  19.             }
  20.         }
  21.         it.next();
  22.     }
  23.  
  24.     QString finalCount = QString::number(ct);
  25.     statusMessage += "\n"  + finalCount + " Images Removed.";
  26.     return statusMessage;
  27. }

Sure it’s about the same length as the original command line code, if not a bit longer, but it feels more intuitive–almost like OO PHP coding. I do not however, like the idea of creating a new QFile for each item to remove, but I’m learning–I’m sure we’ll find a better way in the next few posts.

Final Method Implementation

For the sake of completeness, here’s the final cleanImages() function:

  1. QString Worker::cleanImages()
  2. {
  3.  statusMessage = "Starting Image Clean…\n";
  4.  
  5.  int ct = 0;
  6.  bool t = false;
  7.  
  8.  // loop through directory…
  9.  QDirIterator it(this->imagePath, QDirIterator::Subdirectories);
  10.  while(it.hasNext()){
  11.  QString fl = it.fileName();
  12.  if(fl.contains("_bw_")){
  13.  // remove
  14.  QFile f (it.filePath());
  15.  t = f.remove();
  16.  if(t){
  17.  ct++;
  18.  statusMessage += fl += "\n";
  19.  }
  20.  }
  21.  it.next();
  22.  }
  23.  
  24.  QString finalCount = QString::number(ct);
  25.  statusMessage += "\n"  + finalCount + " Images Removed.";
  26.  return statusMessage;
  27. }

Up next, rewriting the cleanXMLStoryFiles() function.

Leave a Comment

* are Required fields