Діалог вибору файлів на 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;
}


Джерело: Хабрахабр
  • avatar
  • 0

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.