📜 ⬆️ ⬇️

Developing your own File Model (instead of QDirModel and QFileSystemModel)

In this article I will talk about creating your own model file in Qt. I’ll say right away that the model was created for a specific task and was not planned for wide use, so it may not be what you want. I would also like to add that my experience in C ++ / Qt programming is not so great, so I’m quite ready for comments like: “Your code is very efficient.”

Now, about why actually ...


It is known that in Qt 4 there are two built-in models for working with files:
QDirModel and
QFileSystemModel .

However, these models work very slowly with a large number of files.
QDirModel is generally ugly, the second case is better, but even if compared with the Windows Explorer, it is still disgusting. As the research has shown by a profiler, each time you query the contents of a folder, it takes a very long wait for the mutex.
')
Therefore, it was decided to write our own file model with blackjack and whores getting rid of everything superfluous and working as fast as possible.

Work with file system


Actually, to work with the file system, it was decided to use boost , namely the boost filesystem library.

The function to get the contents of the folder looks like this:
  1. QList <FileItem *> * getFileList (FileItem * parent)
  2. {
  3. QList <FileItem *> * result = new QList <FileItem *> ();
  4. // FileItem is the class of the file element, I will describe it later.
  5. QString path = parent-> getFilePath ();
  6. QTextCodec * localeCodec = QTextCodec :: codecForLocale ();
  7. fs :: path full_path (fs :: initial_path <fs :: path> ());
  8. full_path = fs :: system_complete (fs :: path (localeCodec-> fromUnicode (path) .append ( '\ 0' ) .data ())); // Get the full path to the directory in boost view
  9. try // In case of a ban on reading
  10. {
  11. if (fs :: exists (full_path) && fs :: is_directory (full_path)) // Check for the existence and whether this is a directory, and then what is it that
  12. {
  13. fs :: directory_iterator end_iter;
  14. for (fs :: directory_iterator dir_itr (full_path); dir_itr! = end_iter; ++ dir_itr)
  15. {
  16. FileItem * fileInfo = 0;
  17. try // In case of a ban on reading
  18. {
  19. fileInfo = new FileItem (localeCodec-> toUnicode (dir_itr-> path (). string () .c_str ()),
  20. fs :: is_directory (dir_itr-> status ()),
  21. parent,
  22. (! fs :: is_directory (dir_itr-> status ())? fs :: file_size (dir_itr-> path ()): 0),
  23. QDateTime :: fromTime_t (last_write_time (dir_itr-> path ()))); // Create a new tree item
  24. result-> append (fileInfo); // and add it to the result
  25. }
  26. catch ( const std :: exception & ex)
  27. {
  28. delete fileInfo; // If something went wrong need to be removed
  29. }
  30. }
  31. }
  32. }
  33. catch ( const std :: exception & ex)
  34. {
  35. }
  36. return result;
  37. }
* This source code was highlighted with Source Code Highlighter .

The function is quite simple, you can completely do without a boost, but then you have to bother with separate code for * nix and Windows.

Class FileItem


Now let's talk about the elements of the tree ...

This is his announcement (cut unimportant pieces):
  1. class FileItem
  2. {
  3. public :
  4. FileItem (QString filePath, bool fileIsDir, FileItem * parent = 0, int size = 0, QDateTime date = QDateTime :: currentDateTime ());
  5. void setFilePath (QString what);
  6. void setFileSize ( int what) {size = what; }
  7. void setFileDateTime (QDateTime what) {date = what; }
  8. void setIsDir ( bool what) {fileIsDir = what; }
  9. void setChildren (QList <FileItem *> * what);
  10. QString getFilePath () const { return filePath; }
  11. QString getFileName () const ;
  12. QString getFileSize () const ; // In the "human comprehension"
  13. int getFileSizeInBytes () const { return size; } // In bytes
  14. QDateTime getFileDateTime () const { return date; }
  15. bool isDir () const { return fileIsDir; }
  16. QList <FileItem *> * getChildren () { return children; }
  17. void addChild (FileItem * item);
  18. int childCount () const { return children-> count (); }
  19. int row () const ; // Returns your number in the parent list
  20. FileItem * parent () { return itemParent; }
  21. void setFetched ( bool what) {fetched = what; }
  22. bool getFetched () const { return fetched; }
  23. private :
  24. void setParent (FileItem * parent) {itemParent = parent; } // In case the parent is not known at creation
  25. QString filePath;
  26. QString fileName;
  27. int size;
  28. QDateTime date;
  29. bool fileIsDir;
  30. FileItem * itemParent;
  31. QList <FileItem *> * children;
  32. bool fetched; // Did we load the kids yet?
  33. };
* This source code was highlighted with Source Code Highlighter .

From the implementation, only the constructor code is important:
  1. FileItem :: FileItem (QString filePath, bool fileIsDir, FileItem * parent, int size, QDateTime date)
  2. {
  3. this -> filePath = filePath;
  4. if (filePath.isEmpty ())
  5. fileName = "" ;
  6. else
  7. {
  8. #ifdef Q_OS_WIN // Do not forget that there are disks in Windows
  9. if (filePath.size () == 3 && filePath.at (1) == QLatin1Char ( ':' ))
  10. {
  11. fileName = filePath;
  12. fileName.chop (1);
  13. }
  14. else
  15. {
  16. QStringList fileParts = filePath.split ( "/" );
  17. fileName = fileParts.last ();
  18. }
  19. #else // and in * nix there are none
  20. QStringList fileParts = filePath.split ( "/" );
  21. fileName = fileParts.last ();
  22. #endif
  23. }
  24. this -> fileIsDir = fileIsDir;
  25. this -> size = size;
  26. this -> date = date;
  27. this -> itemParent = parent;
  28. this -> children = new QList <FileItem *> ();
  29. if (fileIsDir &&! filePath.isEmpty ())
  30. {
  31. fetched = false ; // We have not yet loaded the children.
  32. addChild ( new FileItem ( "dummy" , false , this ))); // Terrible disgusting, but without this, the widget will not sculpt you with pluses for daddies,
  33. // because it will be assumed that they are empty.
  34. // By the way, the minus of this approach is that we don’t know if the folder is actually non-empty.
  35. // and make pluses for everything,
  36. // unfortunately, kicking every child directory on the subject of "non-emptiness" is too expensive.
  37. }
  38. }
* This source code was highlighted with Source Code Highlighter .

Filemodel


And now, in fact, the model itself. Like any other "tricky" user model, it is inherited from QAbsractItemModel :
  1. class FileItemModel: public QAbstractItemModel
  2. {
  3. public :
  4. enum Columns // Four Obvious Columns Required
  5. {
  6. NameColumn = 0,
  7. SizeColumn,
  8. TypeColumn,
  9. Datecolumn
  10. };
  11. FileItemModel ();
  12. ~ FileItemModel ();
  13. QVariant data ( const QModelIndex & index, int role = Qt :: DisplayRole) const ; // Important virtual function, its definition is mandatory
  14. // responsible for displaying all the information in the tree
  15. Qt :: ItemFlags flags ( const QModelIndex & index) const ; // (virtual, mandatory)
  16. QVariant headerData ( int section, Qt :: Orientation orientation, int role = Qt :: DisplayRole) const ; // Responsible for column headings (virtual, mandatory)
  17. QModelIndex index ( int row, int column, const QModelIndex & parent = QModelIndex ()) const ; // Returns the index of the element (virtual, mandatory)
  18. QModelIndex parent ( const QModelIndex & index) const ; // Returns the parent index (virtual, mandatory)
  19. int rowCount ( const QModelIndex & parent = QModelIndex ()) const ; // (virtual, mandatory)
  20. int columnCount ( const QModelIndex & parent = QModelIndex ()) const ; // (virtual, mandatory)
  21. FileItem * item (QModelIndex index) const { return static_cast <FileItem *> (index.internalPointer ()); } // Returns an item by index
  22. FileItem * getRootItem () { return rootItem; }
  23. bool hasChildren ( const QModelIndex & index) const { return index.isValid ()? item (index) -> childCount (): true ; }
  24. bool canFetchMore ( const QModelIndex & index) const { return index.isValid ()? ! item (index) -> getFetched (): true ; }
  25. void fetchMore ( const QModelIndex & index); // Executed when the plus (or minus) button is pressed (virtual, mandatory)
  26. void refresh () {emit layoutChanged (); }
  27. private :
  28. FileItem * rootItem;
  29. static const int ColumnCount = 4; // Columns always 4
  30. QFileIconProvider iconProvider; // Standard icons for disks, files and folders
  31. protected :
  32. void readDir (FileItem * item); // loads children
  33. };
* This source code was highlighted with Source Code Highlighter .

From the implementation it is important to note the following methods:
  1. FileItemModel :: FileItemModel ()
  2. {
  3. lastSortColumn = 0;
  4. lastSortOrder = Qt :: AscendingOrder;
  5. #ifdef Q_OS_WIN // How many wonderful discoveries are to us ...
  6. rootItem = new FileItem ( "" , true ); // And all because in windows there is no root as such
  7. QFileInfoList drives = QDir :: drives (); // But there are disks
  8. for (QFileInfoList :: iterator driveIt = drives.begin (); driveIt! = drives.end (); ++ driveIt) // This is where we create them
  9. {
  10. FileItem * drive = new FileItem ((* driveIt) .absolutePath (), true );
  11. rootItem-> addChild (drive);
  12. }
  13. #else
  14. QString path = QDir :: rootPath ();
  15. rootItem = new FileItem (path, true );
  16. readDir (rootItem); // read the root content immediately
  17. #endif
  18. }
  19. void FileItemModel :: readDir (FileItem * item)
  20. {
  21. item-> setChildren (getFileList (item)); // Get the children and immediately connect them with the ancestor
  22. // The sorting call is omitted here.
  23. }
  24. void FileItemModel :: fetchMore ( const QModelIndex & index)
  25. {
  26. if (index.isValid () &&! item (index) -> getFetched ()) // If you haven’t loaded the children yet, we’ll load them.
  27. {
  28. readDir (item (index));
  29. item (index) -> setFetched ( true );
  30. refresh ();
  31. }
  32. }
* This source code was highlighted with Source Code Highlighter .


Conclusion


Of the minuses of the class, unfortunately there is no special magic in it so that the model is automatically updated with changes in the file system. But QDirModel has the exact same problem.
In fact, I just did not need it, who needs such functionality, can look in the direction of QFileSystemWatcher

PS


I repeat, this article does not contain a ready-to-use solution, it only describes the difficulties that will arise when developing your own file model and how to solve them. I also cannot post the full code of the class in connection with the license.

Source: https://habr.com/ru/post/69658/


All Articles