Диалог выбора файлов на Wt

от автора


По работе пришлось сделать несколько своих компонентов на Wt: визард, диалог выбора каталогов и файлов на устройстве. Решил выложить на GitHub, может кому-нибудь понадобится. Под катом будет простой диалог выбора файлов на Wt+Boost.

Wt виджет-ориентированный фреймворк, который по API похож на Qt. То что Wt «похож» на Qt, но не Qt!

Модель данных

В основе абстрактная модель Wt::WAbstractItemModel(почти как 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; }; 

При реализации древовидной модели удобно иметь само дерево. Элемент дерева представлен структурой TreeNode

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;         }     } }; 

Метод loadChildren осуществляет чтение файловой системы и подгрузку узлов. Узлы будут подгружаться по требованию, а именно тогда, когда у модели запросят rowCount. Корень дерева создается в конструкторе FileSystemModelPrivate

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

В Wt так же как и в Qt есть метод createIndex создающий модельный индекс(WModelIndex) и позволяющий передавать указатель на TreeNode.
Остальной код очень прост:

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(); } 

Диалог выбора файлов

Диалог выбора файлов наследуется от Wt::WDialog и имеет интерфейс

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; }; 

Класс FileDialog содержит в себе нашу модель и древовидное представление Wt::WTreeView.

Рассмотрим конструктор

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); } 

Внутри тела конструктора создаются две кнопки, которые размещаются в нижнем колонтитуле, вертикальный layout, который размещается на месте содержимого. Вертикальный layout нужен для выставления размеров WTreeView, иначе представление может выйти за пределы диалогового окна.

Конструкция

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

это механизм сигнал/слотов на базе Boost.Signals2(или Boost.Signals, зависит от версии Boost). Два оставшихся методов тривиальны

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; } 

ссылка на оригинал статьи https://habrahabr.ru/post/275453/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *