Reputation: 51
I have been stuck on a problem recently with trying to create a project view in GTK. The issue is that my program constantly deletes and adds files to a project directory, but to account for these changes, the TreeStore of the project tree view needs to be cleared and repopulated. When the TreeStore is cleared and repopulated, the project TreeView collapses all of its headers. Take the following example:
This is the project view before the program adds another folder named "SubProject2" under MainProject, clears the TreeModel, and repopulates it with the same directory.
This is the project view after:
Note that the folder "SubProject2" was added correctly under "MainProject", but the TreeView collapsed "MainProject".
My current code for repopulating the TreeView is the following:
void populate_directory_tree_store(GtkTreeStore *treestore, const char *directory, GtkTreeIter *toplevel) {
DIR *dir;
struct dirent *entry;
GtkTreeIter child;
if (!(dir = opendir(directory)))
return;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_type == DT_DIR) {
char path[1024];
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
snprintf(path, sizeof(path), "%s/%s", directory, entry->d_name);
gtk_tree_store_append(treestore, &child, toplevel);
gtk_tree_store_set(treestore, &child,
0, entry->d_name,
-1);
populate_directory_tree_store(treestore, path, &child);
}
else {
gtk_tree_store_append(treestore, &child, toplevel);
gtk_tree_store_set(treestore, &child,
0, entry->d_name,
-1);
}
}
closedir(dir);
}
Every time the project directory is changed, I call the function like so:
gtk_tree_store_clear(treestore);
populate_directory_tree_store(treestore, "C:\\Projects", &topIterator);
I have thought about using a library like libfswatch (located here) for detecting directory changes in real time and changing the project TreeView with each change, but it requires multi-threading. I don't know if I am overlooking something in GtkTreeView, but something this trivial shouldn't be so difficult. If you have any questions or feedback, please post them.
Thank you, Vikas
Upvotes: 0
Views: 822
Reputation: 51
The way I solved this problem was by storing expanded rows in a data structure before clearing the TreeView. I used a keyed data list to associate the name of the expanded row and its corresponding depth (row name is key and depth is value). I then clear the TreeView and repopulate it as usual. After, I traverse the TreeModel row by row. If the row name is a key in the keyed datalist and the row has the same depth as the depth value of the keyed datalist, I expand that row. Here is the code:
num
{
COL_NAME = 0,
NUM_COLS
};
void populate_directory_tree_store(GtkTreeStore *treestore, const char *directory, GtkTreeIter *toplevel) {
DIR *dir;
struct dirent *entry;
GtkTreeIter child;
if (!(dir = opendir(directory)))
return;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_type == DT_DIR) {
char path[1024];
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
snprintf(path, sizeof(path), "%s/%s", directory, entry->d_name);
gtk_tree_store_append(treestore, &child, toplevel);
gtk_tree_store_set(treestore, &child,
0, entry->d_name,
-1);
populate_directory_tree_store(treestore, path, &child);
}
else {
gtk_tree_store_append(treestore, &child, toplevel);
gtk_tree_store_set(treestore, &child,
0, entry->d_name,
-1);
}
}
closedir(dir);
}
void foreach_expanded_row(GtkTreeView *tree_view, GtkTreePath *path, gpointer user_data) {
GData **datalist = user_data;
GtkTreeStore *treestore = GTK_TREE_STORE(gtk_tree_view_get_model(tree_view));
GValue key = G_VALUE_INIT;
char *treePathString = gtk_tree_path_to_string(path);
GtkTreeIter treeIter;
gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(treestore), &treeIter, treePathString);
gtk_tree_model_get_value(GTK_TREE_MODEL(treestore), &treeIter, COL_NAME, &key);
g_datalist_id_set_data(datalist, g_quark_from_string(g_value_get_string(&key)), GINT_TO_POINTER(gtk_tree_path_get_depth(path)));
}
gboolean foreach_tree_model_row(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) {
struct combined_data {
GData **datalist;
GtkTreeView *treeview;
};
struct combined_data *cd = data;
GData **datalist = cd->datalist;
GtkTreeView *treeview = cd->treeview;
GValue key = G_VALUE_INIT;
char *treePathString = gtk_tree_path_to_string(path);
GtkTreeIter treeIter;
gtk_tree_model_get_iter_from_string(model, &treeIter, treePathString);
gtk_tree_model_get_value(model, &treeIter, COL_NAME, &key);
int depth = g_datalist_get_data(datalist, g_value_get_string(&key));
if (depth && gtk_tree_path_get_depth(path) == depth) {
gtk_tree_view_expand_row(treeview, path, FALSE);
}
return FALSE;
}
void refresh_tree_view(GtkTreeView *treeview, GtkTreeStore *treestore) {
GData *datalist;
g_datalist_init(&datalist);
gtk_tree_view_map_expanded_rows(treeview, foreach_expanded_row, &datalist);
GtkTreeIter toplevel;
gtk_tree_store_clear(treestore);
gtk_tree_store_append(treestore, &toplevel, NULL);
gtk_tree_store_set(treestore, &toplevel, 0, "MainProject", -1);
populate_directory_tree_store(treestore, "C:\\Projects, &toplevel);
struct combined_data {
GData **datalist;
GtkTreeView *treeview;
};
struct combined_data *cd = malloc(sizeof(struct combined_data));
cd->datalist = &datalist;
cd->treeview = treeview;
gtk_tree_model_foreach(GTK_TREE_MODEL(treestore), foreach_tree_model_row, cd);
free(cd);
}
Every time I need to update the project view with the current directories, I simply call
refresh_tree_view(treeview, treestore);
Hope this helps anyone with the same issue.
Upvotes: 1