1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3  * pkg-config-chooser
4  * Copyright (C) Johannes Schmid 2010 <jhs@gnome.org>
5  *
6  * pkg-config-chooser is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation, either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * pkg-config-chooser is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14  * See the GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "anjuta-pkg-config-chooser.h"
21 #include <glib/gi18n.h>
22 #include <libanjuta/anjuta-launcher.h>
23 
24 #define PKG_CONFIG_LIST_ALL "pkg-config --list-all"
25 
26 enum
27 {
28 	PACKAGE_ACTIVATED,
29 	PACKAGE_DEACTIVATED,
30 	LAST_SIGNAL
31 };
32 
33 enum
34 {
35 	COLUMN_ACTIVE,
36 	COLUMN_NAME,
37 	COLUMN_DESCRIPTION,
38 	N_COLUMNS
39 };
40 
41 struct _AnjutaPkgConfigChooserPrivate
42 {
43 	AnjutaLauncher* launcher;
44 	GtkTreeModel* model;
45 	GtkTreeModelFilter* filter_model;
46 	GtkTreeModelSort* sort_model;
47 
48 	gboolean selected_only;
49 	gboolean scanning;
50 
51 	GList* selected_cache;
52 };
53 
54 static guint pkg_config_chooser_signals[LAST_SIGNAL] = { 0 };
55 
56 G_DEFINE_TYPE (AnjutaPkgConfigChooser, anjuta_pkg_config_chooser, GTK_TYPE_TREE_VIEW);
57 
58 static gboolean
filter_visible_func(GtkTreeModel * model,GtkTreeIter * iter,gpointer data)59 filter_visible_func (GtkTreeModel* model,
60                      GtkTreeIter* iter,
61                      gpointer data)
62 {
63 	AnjutaPkgConfigChooser* chooser = data;
64 
65 	if (!chooser->priv->selected_only)
66 		return TRUE;
67 	else
68 	{
69 		gboolean show;
70 		gtk_tree_model_get (model, iter,
71 		                    COLUMN_ACTIVE, &show, -1);
72 		return show;
73 	}
74 }
75 
76 static void
on_listall_output(AnjutaLauncher * launcher,AnjutaLauncherOutputType output_type,const gchar * chars,gpointer user_data)77 on_listall_output (AnjutaLauncher * launcher,
78                    AnjutaLauncherOutputType output_type,
79                    const gchar * chars, gpointer user_data)
80 {
81 	gchar **lines;
82 	const gchar *curr_line;
83 	gint i = 0;
84 	AnjutaPkgConfigChooser* chooser;
85 	GtkListStore* store;
86 
87 	if (output_type == ANJUTA_LAUNCHER_OUTPUT_STDERR)
88 	{
89 		/* no way. We don't like errors on stderr... */
90 		return;
91 	}
92 
93 	chooser = ANJUTA_PKG_CONFIG_CHOOSER (user_data);
94 
95 	store = GTK_LIST_STORE (chooser->priv->model);
96 	lines = g_strsplit (chars, "\n", -1);
97 
98 	while ((curr_line = lines[i++]) != NULL)
99 	{
100 		gchar **pkgs;
101 		GtkTreeIter iter;
102 
103 		pkgs = g_strsplit (curr_line, " ", 2);
104 
105 		/* just take the first token as it's the package-name */
106 		if (pkgs == NULL)
107 			return;
108 
109 		if (pkgs[0] == NULL || pkgs[1] == NULL) {
110 			g_strfreev (pkgs);
111 			continue;
112 		}
113 
114 		gtk_list_store_append (store, &iter);
115 		gtk_list_store_set (store, &iter,
116 		                    COLUMN_ACTIVE, FALSE,
117 		                    COLUMN_NAME, pkgs[0],
118 		                    COLUMN_DESCRIPTION, g_strstrip(pkgs[1]), -1);
119 		g_strfreev (pkgs);
120 	}
121 	g_strfreev (lines);
122 }
123 
124 static void
on_listall_exit(AnjutaLauncher * launcher,int child_pid,int exit_status,gulong time_taken_in_seconds,gpointer user_data)125 on_listall_exit (AnjutaLauncher * launcher, int child_pid,
126 				   int exit_status, gulong time_taken_in_seconds,
127 				   gpointer user_data)
128 {
129 	AnjutaPkgConfigChooser* chooser = ANJUTA_PKG_CONFIG_CHOOSER (user_data);
130 
131 	g_signal_handlers_disconnect_by_func (launcher, on_listall_exit,
132 										  user_data);
133 	chooser->priv->scanning = FALSE;
134 
135 	if (exit_status != 0) g_warning(PKG_CONFIG_LIST_ALL " exit with error code %d" , exit_status);
136 
137 	anjuta_pkg_config_chooser_set_active_packages (chooser, chooser->priv->selected_cache);
138 	g_list_free_full (chooser->priv->selected_cache, g_free);
139 	chooser->priv->selected_cache = NULL;
140 
141 	g_clear_object (&chooser->priv->launcher);
142 }
143 
144 static void
on_package_toggled(GtkCellRenderer * renderer,const gchar * path,AnjutaPkgConfigChooser * chooser)145 on_package_toggled (GtkCellRenderer* renderer,
146                     const gchar* path,
147                     AnjutaPkgConfigChooser* chooser)
148 {
149 	GtkTreeIter sort_iter;
150 	GtkTreeIter filter_iter;
151 	GtkTreeIter iter;
152 	gboolean active;
153 	gchar* package;
154 
155 	GtkTreePath* tree_path = gtk_tree_path_new_from_string (path);
156 
157 	gtk_tree_model_get_iter (GTK_TREE_MODEL (chooser->priv->sort_model),
158 	                         &sort_iter, tree_path);
159 	gtk_tree_model_sort_convert_iter_to_child_iter (chooser->priv->sort_model,
160 	                                                &filter_iter, &sort_iter);
161 	gtk_tree_model_filter_convert_iter_to_child_iter (chooser->priv->filter_model,
162 	                                                  &iter, &filter_iter);
163 	g_object_get (renderer, "active", &active, NULL);
164 
165 	active = !active;
166 
167 	gtk_list_store_set (GTK_LIST_STORE (chooser->priv->model),
168 	                    &iter, COLUMN_ACTIVE, active, -1);
169 	gtk_tree_model_get (chooser->priv->model, &iter,
170 	                    COLUMN_NAME, &package, -1);
171 
172 	if (active)
173 		g_signal_emit_by_name (chooser, "package-activated", package, NULL);
174 	else
175 		g_signal_emit_by_name (chooser, "package-deactivated", package, NULL);
176 }
177 
178 static void
anjuta_pkg_config_chooser_init(AnjutaPkgConfigChooser * chooser)179 anjuta_pkg_config_chooser_init (AnjutaPkgConfigChooser *chooser)
180 {
181 	GtkTreeViewColumn* column;
182 	GtkCellRenderer* renderer;
183 
184 	chooser->priv = G_TYPE_INSTANCE_GET_PRIVATE (chooser, ANJUTA_TYPE_PKG_CONFIG_CHOOSER,
185 	                                             AnjutaPkgConfigChooserPrivate);
186 
187 	/* Create model */
188 	chooser->priv->model = GTK_TREE_MODEL (gtk_list_store_new (N_COLUMNS,
189 	                                                           G_TYPE_BOOLEAN,
190 	                                                           G_TYPE_STRING,
191 	                                                           G_TYPE_STRING));
192 	chooser->priv->filter_model = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (chooser->priv->model,
193 	                                                                                NULL));
194 	gtk_tree_model_filter_set_visible_func (chooser->priv->filter_model,
195 	                                        filter_visible_func,
196 	                                        chooser, NULL);
197 
198 	chooser->priv->sort_model =
199 		GTK_TREE_MODEL_SORT (gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(chooser->priv->filter_model)));
200 	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (chooser->priv->sort_model),
201 	                                      COLUMN_NAME, GTK_SORT_ASCENDING);
202 
203 	gtk_tree_view_set_model (GTK_TREE_VIEW (chooser),
204 	                         GTK_TREE_MODEL (chooser->priv->sort_model));
205 
206 	/* Create columns */
207 	renderer = gtk_cell_renderer_toggle_new ();
208 	g_signal_connect (renderer, "toggled", G_CALLBACK(on_package_toggled), chooser);
209 	column = gtk_tree_view_column_new_with_attributes ("",
210 	                                                   renderer,
211 	                                                   "active", COLUMN_ACTIVE,
212 	                                                   NULL);
213 	gtk_tree_view_append_column (GTK_TREE_VIEW (chooser), column);
214 
215 	renderer = gtk_cell_renderer_text_new ();
216 	column = gtk_tree_view_column_new_with_attributes ("",
217 	                                                   renderer,
218 	                                                   "text", COLUMN_NAME,
219 	                                                   NULL);
220 	gtk_tree_view_append_column (GTK_TREE_VIEW (chooser), column);
221 	renderer = gtk_cell_renderer_text_new ();
222 	column = gtk_tree_view_column_new_with_attributes ("",
223 	                                                   renderer,
224 	                                                   "text", COLUMN_DESCRIPTION,
225 	                                                   NULL);
226 	gtk_tree_view_append_column (GTK_TREE_VIEW (chooser), column);
227 	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (chooser), FALSE);
228 
229 	gtk_tree_view_set_search_column (GTK_TREE_VIEW (chooser),
230 					 COLUMN_NAME);
231 
232 	/* Create launcher */
233 	chooser->priv->scanning = TRUE;
234 	chooser->priv->launcher = anjuta_launcher_new ();
235 	anjuta_launcher_set_check_passwd_prompt (chooser->priv->launcher,
236 	                                         FALSE);
237 
238 	g_signal_connect (G_OBJECT (chooser->priv->launcher), "child-exited",
239 	                  G_CALLBACK (on_listall_exit), chooser);
240 
241 	anjuta_launcher_execute (chooser->priv->launcher,
242 	                         PKG_CONFIG_LIST_ALL, on_listall_output,
243 	                         chooser);
244 }
245 
246 static void
anjuta_pkg_config_chooser_finalize(GObject * object)247 anjuta_pkg_config_chooser_finalize (GObject *object)
248 {
249 	AnjutaPkgConfigChooser *chooser = ANJUTA_PKG_CONFIG_CHOOSER (object);
250 
251 	g_object_unref (chooser->priv->model);
252 	g_object_unref (chooser->priv->filter_model);
253 	g_object_unref (chooser->priv->sort_model);
254 
255 	if (chooser->priv->launcher)
256 		g_object_unref (chooser->priv->launcher);
257 
258 	g_list_free_full (chooser->priv->selected_cache, g_free);
259 
260 	G_OBJECT_CLASS (anjuta_pkg_config_chooser_parent_class)->finalize (object);
261 }
262 
263 static void
anjuta_pkg_config_chooser_class_init(AnjutaPkgConfigChooserClass * klass)264 anjuta_pkg_config_chooser_class_init (AnjutaPkgConfigChooserClass *klass)
265 {
266 	GObjectClass* object_class = G_OBJECT_CLASS (klass);
267 
268 	object_class->finalize = anjuta_pkg_config_chooser_finalize;
269 
270 	/**
271 	 * AnjutaPkgConfigChooser::package-activated:
272 	 * @widget: the AnjutaPkgConfigChooser that received the signal
273 	 * @package: Name of the package that was activated
274 	 *
275 	 * The ::package-activated signal is emitted when a package is activated in the list
276 	 */
277 	pkg_config_chooser_signals[PACKAGE_ACTIVATED] =
278 		g_signal_new ("package-activated",
279 		              G_OBJECT_CLASS_TYPE (klass),
280 		              0,
281 		              G_STRUCT_OFFSET (AnjutaPkgConfigChooserClass, package_activated),
282 		              NULL, NULL,
283 		              g_cclosure_marshal_VOID__STRING,
284 		              G_TYPE_NONE, 1,
285 		              G_TYPE_STRING);
286 	/**
287 	 * AnjutaPkgConfigChooser::package-deactivated:
288 	 * @widget: the AnjutaPkgConfigChooser that received the signal
289 	 * @package: Name of the package that was deactivated
290 	 *
291 	 * The ::package-activated signal is emitted when a package is deactivated in the list
292 	 */
293 	pkg_config_chooser_signals[PACKAGE_DEACTIVATED] =
294 		g_signal_new ("package-deactivated",
295 		              G_OBJECT_CLASS_TYPE (klass),
296 		              0,
297 		              G_STRUCT_OFFSET (AnjutaPkgConfigChooserClass, package_deactivated),
298 		              NULL, NULL,
299 		              g_cclosure_marshal_VOID__STRING,
300 		              G_TYPE_NONE, 1,
301 		              G_TYPE_STRING);
302 
303 	g_type_class_add_private (klass, sizeof(AnjutaPkgConfigChooserPrivate));
304 }
305 
306 /*
307  * anjuta_pkg_config_chooser_new:
308  *
309  * Returns: A new AnjutaPkgConfigChooser widget
310  */
311 GtkWidget*
anjuta_pkg_config_chooser_new(void)312 anjuta_pkg_config_chooser_new (void)
313 {
314 	return GTK_WIDGET (g_object_new (ANJUTA_TYPE_PKG_CONFIG_CHOOSER, NULL));
315 }
316 
317 /*
318  * anjuta_pkg_config_chooser_get_active_packages:
319  * @chooser: A AnjutaPkgConfigChooser
320  *
321  * Return value: (element-type utf8) (transfer full):
322  * List of packages that are activated
323  */
324 GList*
anjuta_pkg_config_chooser_get_active_packages(AnjutaPkgConfigChooser * chooser)325 anjuta_pkg_config_chooser_get_active_packages (AnjutaPkgConfigChooser* chooser)
326 {
327 	GList* packages = NULL;
328 	GtkTreeIter iter;
329 
330 	g_return_val_if_fail (ANJUTA_IS_PKG_CONFIG_CHOOSER (chooser), NULL);
331 
332 	if (gtk_tree_model_get_iter_first (chooser->priv->model, &iter))
333 	{
334 		do
335 		{
336 			gchar* model_pkg;
337 			gboolean active;
338 			gtk_tree_model_get (chooser->priv->model, &iter,
339 			                    COLUMN_NAME, &model_pkg,
340 			                    COLUMN_ACTIVE, &active, -1);
341 			if (active)
342 			{
343 				packages = g_list_append (packages, model_pkg);
344 			}
345 		}
346 		while (gtk_tree_model_iter_next (chooser->priv->model,
347 		                                 &iter));
348 	}
349 	return packages;
350 }
351 
352 /*
353  * anjuta_pkg_config_chooser_get_active_packages:
354  * @chooser: A AnjutaPkgConfigChooser
355  * @packages: (element-type utf8) (transfer full): List of packages to be activated in the list
356  *
357  */
358 void
anjuta_pkg_config_chooser_set_active_packages(AnjutaPkgConfigChooser * chooser,GList * packages)359 anjuta_pkg_config_chooser_set_active_packages (AnjutaPkgConfigChooser* chooser, GList* packages)
360 {
361 	GList* pkg;
362 	GtkTreeIter iter;
363 
364 	g_return_if_fail (ANJUTA_IS_PKG_CONFIG_CHOOSER (chooser));
365 
366 	/* Deselect all packages */
367 	if (gtk_tree_model_get_iter_first (chooser->priv->model, &iter))
368 	{
369 		do
370 		{
371 			gtk_list_store_set (GTK_LIST_STORE (chooser->priv->model), &iter,
372 				                    COLUMN_ACTIVE, FALSE, -1);
373 		}
374 		while (gtk_tree_model_iter_next (chooser->priv->model,
375 		                                 &iter));
376 	}
377 
378 	for (pkg = packages; pkg != NULL; pkg = g_list_next (pkg))
379 	{
380 		if (chooser->priv->scanning)
381 		{
382 			chooser->priv->selected_cache = g_list_append (chooser->priv->selected_cache,
383 			                                               g_strdup(pkg->data));
384 		}
385 		else if (gtk_tree_model_get_iter_first (chooser->priv->model, &iter))
386 		{
387 			do
388 			{
389 				gchar* model_pkg;
390 				gtk_tree_model_get (chooser->priv->model, &iter,
391 				                    COLUMN_NAME, &model_pkg, -1);
392 				if (g_str_equal (model_pkg, pkg->data))
393 				{
394 					gtk_list_store_set (GTK_LIST_STORE (chooser->priv->model), &iter,
395 					                    COLUMN_ACTIVE, TRUE, -1);
396 				}
397 				g_free (model_pkg);
398 			}
399 			while (gtk_tree_model_iter_next (chooser->priv->model,
400 			                                 &iter));
401 		}
402 	}
403 }
404 
405 /*
406  * anjuta_pkg_config_chooser_show_active_only:
407  * @chooser: A AnjutaPkgConfigChooser
408  * @show_selected: whether to show only activated packages
409  *
410  * Show activated packages only, this is mainly useful when the tree is set
411  * insensitive but the user should be able to see which packages have been activated
412  */
413 void
anjuta_pkg_config_chooser_show_active_only(AnjutaPkgConfigChooser * chooser,gboolean show_selected)414 anjuta_pkg_config_chooser_show_active_only (AnjutaPkgConfigChooser* chooser, gboolean show_selected)
415 {
416 	g_return_if_fail (ANJUTA_IS_PKG_CONFIG_CHOOSER (chooser));
417 
418 	chooser->priv->selected_only = show_selected;
419 	gtk_tree_model_filter_refilter (chooser->priv->filter_model);
420 }
421 
422 /*
423  * anjuta_pkg_config_chooser_show_active_column:
424  * @chooser: A AnjutaPkgConfigChooser
425  * @show_column: whether the active column should be shown
426  *
427  * Can be used to hide the active column in situation where you are more interested
428  * in the selection then in the activated packages.
429  */
430 void
anjuta_pkg_config_chooser_show_active_column(AnjutaPkgConfigChooser * chooser,gboolean show_column)431 anjuta_pkg_config_chooser_show_active_column (AnjutaPkgConfigChooser* chooser, gboolean show_column)
432 {
433 	GtkTreeViewColumn* column;
434 
435 	g_return_if_fail (ANJUTA_IS_PKG_CONFIG_CHOOSER (chooser));
436 
437 	column = gtk_tree_view_get_column (GTK_TREE_VIEW (chooser), COLUMN_ACTIVE);
438 	gtk_tree_view_column_set_visible (column, show_column);
439 }
440 
441 /*
442  * anjuta_pkg_config_chooser_get_selected_package:
443  * @chooser: A AnjutaPkgConfigChooser
444  *
445  * Return value: the currently selected packages in the list
446  *
447  */
448 gchar*
anjuta_pkg_config_chooser_get_selected_package(AnjutaPkgConfigChooser * chooser)449 anjuta_pkg_config_chooser_get_selected_package (AnjutaPkgConfigChooser* chooser)
450 {
451 	GtkTreeIter sort_iter;
452 	GtkTreeSelection* selection;
453 
454 	g_return_val_if_fail (ANJUTA_IS_PKG_CONFIG_CHOOSER (chooser), NULL);
455 
456 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (chooser));
457 
458 	if (gtk_tree_selection_get_selected (selection, NULL, &sort_iter))
459 	{
460 		gchar* package;
461 		GtkTreeIter filter_iter;
462 		GtkTreeIter iter;
463 		gtk_tree_model_sort_convert_iter_to_child_iter (chooser->priv->sort_model,
464 		                                                &filter_iter, &sort_iter);
465 		gtk_tree_model_filter_convert_iter_to_child_iter (chooser->priv->filter_model,
466 		                                                  &iter, &filter_iter);
467 		gtk_tree_model_get (chooser->priv->model, &iter, COLUMN_NAME, &package, -1);
468 
469 		return package;
470 	}
471 	return NULL;
472 }
473