Qt novice: code organization for a CAD-like application
this is my first post and I want to apologize if it is not quite suited for this forum, but I couldn't figure out a best place for solving my problem.
I am a C++/Qt novice (even if, now, I'm into tutorials and learning both since 4 months) and I have to develop from scratch a GUI for a CAD-like application, a computational fluid dynamic solver with a mesh generator. I am the main developer of both solver and grid generator, which are written in Fortran, so I am not a novice in programming itself, just novice in (meaningful) OOP.
What the application should do is managing a tree of cases, each case being a collection of STL surfaces (from 0 to many) and a computational grid with one or more zones. So the logical organization of the application is:
application -> cases -> (surfaces + zones + probably some other list of stuff per each case)
Basically, the user can:
- set some options at the general outer application level (some might affect all the cases and/or zones and surfaces of all the cases)
- add/copy/delete cases in/from the tree and set some options for each case
- add/edit/delete STL surfaces for each of the cases and set some options for each surface
- add/edit/delete zones in the grid for each of the cases and set some options for each zone
- the main window will be a qtreeview (or sort of) to manage the cases, next to a Qtabwidget where each case is managed
- the list of STL surfaces and zones appear in different qwidgets of the qtabwidget, in some they are created, in some edited (e.g., some option linked to the given STL surface or zone can be modified), in some just listed according to specific options set up in a different widget
Note that the numbers of cases, STL surfaces and zones has no logical upper limit. Still, their number is typically in the order of units or tens, very rarely in the hundreds, so no database organization is required. Also, all the visualization will be based on VTK, so I don't have to manage that directly. Finally, if this can be of any relevance, I'm using Qt 5.9.
Now, my problem is that I am unsure about how to organize the code in a meaningful (per Qt), scalable (painless addition of further features) way. That is, I think I have figured out most of the widgets that I will need, how to use them, etc., but I somehow miss the overall picture in terms of Qt specific data structures to use in order to organize the main elements of the code as listed above. This might have more to do with general GUI development inexperience than Qt inexperience itself; still, I feel that I need a more specific Qt guidance to do an effective job (as most examples-tutorials I have read don't quite represent my specific case).
What's my picture at the moment:
- each logical component (cases, STL surfaces and zones) will have its own class where i will leverage any possible Qt feature making the job easier, but nothing related to the GUI itself. So, basically, a Qlist of cases objects, each one having Qlists of STL and zone objects (and other stuff where needed).
- the GUI is built on top of the previous classes, making use of models, delegates, views, filters and widget mappers, trying to use the largest possible amount of features from the previous data classes and only adding the minimum required features at the GUI level.
I know this is a very rough view, as I still have to get my hands dirty, but I would like to know if this approach can be considered meaningful or there might be some issues. Also, as a novice, I don't probably see some aspects which might be relevant and you might want to elucidate me on.
Thank you all for your attention
Hi and welcome to devnet,
The overall idea doesn't look bad. Some small suggestions: you should rather use QVector than QList and also depending on how your data are going to move around QSharedDataPointer might also be of interest to implement implicit sharing and thus avoid copying data around when not needed.
If you are using Qt for the GUI, why not use Qt3D as well ?
Thank you both for your suggestions.
Before answering, I try to give some more context.
The application is intended as an interface toward two external executables (the grid generator and the solver). It produces/arranges input for them and reads their output (on a per case basis). The three of them are completely independent.
In order to keep consistency as high as possible in this setting, I plan to have a single XML file as both input for the two executables and saved settings for the single case in the GUI. In practice, each case will have its own directory in the case tree with an XML file in it. When the GUI app starts working on a case, it reads the XML file in the directory and gets all the information for the case; when the user is done setting the case, he saves back the XML file in the directory.
In the end, the user has the freedom to launch the two executables from the GUI or just copying the folder wherever he needs and launch the executables from terminal (the two executables and the GUI are already designed to need no user intervention in between the two executables calls). This is needed to use workstations as well as large clusters.
@SGaist : I admit that, in the beginning, I naively choose QList as per some indication that, thanks to you, I now realize was not up to date (including most examples in the Model/View arena). I managed to read most of the relevant links returned by google on the query "qvector vs qlist", including this very long thread:
but I'm still not sure this is the best option for me. Let me explain. In the vast majority of the cases, the application will not cycle over its lists (cases, zones or STL surfaces) performing a certain operation, because in its very soul the app has the fact that the user has to consciously and properly manage each of them. This for creation, deletion and editing. Also, creation and deletion of items from such lists happen in a very non linear, unpredictable, fashion at a certain moment (initial setup), but then don't typically happen anymore.
So, for a poor Fortran programmer like me, who either has a vector or a list (to be programmed by himself in Fortran) as options, managing very large objects (as cases, stl and zones would be) in a very non linear fashion seems the case for using a list. Now, and you might probably elucidate on this, I see QList just as a smarter list, a list with vector like behavior on the pointers to the objects in the list. What I don't get from what I've read, is if QList will double the allocation of the objects in it per se. What if I just use the following approach:
would I still end up doubling the allocations? I feel that most of the discussion on "qvector vs qlist" in the first link I posted above, originated from a somehow specific use case, like managing a large collection of graphic objects, which is not quite what I'm into at the moment.
Still, there is a very specific corner case where I plan to use QVector, when the user consciously needs to manage a large collection of zones or surfaces as a whole (tens or hundreds), actually performing for each of them the exact same operation. In this case I actually plan to subclass the zones and surfaces classes adding a QVector to them to manage the collection as a whole.
Finally, does the QSharedDataPointer suggestion still apply if I stick with QList instead of QVector? This is probably a nonsense question just revealing my lack of knowledge on Qt, still...
@li0nsar3c00l : I actually gave a lot of thinking to that question. I would really like to use as few libraries as possible. Also, I saw some pure Qt applications (probably not even using Qt3D) being amazingly fast with respect to VTK (i.e., Paraview) for STL visualization. However, what I understood of Qt3D, is that it is basically oriented toward surface meshes. VTK, in contrast, gives me ready to use visualization for 3D fields in computational meshes, besides a lot of additional functionality for managing STL surfaces. All of this with very few lines of code. Besides this, it is also a de facto standard in the field, something I can leverage to reuse the knowledge I gain with this application in additional ones. However, this is the most open part of the project, where most changes are going to happen.
I just want to summarize what my difficulties are at the moment, as the more I read the more I feel confused. Basically, I have my data structure which, in the end, can be represented as follows:
- a folder tree starting from a given root in the file system
- each leaf in the folder tree is a folder corresponding to a case
- each case is completely represented by an XML file in the same folder
- data too heavy to be directly included in the XML file (e.g., STL files) is added as separate file in the same folder and referenced in the XML by strings
What my application needs to do is just managing this collection of XML (and related) files. What I find most difficult at the moment is figuring out the best Qt-based data structure to interact with such data via a Qt-based GUI (ok, I'm not considering a one-to-one GUI representation of the XML, I've already seen examples of that). I keep going trough the model-view framework tutorials, but I still can't understand which part of my data should go to the model, which one to the view, which one to pure data (if any). Also, is QAbstractItemModel/View the right starting point?
It seems that I might not be alone:
and different possibilities exists:
Still, it is difficult, without a computer science background, to get this straight at first shot for such a complex application.
Sorry for keeping this up and thanks for any additional help
Part of the answer is it depends (I know, that's not something one wants to read nor hear).
With Qt the model contains the data that will be used to show something on screen with possibly cues as to how represent them as for example the Icon to use for the decoration role. The view is responsible of showing a visual representation of what the model contains based on the data and other information it may find in the model.
The data you put inside your model will depend on the use you are going to make of it. For example, if you need a fraction of your XML file to show something to your application user then there's no sense in loading all the XML files in memory. The same as it might make no sense to parse all the files at startup rather than on demand when a user access it. As an example, most photo management application create thumbnails of your images only when you access the corresponding files or folders.
Another point for the model is that you can also see it as a "wrapper" around your data structure. So you should first determine how your data would be best represented, is it a simple list ? Should it rather be a tree structure ? Once you are sure about that you can start deciding which container is the best. As for the QVector VS QList, you can also read this excellent article from Marc Mutz and study what he has published (mailing list, KDAB blog and other sources).
thank you again for your time. Doing this as a solo job without an experienced guidance is making it harder than expected. In this respect, even an "it depends" from an experienced developer like you, means a lot to me. I guess it's time for me to stop digging and start doing the real job.
For the record, I'll probably take the wrapper way. Which leads me to my last question (I swear): is it common in Qt, when wrapping your own data structure (class), to actually derive it from a QObject (or using any other possible mean) to make signals and slots available to it? That is, is it common in Qt to derive your own classes from QObjects just to give them their features?
@sbaffini Sure, it's very common. Moreover, deriving QObject directly or through descendant classes is the only way to use signal-slot functionality in Qt.
If you need/want to use signals and slots with your class, then deriving from QObject is mandatory. If you need a reduced set of features then Q_GADGET is worth taking a look.
Note that it's absolutely not mandatory to derive all your classes from QObject to use them in your Qt based application. Not all classes in Qt are derived from QObject. For example QString is not, none of the containers are etc.