Showing posts with label Qt. Show all posts
Showing posts with label Qt. Show all posts

Sunday, 28 April 2013

Developing with Qt on the MSVC Express IDE

You can use Qt Creator with the MSVC compiler. However, you may prefer to use the MSVC IDE, instead. This is harder than it sounds, because Qt's build process may have additional compile steps, such as invoking uic or moc; and, by default, MSVC isn't aware of these steps. There is a Qt plugin, but it doesn't work in MSVC Express. So, if you're using MSVC Express, you need a different solution.

Googling provides several answers for this. The one I found more often was using qmake -tp vc to generate a .vcxproj file out of a .pro file. Works like a charm, and you're guaranteed to get a flawless build on MSVC with no extra work. Before running this command, just make sure your PATH includes qmake, set your QMAKESPEC correctly (if you're in doubt about the correct value for your system, check the mkspecs folder in your Qt installation), and... well, you'll have some relative called Bob, I guess.

This will also set all the required include and lib folders automatically; however, it won't set the runtime path, so before you try debugging your app from the MSVC IDE, go to your project's Property Pages -> Configuration Properties -> Debugging -> Environment and set the PATH to include all the required DLLs.

Running qmake like this takes a snapshot of the .pro file and creates a similar .vcxproj file, which means that further changes to your .pro file will require running qmake again. This will generate a new .vcxproj file (amazing, hey?), so you'd better a) have a good merge tool; or b) don't plan on making any changes to the .vcxproj file.

Why is this a problem? Well, even though we're using MSVC, we'll still have to work in Qt Creator, because: 1) Working in Qt Designer is a lot easier than creating .ui files by hand; 2) adding new forms to a Qt Creator's project also sets up the corresponding C++ files; or 3) when using Designer's tools for setting up signals/slots, you must have Qt's project open, if you just double-click the .ui file in your MSVC project, it opens the corresponding form in Qt Designer, but there's some things you won't be able to do (like using the Go to slot... option) because your project isn't open in Qt Creator (i.e., Qt Creator is editing a form, but doesn't know where are the corresponding C++ files, where the code must go). So, using MSVC as your IDE doesn't mean you'll never need to fire Qt Creator again.

We could create our MSVC project like this and then, whenever we added something to our .pro file, we'd manually add it to our MSVC project (and set its Item Type to Custom Build Tool and fill all the other required fields, if necessary).

Well, we could, but this wouldn't be much of a post if there wasn't some sort of alternative, right?

And we do have an alternative. I've first come across it here. Just by using what you find there (.target files for MSBuild), you'll have automatic handling of .ui and .qrc files. MOC compilation is treated on an item-by-item basis. I've looked at this, and decided to make some changes that better fit my planned workflow.

Before we start - everything written below assumes none of your own source files follows these specs: moc_*.cpp, ui_*.h, and qrc_*.cpp. The only files that follow these specs should be the files generated by moc, uic, and rcc. If that's not the case, then do not to use anything that I present/suggest on this post; doing so can not only mess up your build process in MSVC, but can also delete your source files by erroneously assuming they were generated. You've been warned.

moc compiler

My first goal is automating moc.

Note: The post linked above states that "since usually not all headers in a Qt project use the Q_OBJECT macro moc.exe would run and fail for those headers". This might have been correct at the time it was written, but not anymore. If you run moc on a file without the Q_OBJECT macro, this is what you get

>moc sometestclass.h
sometestclass.h(0): Note: No relevant classes found. No output generated.

>echo %ERRORLEVEL%
0


Contrast that with running moc on a non-existent file

>moc mainrrr.cpp
moc: mainrrr.cpp: No such file

>echo %ERRORLEVEL%
1


So, while I could just run every .h and .cpp file through moc, I prefer running just those that actually require it. So, I created a batch script that runs a grep for Q_OBJECT and uses wc -l to count the matches.

for /f "usebackq" %%a in (`grep "Q_OBJECT" %1 ^| wc -l`) do @set ret=%%a
if not %ret% == 0 echo %1 >> %2


Yes, this is on Windows; you can either install these utilities or use another alternative. The two parameters (%1 and %2) are the source file to check for Q_OBJECT and the result file where we'll write the source file's path, if a match is found.

I've then used the .targets file for uic on the linked article and adapted it to work with my script and invoke moc. So, what does it do?
  • It runs on every .h and .cpp file on the project root and its sub-folders, except files that follow the moc_*.cpp spec.
  • For each of these files, it runs the batch script described above.
  • It reads the result file, and runs each source file on the list through moc. moc's output is placed on either the debug or the release folder.
  • It adds the moc-compiled files to ClCompile.
The last step means we don't have to manually add the moc-generated files to our project. Let's look at an example. Suppose we have a mainwindow.h that looks like this:

class MainWindow : public QMainWindow
{
    Q_OBJECT

    etc...
};

We need to run mainwindow.h through moc, which will then create moc_mainwindow.cpp. However, unless moc_mainwindow.cpp is already part of our project, it won't be included in the build. And the build will fail, obviously. This would mean that, for every x file that needed moc, we would have to manually add a moc_x.cpp to our project. Not that much work, yes, but not my cup of tea, either. Especially when we can just let the build sort it out and add it automatically.

There is just one thing I haven't yet figured out - even when this target is skipped, the moc_*.cpp files are still added to the build, which doesn't make sense to me.

uic & qrc compilers

For these, I've made almost no changes to the .targets file on the linked article.
  • I've placed the output files in the debug and release folders.
  • I've added the .h files to ClInclude and .cpp files to ClCompile.

Bringing it all together

So, this is my suggested approach:
  • Create your Qt project, and immediately run qmake -tp vc to create the MSVC project.
  • Open the MSVC IDE, and remove the following files from the project: *.ui files, moc_*.cpp files, ui_*.h files, *.qrc files, qrc_*.cpp files.
  • Check the remaining files properties for any Item Type that is set as Custom Build Tool - e.g., if you have a form, qmake -tp vc will turn your form's .h file (say, mainwindow.h) Item Type to Custom Build Tool and call moc on it (because your form has the Q_OBJECT macro). Change all Item Types to C/C++ header or C/C++ compiler.
At this point, you should have a project with only .h and .cpp files, and there should be no custom build steps. Naturally, if you try to build it in this state, the build will fail, because neither moc nor uic will be called. You'll need to add the rules to call those tools. Right-click the project on the Solution Explorer and select Build Customizations. Click Find Existing... and navigate to the folder where you have the 3 .targets files described above (I've called mine QtMOC.targets, QtUIC.targets, and QtRCC.targets). Add them, and enable them (i.e., check the check-boxes before their names).

If you build now, you should have a successful build.

So, how do I plan to use this setup, then?
  • Changes to the form should be made in Qt Designer, opening the Qt project with Qt Creator.
  • Any UI additions will require us to manually add the .h and .cpp files. There's no need to add the .ui file, it'll be picked up by our rule.
  • Any new C++ source file will have to be manually added.
  • Nothing else needs to be added to the MSVC project. Our rules will pick up all the necessary files and include them in the build.
This is a level of manual work I'm comfortable with, because all it entails is adding C++ files. I'm not worrying about adding any specific Qt files, those are handled automatically.

I'll be using the same folder as project root for both Qt Creator's and MSVC's projects. The generated files will go on different sub-folders, though - I'll use MSVC's default debug and release folders, and for Qt Creator, I'll call them something like QtC-<Kit>-debug and QtC-<Kit>-release.

The .targets files output messages to the build log. However, to see those messages, you have to go to Tools -> Options -> Projects and Solutions -> Build and Run and set both MSBuild project build output verbosity and MSBuild project build log file verbosity to Normal. Alternatively, you can tweak the Importance parameter of the Message tasks.

Clean

You'll notice when you run Build -> Clean Solution, the generated files remain. If you don't want that, there's a simple way to take care of it. Open your project's Property Pages, go to Configuration Properties -> General -> Extensions to Delete on Clean, and select Edit from the combo box. Add the following lines:
moc_*.cpp
ui_*.h
qrc_*.cpp

Yep, even though it says extensions, it actually uses the whole file spec, which is the intelligent thing to do, actually. I'll say it again, obvious though it may be: If any of your own files follow these specs, then you can't use this.

You don't need to worry about dealing with the debug or release folders, because MSVC runs Clean on those folders, depending on which configuration you have active - if you copy a moc_*.cpp file and put it on your project's root folder, or on any other sub-folder, it won't be deleted.

There's an alternative to this, which is to place all the generated files in one folder (e.g., generated_files), and adding something like this:
generated_files\moc_*.cpp
generated_files\ui_*.h
generated_files\qrc_*.cpp

This means you'd have to create this folder in both debug and release folders. If you wanted a single generated_files folder, under your project's root folder, you'd have to add "..\" to each of the 3 lines above.

A final note

I'm just learning this, and none of the above has been thoroughly tested. Hence, the disclaimers on the .targets files. Use this at your own risk.

You can get the files here.

Thursday, 25 April 2013

Qt Layout Tutorial - Part III

And here we are, in the third - and last - part of our Qt layout tutorial.

As I was preparing this post, I realized one of the group boxes we added in part I, namely the group box corresponding to area B, is not actually necessary. So, we're going to remove it (you may want to refer to part I of this tutorial, since I'll be using the names I defined there):
  • Drag group box B1 to area A. The group box corresponding to area A should "light up" its entire area, to show it has the focus and that's where you'll drop the dragged widgets.
  • Do the same with group box B2 and the vertical spacer (assuming it didn't disappear, see part II).
  • Delete the now empty group box on the right. If you did this before performing the previous steps, you'd delete the group box and everything it contains.
  • Place the group boxes and spacer back where they were.
A very important note: Before you start applying layouts, make sure area B is the exact size you want it to be. When you apply layouts in area B, it should cause a minimum amount of resizing.

So, let's finish this.

As we apply layouts, all the already existing layouts should become perfectly aligned and identically sized, and the widgets' size and position should keep their expected values. If that's not the case, then you need to review the properties on the widgets.

1. Select group box B1 and apply a vertical layout. Do the same with group box B2.

You may have to do some tweaking at this point - e.g., I've had to tweak the spacer in the 2nd row of B1, in order to get the 3 push buttons to the same size as the buttons in B2.

Notice that the push button in row 6 of B2 fills one whole row. If you don't like it, you have several options:
  • If you want the button aligned to the left of the group box, change its sizePolicy.HorizontalPolicy to "Fixed" or "Maximum". You can then set the button's minimumSize.Width to set its width.
  • If you want the button in the center of the row, you'll have to add 1 horizontal spacer to each side of the button. However, you'll have to do this BEFORE you apply the vertical layout to group box B2, because once the button is in a vertical layout, anything you add can only go above or below the button. So, just press "undo", add the 2 spacers, select all 3 widgets, and apply a horizontal layout.
  • If you want the button aligned to the right, follow the procedure in the previous point, but add only 1 spacer, to the left of the button.
If you add spacers, remember to set their sizeType to "Minimum", or area B will expand later.

2. Select the two groupboxes (B1 and B2) and the vertical spacer on the right side of the form and apply a vertical layout. This should give you both group boxes perfectly aligned, one below the other, and all the layouts within each group box (and, consequently, the widgets within each layout) should also be perfectly sized/aligned. If it doesn't - e.g., if some push buttons become larger than others, then... you've guessed it, it's time for some more tweaking.

I needed some tweaking on 4 of the push buttons that were aligned at the right side, and I've decided to change the maximumSize.Width on these buttons, instead of fine-tuning the spacers.

Area B is done, now let's turn to area A. My original idea was that it would contain a single widget, e.g., a label, that would grow and shrink as we resized the form. However, I've decided to complicate matters a bit. Instead, it will contain two widgets, a list widget on top of a text edit widget. The list will resize horizontally, but not vertically; and the text edit will resize both vertically and horizontally.

3. Add a list widget and a text edit to group box A.

4. Select the list. Its sizePolicy.HorizontalPolicy should be "Expanding". We'll set its sizePolicy.VerticalPolicy to "Minimum".

5. Now, select the text edit. Both sizePolicy.HorizontalPolicy and sizePolicy.VerticalPolicy should be "Expanding".

6. Select group box A and apply a vertical layout.

When you resize group box A, this is what should happen:
  • When the group box resizes horizontally, both widgets should resize accordingly.
  • When the group box expands vertically, the list may expand, but should stop doing so at some point, and only the text edit should keep expanding.
  • When the group box shrinks vertically, the edit box should shrink and, after some point, the list should begin shrinking, too.
When we apply all layouts, the list will stop resizing vertically.

Area A is done. It was a lot simpler because we didn't want to fine-tune our control of the widgets in this area. Keep this in mind when designing your UIs - the less control you want over size, position, and resizing, the better.

7. Before we turn to area C, let's select areas A and B and apply a horizontal layout. When you resize this layout, area B should move, but always keep the same size, and area A should maintain the resize rules outlined above.

Also notice that as you shrink this layout, either vertically or horizontally, all the widgets eventually get shrunk to oblivion. Don't worry, this will be solved soon.

Now, area C. Since I don't know very well what I could place here, I'll keep it simple - just a text edit that will resize horizontally, but not vertically.

8. Add a text edit to area C. Set its sizePolicy.VerticalPolicy to "Minimum".

9. Select the group box C and apply a horizontal layout.

10. Finally, select the form or its top widget (e.g., if it's a QMainWindow, you can select the main window or the central widget) and apply a vertical layout.

Build and run! You may need some additional touches. I did, had to tweak the sizeHint.Width on spacers to get all the buttons properly aligned.

Anyway, it's ready! Get a liquid beverage (as opposed to a "solid beverage", obviously) of your choice and indulge in as much rejoicing as legally permitted. And, while indulging in said rejoicing, notice that if you try to shrink the form it eventually stops - e.g., area B acts as a vertical break, it will stop shrinking when the vertical spacer on bottom reaches 0.

I hope you've found it useful, and not overly boring.

I'll end by saying, once again, that this tutorial is based mostly on trial and error. While I've advised certain values for widget properties, the same effect can be achieved using different values. And, naturally, there may even be a faster/better way of achieving this. And, if you happen to know of such a faster/better, you're welcome to drop me a line on the comments.

You can find the files here.

Sunday, 21 April 2013

Qt Layout Tutorial - Part II

Note: Now that the tutorial is finished, here are links to part I, part III, and the files.

Before we proceed with our tutorial, a few notes about maximumSize:
  • Qt Designer won't let you resize a widget beyond its maximumSize. So, when you want to experiment with it, make sure its value is someHugeValueDefinitelyLargerThan42 (don't ask, just read part I), so you can resize it to your heart's content.
  • If you want your widget to expand with the layout, you'll have to take that into account when you change maximumSize. In this example, since I don't want area B to expand, I'm setting maximumSize equal to the current size.
  • Even though I said you don't need to change anything on push buttons, setting their maximumSize is always an option if you're having trouble getting them to keep your desired size.
Also, if you're having trouble with widget sizes in different layouts, the first thing to do is resize the layouts so they have similar dimensions. This is especially helpful with the push buttons in this tutorial, if you don't want to change any of their properties.

One final note: For some reason I didn't figure out (but I admit I didn't do much research on it), the vertical spacer I placed on step 3 of part I went missing when I reopened the project for this post. I added it back, closed and re-opened Qt Creator, opened the project and... gone again. I know once we apply a layout (which we'll do in part III) it stays put, so I've just accepted it as another little quirk I'll have to live with.

Now, let's continue our Qt layout tutorial.

In part I we've divided the form in 3 areas, and filled area B1 (you may want to refer to part I, since I've defined these names there). Now, let's take care of area B2.

Area B2 will contain 6 rows of widgets (don't add them yet, just keep reading):
  • label + line edit + label + line edit + label + line edit + horizontal spacer + push button.
  • label + line edit + horizontal spacer + push button.
  • label + line edit + label + line edit + label + line edit + horizontal spacer + push button.
  • label + line edit + horizontal spacer + label + line edit + horizontal spacer.
  • label + combo box.
  • push button.
These widgets will need the same changes as we applied to the widgets we added to B1, otherwise B2 would cause area B to expand. So, instead of adding the widgets and changing all the properties again, I'll copy/paste as much as I can. Naturally, you're encouraged to change the widgets text and size, and play around with it; if a widget doesn't behave as expected when you apply a layout, take a look at its maximumSize and sizePolicy.

Also, don't worry if the layouts aren't perfectly aligned or have small differences in size. When we apply the final layouts in part III, everything will fall into place.

1. B2 - Row 1
1.1. Select a label and a line edit in B1, and copy/paste. Then, drag the copies to B2. Adjust the text and size of both widgets according to what you want. Repeat until you have 3 labels and 3 line edits in B2. Notice that you won't have to change sizePolicy, because the original widgets already had the correct value for our goal.

1.2. Select the horizontal spacer in B1, copy/paste, and drag it to B2. Once again, you won't have to change anything else.

1.3. You can add a new push button, instead of using copy/paste, since we didn't change anything on the push buttons.

2. B2 - Rows 2-6
For row 2, select a label, a line edit, a horizontal spacer and a push button from row 1. Copy/paste, and drag. Row 3 happens to have the exact same widgets as row 1, so you can just copy/paste row 1. For row 4, you can copy/paste a label + edit + spacer twice. And for rows 5 & 6, just add the widgets, setting the combo box's sizePolicy.HorizontalPolicy to "Minimum".

3. Next, we apply a horizontal layout to row 1. Everything should become neatly spaced, within the layout. Then, we apply a horizontal layout to row 2.

Now, since row 2 also has a push button at the right end, I'd like those buttons aligned. In order to achieve that, I aligned both layouts at the left border and fiddled with the sizeHint.Width on row 2's spacer. This is the best process I've discovered to align widgets on a layout - change their size in the property sheet. I did the same with the edit widget on row 2 to align it on the right with the 2nd edit widget on row 1.

4. Row 3 should be easy, since it's identical to row 1, so we just apply a horizontal layout. Row 4, however, may require some tweaking to get a widget alignment that looks good with the other rows. Some options are - change the dimension of the edits in rows 1-3 to align them with the second edit on row 4, or remove the second spacer in row 4 and let the 2nd edit align to the right.

I went with the first option. And that was good, because it showed me again that, while I can get around designing this UI, that doesn't mean I know exactly what's going on. I increased the size on the 3 edits in row 1. Instead of "compressing" the spacer, this sent the push button off the edge of group box. I resized the layout to fit within the group box, and, again, the spacer remained constant and the push button shrank. I only got the spacer to shrink and the push button to return to its "normal" (read "desired") size by manually reducing the spacer's sizeHint.Width on the property inspector.

However, when I did the same thing in row 2, everything went smoothly. Even though the push button was also pushed off, when I resized the layout, it went back into place and the spacer shrank automatically. I even increased the spacer's sizeHint.Width, which pushed the button off again, but when I resized the layout, back into place it went, with no need for manual resizing.

I checked the properties on all the widgets in rows 1 and 2, matching each identical widget - label to label, edit to edit, etc. All size policies were identical, all the mix/max sizes were set in the same fashion. So, why the different behaviour? No idea. At least row 3 behaved like row 1, so whatever's happening, it's consistent.

Then, I set the first spacer on row 4 with sizeType "Maximum" and adjusted the maximumSize.Width on the second edit, until it aligned on the right with the first edit on row 3.

5. Row 5 is easy, we just select both widgets and apply a horizontal layout. Then, we resize the layout, and the combo box will expand. Row 6 is just the push button, so we'll leave it be, there's no need to apply any layout.

That's it for now, we'll wrap it up next time.

Tuesday, 16 April 2013

Qt Layout Tutorial - Part I

Note: Now that the tutorial is finished, here are links to part II, part III, and the files.

Laying out a roadmap

Over the next few posts, I'll show how to get Qt's layouts to do what you want, instead of totally ignoring you and just "doing their thing".

I'll start by saying what this is not:
  • It's not a reference of Qt's widgets/layouts.
  • It's not an introduction to Qt's layouts.
In fact, it may not even be the best/cleanest way to achieve the result I present here. It's just the way I figured out. It's also the tutorial I wish I had found when I had to deal with layouts in Qt, namely, something that explained step-by-step how to create a non-trivial UI in Qt Designer.

We'll be creating an UI similar to this (riveting, heh?):

+-------++----+
|       ||    |
|   A   ||  B |
|       ||    |
+-------++----+
+-------------+
|      C      |
+-------------+

A - Visualization area. When we resize the form, this area expands/shrinks both horizontally and vertically. In this tutorial, it'll probably contain a QLabel (e.g., for image viewing).
B - Input area. This will contain two group boxes, each with several widgets. When we resize the form, this area will move, but won't grow/shrink. This area will anchor on the form's left side.
C - Feedback area. I have nothing defined for this area, and will improvise a bit once we get to it. This area will grow/shrink horizontally, will move vertically, and it will anchor on the form's bottom.

By the way, when I use the word "form", I'm referring to any of the options we can use for a .ui file in Qt - main window, dialog, page, widget, etc.

Let's get started

1. We'll start by creating a new GUI project in Qt Creator. As I said, this is not an introduction, so I expect you know how to create a GUI project in Qt Creator.

2. Add 3 group boxes to the form, with positions roughly similar to areas A, B, and C, above.

3. Add 2 more group boxes to area B, one on top of the other; we'll call them B1 (top) and B2 (bottom). Add a vertical spacer below B2. Since area A will grow vertically and area B won't, we'll use this spacer to fill the remaining space.

4. Add the following to B1, in 2 rows:
  • Top row: label + line edit + label + line edit + horizontal spacer.
  • Bottom row: 3 push buttons.

5. Edit the labels and input your description. You can then resize the labels by selecting them and pressing Ctrl+J. Or right-click and select "Lay out -> Adjust Size". Notice that the labels will adjust to the text you've entered.

6. Next, resize the line edits to a sensible width for what you expect to input. E.g., in my case, I'll be inputting numbers in the range 0-255.

7. Now, we'll apply our first layout. Select the top row (label + line edit + label + line edit + spacer) and apply a horizontal layout. What happens next depends on the width of the form and the widgets; in my case, the labels kept their width, but the edits increased in width and threw the spacer off the form, to the right.

So, what's going on? If you take a look at the labels, their sizePolicy is "Preferred, Preferred". However, for the edits, sizePolicy.HorizontalPolicy is "Expanding". And maximumSize.Width is someHugeValueDefinitelyLargerThan42 (16777215, in my case). We need to put some sane values here.

First, press Ctrl+Z to undo the layout.

We'll start with the labels. Even though they kept their size, I believe it's cleaner to change them to state our intent - change their maximumSize.Width to match their current size, i.e., geometry.Width.

Next, we'll change the edits, setting sizePolicy.HorizontalPolicy to "Minimum", and maximumSize.Width to each edit's geometry.Width.

Now, we reapply the horizontal layout. Et voilà. The madness is gone, everything's sane. Well, for now. There's still one change missing, and we'd better do it now. Why? Because if we don't do it now, when we have just a few widgets, we'll have to go through a most-certainly-not-boring-no-no-not-at-all cycle of trial and error later, when we have, say, some 20+ widgets on our form.

So, select the spacer and change its sizeType from "Expanding" to "Minimum". And why are we changing this? Because if we don't, when we apply a layout to the form, area B will expand, and that's not what we want.

So, to recap: After setting the labels and edits to your desired width, set their maximumSize.Width to that value, and set sizePolicy.HorizontalPolicy to "Minimum" for both edits and the spacer.

8. Now, we'll apply our second layout, on the 3 push buttons in the bottom row of B1.

Here, we won't have to do anything else. Even though each button's maximumSize.Width is someHugeValueDefinitelyLargerThan42, their sizePolicy.HorizontalPolicy is "Minimum"; this ensures that these buttons won't cause B1 to expand, which is our goal.

If you want to keep the buttons smaller, add 2 spacers between them. If you want to keep 2 buttons together and separated from the third one, add a spacer between them. Just remember that the spacers on B can't have sizeType "Expanding", as that'll cause B to expand later on. I'm using "Minimum" since it's what worked best for me.

A dose of reality

Even though the steps presented above are nice, clean, and simple, the process to get there was anything but. It involved generous amounts of trial and error, experimenting and fiddling.

I now have a better understanding of how to get the layouts to do what I want, but I still think this is not practical. We shouldn't have to fiddle with each control to get the results we want, there should be overriding policies at both the layout level and the container level. And Qt Designer should allow us to resize the widgets after a layout has been applied and should have options to automatically change the necessary values to keep the appearance we want within the layout.

Yes, I know. This is Open Source, if you don't like it, contribute. The thing is, my sentence above, "I now have a better understanding of how to get the layouts to do what I want", is only half the story; the other half is "but I still don't actually understand how layouts work, I still can't visualize all the parts that contribute to it and grok their interactions". And this is definitely not high on my list of priorities, nor should it be.

Nor should it be, and that is actually my main complaint about the layout managers I've had to deal with, both in Qt and in Java - for something that should be simple and transparent, they sure require a lot of investment just to get it working, once you move away from Trivialville.

Anyway, that's it for now. We'll continue this tutorial on my next post, when we'll add a boatload of widgets to area B2.

Friday, 22 March 2013

Building changes

So, long time, no writing... again.

After some more testing, and a fair bit of reading, I've come to the conclusion that I should change my work environment.

I became more aware of the MinGW fracture, and its implications. I've been using mingw.org's MinGW (I'll just call it MinGW), and I had planned to have 3 environments - MinGW, MSVC, and Clang. I've decided to add mingw-32 (already added by Qt Creator 2.6.2) and mingw-64. Yes, there has been plenty of building going on (I might change my name to Bob, eventually), but, as usual, there's also been plenty of learning (this should be a hint that things went less than smoothly).

The plan was as follows: Build for mingw, mingw-32 and MSVC. Then, add mingw-64. And, finally, Clang. I've since decided I'll leave these two for a later time, for now I'll just focus on what I already have on my system.

Building... never a dull moment, heh? And I'm fortunate enough to count Mr. Murphy among my friends, so I like to think I'm given a bonus, as far as non-dull-moments are concerned.

On my first attempt(s) at building Qt 5 (with MinGW), I included WebKit. And Mr. Murphy was kind enough to provide me with a token of our aforementioned friendship (and a new learning experience). Consider this:

g++ <snip an obscene amount of -Ds and -Is> -o path\to\InspectorBackendCommands.o path\to\InspectorBackendCommands.cpp

This was where the build first failed. And the error message complained about not being able to find the file InspectorBackendCommands.cpp. More exactly, the file path\toInspectorBackendCommands.cpp. Ah, you noticed the missing separator as well? Good. Because it sent me on a lovely quest to understand what exactly could be causing this particular concatenation to fail. The fact that it failed on both a path separator and an escape character gave this theory a bit more credibility.

Naturally, after taking a look at the makefiles (and the .pro files, and the .pri files) I realized the concatenation couldn't be the cause of the problem. I decided to run the command individually, and when I pasted it on the cmd prompt, it got truncated. Which led me to suspect some sort of limit was hit by this particular g++ invocation. Which was then confirmed by this.

However, something else was going on here, because the error message didn't truncate anything. It just removed a character. Since I ran make with debug on, I knew it was creating a temporary .bat file and running it, so I recreated the .bat file, I inserted "-DWEBKIT_DO_YOU_REALLY_NEED_ALL_THIS_CRAP_TO_BUILD" into the g++ invocation and ran it. And, naturally, it was now missing some other character.

I still don't understand what exactly is going on here. It's a sort of limit, but one that apparently removes one character, instead of truncating a string. Still, as much as I like a good challenge, I've decided to let this one pass. I made an half-assed attempt at recovering from this failure - I shortened this invocation by removing some stuff I suspected wasn't necessary and reran make. It soon stopped again, on another obscenely long line, much longer than this one. I could've tried to shorten this one, too, but I didn't even know where to start.

In the end, I decided to follow this piece of advice from here:
Consider skipping qtwebkit (...). This module is quite big, takes a long time to compile and if often a source of compile errors, so it is recommend to only download it if you intend to use it.

I don't know if I intend to use it, but I know I don't intend to spend the necessary time to solve what is not, at this moment, an important problem.

So, I deleted everything, recloned the git repo, and ran init-repository with --no-webkit. And it built.

I've since found out that there's a solution for this, but it involves changing mingw's make and rebuilding it. I'm not going to do that now.

All the other builds went smoothly with both versions of MinGW. Qt posed no problems because it was already built with mingw-32.

Then, I started building with MSVC 2012 Desktop. Everything went OK (some minor problems, easily solved), until Qt. While it built, it didn't build properly, some modules were missing (e.g., webkit wasn't even showing up in the logs). I've posted a request for help here, but got no luck. Then, I got suspicious that maybe Qt's cleaning process wasn't quite working as it should, so I deleted my local repo and cloned it again.

And this time, nmake gave me an error, about webkit requiring zlib. Which was odd, because I had zlib, and I was including it in the build paths. Then, I decided to just use -qt-zlib, and everything went smoothly.

So, of all the libs I've built (openssl, zlib, libssh2, icu, boost, poco, qt), qt is definitely the more finicky, at least on Windows. But, since it's also the lib with the largest scope, I guess that's to be expected.

Next time: Testing the builds.

Friday, 15 February 2013

Qt upgrade

I've just installed Qt Creator 2.6.2 and Qt 5.

Right now, I have:
  • Qt Creator 2.5 + Qt 4.8 using mingw/gcc 4.7.2 (let's call it "my mingw"); and
  • Qt Creator 2.6.2 + Qt 5 using mingw/gcc 4.7.0 (let's call it "Qt Creator's mingw", since this is the mingw installed with Qt Creator 2.6.2).
My goal is to have Qt Creator 2.6.2 + Qt 5 using my mingw. But right now, I'm testing the default Qt 5 env with my existing libs, to make sure I understand what's going on during linking, especially concerning mingw's std libs.

For these tests, I'm using a new concept introduced in Qt Creator, called "Kit". Each kit defines an environment (compiler, debugger, and Qt version, among other options), and once you create a kit, you can then switch to Projects mode to add it and configure its build/run settings.

E.g., I can have a kit with Qt Creator's mingw and Qt 5, another kit with my mingw and Qt5, and another kit with my mingw and Qt 4.8 (my current stable env). Then, in Projects mode, I select a kit and build. Each kit builds to its own folder, which is perfect for this kind of tests.

I did find a couple of oddities:
  • The default kit used my mingw, instead of using Qt Creator's mingw.
  • I cloned the default kit and changed it to use Qt Creator's mingw. By default, the build config was Release. I changed it to Debug and built; and it placed the binaries on the Release folder. Then, I changed it back to Release and rebuilt; and it placed the binaries on the Debug folder. Bug?

Sunday, 26 August 2012

Updating a Qt GUI from a boost::thread

OK, first things first.

After seeing how horrible the previous post looked, I've decided to look into the subject, to improve the readability here. After checking several alternatives, I've decided to use an online formatter to format the code and copy/paste the HTML into this blog. Some problems remain unsolved, but I'll leave that to another day.

Now, then. Lately, most of my work has been on PoCs (Proof-of-Concept), and I'll discuss one of those here, now: Updating a Qt GUI from a boost::thread.

For this, I used the following classes:
  • WorkWindow. The GUI; inherits from QDialog.
  • Task. The class that does the actual work and updates the GUI.
  • TaskController. I use this to glue both classes together. The PoC doesn't require it, but a more real-world (i.e., complex) example would require something to fulfill this role.
WorkWindow contains a button that starts the task:
boost::function<void(std::string)> f_res =
        boost::bind(&WorkWindow::AddSearchResult, this, ui->treeWidget_1, _1);

    m_tc->StartTask(f_res);

We create a boost::function to call WorkWindow::AddSearchResult(), binding a particular widget to the call. This is part of what I called a few posts back a callback-with-a-sideline-in-information-hiding - whoever invokes this function object has no knowledge that one of the arguments of its invocation is actually a GUI widget. m_tc is a pointer to a TaskController, and this is the invoked method:

void TaskController::StartTask(boost::function<void(std::string)> f_res)
{
    Task t(f_res);
    m_thread = new boost::thread(t);
}

We simply create a new task and fire its thread. m_thread is a pointer to a boost::thread, held by TaskController. This is the task's operator(), invoked by boost::thread:

void Task::operator()()
{
    for(auto i : str_vec)
    {
        m_UpdateUI(i);
    }
}

str_vec is an std::vector<std::string>, and simulates the results of Task's processing. m_UpdateUI is a boost::function<void(std::string)>, and is initialized with f_res.

So, Task::operator() invokes WorkWindow::AddSearchResult(), passing a string:

void WorkWindow::AddSearchResult(QTreeWidget* w, std::string s)
{
    emit SigAddSearchResult(w, s);
}

As we can see, AddSearchResult() emits a signal, forwarding both arguments. And, on WorkWindow's constructor, we connected this signal with its slot:

connect(this, SIGNAL(SigAddSearchResult(QTreeWidget*, std::string)),
        SLOT(SlotAddSearchResult(QTreeWidget*, std::string)),
        Qt::QueuedConnection);

We use Qt::QueuedConnection to ensure the slot is executed in the GUI's thread, not on Task's thread. We could use the default value, Qt::AutoConnection, and Qt would choose the correct mode, by comparing native thread IDs.

Finally, the slot:

void WorkWindow::SlotAddSearchResult(QTreeWidget* w, std::string s)
{
    QTreeWidgetItem* elem = new QTreeWidgetItem();
    elem->setText(0, s.c_str());
    w->invisibleRootItem()->addChild(elem);
}

It's actually pretty simple. To recap:
- The GUI requests a task execution, handing the task a callback to update the GUI.
- The task is started in its own thread. When it has data, it invokes the callback.
- The callback emits a signal.
- The slot processes the signal, updating the UI. The slot must be processed in the GUI's thread, and this is enforced when both signal and slot are connected.

And that's it. As a final note, using QThread provides better integration, and simplifies all this somewhat, but the steps above remain unchanged.

Thursday, 9 August 2012

Fake child window implementation in Qt

Well, the unorthodox idea didn't really pan out; the event handlers are called before the actual event finishes. So, on minimize, I had no problem, as I set the fake-child (FC) windows as children of the main window and they were all minimized together. However, on restore, things got ugly - by removing the parent/child relationship before the event finishes, all I achieved was preventing the FC windows from being restored. Out of desperation, I even did some experimentation with a timer, but, by this point, the "smell factor" was pretty much overwhelming.

So, inheritance it is. And how does it work?

I've created 2 classes: OrphanQMainWindow (which inherits from QMainWindow) and OrphanQDialog (which inherits from QDialog). These will be the two base classes for any app that needs to show the main window above (on the Z-order) its "child" windows.

OrphanQMainWindow is the simplest, but it dependes heavily on OrphanQDialog, so we'll show this class first.

We added the following (private) data members:

int zOrderIndex;
QPoint restorePosition;

static std::vector<OrphanQDialog*> orphanDialogs;
static unsigned int zOrderCounter;


orphanDialogs contains pointers to all the OrphanQDialog instances, until they're deleted. restorePosition is set when the dialog is hidden, so we can move it back to its position when it's restored. Without this measure, the dialog would be placed at the position it had when it was created. It's another design decision I don't understand, but let's not dwell on that.

zOrderCounter is used to keep track of the dialogs' Z-order. Each time a dialog is activated, zOrderCounter will be incremented, and its value will be assigned to the dialog's zOrderIndex. We handle this on the event handler for the Change event.

void OrphanQDialog::changeEvent(QEvent *event)
{
    QDialog::changeEvent(event);

    if (event->type() != QEvent::ActivationChange)
    {
        return;
    }

    if (isActiveWindow())
    {
        zOrderIndex = ++zOrderCounter;
    }
}


Next, we have three static member functions. These are called by the main window, when it minimizes, closes, and restores.

void OrphanQDialog::MinimizeAll()
{
    for (auto window : orphanDialogs)
    {
        window->restorePosition = window->pos();
        window->hide();
    }
}

void OrphanQDialog::CloseAll()
{
    for (auto window : orphanDialogs)
    {
        window->close();
    }
}

These two are self-explanatory.

void OrphanQDialog::RestoreAll()
{
    zOrderCounter = 0;

    std::sort(orphanDialogs.begin(), orphanDialogs.end(), [](OrphanQDialog* rhs,
        OrphanQDialog* lhs) -> bool
        { return rhs->zOrderIndex < lhs->zOrderIndex; });

    for (auto window : orphanDialogs)
    {
        window->move(window->restorePosition);
        window->show();
        window->zOrderIndex = ++zOrderCounter;
    }
}

Restoring is where everything comes together. First, we reset the Z-order counter. It's not really necessary, but it saves us from a highly-unlikely overflow, should our little app miraculously achieve an uptime measured in light years.

Then, we sort the vector according to the dialogs' ascending Z-order index. This makes sure we iterate through the vector to show the dialogs in the correct order, and also that we reset the dialogs' Z-order index without losing this order. As far as the user is concerned, everything is restored to its previous state, which was our goal, actually.

Finally, the ctor and dtor.

OrphanQDialog::OrphanQDialog(QWidget *parent) :
    QDialog(parent), zOrderIndex(++zOrderCounter)
{
    setAttribute(Qt::WA_DeleteOnClose);
    setWindowFlags(windowFlags() | Qt::WindowMinimizeButtonHint);
    orphanDialogs.push_back(this);
}

OrphanQDialog::~OrphanQDialog()
{
    // NOTE TO SELF: DON'T TRY TO USE RANGE-FOR WHEN YOU CALL
    // METHODS THAT NEED AN ITERATOR
    for(auto it = orphanDialogs.begin(); it != orphanDialogs.end(); ++it)
    {
        if (*it == this)
        {
            orphanDialogs.erase(it);
            break;
        }
    }
}

The dialog is created with the attribute Qt::WA_DeleteOnClose in order to avoid more logic to handle the case of dialogs that are still in the orphanDialogs vector but have already been closed, as we wouldn't want to show those. I've also added the expected frame buttons for a modeless dialog. And, yes, as you can see from the comment, I've tried the totally logic and even more totally wrong invocation of vector::erase() in the context of a range-for.

That's it for OrphanQDialog. Now, for OrphanQMainWindow, where we just implement the necessary events.

void OrphanQMainWindow::closeEvent(QCloseEvent *event)
{
    OrphanQDialog::CloseAll();
    event->accept();
}

void OrphanQMainWindow::changeEvent(QEvent *event)
{
    QMainWindow::changeEvent(event);

    if (event->type() != QEvent::WindowStateChange)
        return;

    Qt::WindowStates os = static_cast<QWindowStateChangeEvent*>(event)->oldState();
    Qt::WindowStates ns = windowState();

    // IF WE'RE MINIMIZING, MINIMIZE ALL THE OTHER WINDOWS
    if (ns & Qt::WindowMinimized)
    {
        OrphanQDialog::MinimizeAll();
    }

    //IF WE'RE RESTORING FROM A MINIMIZE, RESTORE ALL THE OTHER WINDOWS
    if (os & Qt::WindowMinimized)
    {
        OrphanQDialog::RestoreAll();
    }
}

Again, pretty much self-explanatory.

To get this working, you just add these classes to your Qt project, and inherit from them, instead of inheriting from QMainWindow and QDialog. And you may send my regards to Uncle Robert.

Two things to bear in mind:
  • If you implement the changeEvent() event handler in your class that inherits from OrphanQDialog, don't forget to call OrphanQDialog::changeEvent(), or you'll lose the Z-order tracking. The same rule applies if you implement the changeEvent()/closeEvent() handlers on your main window class - e.g., if you implement closeEvent() and don't call OrphanQMainWindow::closeEvent(), the dialogs won't be closed when you close the main window.
  • The dialogs aren't actually being minimized/restored, just hidden/shown. If your dialog classes need to do something when this happens, implement handlers for the hide/show events.
Points for future research:
  • There might be a more Qt-ish way of doing this, perhaps using signals and slots.
  • Take a look at how to actually minimize/restore the dialogs, instead of doing hide/show.

Tuesday, 7 August 2012

Ping again...

Still going through design, and plenty of PoCs (Proof-of-Concept).

And one - minor - rant...

An app creates a window. Call it a MainWindow, e.g., à-la-Qt. When the user works in that window, he may create other windows. If these windows are created as children of the main window, we have some tasks conveniently taken care of, such as minimizing/restoring those windows when the main window is minimized/restored.

However, in this arrangement, a child window can never be placed below (on the Z-order) its parent window; i.e., assuming no modal windows, when you click the parent window, it won't come to the top, it'll remain obscured by whatever child windows (children windows?) are on top of it. I realize this may be correct default behaviour. What I fail to understand is why is this dogma, why there's no property/setting to turn this off.

Why would I need to turn this off? My main window is what we might conceptually call a toolbox. On the course of executing a task, several tools may be needed, each tool corresponding to a window. So, the user may open a window (call it WinA), do some work and then need to go to the main window and open another window. It won't do to force him to move WinA out of the way to have uncluttered access to the main window.

I had this issue in C#, with WinForms. In the end, I had to create the windows as non-children and manage their behaviour myself. Now, with Qt's MainWindow/Dialog, I'm faced with the same problem. In C#, I created a separate class to track the main window and its "children". In C++, I wanted to try a different approach.

On my first attempt, I've created two static members in my fake-child (FC) window class:
  • A counter. It's increased every time an FC window is created or activated. This counter corresponds to the FC window's Z-order index. This allows me to restore the FC windows in the correct Z-order.
  • A container, to keep track of the FC windows.

With this setup, it was just a matter of implementing the ChangeEvent and CloseEvent on the FC window's class and on the MainWindow. Of course, I'd have to duplicate all this work the next time I needed it. So, I started looking for ways to avoid this duplication.

My first thought was a template class. After some experimentation, I've got a working solution for the Z-order that's mildly intrusive - requires implementation of a member function and a callback. I believe I can get rid of the member function, but not the callback - the event entry point is the parameter class, but the management occurs in the template class, which must be notified; hence, the callback. However, the template may not be the best solution, since I still need to implement the event handlers - this means we'd have more situations where the flow is from the parameter class to the template class. I'm still working on it, but I'm already considering other alternatives: 
  • Inheritance. Instead of inheriting from QDialog, I could inherit from MyQDialog class, which would implement my solution. The same for MainWindow.
  • An unorthodox idea that has popped up in my head - when the main window is minimized, set all the FC windows as its children, and when it's restored, set them as non-children again. I'd also have to set them as children when the main window is closed. If this works (i.e., if it has no side-effects), it might be the simplest solution, even though the "smell factor" is somewhat high.