📜 ⬆️ ⬇️

Wt file selection dialog



At work, I had to make some of my components on Wt: a wizard, a dialog for selecting directories and files on the device. I decided to post it on GitHub , maybe someone will need it. Under the cut there will be a simple file selection dialog on Wt + Boost.

Wt is a widget-oriented framework that is similar to Qt in API. The fact that Wt is “similar” to Qt, but not Qt!

Data model


At the core of the abstract model Wt :: WAbstractItemModel (almost like QAbstractItemModel ):
')
class FileSystemModel: public Wt::WAbstractItemModel { public: enum Roles { Name = Wt::UserRole + 1, Path = Wt::UserRole }; FileSystemModel(Wt::WObject* parent = nullptr); bool isFile(const Wt::WModelIndex& index) const; bool isDir(const Wt::WModelIndex& index) const; virtual int columnCount(const Wt::WModelIndex& parent = Wt::WModelIndex()) const; virtual int rowCount(const Wt::WModelIndex& parent = Wt::WModelIndex()) const; virtual Wt::WModelIndex parent(const Wt::WModelIndex& index) const; virtual boost::any data(const Wt::WModelIndex& index, int role = Wt::DisplayRole) const; virtual Wt::WModelIndex index(int row, int column, const Wt::WModelIndex& parent = Wt::WModelIndex()) const; virtual boost::any headerData(int section, Orientation orientation = Horizontal, int role = DisplayRole) const; private: std::unique_ptr<FileSystemModelPrivate> m_impl; }; 

When implementing a tree model, it is convenient to have the tree itself. The tree element is represented by the TreeNode structure:

 struct TreeNode { enum Type { File, Directory }; std::string filename; std::string path; TreeNode* parent; std::vector<TreeNode*> children; Type type; bool childrenLoaded; TreeNode(TreeNode* prnt = nullptr) : parent(prnt), type(Directory), childrenLoaded(false) { if (parent) { parent->children.push_back(this); } } ~TreeNode() { parent = nullptr; for (TreeNode* child : children) { delete child; } children.clear(); } size_t loadChildren() { if (childrenLoaded) { return children.size(); } boost::filesystem::path p(path); childrenLoaded = true; size_t count = 0; try { for (directory_iterator iter(p), end; iter != end; ++iter) { auto itm = new TreeNode(this); itm->filename = iter->path().filename().string(); itm->path = iter->path().string(); itm->type = is_directory(iter->path()) ? TreeNode::Directory : TreeNode::File; ++count; } std::sort(children.begin(), children.end(), [](const TreeNode* a, const TreeNode* b) { return a->filename<b->filename; }); return count; } catch (const filesystem_error&) { return 0; } } }; 

The loadChildren method reads the file system and loads nodes. Nodes will be loaded on demand, namely when rowCount is requested from the model. The root of the tree is created in the FileSystemModelPrivate constructor:

 struct FileSystemModelPrivate { FileSystemModelPrivate() : root(new TreeNode) { root->filename = "/"; root->path = "/"; } std::unique_ptr<TreeNode> root; }; 

In Wt, just as in Qt, there is a createIndex method that creates a model index (WModelIndex) and allows you to pass a pointer to a TreeNode .

The rest of the code is very simple:

 FileSystemModel::FileSystemModel(WObject* parent) : WAbstractItemModel(parent), m_impl(new FileSystemModelPrivate) { } int FileSystemModel::columnCount(const WModelIndex& parent) const { return 1; } int FileSystemModel::rowCount(const WModelIndex& parent) const { if (parent.isValid()) { TreeNode* node = static_cast<TreeNode*>(parent.internalPointer()); if (node == nullptr || node->type == TreeNode::File) { return 0; } return node->childrenLoaded ? node->children.size() : node->loadChildren(); } else { //Unix root '/' return 1; } return 0; } WModelIndex FileSystemModel::parent(const WModelIndex& index) const { if (!index.isValid()) { return WModelIndex(); } auto node = static_cast<TreeNode*>(index.internalPointer()); if (node->parent == nullptr) { return WModelIndex(); } if (node->parent->parent == nullptr) { return createIndex(0, 0, m_impl->root.get()); } const auto grand = node->parent->parent; const auto parent = node->parent; const auto res = std::lower_bound(grand->children.cbegin(), grand->children.cend(), parent); const size_t row = std::distance(grand->children.cbegin(), res); return createIndex(row, 0, parent); } boost::any FileSystemModel::data(const WModelIndex &index, int role) const { if (!index.isValid()) { return boost::any(); } auto node = static_cast<TreeNode*>(index.internalPointer()); if (node == nullptr) { return boost::any(); } switch (role) { case DisplayRole: { return node->filename; } case Path: { return node->path; } case DecorationRole: { try { return FILE_SYSTEM_ICONS.at(node->type); } catch (...) { return boost::any(); } } break; default: return boost::any(); } } WModelIndex FileSystemModel::index(int row, int column, const Wt::WModelIndex& parent) const { if (!parent.isValid()) { return createIndex(0, 0, m_impl->root.get()); } TreeNode* pNode = static_cast<TreeNode*>(parent.internalPointer()); if (pNode == nullptr) { return WModelIndex(); } return createIndex(row, column, pNode->children[row]); } boost::any FileSystemModel::headerData(int section, Orientation orientation, int role) const { if (role == DisplayRole && orientation == Horizontal) { return "File name"; } return boost::any(); } 


File Selection Dialog



The file selection dialog is inherited from Wt :: WDialog and has an interface:

 class FileDialog: public Wt::WDialog { public: FileDialog(WObject* parent = nullptr); virtual void accept(); Wt::WStringList selected() const; private: Wt::WTreeView* m_view; FileSystemModel* m_fs; }; 

The FileDialog class contains our model and the Wt :: WTreeView tree view.

Consider the constructor:

 FileDialog::FileDialog(WObject* parent) : WDialog(parent), m_view(new WTreeView()), m_fs(new FileSystemModel(this)) { setWindowTitle("Selecting files and directories"); auto cancel = new WPushButton("Cancel", footer()); cancel->clicked().connect(this, &WDialog::reject); m_view->setModel(m_fs); m_view->setSelectionBehavior(SelectItems); m_view->setSelectionMode(ExtendedSelection); auto select = new WPushButton("Select", footer()); select->clicked().connect(this, &FileDialog::accept); m_view->setSortingEnabled(false); m_view->setHeaderHeight(0); m_view->expandToDepth(1); auto layout = new WVBoxLayout; layout->addWidget(m_view); contents()->setLayout(layout); resize(600, 500); } 

Inside the body of the constructor, two buttons are created that are placed in the footer, a vertical layout that is placed in place of the content. Vertical layout is needed for setting WTreeView dimensions, otherwise the view may go beyond the dialog box.

Design
 cancel->clicked().connect(this, &WDialog::reject); 

this is a signal / slot mechanism based on Boost.Signals2 (or Boost.Signals, depends on the version of Boost). The two remaining methods are trivial.
 void FileDialog::accept() { const auto indxs = m_view->selectedIndexes(); if (indxs.size() > 0) { WDialog::accept(); } } Wt::WStringList FileDialog::selected() const { WStringList list; const auto indxs = m_view->selectedIndexes(); for (auto indx : indxs) { const WString pt = boost::any_cast<std::string>( indx.data(FileSystemModel::Path)); list << pt; } return list; } 

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


All Articles