I am trying to create a multiple level sub-menus in my Qt app.
For this purpose, I am using a vector with directories' tree, example is:
C:\Users\meine\Main_menu_dir\folder_1\sub1
C:\Users\meine\Main_menu_dir\folder_1\sub2
C:\Users\meine\Main_menu_dir\folder_1\sub2\subsub1
C:\Users\meine\Main_menu_dir\folder_2\sub1
C:\Users\meine\Main_menu_dir\folder_2\sub1\subsub1
C:\Users\meine\Main_menu_dir\folder_2\sub2\subsub1
C:\Users\meine\Main_menu_dir\folder_2\sub2\subsub2
I am using boost lib as follow:
#include "boost/filesystem.hpp"
#include <iostream>
namespace fs = ::boost::filesystem;
I am using an iterator to go trough the string of names and create the submenu tree:
for (// iter --> iterator in the list of files//)
{
if (fs::is_directory(*iter)) // from boost lib
{
QMenu *subMenu; // create a QMenu object
// name --> name of the directory, i.e.: folder_1, sub1, sub2, ....
subMenu = new QMenu(QString::fromStdString(name), recursiveMenu);
recursiveMenu->addMenu(subMenu);
}
}
in this way I create all the sub-menus under folder_1 (or, equivalently under Main_menu_dir depending from the starting point in the iterator). recursiveMenu is the menu at which I am appending the submenus. Maybe I should update it, something like:
recursiveMenu = subMenu;
How I can change the Menu structure to have the following menu levels:
1. Folder_1
1.1 sub1
1.2 sub2
1.2.1 subsub1
2. Folder_2
2.1 sub1
2.1.1 subsub1
2.1.2 subsub2
....
Thanks a lot.
Using QDirIterator with QFileInfo:
#include <QtWidgets>
static void fill_menu(QMenu *menu, const QString & path, const QString & prefix={}){
QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
int number = 0;
while (it.hasNext()) {
number++;
QString newprefix = QString::number(number);
if(!prefix.isEmpty())
newprefix.prepend(prefix + ".");
QFileInfo info(it.next());
QString name = newprefix + " " + info.fileName();
if(info.isDir()){
QMenu *dirmenu = menu->addMenu(name);
fill_menu(dirmenu, info.absoluteFilePath(), newprefix);
}
else if(info.isFile()){
menu->addAction(name);
}
}
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QString directory{QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)};
QMainWindow w;
QMenu menu{"Files"};
w.menuBar()->addMenu(&menu);
fill_menu(&menu, directory);
w.show();
return a.exec();
}
Related
I'm trying to generate a simple QTreeView inside another widget (QMainWindow). The following code works as expected and displays the tree-view,
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MainWindow w;
w.show();
QString rootPath = "C:/";
QFileSystemModel model;
model.setRootPath("");
QTreeView tree;
tree.setModel(&model);
if (!rootPath.isEmpty()) {
const QModelIndex rootIndex = model.index(QDir::cleanPath(rootPath));
if (rootIndex.isValid())
tree.setRootIndex(rootIndex);
}
tree.setParent(&w);
tree.show();
return app.exec();
}
but if I extract the code that generates the tree-view, nothing seems to happen. The extracted function is as follows:
void create_tree(QMainWindow *w) {
QString rootPath = "C:/";
QFileSystemModel model;
model.setRootPath("");
QTreeView tree;
tree.setModel(&model);
if (!rootPath.isEmpty()) {
const QModelIndex rootIndex = model.index(QDir::cleanPath(rootPath));
if (rootIndex.isValid())
tree.setRootIndex(rootIndex);
}
tree.setParent(w);
tree.show();
}
and the corresponding function call in main function is as follows:
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MainWindow w;
w.show();
create_tree(&w);
return app.exec();
}
How does the extracted function create_tree work and why is it not showing the tree view?
QFileSystemModel model;
and
QTreeView tree;
Are local stack variables, meaning they will be gone once you exit the create_tree function.
You can solve your issue by creating them on the heap by using new, which will keep them alive. Be careful, that you need to think about how you destroy these created objects. The Qt parenting system is a great help there, because the parent will destroy its children when it is destroyed, so your tree view is fine. You should think about good parent for your model to make sure you create no memory leak.
A working version of your function looks like this - be careful that you still need to handle the models deletion:
void create_tree(QMainWindow *w) {
QString rootPath = "C:/";
QFileSystemModel* model = new QFileSystemModel();
model->setRootPath("");
QTreeView* tree = new QTreeView();
tree->setModel(model);
if (!rootPath.isEmpty()) {
const QModelIndex rootIndex = model->index(QDir::cleanPath(rootPath));
if (rootIndex.isValid())
tree->setRootIndex(rootIndex);
}
tree->setParent(w);
tree->show();
}
I have a simple QTreeWidget pointing to the root directory:
#include <QTreeWidget>
#include <QStringList>
#include <QApplication>
int main(int argc, char **argv)
{
QApplication application(argc, argv);
QStringList fileNames{"TEST/branch", "trunk"};
QTreeWidget treeWidget;
treeWidget.setColumnCount(1);
for (const auto& filename : fileNames)
{
QTreeWidgetItem *parentTreeItem = new QTreeWidgetItem(&treeWidget);
parentTreeItem->setText(0, filename.split('/').first());
QStringList filenameParts = filename.split('/').mid(1);
for(const auto& filenamePart : filenameParts)
{
QTreeWidgetItem *treeItem = new QTreeWidgetItem();
treeItem->setText(0, filenamePart);
parentTreeItem->addChild(treeItem);
parentTreeItem = treeItem;
}
}
treeWidget.show();
return application.exec();
}
Output:
The item I have selected above is /TEST/branches. How can I get the absolute path of the currently selected item?
Well, I don't think there is a built in function does that but you can write a function yourself like
QString treeItemToFullPath(QTreeWidgetItem* treeItem)
{
QString fullPath= treeItem->text(0);
while (treeItem->parent() != NULL)
{
fullPath= treeItem->parent()->text(0) + "/" + fullPath;
treeItem = treeItem->parent();
}
return fullPath;
}
edit:
Input treeItem is the selected tree item that you want to show its path. if you are sure at least one item is selected, you can get it by
treeWidget.selectedItems().first();
Another mehtod is using tooltips. You can add tip for each item, while you are adding them to tree, but you can do this after you add them in their final place.
change this
for(const auto& filenamePart : filenameParts)
{
QTreeWidgetItem *treeItem = new QTreeWidgetItem();
treeItem->setText(0, filenamePart);
parentTreeItem->addChild(treeItem);
parentTreeItem = treeItem;
}
as this
for(const auto& filenamePart : filenameParts)
{
QTreeWidgetItem *treeItem = new QTreeWidgetItem();
treeItem->setText(0, filenamePart);
parentTreeItem->addChild(treeItem);
parentTreeItem = treeItem;
treeItem->setToolTip(0, treeItemToFullPath(treeItem));
}
this way you will see the full path whenever you hover the mouse on the item.
To get notified of the current item change, one can use QTreeWidget::currentItemChanged or QItemSelectionModel::currentChanged.
There are two main approaches to obtaining the full path:
Iterate up the tree from the selected item and reconstruct the path. This keeps the data model normalized - without redundant information.
Store full path to each item.
If the tree is large, storing the model normalized will use less memory. Given that selection of the items is presumably rare because it's done on explicit user input, the cost of iterating the tree to extract the full path is minuscule. Humans aren't all that fast when it comes to mashing the keys or the mouse button.
The example demonstrates both approaches:
// https://github.com/KubaO/stackoverflown/tree/master/questions/tree-path-41037995
#include <QtWidgets>
QTreeWidgetItem *get(QTreeWidgetItem *parent, const QString &text) {
for (int i = 0; i < parent->childCount(); ++i) {
auto child = parent->child(i);
if (child->text(0) == text)
return child;
}
return new QTreeWidgetItem(parent, {text});
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QStringList filenames{"TEST/branch", "TEST/foo", "trunk"};
QWidget window;
QVBoxLayout layout(&window);
QTreeWidget treeWidget;
QLabel label1, label2;
for (const auto &filename : filenames) {
QString path;
auto item = treeWidget.invisibleRootItem();
for (auto const &chunk : filename.split('/')) {
item = get(item, chunk);
path.append(QStringLiteral("/%1").arg(chunk));
item->setData(0, Qt::UserRole, path);
}
}
QObject::connect(&treeWidget, &QTreeWidget::currentItemChanged, [&](const QTreeWidgetItem *item){
QString path;
for (; item; item = item->parent())
path.prepend(QStringLiteral("/%1").arg(item->text(0)));
label1.setText(path);
});
QObject::connect(&treeWidget, &QTreeWidget::currentItemChanged, [&](const QTreeWidgetItem *item){
label2.setText(item->data(0, Qt::UserRole).toString());
});
layout.addWidget(&treeWidget);
layout.addWidget(&label1);
layout.addWidget(&label2);
window.show();
return app.exec();
}
In my project, I have a QVector full of QStrings containing file paths/names that don't actually exist on any drive:
QVector<QString> fileNames["level.dat", "data/villages.dat", "players/player1.dat"]//etc
I want to create a tree in my QTreeWidget that resembles a sort of file directory like this:
How would I go about creating something like this quickly and efficiently?
Thanks for your time :)
This solution does not avoid duplicate, so if you need that in the future, you could extend this piece of code with adding valiation for that. Anyway, this code produces the exact ame output for me that you have just described to wish to have.
main.cpp
#include <QTreeWidget>
#include <QStringList>
#include <QApplication>
int main(int argc, char **argv)
{
QApplication application(argc, argv);
QStringList fileNames{"level.dat", "data/villages.dat", "players/player1.dat"};
QTreeWidget treeWidget;
treeWidget.setColumnCount(1);
for (const auto& filename : fileNames) {
QTreeWidgetItem *parentTreeItem = new QTreeWidgetItem(&treeWidget);
parentTreeItem->setText(0, filename.split('/').first());
QStringList filenameParts = filename.split('/').mid(1);
for (const auto& filenamePart : filenameParts) {
QTreeWidgetItem *treeItem = new QTreeWidgetItem();
treeItem->setText(0, filenamePart);
parentTreeItem->addChild(treeItem);
parentTreeItem = treeItem;
}
}
treeWidget.show();
return application.exec();
}
main.pro
TEMPLATE = app
TARGET = main
QT += widgets
CONFIG += c++14
SOURCES += main.cpp
Build and Run
qmake && make && ./main
On a site node: you ought to use QStringList rather than QVector. In addition, your current initialization attempt surely results in compiler error. That is invalid initialization.
I am trying to create pop-up menu depending on a variable as follows:
QMenu menu(widget);
for(int i = 1; i <= kmean.getK(); i++)
{
stringstream ss;
ss << i;
string str = ss.str();
string i_str = "Merge with " + str;
QString i_Qstr = QString::fromStdString(i_str);
menu.addAction(i_Qstr, this, SLOT(mergeWith1()));
}
menu.exec(position);
where:
kmean.get(K) returns an int value,
mergeWith1() is some `SLOT()` which works fine
Issue:
The loop creates an action on menu only for i=1 case, and ignores other values of i.
Additional information
When doing the same loop with casual int values (without convert) everything works fine. e.g. if I do in loop only menu.addAction(i, this, SLOT(...))) and my K=4, a menu will be created with four actions in it, named 1, 2, 3, 4 correspondingly.
What can be the problem caused by
I think the issue is in convert part, when I convert i to string using stringstream and after to QString. May be the value is somehow lost. I am not sure.
QESTION:
How to make the loop accept the convert part?
What do I do wrong in convert part?
In Qt code, you shouldn't be using std::stringstream or std::string. It's pointless.
You have a crashing bug by having the menu on the stack and giving it a parent. It'll be double-destructed.
Don't use the synchronous blocking methods like exec(). Show the menu asynchronously using popup().
In order to react to the actions, connect a slot to the menu's triggered(QAction*) signal. That way you can deal with arbitrary number of automatically generated actions.
You can use the Qt property system to mark actions with custom attributes. QAction is a QObject after all, with all the benefits. For example, you can store your index in an "index" property. It's a dynamic property, created on the fly.
Here's a complete example of how to do it.
main.cpp
#include <QApplication>
#include <QAction>
#include <QMenu>
#include <QDebug>
#include <QPushButton>
struct KMean {
int getK() const { return 3; }
};
class Widget : public QPushButton
{
Q_OBJECT
KMean kmean;
Q_SLOT void triggered(QAction* an) {
const QVariant index(an->property("index"));
if (!index.isValid()) return;
const int i = index.toInt();
setText(QString("Clicked %1").arg(i));
}
Q_SLOT void on_clicked() {
QMenu * menu = new QMenu();
int last = kmean.getK();
for(int i = 1; i <= last; i++)
{
QAction * action = new QAction(QString("Merge with %1").arg(i), menu);
action->setProperty("index", i);
menu->addAction(action);
}
connect(menu, SIGNAL(triggered(QAction*)), SLOT(triggered(QAction*)));
menu->popup(mapToGlobal(rect().bottomRight()));
}
public:
Widget(QWidget *parent = 0) : QPushButton("Show Menu ...", parent) {
connect(this, SIGNAL(clicked()), SLOT(on_clicked()));
}
};
int main (int argc, char **argv)
{
QApplication app(argc, argv);
Widget w;
w.show();
return app.exec();
}
#include "main.moc"
Pretty simple task but I didn't manage to find anything useful in documentation. I want a QTreeView to contain a single column called "Files" with data from QFileSystemView. Here's what I've got:
QFileSystemModel *projectFiles = new QFileSystemModel();
projectFiles->setRootPath(QDir::currentPath());
ui->filesTree->setModel(projectFiles);
ui->filesTree->setRootIndex(projectFiles->index(QDir::currentPath()));
// hide all but first column
for (int i = 3; i > 0; --i)
{
ui->filesTree->hideColumn(i);
}
That gives me a single column with "Name" header. How do I rename this header?
QAbstractItemModel::setHeaderData() should work. If not, you can always inherit from QFileSystemModel and override headerData().
Quick but a little dirty trick (please note w.hideColumn()):
#include <QApplication>
#include <QFileSystemModel>
#include <QTreeView>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTreeView w;
QFileSystemModel m;
m.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
m.setRootPath("C:\\");
w.setModel(&m);
w.setRootIndex(m.index(m.rootPath()));
w.hideColumn(3);
w.hideColumn(2);
w.hideColumn(1);
w.show();
return a.exec();
}
You can subclass QFileSystemModel and overide method headerData(). For example, if you want only to change first header label and leave the rest with their original values, you can do:
QVariant MyFileSystemModel::headerData(int section, Qt::Orientation orientation, int role) const {
if ((section == 0) && (role == Qt::DisplayRole)) {
return "Folder";
} else {
return QFileSystemModel::headerData(section,orientation,role);
}
}