1 /* Copyright (C) 2005-2006 Fabio Marzocca <thesaltydog@gmail.com>
2  * Copyright (C) 2012-2021 MATE Developers
3  *
4  * This file is part of MATE Utils.
5  *
6  * MATE Utils is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * MATE Utils is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with MATE Utils.  If not, see <https://www.gnu.org/licenses/>.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23 
24 #include <gtk/gtk.h>
25 #include <glib.h>
26 #include <glib/gprintf.h>
27 #include <glib/gi18n.h>
28 #include <string.h>
29 
30 #include "baobab.h"
31 #include "baobab-treeview.h"
32 #include "baobab-cell-renderer-progress.h"
33 #include "baobab-utils.h"
34 #include "callbacks.h"
35 
36 #define GET_WIDGET(x) (GTK_WIDGET (gtk_builder_get_object (baobab.main_ui, (x))))
37 
38 static GtkTreeStore *
create_model(void)39 create_model (void)
40 {
41 	GtkTreeStore *mdl = gtk_tree_store_new (NUM_TREE_COLUMNS,
42 						G_TYPE_STRING,	/* COL_DIR_NAME */
43 						G_TYPE_STRING,	/* COL_H_PARSENAME */
44 						G_TYPE_DOUBLE,	/* COL_H_PERC */
45 						G_TYPE_STRING,	/* COL_DIR_SIZE */
46 						G_TYPE_UINT64,	/* COL_H_SIZE */
47 						G_TYPE_UINT64,	/* COL_H_ALLOCSIZE */
48 						G_TYPE_STRING,	/* COL_ELEMENTS */
49 						G_TYPE_INT,	/* COL_H_ELEMENTS */
50 						G_TYPE_STRING,	/* COL_HARDLINK */
51 						G_TYPE_UINT64	/* COL_H_HARDLINK */
52 						);
53 
54 	return mdl;
55 }
56 
57 static void
on_tv_row_expanded(GtkTreeView * treeview,GtkTreeIter * arg1,GtkTreePath * arg2,gpointer data)58 on_tv_row_expanded (GtkTreeView *treeview,
59 		    GtkTreeIter *arg1,
60 		    GtkTreePath *arg2,
61 		    gpointer data)
62 {
63 	gtk_tree_view_columns_autosize (treeview);
64 }
65 
66 static void
on_tv_cur_changed(GtkTreeView * treeview,gpointer data)67 on_tv_cur_changed (GtkTreeView *treeview, gpointer data)
68 {
69 	GtkTreeIter iter;
70 	gchar *parsename = NULL;
71 
72 	gtk_tree_selection_get_selected (gtk_tree_view_get_selection (treeview), NULL, &iter);
73 
74 	if (gtk_tree_store_iter_is_valid (baobab.model, &iter)) {
75 		gtk_tree_model_get (GTK_TREE_MODEL (baobab.model), &iter,
76 				    COL_H_PARSENAME, &parsename, -1);
77 	}
78 }
79 
80 static void
contents_changed(void)81 contents_changed (void)
82 {
83 	if (messageyesno (_("Rescan your home folder?"),
84 			  _("The content of your home folder has changed. Select rescan to update the disk usage details."),
85 			  GTK_MESSAGE_QUESTION, _("_Rescan"), baobab.window) == GTK_RESPONSE_OK) {
86 		baobab_rescan_current_dir ();
87 	}
88 	else {
89 		/* Just update the total */
90 		baobab_update_filesystem ();
91 	}
92 }
93 
94 static gboolean
on_tv_button_press(GtkWidget * widget,GdkEventButton * event,gpointer data)95 on_tv_button_press (GtkWidget *widget,
96 		    GdkEventButton *event,
97 		    gpointer data)
98 {
99 	GtkTreePath *path;
100 	GtkTreeIter iter;
101 	GFile *file;
102 
103 	gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
104 	                               (gint) event->x, (gint) event->y,
105 	                               &path, NULL, NULL, NULL);
106 	if (!path)
107 		return TRUE;
108 
109 	/* get the selected path */
110 	g_free (baobab.selected_path);
111 	gtk_tree_model_get_iter (GTK_TREE_MODEL (baobab.model), &iter,
112 				 path);
113 	gtk_tree_model_get (GTK_TREE_MODEL (baobab.model), &iter,
114 			    COL_H_PARSENAME, &baobab.selected_path, -1);
115 
116 	file = g_file_parse_name (baobab.selected_path);
117 
118 	if (baobab.CONTENTS_CHANGED_DELAYED) {
119 		GFile *home_file;
120 
121 		home_file = g_file_new_for_path (g_get_home_dir ());
122 		if (g_file_has_prefix (file, home_file)) {
123 			baobab.CONTENTS_CHANGED_DELAYED = FALSE;
124 			if (baobab.STOP_SCANNING) {
125 				contents_changed ();
126 			}
127 		}
128 		g_object_unref (home_file);
129 	}
130 
131 	/* right-click */
132 	if (event->button == 3) {
133 
134 		if (g_file_query_exists (file, NULL)) {
135 		     popupmenu_list (path, event, can_trash_file (file));
136 		}
137 	}
138 
139 	gtk_tree_path_free (path);
140 	g_object_unref (file);
141 
142 	return FALSE;
143 }
144 
145 static gboolean
baobab_treeview_equal_func(GtkTreeModel * model,gint column,const gchar * key,GtkTreeIter * iter,gpointer data)146 baobab_treeview_equal_func (GtkTreeModel *model,
147                             gint column,
148                             const gchar *key,
149                             GtkTreeIter *iter,
150                             gpointer data)
151 {
152 	gboolean results = TRUE;
153 	gchar *name;
154 
155 	gtk_tree_model_get (model, iter, 1, &name, -1);
156 
157 	if (name != NULL) {
158 		gchar * casefold_key;
159 		gchar * casefold_name;
160 
161 		casefold_key = g_utf8_casefold (key, -1);
162 		casefold_name = g_utf8_casefold (name, -1);
163 
164 		if ((casefold_key != NULL) &&
165 		    (casefold_name != NULL) &&
166 		    (strstr (casefold_name, casefold_key) != NULL)) {
167 			results = FALSE;
168 		}
169 		g_free (casefold_key);
170 		g_free (casefold_name);
171 		g_free (name);
172 	}
173 	return results;
174 }
175 
176 static void
perc_cell_data_func(GtkTreeViewColumn * col,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)177 perc_cell_data_func (GtkTreeViewColumn *col,
178 		     GtkCellRenderer *renderer,
179 		     GtkTreeModel *model,
180 		     GtkTreeIter *iter,
181 		     gpointer user_data)
182 {
183 	gdouble perc;
184 	gchar textperc[10];
185 
186 	gtk_tree_model_get (model, iter, COL_H_PERC, &perc, -1);
187 
188  	if (perc < 0)
189 		strcpy (textperc, "-.- %");
190 	else if (perc == 100.0)
191 		strcpy (textperc, "100 %");
192 	else
193  		g_sprintf (textperc, " %.1f %%", perc);
194 
195 	g_object_set (renderer, "text", textperc, NULL);
196 }
197 
198 GtkWidget *
create_directory_treeview(void)199 create_directory_treeview (void)
200 {
201 	GtkCellRenderer *cell;
202 	GtkTreeViewColumn *col;
203 
204 	GtkWidget *tvw = GET_WIDGET ("treeview1");
205 
206 	g_signal_connect (tvw, "row-expanded",
207 			  G_CALLBACK (on_tv_row_expanded), NULL);
208 	g_signal_connect (tvw, "cursor-changed",
209 			  G_CALLBACK (on_tv_cur_changed), NULL);
210 	g_signal_connect (tvw, "button-press-event",
211 			  G_CALLBACK (on_tv_button_press), NULL);
212 
213 	/* dir name column */
214 	g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (tvw)), "changed",
215 			  G_CALLBACK (on_tv_selection_changed), NULL);
216 	cell = gtk_cell_renderer_text_new ();
217 	col = gtk_tree_view_column_new_with_attributes (NULL, cell, "markup",
218 							COL_DIR_NAME, "text",
219 							COL_DIR_NAME, NULL);
220 	gtk_tree_view_column_set_sort_column_id (col, COL_DIR_NAME);
221 	gtk_tree_view_column_set_reorderable (col, TRUE);
222 	gtk_tree_view_column_set_title (col, _("Folder"));
223 	gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
224 	gtk_tree_view_column_set_resizable (col, TRUE);
225 	gtk_tree_view_append_column (GTK_TREE_VIEW (tvw), col);
226 
227 	/* percentage bar & text column */
228 	col = gtk_tree_view_column_new ();
229 
230 	cell = baobab_cell_renderer_progress_new ();
231 	gtk_tree_view_column_pack_start (col, cell, FALSE);
232 	gtk_tree_view_column_set_attributes (col, cell, "perc",
233 	                                     COL_H_PERC, NULL);
234 
235 	cell = gtk_cell_renderer_text_new ();
236 	gtk_tree_view_column_pack_start (col, cell, TRUE);
237 	gtk_tree_view_column_set_cell_data_func (col, cell,
238 						 perc_cell_data_func,
239 						 NULL, NULL);
240 
241 	g_object_set (G_OBJECT (cell), "xalign", (gfloat) 1.0, NULL);
242 	gtk_tree_view_column_set_sort_column_id (col, COL_H_PERC);
243 	gtk_tree_view_column_set_reorderable (col, TRUE);
244 	gtk_tree_view_column_set_title (col, _("Usage"));
245 	gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
246 	gtk_tree_view_column_set_resizable (col, FALSE);
247 	gtk_tree_view_append_column (GTK_TREE_VIEW (tvw), col);
248 
249 	/* directory size column */
250 	cell = gtk_cell_renderer_text_new ();
251 	col = gtk_tree_view_column_new_with_attributes (NULL, cell, "markup",
252 							COL_DIR_SIZE, "text",
253 							COL_DIR_SIZE, NULL);
254 	g_object_set (G_OBJECT (cell), "xalign", (gfloat) 1.0, NULL);
255 	gtk_tree_view_column_set_sort_column_id (col,
256 						 baobab.show_allocated ? COL_H_ALLOCSIZE : COL_H_SIZE);
257 	gtk_tree_view_column_set_reorderable (col, TRUE);
258 	gtk_tree_view_column_set_title (col, _("Size"));
259 	gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
260 	gtk_tree_view_column_set_resizable (col, TRUE);
261 	gtk_tree_view_append_column (GTK_TREE_VIEW (tvw), col);
262 
263 	/* store this column, we need it when toggling 'allocated' */
264 	g_object_set_data (G_OBJECT (tvw), "baobab_size_col", col);
265 
266 	/* objects column */
267 	cell = gtk_cell_renderer_text_new ();
268 	col = gtk_tree_view_column_new_with_attributes (NULL, cell, "markup",
269 							COL_ELEMENTS, "text",
270 							COL_ELEMENTS, NULL);
271 	g_object_set (G_OBJECT (cell), "xalign", (gfloat) 1.0, NULL);
272 	gtk_tree_view_column_set_sort_column_id (col, COL_H_ELEMENTS);
273 	gtk_tree_view_column_set_reorderable (col, TRUE);
274 	gtk_tree_view_column_set_title (col, _("Contents"));
275 	gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
276 	gtk_tree_view_column_set_resizable (col, TRUE);
277 	gtk_tree_view_append_column (GTK_TREE_VIEW (tvw), col);
278 
279 	/* hardlink column */
280 	cell = gtk_cell_renderer_text_new ();
281 	col = gtk_tree_view_column_new_with_attributes (NULL, cell, "markup",
282 							COL_HARDLINK, "text",
283 						 	COL_HARDLINK, NULL);
284 	gtk_tree_view_append_column (GTK_TREE_VIEW (tvw), col);
285 
286 	gtk_tree_view_collapse_all (GTK_TREE_VIEW (tvw));
287 	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tvw), FALSE);
288 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (GET_WIDGET ("scrolledwindow1")),
289 					GTK_POLICY_AUTOMATIC,
290 					GTK_POLICY_AUTOMATIC);
291 
292 	gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (tvw),
293 	                                     baobab_treeview_equal_func,
294 	                                     NULL, NULL);
295 
296 	baobab.model = create_model ();
297 
298 	/* By default, sort by size */
299 	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (baobab.model),
300 					      baobab.show_allocated ? COL_H_ALLOCSIZE : COL_H_SIZE,
301 					      GTK_SORT_DESCENDING);
302 
303 	gtk_tree_view_set_model (GTK_TREE_VIEW (tvw),
304 				 GTK_TREE_MODEL (baobab.model));
305 	g_object_unref (baobab.model);
306 
307 	return tvw;
308 }
309 
310 void
baobab_treeview_show_allocated_size(GtkWidget * tv,gboolean show_allocated)311 baobab_treeview_show_allocated_size (GtkWidget *tv,
312 				     gboolean show_allocated)
313 {
314 	gint sort_id;
315 	gint new_sort_id;
316 	GtkSortType order;
317 	GtkTreeViewColumn *size_col;
318 
319 	g_return_if_fail (GTK_IS_TREE_VIEW (tv));
320 
321 	gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (baobab.model),
322 					      &sort_id, &order);
323 
324 	/* set the sort id for the size column */
325 	size_col = g_object_get_data (G_OBJECT (tv), "baobab_size_col");
326 	new_sort_id = show_allocated ? COL_H_ALLOCSIZE : COL_H_SIZE;
327 	gtk_tree_view_column_set_sort_column_id (size_col, new_sort_id);
328 
329 	/* if we are currently sorted on size or allocated size,
330 	 * then trigger a resort (with the same order) */
331 	if (sort_id == COL_H_SIZE || sort_id == COL_H_ALLOCSIZE) {
332 		gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (baobab.model),
333 						      new_sort_id, order);
334 	}
335 }
336