anjuta r4507 - in trunk: . libanjuta libanjuta/interfaces plugins plugins/gbf-am plugins/gbf-am/GBF plugins/gbf-mkfile plugins/gbf-mkfile/GBF plugins/project-import plugins/project-manager



Author: sgranjoux
Date: Wed Dec 31 11:55:52 2008
New Revision: 4507
URL: http://svn.gnome.org/viewvc/anjuta?rev=4507&view=rev

Log:
	* configure.in
	libanjuta/interfaces/libanjuta.idl
	(added) libanjuta/gbf-project.c
	(added) libanjuta/gbf-project.h
	libanjuta/Makefile.am
	plugins/Makefile.am
	plugins/project-import/project-import.c
	plugins/project-manager/plugin.c
	plugins/project-manager/plugin.h
	(added) plugins/project-manager/gbf-project-model.c
	(added) plugins/project-manager/gbf-project-model.h
	(added) plugins/project-manager/gbf-tree-data.c
	(added) plugins/project-manager/gbf-tree-data.h
	(added) plugins/project-manager/gbf-project-view.c
	(added) plugins/project-manager/gbf-project-view.h
	(added) plugins/project-manager/gbf-project-util.c
	(added) plugins/project-manager/gbf-project-util.h
	(added) plugins/project-manager/create_dialogs.glade
	plugins/project-manager/Makefile.am
	(added) plugins/gbf-am/plugin.c
	(added) plugins/gbf-am/plugin.h
	(added) plugins/gbf-am/gbf-am.plugin.in
	(added) plugins/gbf-am/gbf-am-plugin-48.png
	(added) plugins/gbf-am/gbf-am-config.h
	(added) plugins/gbf-am/gbf-am-config.c
	(added) plugins/gbf-am/gbf-am-project.h
	(added) plugins/gbf-am/gbf-am-project.c
	(added) plugins/gbf-am/gbf-am-properties.c
	(added) plugins/gbf-am/gbf-am-properties.h
	(added) plugins/gbf-am/gbf-am-parse.in
	(added) plugins/gbf-am/gbf-am-dialogs.glade
	(added) plugins/gbf-am/run-test.sh
	(added) plugins/gbf-am/test.c
	(added) plugins/gbf-am/output.dtd
	(added) plugins/gbf-am/shared.xpm
	(added) plugins/gbf-am/static.xpm
	(added) plugins/gbf-am/unknown.xpm
	(added) plugins/gbf-am/program.xpm
	(added) plugins/gbf-am/GBF/General.pm
	(added) plugins/gbf-am/GBF/AmFiles.pm
	(added) plugins/gbf-am/GBF/.cvsignore
	(added) plugins/gbf-am/GBF/Makefile.am
	(added) plugins/gbf-am/.cvsignore
	(added) plugins/gbf-am/Makefile.am
	(added) plugins/gbf-mkfile/plugin.c
	(added) plugins/gbf-mkfile/plugin.h
	(added) plugins/gbf-mkfile/gbf-mkfile.plugin.in
	(added) plugins/gbf-mkfile/gbf-mkfile-plugin-48.png
	(added) plugins/gbf-mkfile/gbf-mkfile-properties.c
	(added) plugins/gbf-mkfile/gbf-mkfile-properties.h
	(added) plugins/gbf-mkfile/gbf-mkfile-config.c
	(added) plugins/gbf-mkfile/gbf-mkfile-config.h
	(added) plugins/gbf-mkfile/gbf-mkfile-project.c
	(added) plugins/gbf-mkfile/gbf-mkfile-project.h
	(added) plugins/gbf-mkfile/gbf-mkfile-parse.in
	(added) plugins/gbf-mkfile/GBF/General.pm
	(added) plugins/gbf-mkfile/GBF/Make.pm
	(added) plugins/gbf-mkfile/GBF/.cvsignore
	(added) plugins/gbf-mkfile/GBF/Makefile.am
	(added) plugins/gbf-mkfile/.cvsignore
	(added) plugins/gbf-mkfile/Makefile.am:
	Move gnome-build library revision 643 in Anjuta tree
	The file names have not changed


Added:
   trunk/libanjuta/gbf-project.c   (contents, props changed)
   trunk/libanjuta/gbf-project.h   (contents, props changed)
   trunk/plugins/gbf-am/
   trunk/plugins/gbf-am/.cvsignore
   trunk/plugins/gbf-am/GBF/
   trunk/plugins/gbf-am/GBF/.cvsignore
   trunk/plugins/gbf-am/GBF/AmFiles.pm
   trunk/plugins/gbf-am/GBF/General.pm
   trunk/plugins/gbf-am/GBF/Makefile.am
   trunk/plugins/gbf-am/Makefile.am
   trunk/plugins/gbf-am/gbf-am-config.c   (contents, props changed)
   trunk/plugins/gbf-am/gbf-am-config.h   (contents, props changed)
   trunk/plugins/gbf-am/gbf-am-dialogs.glade
   trunk/plugins/gbf-am/gbf-am-parse.in
   trunk/plugins/gbf-am/gbf-am-plugin-48.png   (contents, props changed)
   trunk/plugins/gbf-am/gbf-am-project.c   (contents, props changed)
   trunk/plugins/gbf-am/gbf-am-project.h   (contents, props changed)
   trunk/plugins/gbf-am/gbf-am-properties.c   (contents, props changed)
   trunk/plugins/gbf-am/gbf-am-properties.h   (contents, props changed)
   trunk/plugins/gbf-am/gbf-am.plugin.in
   trunk/plugins/gbf-am/output.dtd
   trunk/plugins/gbf-am/plugin.c   (contents, props changed)
   trunk/plugins/gbf-am/plugin.h   (contents, props changed)
   trunk/plugins/gbf-am/program.xpm
   trunk/plugins/gbf-am/run-test.sh   (contents, props changed)
   trunk/plugins/gbf-am/shared.xpm
   trunk/plugins/gbf-am/static.xpm
   trunk/plugins/gbf-am/test.c   (contents, props changed)
   trunk/plugins/gbf-am/unknown.xpm
   trunk/plugins/gbf-mkfile/
   trunk/plugins/gbf-mkfile/.cvsignore
   trunk/plugins/gbf-mkfile/GBF/
   trunk/plugins/gbf-mkfile/GBF/.cvsignore
   trunk/plugins/gbf-mkfile/GBF/General.pm
   trunk/plugins/gbf-mkfile/GBF/Make.pm
   trunk/plugins/gbf-mkfile/GBF/Makefile.am
   trunk/plugins/gbf-mkfile/Makefile.am
   trunk/plugins/gbf-mkfile/gbf-mkfile-config.c   (contents, props changed)
   trunk/plugins/gbf-mkfile/gbf-mkfile-config.h   (contents, props changed)
   trunk/plugins/gbf-mkfile/gbf-mkfile-parse.in
   trunk/plugins/gbf-mkfile/gbf-mkfile-plugin-48.png   (contents, props changed)
   trunk/plugins/gbf-mkfile/gbf-mkfile-project.c   (contents, props changed)
   trunk/plugins/gbf-mkfile/gbf-mkfile-project.h   (contents, props changed)
   trunk/plugins/gbf-mkfile/gbf-mkfile-properties.c   (contents, props changed)
   trunk/plugins/gbf-mkfile/gbf-mkfile-properties.h   (contents, props changed)
   trunk/plugins/gbf-mkfile/gbf-mkfile.plugin.in
   trunk/plugins/gbf-mkfile/plugin.c   (contents, props changed)
   trunk/plugins/gbf-mkfile/plugin.h   (contents, props changed)
   trunk/plugins/project-manager/create_dialogs.glade
   trunk/plugins/project-manager/gbf-project-model.c   (contents, props changed)
   trunk/plugins/project-manager/gbf-project-model.h   (contents, props changed)
   trunk/plugins/project-manager/gbf-project-util.c   (contents, props changed)
   trunk/plugins/project-manager/gbf-project-util.h   (contents, props changed)
   trunk/plugins/project-manager/gbf-project-view.c   (contents, props changed)
   trunk/plugins/project-manager/gbf-project-view.h   (contents, props changed)
   trunk/plugins/project-manager/gbf-tree-data.c   (contents, props changed)
   trunk/plugins/project-manager/gbf-tree-data.h   (contents, props changed)
Modified:
   trunk/ChangeLog
   trunk/configure.in
   trunk/libanjuta/Makefile.am
   trunk/libanjuta/interfaces/libanjuta.idl
   trunk/plugins/Makefile.am
   trunk/plugins/project-import/project-import.c
   trunk/plugins/project-manager/Makefile.am
   trunk/plugins/project-manager/plugin.c
   trunk/plugins/project-manager/plugin.h

Modified: trunk/configure.in
==============================================================================
--- trunk/configure.in	(original)
+++ trunk/configure.in	Wed Dec 31 11:55:52 2008
@@ -34,7 +34,6 @@
 LIBXML_REQUIRED=2.4.23
 LIBDEVHELP_REQUIRED=0.22
 GDL_REQUIRED=0.7.3
-GNOMEBUILD_REQUIRED=0.3.0
 GLADEUI_REQUIRED=3.2.0
 LIBGRAPHVIZ_REQUIRED=1.0
 NEON_REQUIRED=0.24.5
@@ -61,7 +60,6 @@
 AC_SUBST(VTE_NEW_REQUIRED)
 AC_SUBST(LIBDEVHELP_REQUIRED)
 AC_SUBST(GDL_REQUIRED)
-AC_SUBST(GNOMEBUILD_REQUIRED)
 AC_SUBST(GLADEUI_REQUIRED)
 AC_SUBST(GLADEUI_SVN_REQUIRED)
 AC_SUBST(LIBGRAPHVIZ_REQUIRED)
@@ -206,11 +204,6 @@
 
 AM_CONDITIONAL(HAVE_PLUGIN_DEVHELP, [test x$devhelp_enabled = xyes])
 
-dnl Check for Gnome Build
-dnl ---------------------
-
-PKG_CHECK_MODULES(PLUGIN_GNOMEBUILD, gnome-build-1.0 >= $GNOMEBUILD_REQUIRED)
-
 dnl Check for Glade3
 dnl ---------------------
 
@@ -943,6 +936,14 @@
 plugins/build-basic-autotools/Makefile
 plugins/profiler/Makefile
 plugins/project-manager/Makefile
+plugins/gbf-am/Makefile
+plugins/gbf-am/gbf-am-parse
+plugins/gbf-am/GBF/Makefile
+plugins/gbf-mkfile/Makefile
+plugins/gbf-mkfile/gbf-mkfile-parse
+plugins/gbf-mkfile/GBF/Makefile
+plugins/symbol-browser/Makefile
+plugins/symbol-browser/images/Makefile
 plugins/symbol-db/images/Makefile
 plugins/symbol-db/Makefile
 plugins/symbol-db/test/Makefile
@@ -1023,6 +1024,8 @@
 manuals/anjuta-build-tutorial/Makefile
 mime/Makefile
 ])
+chmod +x ${ac_top_build_dir}plugins/gbf_am/gbf-am-parse
+chmod +x ${ac_top_build_dir}plugins/gbf_mkfile/gbf-mkfile-parse
 echo " "
 echo "-------------------------------------------------------------------"
 echo "Conditionally built plugins:"

Modified: trunk/libanjuta/Makefile.am
==============================================================================
--- trunk/libanjuta/Makefile.am	(original)
+++ trunk/libanjuta/Makefile.am	Wed Dec 31 11:55:52 2008
@@ -75,7 +75,9 @@
 	anjuta-async-notify.c \
 	anjuta-message-area.h \
 	anjuta-debug.c \
-	anjuta-debug.h
+	anjuta-debug.h \
+	gbf-project.c \
+	gbf-project.h
 
 if HAVE_PLUGIN_GLADE
 

Added: trunk/libanjuta/gbf-project.c
==============================================================================
--- (empty file)
+++ trunk/libanjuta/gbf-project.c	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,568 @@
+/* gbf-project.c
+ *
+ * Copyright (C) 2002 Jeroen Zwartepoorte
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <libgnome/gnome-macros.h>
+#include "gbf-project.h"
+
+GNOME_CLASS_BOILERPLATE (GbfProject, gbf_project, GObject, G_TYPE_OBJECT);
+
+void 
+gbf_project_load (GbfProject  *project,
+		  const gchar *path,
+		  GError     **error)
+{
+	g_return_if_fail (project != NULL);
+	g_return_if_fail (GBF_IS_PROJECT (project));
+	g_return_if_fail (path != NULL);
+	g_return_if_fail (error == NULL || *error == NULL);
+
+	GBF_PROJECT_GET_CLASS (project)->load (project, path, error);
+}
+
+gboolean 
+gbf_project_probe (GbfProject  *project,
+		   const gchar *path,
+		   GError     **error)
+{
+	g_return_val_if_fail (project != NULL, FALSE);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), FALSE);
+	g_return_val_if_fail (path != NULL, FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	return GBF_PROJECT_GET_CLASS (project)->probe (project, path, error);
+}
+
+void
+gbf_project_refresh (GbfProject *project,
+		     GError    **error)
+{
+	g_return_if_fail (project != NULL);
+	g_return_if_fail (GBF_IS_PROJECT (project));
+	g_return_if_fail (error == NULL || *error == NULL);
+
+	GBF_PROJECT_GET_CLASS (project)->refresh (project, error);
+}
+
+GbfProjectCapabilities
+gbf_project_get_capabilities (GbfProject *project,
+		     GError    **error)
+{
+	g_return_val_if_fail (project != NULL, GBF_PROJECT_CAN_ADD_NONE);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), GBF_PROJECT_CAN_ADD_NONE);
+	g_return_val_if_fail (error == NULL || *error == NULL, GBF_PROJECT_CAN_ADD_NONE);
+
+	return GBF_PROJECT_GET_CLASS (project)->get_capabilities (project, error);
+}
+
+/* Groups. */
+gchar * 
+gbf_project_add_group (GbfProject  *project,
+		       const gchar *parent_id,
+		       const gchar *name,
+		       GError     **error)
+{
+	g_return_val_if_fail (project != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+	g_return_val_if_fail (parent_id != NULL, NULL);
+	g_return_val_if_fail (name != NULL, NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	return GBF_PROJECT_GET_CLASS (project)->add_group (project, 
+							   parent_id, 
+							   name, 
+							   error);
+}
+
+void 
+gbf_project_remove_group (GbfProject  *project,
+			  const gchar *id,
+			  GError     **error)
+{
+	g_return_if_fail (project != NULL);
+	g_return_if_fail (GBF_IS_PROJECT (project));
+	g_return_if_fail (id != NULL);
+	g_return_if_fail (error == NULL || *error == NULL);
+
+	GBF_PROJECT_GET_CLASS (project)->remove_group (project, id, error);
+}
+
+GbfProjectGroup * 
+gbf_project_get_group (GbfProject  *project,
+		       const gchar *id,
+		       GError     **error)
+{
+	g_return_val_if_fail (project != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+	g_return_val_if_fail (id != NULL, NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	return GBF_PROJECT_GET_CLASS (project)->get_group (project, id, error);
+}
+
+GtkWidget * 
+gbf_project_configure_group (GbfProject  *project,
+			     const gchar *id,
+			     GError     **error)
+{
+	g_return_val_if_fail (project != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+	g_return_val_if_fail (id != NULL, NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	return GBF_PROJECT_GET_CLASS (project)->configure_group (project, 
+								 id, 
+								 error);
+}
+
+GtkWidget * 
+gbf_project_configure_new_group (GbfProject *project,
+				 GError    **error)
+{
+	g_return_val_if_fail (project != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	return GBF_PROJECT_GET_CLASS (project)->configure_new_group (project, 
+								     error);
+}
+
+/* Targets. */
+gchar * 
+gbf_project_add_target (GbfProject  *project,
+			const gchar *group_id,
+			const gchar *name,
+			const gchar *type,
+			GError     **error)
+{
+	g_return_val_if_fail (project != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+	g_return_val_if_fail (group_id != NULL, NULL);
+	g_return_val_if_fail (name != NULL, NULL);
+	g_return_val_if_fail (type != NULL, NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	return GBF_PROJECT_GET_CLASS (project)->add_target (project, 
+							    group_id,
+							    name,
+							    type,
+							    error);
+}
+
+void 
+gbf_project_remove_target (GbfProject  *project,
+			   const gchar *id,
+			   GError     **error)
+{
+	g_return_if_fail (project != NULL);
+	g_return_if_fail (GBF_IS_PROJECT (project));
+	g_return_if_fail (id != NULL);	
+	g_return_if_fail (error == NULL || *error == NULL);
+
+	GBF_PROJECT_GET_CLASS (project)->remove_target (project, id, error);
+}
+
+GbfProjectTarget * 
+gbf_project_get_target (GbfProject  *project,
+			const gchar *id,
+			GError     **error)
+{
+	g_return_val_if_fail (project != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+	g_return_val_if_fail (id != NULL, NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	return GBF_PROJECT_GET_CLASS (project)->get_target (project, 
+							    id, 
+							    error);
+}
+
+GList * 
+gbf_project_get_all_targets (GbfProject *project,
+			     GError    **error)
+{
+	g_return_val_if_fail (project != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	return GBF_PROJECT_GET_CLASS (project)->get_all_targets (project, 
+								 error);
+}
+
+GList * 
+gbf_project_get_all_groups (GbfProject *project,
+			    GError    **error)
+{
+	g_return_val_if_fail (project != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	return GBF_PROJECT_GET_CLASS (project)->get_all_groups (project, 
+								error);
+}
+
+GtkWidget * 
+gbf_project_configure_target (GbfProject  *project,
+			      const gchar *id,
+			      GError     **error)
+{
+	g_return_val_if_fail (project != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+	g_return_val_if_fail (id != NULL, NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	return GBF_PROJECT_GET_CLASS (project)->configure_target (project, 
+								  id, 
+								  error);
+}
+GtkWidget * 
+gbf_project_configure_new_target (GbfProject *project,
+				  GError    **error)
+{
+	g_return_val_if_fail (project != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	return GBF_PROJECT_GET_CLASS (project)->configure_new_target (project,
+								      error);
+}
+
+/* Sources. */
+gchar * 
+gbf_project_add_source (GbfProject  *project,
+			const gchar *target_id,
+			const gchar *uri,
+			GError     **error)
+{
+	g_return_val_if_fail (project != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+	g_return_val_if_fail (target_id != NULL, NULL);
+	g_return_val_if_fail (uri != NULL, NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	return GBF_PROJECT_GET_CLASS (project)->add_source (project, 
+							    target_id,
+							    uri,
+							    error);
+}
+
+void 
+gbf_project_remove_source (GbfProject  *project,
+			   const gchar *id,
+			   GError     **error)
+{
+	g_return_if_fail (project != NULL);
+	g_return_if_fail (GBF_IS_PROJECT (project));
+	g_return_if_fail (id != NULL);
+	g_return_if_fail (error == NULL || *error == NULL);
+	
+	GBF_PROJECT_GET_CLASS (project)->remove_source (project, id, error);
+}
+
+GbfProjectTargetSource * 
+gbf_project_get_source (GbfProject  *project,
+			const gchar *id,
+			GError     **error)
+{
+	g_return_val_if_fail (project != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+	g_return_val_if_fail (id != NULL, NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	return GBF_PROJECT_GET_CLASS (project)->get_source (project, 
+							    id, 
+							    error);
+}
+
+GList * 
+gbf_project_get_all_sources (GbfProject *project,
+			     GError    **error)
+{
+	g_return_val_if_fail (project != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	return GBF_PROJECT_GET_CLASS (project)->get_all_sources (project, 
+								 error);
+}
+
+GtkWidget * 
+gbf_project_configure_source (GbfProject  *project,
+			      const gchar *id,
+			      GError     **error)
+{
+	g_return_val_if_fail (project != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+	g_return_val_if_fail (id != NULL, NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	return GBF_PROJECT_GET_CLASS (project)->configure_source (project, 
+								  id, 
+								  error);
+}
+
+GtkWidget * 
+gbf_project_configure_new_source (GbfProject *project,
+				  GError    **error)
+{
+	g_return_val_if_fail (project != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	return GBF_PROJECT_GET_CLASS (project)->configure_new_source (project, 
+								      error);
+}
+
+/* Project */
+
+GtkWidget *
+gbf_project_configure (GbfProject *project, GError **error)
+{
+	g_return_val_if_fail (project != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	return GBF_PROJECT_GET_CLASS (project)->configure (project, error);
+}
+
+GList *
+gbf_project_get_config_modules   (GbfProject *project, GError **error)
+{
+	g_return_val_if_fail (project != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+	
+	return GBF_PROJECT_GET_CLASS (project)->get_config_modules (project, error);
+}
+
+GList *
+gbf_project_get_config_packages  (GbfProject *project,
+																	const gchar* module,
+																	GError **error)
+{
+	g_return_val_if_fail (project != NULL, NULL);
+	g_return_val_if_fail (module != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+	
+	return GBF_PROJECT_GET_CLASS (project)->get_config_packages (project, module,
+																															 error);
+}
+
+/* Types. */
+const gchar * 
+gbf_project_name_for_type (GbfProject  *project,
+			   const gchar *type)
+{
+	g_return_val_if_fail (project != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+	g_return_val_if_fail (type != NULL, NULL);
+	
+	return GBF_PROJECT_GET_CLASS (project)->name_for_type (project, type);
+}
+
+const gchar * 
+gbf_project_mimetype_for_type (GbfProject  *project,
+			       const gchar *type)
+{
+	g_return_val_if_fail (project != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+	g_return_val_if_fail (type != NULL, NULL);
+
+	return GBF_PROJECT_GET_CLASS (project)->mimetype_for_type (project, type);
+}
+
+gchar **
+gbf_project_get_types (GbfProject *project) 
+{
+	g_return_val_if_fail (project != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+	g_return_val_if_fail (
+		GBF_PROJECT_GET_CLASS (project)->get_types != NULL, NULL);
+	
+	return GBF_PROJECT_GET_CLASS (project)->get_types (project);
+}
+
+static void
+gbf_project_class_init (GbfProjectClass *klass) 
+{
+	parent_class = g_type_class_peek_parent (klass);
+
+	g_signal_new ("project-updated",
+		      G_TYPE_FROM_CLASS (klass),
+		      G_SIGNAL_RUN_LAST,
+		      G_STRUCT_OFFSET (GbfProjectClass, project_updated),
+		      NULL, NULL,
+		      g_cclosure_marshal_VOID__VOID,
+		      G_TYPE_NONE, 0);
+}
+
+static void
+gbf_project_instance_init (GbfProject *project)
+{
+}
+
+GQuark
+gbf_project_error_quark (void)
+{
+	static GQuark quark = 0;
+
+	if (quark == 0)
+		quark = g_quark_from_string ("gbf-project-quark");
+
+	return quark;
+}
+
+GbfProjectGroup *
+gbf_project_group_copy (GbfProjectGroup *group)
+{
+	GbfProjectGroup *new_group;
+	GList *l;
+	
+	new_group = g_new0 (GbfProjectGroup, 1);
+	new_group->id = g_strdup (group->id);
+	new_group->parent_id = g_strdup (group->parent_id);
+	new_group->name = g_strdup (group->name);
+	
+	for (l = group->groups; l; l = l->next)
+		new_group->groups = g_list_prepend (new_group->groups, g_strdup (l->data));
+	new_group->groups = g_list_reverse (new_group->groups);
+	
+	for (l = group->targets; l; l = l->next)
+		new_group->targets = g_list_prepend (new_group->targets, g_strdup (l->data));
+	new_group->targets = g_list_reverse (new_group->targets);
+
+	return new_group;
+}
+
+void
+gbf_project_group_free (GbfProjectGroup *group)
+{
+	g_free (group->id);
+	g_free (group->parent_id);
+	g_free (group->name);
+
+	while (group->groups) {
+		g_free (group->groups->data);
+		group->groups = g_list_delete_link (group->groups, group->groups);
+	}
+	
+	while (group->targets) {
+		g_free (group->targets->data);
+		group->targets = g_list_delete_link (group->targets, group->targets);
+	}
+	g_free (group);
+}
+
+GbfProjectTarget *
+gbf_project_target_copy (GbfProjectTarget *target)
+{
+	GbfProjectTarget *new_target;
+	GList *l;
+	
+	new_target = g_new0 (GbfProjectTarget, 1);
+	new_target->id = g_strdup (target->id);
+	new_target->group_id = g_strdup (target->group_id);
+	new_target->name = g_strdup (target->name);
+	new_target->type = g_strdup (target->type);
+	
+	for (l = target->sources; l; l = l->next)
+		new_target->sources = g_list_prepend (new_target->sources, g_strdup (l->data));
+	new_target->sources = g_list_reverse (new_target->sources);
+
+	return new_target;
+}
+
+void
+gbf_project_target_free (GbfProjectTarget *target)
+{
+	g_free (target->id);
+	g_free (target->group_id);
+	g_free (target->name);
+	g_free (target->type);
+
+	while (target->sources) {
+		g_free (target->sources->data);
+		target->sources = g_list_delete_link (target->sources, target->sources);
+	}
+
+	g_free (target);
+}
+
+GbfProjectTargetSource *
+gbf_project_target_source_copy (GbfProjectTargetSource *source)
+{
+	GbfProjectTargetSource *new_source;
+	
+	new_source = g_new0 (GbfProjectTargetSource, 1);
+	new_source->id = g_strdup (source->id);
+	new_source->target_id = g_strdup (source->target_id);
+	new_source->source_uri = g_strdup (source->source_uri);
+	
+	return new_source;
+}
+
+void
+gbf_project_target_source_free (GbfProjectTargetSource *source)
+{
+	g_free (source->id);
+	g_free (source->target_id);
+	g_free (source->source_uri);
+	g_free (source);
+}
+
+GType 
+gbf_project_group_get_type (void)
+{
+	static GType our_type = 0;
+  
+	if (our_type == 0)
+		our_type = g_boxed_type_register_static ("GbfProjectGroup",
+							 (GBoxedCopyFunc) gbf_project_group_copy,
+							 (GBoxedFreeFunc) gbf_project_group_free);
+	
+	return our_type;
+}
+
+GType 
+gbf_project_target_get_type (void)
+{
+	static GType our_type = 0;
+  
+	if (our_type == 0)
+		our_type = g_boxed_type_register_static ("GbfProjectTarget",
+							 (GBoxedCopyFunc) gbf_project_target_copy,
+							 (GBoxedFreeFunc) gbf_project_target_free);
+	
+	return our_type;
+}
+
+GType 
+gbf_project_target_source_get_type (void)
+{
+	static GType our_type = 0;
+  
+	if (our_type == 0)
+		our_type = g_boxed_type_register_static ("GbfProjectTargetSource",
+							 (GBoxedCopyFunc) gbf_project_target_source_copy,
+							 (GBoxedFreeFunc) gbf_project_target_source_free);
+	
+	return our_type;
+}

Added: trunk/libanjuta/gbf-project.h
==============================================================================
--- (empty file)
+++ trunk/libanjuta/gbf-project.h	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,337 @@
+/* gbf-project.h
+ *
+ * Copyright (C) 2002 Jeroen Zwartepoorte
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _GBF_PROJECT_H_
+#define _GBF_PROJECT_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GBF_TYPE_PROJECT		(gbf_project_get_type ())
+#define GBF_PROJECT(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), GBF_TYPE_PROJECT, GbfProject))
+#define GBF_PROJECT_CLASS(obj)		(G_TYPE_CHECK_CLASS_CAST ((klass), GBF_TYPE_PROJECT, GbfProjectClass))
+#define GBF_IS_PROJECT(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GBF_TYPE_PROJECT))
+#define GBF_IS_PROJECT_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((obj), GBF_TYPE_PROJECT))
+#define GBF_PROJECT_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), GBF_TYPE_PROJECT, GbfProjectClass))
+
+#define GBF_PROJECT_ERROR		(gbf_project_error_quark ())
+
+#define GBF_TYPE_PROJECT_GROUP          (gbf_project_group_get_type ())
+#define GBF_TYPE_PROJECT_TARGET         (gbf_project_target_get_type ())
+#define GBF_TYPE_PROJECT_TARGET_SOURCE  (gbf_project_target_source_get_type ())
+
+#define GBF_BUILD_ID_DEFAULT		"DEFAULT"
+
+typedef struct _GbfProject		GbfProject;
+typedef struct _GbfProjectClass		GbfProjectClass;
+typedef struct _GbfProjectGroup		GbfProjectGroup;
+typedef struct _GbfProjectTarget	GbfProjectTarget;
+typedef struct _GbfProjectTargetSource	GbfProjectTargetSource;
+typedef enum   _GbfProjectError		GbfProjectError;
+typedef enum   _GbfProjectCapabilities GbfProjectCapabilities;
+
+struct _GbfProjectGroup {
+	gchar   *id;
+	gchar   *parent_id;
+
+	gchar   *name;
+
+	GList   *groups;
+	GList   *targets;
+};
+
+struct _GbfProjectTarget {
+	gchar   *id;
+	gchar   *group_id;
+
+	gchar   *name;
+	gchar   *type;
+
+	GList   *sources;
+};
+
+struct _GbfProjectTargetSource {
+	gchar   *id;
+	gchar   *target_id;
+
+	gchar   *source_uri;
+};
+
+/* FIXME: extend this list */
+enum _GbfProjectError {
+	GBF_PROJECT_ERROR_SUCCESS = 0,
+	GBF_PROJECT_ERROR_DOESNT_EXIST,
+	GBF_PROJECT_ERROR_ALREADY_EXISTS,
+	GBF_PROJECT_ERROR_VALIDATION_FAILED,
+	GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+	GBF_PROJECT_ERROR_GENERAL_FAILURE,
+};
+
+enum _GbfProjectCapabilities {
+	GBF_PROJECT_CAN_ADD_NONE              = 0,
+	GBF_PROJECT_CAN_ADD_GROUP             = 1 << 0,
+	GBF_PROJECT_CAN_ADD_TARGET            = 1 << 1,
+	GBF_PROJECT_CAN_ADD_SOURCE            = 1 << 2,
+	GBF_PROJECT_CAN_PACKAGES							= 1 << 3
+};
+
+struct _GbfProject {
+	GObject parent;
+};
+
+struct _GbfProjectClass {
+	GObjectClass parent_class;
+
+	void                     (* project_updated)       (GbfProject *project);
+
+	/* Virtual Table */
+	/* Project. */
+	void                     (* load)                  (GbfProject  *project,
+							    const gchar *path,
+							    GError     **error);
+	gboolean                 (* probe)                 (GbfProject  *project,
+							    const gchar *path,
+							    GError     **error);
+	void                     (* refresh)               (GbfProject  *project,
+							    GError     **error);
+	GbfProjectCapabilities   (* get_capabilities)      (GbfProject  *project,
+							    GError     **error);
+
+	/* Groups. */
+	gchar *                  (* add_group)             (GbfProject  *project,
+							    const gchar *parent_id,
+							    const gchar *name,
+							    GError     **error);
+	void                     (* remove_group)          (GbfProject  *project,
+							    const gchar *id,
+							    GError     **error);
+	GbfProjectGroup *        (* get_group)             (GbfProject  *project,
+							    const gchar *id,
+							    GError     **error);
+	GList *                  (* get_all_groups)        (GbfProject  *project,
+							    GError     **error);
+	GtkWidget *              (* configure_group)       (GbfProject  *project,
+							    const gchar *id,
+							    GError     **error);
+	GtkWidget *              (* configure_new_group)   (GbfProject  *project,
+							    GError     **error);
+
+	/* Targets. */
+	gchar *                  (* add_target)            (GbfProject  *project,
+							    const gchar *group_id,
+							    const gchar *name,
+							    const gchar *type,
+							    GError     **error);
+	void                     (* remove_target)         (GbfProject  *project,
+							    const gchar *id,
+							    GError     **error);
+	GbfProjectTarget *       (* get_target)            (GbfProject  *project,
+							    const gchar *id,
+							    GError     **error);
+	GList *                  (* get_all_targets)       (GbfProject  *project,
+							    GError     **error);
+	GtkWidget *              (* configure_target)      (GbfProject  *project,
+							    const gchar *id,
+							    GError     **error);
+	GtkWidget *              (* configure_new_target)  (GbfProject *project,
+							    GError     **error);
+
+	/* Sources. */
+	gchar *                  (* add_source)            (GbfProject  *project,
+							    const gchar *target_id,
+							    const gchar *uri,
+							    GError     **error);
+	void                     (* remove_source)         (GbfProject  *project,
+							    const gchar *id,
+							    GError     **error);
+	GbfProjectTargetSource * (* get_source)            (GbfProject  *project,
+							    const gchar *id,
+							    GError     **error);
+	GList *                  (* get_all_sources)       (GbfProject  *project,
+							    GError     **error);
+	GtkWidget *              (* configure_source)      (GbfProject  *project,
+							    const gchar *id,
+							    GError     **error);
+	GtkWidget *              (* configure_new_source)  (GbfProject  *project,
+							    GError     **error);
+	GtkWidget *              (* configure)             (GbfProject  *project,
+							    GError     **error);
+
+	GList *									 (* get_config_modules)		 (GbfProject  *project,
+																											GError **error);
+	GList *									 (* get_config_packages)	 (GbfProject  *project,
+																											const gchar* module,
+																											GError **error);
+	
+	
+	/* Types. */
+	const gchar *            (* name_for_type)         (GbfProject  *project,
+							    const gchar *type);
+	const gchar *            (* mimetype_for_type)     (GbfProject  *project,
+							    const gchar *type);
+	gchar **                 (* get_types)             (GbfProject  *project);
+};
+
+GQuark                  gbf_project_error_quark          (void);
+GType                   gbf_project_get_type             (void);
+GType                   gbf_project_group_get_type       (void);
+GType                   gbf_project_target_get_type      (void);
+GType                   gbf_project_target_source_get_type (void);
+void                    gbf_project_load                 (GbfProject    *project,
+							  const gchar   *path,
+							  GError       **error);
+gboolean                gbf_project_probe                (GbfProject    *project,
+							  const gchar   *path,
+							  GError       **error);
+void                    gbf_project_refresh              (GbfProject    *project,
+							  GError       **error);
+GbfProjectCapabilities  gbf_project_get_capabilities     (GbfProject *project,
+							  GError    **error);
+
+/* Groups. */
+gchar                  *gbf_project_add_group            (GbfProject    *project,
+							  const gchar   *parent_id,
+							  const gchar   *name,
+							  GError       **error);
+void                    gbf_project_remove_group         (GbfProject    *project,
+							  const gchar   *id,
+							  GError       **error);
+GbfProjectGroup        *gbf_project_get_group            (GbfProject    *project,
+							  const gchar   *id,
+							  GError       **error);
+GList                  *gbf_project_get_all_groups       (GbfProject    *project,
+							  GError       **error);
+GtkWidget              *gbf_project_configure_group      (GbfProject    *project,
+							  const gchar   *id,
+							  GError       **error);
+GtkWidget              *gbf_project_configure_new_group  (GbfProject    *project,
+							  GError       **error);
+
+
+/* Targets. */
+gchar                  *gbf_project_add_target           (GbfProject    *project,
+							  const gchar   *group_id,
+							  const gchar   *name,
+							  const gchar   *type,
+							  GError       **error);
+void                    gbf_project_remove_target        (GbfProject    *project,
+							  const gchar   *id,
+							  GError       **error);
+GbfProjectTarget       *gbf_project_get_target           (GbfProject    *project,
+							  const gchar   *id,
+							  GError       **error);
+GList                  *gbf_project_get_all_targets      (GbfProject    *project,
+							  GError       **error);
+GtkWidget              *gbf_project_configure_target     (GbfProject    *project,
+							  const gchar   *id,
+							  GError       **error);
+GtkWidget              *gbf_project_configure_new_target (GbfProject    *project,
+							  GError       **error);
+
+
+/* Sources. */
+gchar                  *gbf_project_add_source           (GbfProject    *project,
+							  const gchar   *target_id,
+							  const gchar   *uri,
+							  GError       **error);
+void                    gbf_project_remove_source        (GbfProject    *project,
+							  const gchar   *id,
+							  GError       **error);
+GbfProjectTargetSource *gbf_project_get_source           (GbfProject    *project,
+							  const gchar   *id,
+							  GError       **error);
+GList                  *gbf_project_get_all_sources      (GbfProject    *project,
+							  GError       **error);
+GtkWidget              *gbf_project_configure_source     (GbfProject    *project,
+							  const gchar   *id,
+							  GError       **error);
+GtkWidget              *gbf_project_configure_new_source (GbfProject    *project,
+							  GError       **error);
+/* Project */
+
+GtkWidget              *gbf_project_configure            (GbfProject *project,
+							  GError **error);
+
+/* Packages */
+GList									 *gbf_project_get_config_modules   (GbfProject *project,
+																													GError** error);
+
+GList									 *gbf_project_get_config_packages  (GbfProject *project,
+																													const gchar* module,
+																													GError** error);
+
+
+/* Types. */
+const gchar            *gbf_project_name_for_type        (GbfProject    *project,
+							  const gchar   *type);
+const gchar            *gbf_project_mimetype_for_type    (GbfProject    *project,
+							  const gchar   *type);
+
+gchar                 **gbf_project_get_types            (GbfProject    *project);
+
+/* functions for copying/freeing data structures */
+
+GbfProjectGroup        *gbf_project_group_copy         (GbfProjectGroup        *group);
+void                    gbf_project_group_free         (GbfProjectGroup        *group);
+
+GbfProjectTarget       *gbf_project_target_copy        (GbfProjectTarget       *target);
+void                    gbf_project_target_free        (GbfProjectTarget       *target);
+
+GbfProjectTargetSource *gbf_project_target_source_copy (GbfProjectTargetSource *source);
+void                    gbf_project_target_source_free (GbfProjectTargetSource *source);
+
+
+
+
+#define GBF_BACKEND_BOILERPLATE(class_name, prefix)					\
+GType											\
+prefix##_get_type (GTypeModule *module)							\
+{											\
+	static GType type = 0;								\
+	if (!type) {									\
+		static const GTypeInfo type_info = {					\
+			sizeof (class_name##Class),					\
+			NULL,								\
+			NULL,								\
+			(GClassInitFunc)prefix##_class_init,				\
+			NULL,								\
+			NULL,								\
+			sizeof (class_name),						\
+			0,								\
+			(GInstanceInitFunc)prefix##_instance_init			\
+		};									\
+		if (module == NULL) {							\
+			type = g_type_register_static (GBF_TYPE_PROJECT,		\
+						       #class_name,			\
+						       &type_info, 0);			\
+		} else {								\
+			type = g_type_module_register_type (module,	\
+							    GBF_TYPE_PROJECT,		\
+							    #class_name,		\
+							    &type_info, 0);		\
+		}									\
+	}										\
+	return type;									\
+}
+
+G_END_DECLS
+
+#endif /* _GBF_PROJECT_H_ */

Modified: trunk/libanjuta/interfaces/libanjuta.idl
==============================================================================
--- trunk/libanjuta/interfaces/libanjuta.idl	(original)
+++ trunk/libanjuta/interfaces/libanjuta.idl	Wed Dec 31 11:55:52 2008
@@ -2868,6 +2868,31 @@
 }
 
 /**
+ * SECTION:ianjuta-project-backend
+ * @title: IAnjutaProjectBackend
+ * @short_description: Interface for creating new project
+ * @see_also: 
+ * @stability: Unstable
+ * @include: libanjuta/interfaces/ianjuta-project-backend.h
+ * 
+ */
+interface IAnjutaProjectBackend
+{
+	#include <libanjuta/gbf-project.h>
+	
+	/**
+	 * ianjuta_project_backend_new_project:
+	 * @obj: Self
+	 * @err: Error propagation and reporting
+	 *
+	 * Get a new GbfProject
+	 *
+	 * Return value: An object derived from GbfProject
+	 */
+	GbfProject* new_project ();
+}
+
+/**
  * SECTION:ianjuta-project-manager
  * @title: IAnjutaProjectManager
  * @short_description: Interface for project managers

Modified: trunk/plugins/Makefile.am
==============================================================================
--- trunk/plugins/Makefile.am	(original)
+++ trunk/plugins/Makefile.am	Wed Dec 31 11:55:52 2008
@@ -35,6 +35,8 @@
 	sourceview \
 	run-program \
 	scratchbox \
+	gbf-am \
+	gbf-mkfile \
 	starter
 
 # indent

Added: trunk/plugins/gbf-am/.cvsignore
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/.cvsignore	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,6 @@
+Makefile.in
+Makefile
+.deps
+.libs
+test
+gbf-am-parse

Added: trunk/plugins/gbf-am/GBF/.cvsignore
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/GBF/.cvsignore	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in

Added: trunk/plugins/gbf-am/GBF/AmFiles.pm
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/GBF/AmFiles.pm	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,1647 @@
+package GBF::AmFiles;
+
+use Exporter;
+use strict;
+use Fcntl ":seek";
+
+our (@ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
+
+ ISA = qw (Exporter);
+
+ EXPORT = qw (&debug &enable_debug
+	      &parse_am_file &output_am_file 
+	      &parse_configure_in &parse_configure_in_buffer
+	      &output_configure_in
+	      &configure_rewrite_variable &configure_remove_variable
+	      &configure_rewrite_packages
+	      &configure_rewrite_package_info
+	      &use_macro &use_rule &expand_one_var
+	      &macro_create &macro_remove &macro_rewrite 
+	      &macro_append_text &macro_prepend_text &macro_remove_text
+	      &rule_remove &am_file_reset
+	      &edit_config_files);
+ EXPORT_OK = qw (&test_print_am_file &get_line_from_hint &get_config_files);
+%EXPORT_TAGS = (DEBUG => [qw (&get_config_files &test_print_am_file)]);
+
+
+use GBF::General;
+
+
+my $debug = 0;
+
+sub debug { my $message = $_[0]; print STDERR "DEBUG: $message\n" if $debug; }
+sub enable_debug { $debug = 1; }
+
+# +----------------
+# | makefile              : information extracted from the parsing of Makefile.am
+# +-----------------
+# | filename              : full path to Makefile.am file
+# | lines                 : list of lines of text of the file
+# | macros                : hash of macro hashes (defined below) 
+# |                         (macro_name => macro_hash)
+# | rules                 : hash of rule hashes (defined below) (rule_name => rule_hash)
+# | extra                 : list of other lines of text (i.e. not rules neither macros)
+# | dirty                 : whether the makefile needs flushing
+# | 
+#
+# +----------------
+# | macro                 : a Makefile.am macro definition
+# +-----------------
+# | contents              : string with the contents of the macro
+# | atline                : line number at the makefile.am where the macro is 
+# |                         first defined
+# | used                  : flag indicating whether the macro has already been used
+# |                         in target extraction
+# | 
+#
+# +----------------
+# | rule                  : a Makefile.am rule definition
+# +-----------------
+# | dependencies          : string containing the dependencies of the rule
+# | actions               : string containing the rule actions
+# | atline                : line number at the makefile.am where the rule is defined
+# | used                  : flag indicating whether the rule has already been used
+# |                         in target extraction
+# | 
+#
+
+######################################################################
+# Constants and Variables
+######################################################################
+
+# String constants.
+# These are stolen from automake
+
+my $IGNORE_PATTERN = '^\s*##([^#\n].*)?\n';
+my $WHITE_PATTERN = '^\s*' . "\$";
+my $COMMENT_PATTERN = '^#';
+my $TARGET_PATTERN='[$a-zA-Z_  %][- a-zA-Z0-9_(){}/$+ %]*';
+# A rule has three parts: a list of targets, a list of dependencies,
+# and optionally actions.
+my $RULE_PATTERN =
+  "^($TARGET_PATTERN(?:(?:\\\\\n|\\s)+$TARGET_PATTERN)*) *:([^=].*|)\$";
+
+# Only recognize leading spaces, not leading tabs.  If we recognize
+# leading tabs here then we need to make the reader smarter, because
+# otherwise it will think rules like `foo=bar; \' are errors.
+my $ASSIGNMENT_PATTERN = '^ *([^ \t=:+]*)\s*([:+]?)=\s*(.*)' . "\$";
+# This pattern recognizes a Gnits version id and sets $1 if the
+# release is an alpha release.  We also allow a suffix which can be
+# used to extend the version number with a "fork" identifier.
+my $GNITS_VERSION_PATTERN = '\d+\.\d+([a-z]|\.\d+)?(-[A-Za-z0-9]+)?';
+
+my $IF_PATTERN = '^if\s+(!?)\s*([A-Za-z][A-Za-z0-9_]*)\s*(?:#.*)?' . "\$";
+my $ELSE_PATTERN =
+  '^else(?:\s+(!?)\s*([A-Za-z][A-Za-z0-9_]*))?\s*(?:#.*)?' . "\$";
+my $ENDIF_PATTERN =
+  '^endif(?:\s+(!?)\s*([A-Za-z][A-Za-z0-9_]*))?\s*(?:#.*)?' . "\$";
+my $PATH_PATTERN = '(\w|[+/.-])+';
+# This will pass through anything not of the prescribed form.
+my $INCLUDE_PATTERN = ('^include\s+'
+		       . '((\$\(top_srcdir\)/' . $PATH_PATTERN . ')'
+		       . '|(\$\(srcdir\)/' . $PATH_PATTERN . ')'
+		       . '|([^/\$]' . $PATH_PATTERN . '))\s*(#.*)?' . "\$");
+
+# Match `-d' as a command-line argument in a string.
+my $DASH_D_PATTERN = "(^|\\s)-d(\\s|\$)";
+# Directories installed during 'install-exec' phase.
+my $EXEC_DIR_PATTERN =
+  '^(?:bin|sbin|libexec|sysconf|localstate|lib|pkglib|.*exec.*)' . "\$";
+
+my %default_macro_style = ( bs_column          => -1,
+			    items_per_line     => 1,
+			    first_line_ignored => 1,
+			    leading_space      => "\t" );
+
+
+######################################################################
+# Parser Functions
+######################################################################
+
+# &check_trailing_slash ( $LINE)
+# --------------------------------------
+# Return 1 iff $LINE ends with a slash.
+# Might modify $LINE.
+sub check_trailing_slash (\$)
+{
+  my ($line) = @_;
+
+  # Ignore `##' lines.
+  return 0 if $$line =~ /$IGNORE_PATTERN/o;
+
+  # Catch and fix a common error.
+  #msg "syntax", $where, "whitespace following trailing backslash"
+  $$line =~ s/\\\s+\n$/\\\n/;
+
+  return $$line =~ /\\$/;
+}
+
+# Function parse_file 
+
+# Parses a Makefile.am file extracting the macros and rules in it, while saving
+# its contents to an list of lines.
+
+# FIXME: handle includes and conditionals
+
+sub parse_am_file
+{
+    my $filename = $_[0];
+
+    my (%macros, %rules, @extra_text);
+
+    if (! open (MAKEFILE, "< $filename")) {
+	&report_error (100, "Can't open file $filename: $!");
+	return {};
+    }
+
+
+    use constant IN_VAR_DEF => 0;
+    use constant IN_RULE_DEF => 1;
+    use constant IN_COMMENT => 2;
+    my $prev_state = IN_RULE_DEF;
+    my $saw_bk = 0;
+    my $current_rule = "";
+    my @lines = ();
+    my $rule_indentation = "";
+
+    while (<MAKEFILE>) {
+	push @lines, $_;
+
+	if (/$IGNORE_PATTERN/o) {
+	    # Merely delete comments beginning with two hashes.
+
+	} elsif (/$WHITE_PATTERN/o) {
+	    if ($saw_bk) {
+		&report_error (101, "$filename:$.: blank line following " .
+			       "trailing backslash");
+		return {};
+	    };
+
+	} elsif (/$COMMENT_PATTERN/o) {
+	    $prev_state = IN_COMMENT;
+	} else {
+	    last;
+	}
+	$saw_bk = check_trailing_slash ($_);
+    }
+
+    my $last_var_name = '';
+    my $last_var_type = '';
+    my $last_var_value = '';
+    # FIXME: shouldn't use $_ in this loop; it is too big.
+    while ($_) {
+	# Make sure the line is \n-terminated.
+	chomp;
+	$_ .= "\n";
+
+	my $new_saw_bk = check_trailing_slash ($_);
+
+	if (/$IGNORE_PATTERN/o) {
+	    # Merely delete comments beginning with two hashes.
+
+	    # Keep any backslash from the previous line.
+	    $new_saw_bk = $saw_bk;
+	} elsif (/$WHITE_PATTERN/o) {
+	    if ($saw_bk) {
+		&report_error (101, "$filename:$.: blank line following " .
+			       "trailing backslash");
+		return {};
+	    };
+
+	} elsif (/$COMMENT_PATTERN/o) {
+	    if ($saw_bk && $prev_state != IN_COMMENT) {
+		&report_error (101, "$filename:$.: comment following " .
+			       "trailing backslash");
+		return {};
+	    };
+	    $prev_state = IN_COMMENT;
+	} elsif ($saw_bk) {	    
+	    if ($prev_state == IN_RULE_DEF) {
+		if ($rules{$current_rule}{actions} eq "") {
+		    # Strip trailing backslash from rule deps
+		    s/\\\s*$//;
+		    $rules{$current_rule}{dependencies} .= $_;
+		} else {
+		    $rules{$current_rule}{actions} .= $_;
+		};
+	    } elsif ($prev_state == IN_COMMENT) {
+	    } else # $prev_state == IN_VAR_DEF
+	    {
+		# Chop newline and backslash if this line is
+		# continued.  ensure trailing whitespace exists.
+		s/\\\s*$//;		
+		my $var_value = '';
+		$var_value = ' '
+		    unless $last_var_value =~ /\s$/;
+		$var_value .= $_;
+		$macros{$last_var_name}{contents} .= $var_value;
+		$last_var_value .= $var_value;
+	    }
+	} elsif (/$IF_PATTERN/o) {
+	    #$cond = cond_stack_if ($1, $2, $where);
+	} elsif (/$ELSE_PATTERN/o) {
+	    #$cond = cond_stack_else ($1, $2, $where);
+	} elsif (/$ENDIF_PATTERN/o) {
+	    #$cond = cond_stack_endif ($1, $2, $where);
+	} elsif (/$RULE_PATTERN/o) {
+	    # Found a rule.
+	    $current_rule = $1;
+	    &debug ("$filename:$.: Found rule '$current_rule'");
+	    $prev_state = IN_RULE_DEF;
+	    $rule_indentation = "";
+
+	    my $value = $2;
+	    if (defined ($value)) {
+		$value =~ s/\\\s*$//;
+	    } else {
+		$value = "";
+	    };
+	    $rules{$current_rule} = { actions      => "",
+				      dependencies => $value,
+				      atline       => $.,
+				      used         => 0 };
+	} elsif (/$ASSIGNMENT_PATTERN/o) {
+	    # Found a macro definition.
+	    $prev_state = IN_VAR_DEF;
+	    $last_var_name = $1;
+	    $last_var_type = $2;
+	    if ($3 ne '' && substr ($3, -1) eq "\\") {
+		$last_var_value = substr ($3, 0, length ($3) - 1);
+	    } else {
+		$last_var_value = $3;
+	    }
+
+	    if ($2 ne '+') {
+		$macros{$1} = { contents => $last_var_value,
+				atline => $.,
+				used => 0};
+	    } else {
+		# Add to the previously defined var
+		if (defined ($macros{$1})) {
+		    $macros{$1}{contents} .= ' ' . $last_var_value;
+		} else {
+		    &report_warning (100, "$filename:$.: Macro '$1' not " .
+				     "previously defined");
+		    $macros{$1} = { contents => $last_var_value,
+				    atline => $.,
+				    used => 0};
+		}
+	    };
+	} elsif (/$INCLUDE_PATTERN/o) {
+	    &debug ("$filename:$.: Found include, FIXME: handle it!");
+        } else {
+	    # This isn't an error; it is probably a continued rule.
+	    # In fact, this is what we assume.
+
+	    if (/^(\s+)/) {
+		if ($rule_indentation eq "") {
+		    $rule_indentation = $1;
+		} elsif ($rule_indentation ne $1) {
+		    &report_error (102, "$filename:$.: Incorrect indentation");
+		    return {};
+		};
+	    } else {
+		$prev_state = -1;
+	    }
+
+	    $last_var_name = "";
+	    
+	    if ($prev_state == IN_RULE_DEF) {
+		$rules{$current_rule}{actions} .= $_;
+	    } else {
+		push @extra_text, $_;
+	    };
+	}
+
+	$saw_bk = $new_saw_bk;
+        $_ = <MAKEFILE>;
+	push @lines, $_;
+    };
+
+    &report_error (100, "trailing backslash on last line")
+	if $saw_bk;    
+    
+    close MAKEFILE;
+
+    my $makefile = { filename => $filename,
+		     lines    => \ lines,
+		     macros   => \%macros,
+		     rules    => \%rules,
+		     extra    => \ extra_text,
+		     dirty    => 0 };
+
+    return $makefile;
+}
+
+sub read_entire_file ($)
+{
+    my $filename = shift;
+    my $file_contents = "";
+    my $file_offset = 0;
+    my $bytes_read = 0;
+
+    if (!open MYFILE, $filename) {
+	report_error 100, "Can't open file $filename: $!";
+	return "";
+    };
+
+    while ($bytes_read = read MYFILE, $file_contents, 4096, $file_offset) {
+	$file_offset += $bytes_read;
+    };
+    close MYFILE;
+
+    return $file_contents;
+}
+
+sub extract_syntactic_arguments ($$$$)
+{
+    my ($string, $starting, $opening, $closing) = @_;
+
+    my $working_string = $string;
+    # offset always refers to $working_string
+    my $offset = 0;
+
+    my @result = ();
+
+    while ($working_string) {
+	# start by searching the starting pattern
+	if ($working_string =~ m/$starting/) {
+	    # $balance keeps track of the paren count and starts at one because
+	    # the opening paren must be included in the starting pattern
+	    my $balance = 1;
+	    # $index is the offset at which the starting pattern was found
+	    my $index;
+
+	    # chop $working_string up to the starting pattern
+	    $working_string = substr $working_string, $+[0];
+	    $offset += $+[0];
+	    $index = $offset;
+
+	    while ($working_string && $balance > 0) {
+		# search the first occurrence of $opening, $closing 
+		# or an escaping backslash
+		my $found = ($working_string =~ m/($opening|$closing|\\)/);
+
+		if ($found) {
+		    my ($match, $start, $end) = ($1, $-[1], $+[1]);
+
+		    if ($match eq "\\") {
+			# just skip next character
+			$end++;
+		    }
+		    elsif ($match =~ m/$opening/) {
+			# increment paren balance
+			$balance++;
+		    }
+		    else {
+			# decrement paren balance
+			$balance--;
+
+			if ($balance == 0) {
+			    my $result_length = $offset - $index + $start;
+			    my $result_string = substr $string, $index, $result_length;
+			    push @result, [ $index, $result_string ];
+			};
+		    };
+
+		    # chop already processed portion of $working_string
+		    $working_string = substr $working_string, $end;
+		    $offset += $end;
+
+		} else {
+		    # unbalanced parentheses
+		    $working_string = "";
+		    last;
+		};
+	    };
+
+	} else {
+	    # no more matches
+	    last;
+	};
+    };
+    
+    return @result;
+}
+
+sub get_config_files ($)
+{
+    my $contents = shift;
+
+    my @tmp = &extract_syntactic_arguments ($contents, "AC_CONFIG_FILES\\(", "\\(", "\\)");
+    @tmp = &extract_syntactic_arguments ($contents, "AC_OUTPUT\\(", "\\(", "\\)") if ! @tmp;
+
+    if (@tmp) {
+	# get first match...
+        my ($offset, $tmp) = @{$tmp[0]};
+	# and remove possibly enclosing square brackets
+	($tmp) = split /\s*,\s*/, $tmp;
+	if ($tmp =~ m/\s*\[ ( [^\]]+ ) \]/x) {
+	    $tmp = $1;
+	    $offset += $-[1];
+	};
+	return ($offset, $tmp);
+    } else {
+	return ();
+    };
+}
+
+sub parse_configure_in_buffer
+{
+    my ($config_file, $contents) = @_;
+    
+    # get configured files, either from AC_OUTPUT or from AC_CONFIG_FILES
+    my (undef, $output) = &get_config_files ($contents);
+
+    # get substed vars
+    my (%vars, %tmpvars, @vars_order);
+    my @tmp = &extract_syntactic_arguments ($contents, "AC_SUBST\\(", "\\(", "\\)");
+    # catch variable definitions
+    while ($contents =~ m/(\w+)=(.+)$/mg) {
+	$tmpvars{$1} = $2;
+    };
+
+    # Only store those AC_SUBSTed vars
+    foreach my $tmp (@tmp) {
+	my (undef, $varname) = @$tmp;
+	if (defined ($tmpvars{$varname})) {
+	    $vars{$varname} = $tmpvars{$varname};
+	    push (@vars_order, $varname);
+	};
+    };
+    
+    # Process AC_OUTPUT
+    my %other_files;
+ 
+    if (!defined ($output)) {
+	report_warning 101, "Missing AC_OUTPUT or AC_CONFIG_FILES";
+	$output = "";
+    };
+
+  GENFILE: 
+    foreach my $file (split /\s+/, &trim ($output)) {
+	my ($gen, $input) = split /:/, $file;
+	if (!defined ($input)) {
+	    $input = "$gen.in";
+	};
+	
+	# Skip makefiles
+	if ($gen =~ /Makefile[^\/]*\z/) {
+	    next GENFILE;
+	};
+	
+	$other_files{$gen} = $input;
+    };
+    
+    my $configure_in = { filename          => $config_file,
+			 contents          => $contents,
+			 other_files       => \%other_files,
+			 vars              => \%vars,
+			 vars_order        => \ vars_order,
+			 dirty             => 0 };
+
+    return $configure_in;
+}
+
+sub parse_configure_in ($)
+{
+    my $project_dir = shift;
+    my $config_file;
+
+    if (-e "$project_dir/configure.ac") {
+	$config_file = "$project_dir/configure.ac"
+    } elsif (-e "$project_dir/configure.in") {
+	$config_file = "$project_dir/configure.in";
+    } else {
+	&report_error (100, "Neither configure.in nor configure.ac exist");
+	return {};
+    };
+
+    my $contents = &read_entire_file ($config_file);
+    if (! $contents) {
+	return {};
+    };
+    return parse_configure_in_buffer ($config_file, $contents);
+}
+
+sub configure_rewrite_packages
+{
+    my ($configure_in, $module, $packages) = @_;
+
+    $packages =~ s/,/ /sg;
+    $packages =~ s/ +/ /sg;
+    # Module is defined.
+    if ($configure_in->{'contents'} =~ s/(PKG_CHECK_MODULES\([\s\[]*$module[\s\]]*,[\s\[]*)[^\,\)\]]+(.*?\))/$1$packages$2/sm) {
+    } else {
+	## Could not find any proper place to put the new variable.
+	## Just putting it on top.
+	$configure_in->{'contents'} =~ s/AC_OUTPUT/PKG_CHECK_MODULES($module, $packages)\nAC_OUTPUT/sm;
+    }
+    $configure_in->{"dirty"} = 1;
+}
+
+sub configure_rewrite_variable
+{
+    my ($configure_in, $var, $value) = @_;
+    
+    if ($configure_in->{'contents'} =~ /^\Q$var\E=/m) {
+	$configure_in->{'contents'} =~
+	    s/^\Q$var\E=.*$/$var=$value/m;
+    } elsif ($configure_in->{'contents'} =~ /^AC_INIT\(.*?\)\s*$/m) {
+	$configure_in->{'contents'} =~
+	    s/^(AC_INIT\(.*?\))\s*$/$1\n\n$var=$value\nAC_SUBST($var)\n/m;
+    } else {
+	## Could not find any proper place to put the new variable.
+	## Just putting it on top.
+	$configure_in->{'contents'} =
+	    "$var=$value\nAC_SUBST($var)\n". $configure_in->{'contents'};
+    }
+    $configure_in->{"dirty"} = 1;
+}
+
+sub configure_remove_variable
+{
+    my ($configure_in, $var) = @_;
+    if ($configure_in->{'contents'} =~ /^\Q$var\E=/m)
+    {
+	$configure_in->{'contents'} =~
+	    s/^\Q$var\E=.*$//m;
+	$configure_in->{dirty} = 1;
+    }
+}
+
+sub configure_rewrite_package_info
+{
+    my ($configure_in, $package_name, $package_version, $package_url) = @_;
+    
+    if ($package_url ne "")
+    {
+	$configure_in->{contents} =~ s/AC_INIT\(.*?\)/AC_INIT($package_name, $package_version, $package_url)/sm;
+    }
+    elsif ($package_version ne "")
+    {
+	$configure_in->{contents} =~ s/AC_INIT\(.*?\)/AC_INIT($package_name, $package_version)/sm;
+    }
+    elsif ($package_name ne "")
+    {
+	$configure_in->{contents} =~ s/AC_INIT\(.*?\)/AC_INIT($package_name)/sm;
+    }    
+    
+    $configure_in->{dirty} = 1;
+}
+
+######################################################################
+# Direct file modification functions
+######################################################################
+
+sub output_am_file
+{
+    my ($makefile, $new_file) = @_;
+
+    rename ($new_file, "${new_file}.bak");
+
+    open NEWFILE, "> $new_file" || 
+	return &report_error (3, "Can't open $new_file for writing");;
+
+    my $last_line = scalar (@{$makefile->{lines}});
+    my $current_line = 1;
+    foreach my $line (@{$makefile->{lines}}) {
+	chomp $line;
+	
+	## last line is empty, do not output a newline together with it.
+	if (($current_line == $last_line) && $line eq "") {
+	    print NEWFILE $line;
+	} else {
+	    print NEWFILE $line, "\n";
+	}
+	$current_line++;
+    }
+    close NEWFILE;
+    return 0;
+}
+
+sub output_configure_in ($)
+{
+    my ($configure_in) = @_;
+    my $filename = $configure_in->{filename};
+
+    rename ($filename, "${filename}.bak");
+
+    open NEWFILE, "> $filename" || 
+	return &report_error (3, "Can't open $filename for writing");;
+    print NEWFILE $configure_in->{contents};
+    close NEWFILE;
+
+    $configure_in->{dirty} = 0;
+
+    return 0;
+}
+
+sub file_insert_lines
+{
+    # Insert a line of text
+    my $makefile = shift;
+    my $at = shift;
+    my @new_lines = @_;
+
+    if (scalar @new_lines == 0) {
+	return;
+    }
+
+    if ($at < 1 || $at > $#{$makefile->{lines}} + 2) {
+	&report_warning (301, "Out of bounds line number $at.  " .
+			 "Will insert at the end\n");
+	$at = $#{$makefile->{lines}} + 2;
+    };
+
+    my $count = $#new_lines + 1;
+    splice @{$makefile->{lines}}, $at - 1, 0, @new_lines;
+    $makefile->{dirty} = 1;
+
+    foreach my $macro (values %{$makefile->{macros}}) {
+	$macro->{atline} += $count if ($macro->{atline} >= $at);
+    };
+    foreach my $rule (values %{$makefile->{rules}}) {
+	$rule->{atline} += $count if ($rule->{atline} >= $at);
+    };
+}
+
+sub file_remove_lines
+{
+    # Remove a line of text
+    my ($makefile, $at, $count, $remove_trailing_blank) = @_;
+
+    if ($count <= 0) {
+	return;
+    }
+
+    if ($at < 1 || $at > $#{$makefile->{lines}} + 2) {
+	&report_warning (301, "Out of bounds line number $at.  " .
+			 "I will not remove it\n");
+	return;
+    };
+
+    $_ = @{$makefile->{lines}}[$at + $count - 1];
+    if ($at + $count < scalar @{$makefile->{lines}} && 
+	/^\s+$/ && $remove_trailing_blank) {
+	$count++;
+    };
+    &debug ("Removing lines from $at to " . ($at + $count));
+    splice @{$makefile->{lines}}, $at - 1, $count;
+    $makefile->{dirty} = 1;
+
+    foreach my $macro (values %{$makefile->{macros}}) {
+	$macro->{atline} -= $count if ($macro->{atline} > $at);
+    };
+    foreach my $rule (values %{$makefile->{rules}}) {
+	$rule->{atline} -= $count if ($rule->{atline} > $at);
+    };
+}
+
+sub get_line_from_hint
+{
+    # Return the nearest line to hint into which to insert contents
+    my ($makefile, $hint) = @_;
+
+    # Add at the end by default
+    my $leading_blank = 0;
+    my $trailing_blank = 0;
+
+    my $total = $#{$makefile->{lines}} + 1;
+    my $line = $total + 1;
+
+  THINGS:
+    foreach my $things ($makefile->{macros}, $makefile->{rules}) {
+	foreach my $thing_name (keys %$things) {
+	    if ($thing_name eq $hint) {
+		$line = $things->{$thing_name}{atline} + 1;
+		
+		# Now find the next available line
+		my $continued = $makefile->{lines}[$line - 2] =~ /\\$/;
+		
+		while ($line <= $total) {
+		    $_ = $makefile->{lines}[$line - 1];
+		    if (/\\$/) {
+			# Line is continued, skip next too
+			$continued = 1;
+			$line++;
+		    } elsif (/^\t/ || $continued) {
+			# Last line was continued or line starts with a tab char, 
+			# so it's a rule action
+			$line++;
+			$continued = 0;
+		    } else {
+			# An available line here
+			# If it's blank, skip it to produce prettier output
+			if (/^\s*$/) {
+			    $line++;
+			    $leading_blank = 1;
+			};
+			last;
+		    };
+		};
+		last THINGS;
+	    };
+	};
+    };
+
+    if ($line < $total) {
+	$trailing_blank = int ($makefile->{lines}[$line - 1] =~ /^\s+$/);
+    };
+		
+    &debug ("Hint for $hint is ($line, $leading_blank, $trailing_blank)");
+    return ($line, $leading_blank, $trailing_blank);
+}
+
+
+######################################################################
+# Macro editing functions
+######################################################################
+
+sub last_macro
+{
+    # Returns last (in the text file) defined macro
+    my $makefile = $_[0];
+
+    my %macros = %{$makefile->{macros}};
+
+    my $line = 0;
+    my $macro_name = "";
+
+    foreach my $tmp (keys %macros) {
+	if (!$macro_name || $macros{$tmp}{atline} > $line) {
+	    $macro_name = $tmp;
+	    $line = $macros{$tmp}{atline};
+	}
+    }
+    return $macro_name;
+}
+
+sub analyze_macro_style
+{
+    my ($makefile, $macro_name) = @_;
+    
+    my $macro = $makefile->{macros}{$macro_name};
+
+    if (!defined $macro) {
+	return (0, %default_macro_style);
+    }
+
+    # Analyze the current macro text and get the backslash column and the
+    # items per line
+    my $bs_column = 0;         # backslash column.
+    my $items_per_line = 1;    # number of text elements per text line
+    my $line = "";
+    my $line_num = $macro->{atline} - 1;
+    my $first_line = 1;
+    my $first_line_ignored = 0;
+    my $leading_space;
+
+    # Iterate over the continued lines
+    do {
+	$line_num++;
+	$line = @{$makefile->{lines}}[$line_num - 1];
+	
+	my $line_copy = &expand_tabs ($line);
+	my $bs = $bs_column;
+	
+	if ($line_copy =~ /\\\s*$/) {
+	    # first get the column of the backslash
+	    $bs = rindex $line_copy, '\\';
+	};
+	
+	if ($first_line) {
+	    $line =~ /$ASSIGNMENT_PATTERN/;
+	    $line_copy = &empty_if_undef ($3);
+	} elsif (!$leading_space) {
+	    $line =~ /^(\s+)\S+/;
+	    $leading_space = $1;
+	};
+	$line_copy =~ s/\\\s*$//;
+	
+	my @items = split /\s+/, &trim ($line_copy);
+	
+	if ($first_line && scalar @items == 0) {
+	    $first_line_ignored = 1;
+	};
+
+	# get the maximum items per line seen
+	if (scalar @items + 1 > $items_per_line) {
+	    $items_per_line = scalar @items;
+	};
+	
+	if ((!$first_line && $bs_column == 0) || ($first_line && $first_line_ignored)) {
+	    # initialize bs_column
+	    $bs_column = $bs;
+	} elsif ($bs_column != 0 && $bs != $bs_column) {
+	    # bs column changed, get amount of separation
+	    chomp $line_copy;
+	    if ($line_copy =~ s/.+[^ ](\s+)\z/$1/) {
+		$bs_column = - length ($line_copy);
+	    } else {
+		## Line has zero spacing. So use 1 instead.
+		$bs_column = -1;
+	    };
+	};
+	
+	$first_line = 0;
+    } while ($line =~ /\\\s*$/);
+    
+    &debug ("Macro: $macro_name, " .
+	    "bs_column = $bs_column, items_per_line = $items_per_line, ".
+	    "last_line = $line_num, ignore_first = $first_line_ignored");
+
+    my %macro_style = ( bs_column          => $bs_column,
+			items_per_line     => $items_per_line,
+			first_line_ignored => $first_line_ignored,
+			leading_space      => $leading_space );
+
+    return ($line_num, \%macro_style);
+}
+
+sub format_macro
+{
+    my ($line, $text_to_add, $macro_style, $add_trailing_bs) = @_;
+
+    my (@result, $one_line, $item_count, @items);
+
+    @items = split /\s+/, &trim ($text_to_add);
+
+    if (!defined ($macro_style)) {
+	$macro_style = \%default_macro_style;
+    };
+
+    my $leading_space = $macro_style->{leading_space};
+    my $bs_column = $macro_style->{bs_column};
+
+    if (!defined ($leading_space)) {
+	$leading_space = "\t";
+    }
+
+    if ($line =~ /$ASSIGNMENT_PATTERN/) {
+	my @current_items = split /\s+/, &trim (&empty_if_undef ($3));
+	$item_count = scalar @current_items;
+
+	if (!$bs_column) {
+	    if ($item_count == 1) {
+		$bs_column = -1;
+	    } else {
+		$bs_column = $default_macro_style{bs_column};
+	    };
+	};
+	my $line_copy = &expand_tabs ($line);
+	if ($bs_column > 0 && length ($line_copy) + 1 > $bs_column) {
+	    $bs_column = length ($line_copy) + 1;
+	};
+
+    } else {
+	# Extract leading space
+	$line =~ /^(\s*)\S+/;
+	$leading_space = $1;
+	
+	# count items already in 
+	my @current_items = split /\s+/, &trim ($line);
+	$item_count = scalar @current_items;
+    }
+
+    my $items_per_line = $macro_style->{items_per_line};
+    if (!$items_per_line) {
+	if ($bs_column < 0) {
+	    $items_per_line = 1;
+	} else {
+	    my $avg_width = 0;
+	    foreach (@items) {
+		$avg_width += length ($_);
+	    };
+	    $avg_width /= ($#items + 1);
+	    # Wild guess here... could be adjusted
+	    $items_per_line = int ($bs_column * .75 / $avg_width);
+	};
+    };
+
+    $one_line = $line;
+    $one_line =~ s/\s+$/ /;
+    my $max_width = ($bs_column > 0 ? $bs_column : 70);  # FIXME: don't hardcode
+    
+    &debug ("Formatting '$one_line' which already has $item_count items, ".
+	    "bs_column = $bs_column, items_per_line = $items_per_line");
+
+    my $ignore_first_line = $macro_style->{first_line_ignored};
+
+    foreach my $item (@items) {
+	my $line_copy = &expand_tabs ($one_line);
+
+	# if items_per_line is more than 2 we assume the user wants to fill in
+	# the space until the bs column (or max_width,
+	# but normally, when bs_column < 0, items_per_line will be 1)
+	if (($items_per_line <= 2 && $item_count >= $items_per_line) || 
+	    length ($line_copy) + length ($item) > $max_width ||
+	    $ignore_first_line) {
+	    
+	    $ignore_first_line = 0;
+	    # close this line
+	    $one_line =~ s/\s+$//;
+	    if ($bs_column > 0) {
+		# we need the bs at a fixed column
+		$one_line .= " " x ($bs_column - length ($line_copy));
+	    } else {
+		$one_line .= " " x (- $bs_column);
+	    }
+	    $one_line .= "\\\n";
+	    push @result, $one_line;
+	    $one_line = $leading_space;
+	    $item_count = 0;
+	}
+	$one_line .= ($item_count ? ' ' : '' ) . "$item";
+	$item_count++;
+    };
+    # Add last line
+    if ($add_trailing_bs) {
+	my $line_copy = &expand_tabs ($one_line);
+
+	# close this line
+	$one_line =~ s/\s+$//;
+	if ($bs_column > 0) {
+	    # we need the bs at a fixed column
+	    $one_line .= " " x ($bs_column - length ($line_copy));
+	} else {
+	    $one_line .= " " x (- $bs_column);
+	}
+	$one_line .= "\\\n";
+
+    } else {
+	$one_line .= "\n";
+    }
+
+    push @result, $one_line;
+
+    return @result;
+}
+
+sub macro_prepend_text
+{
+    # Prepends some text to the macro
+    my ($makefile, $macro_name, $text) = @_;
+
+    my $macro = $makefile->{macros}{$macro_name};
+    if (!defined ($macro)) {
+	# If the macro doesn't exist create it
+	&macro_create ($makefile, $macro_name, $text, &last_macro ($makefile));
+
+    } elsif ($macro->{atline} > 0) {
+	my ($last_line_num, $macro_style) = 
+	    &analyze_macro_style ($makefile, $macro_name);
+	my $first_line_num = $macro->{atline};
+	my $first_line = @{$makefile->{lines}}[$first_line_num - 1];
+
+	$first_line =~ /$ASSIGNMENT_PATTERN/;
+	my $leading_text = $3;
+	$text .= " " . $leading_text;
+	$text =~ s/\\\s*$//;
+	$first_line = substr $first_line, 0, -length ($leading_text) - 1;
+
+	my @lines = &format_macro ($first_line, $text, $macro_style, 
+				   ($last_line_num != $first_line_num));
+
+	$first_line = shift @lines;
+	@{$makefile->{lines}}[$first_line_num - 1] = $first_line;
+	$makefile->{dirty} = 1;
+	&file_insert_lines ($makefile, $first_line_num + 1, @lines);
+
+	# FIXME: this is not right, since maybe $text doesn't have backslashes 
+	# and newlines
+	$macro->{contents} = $text . " " . $macro->{contents};
+    }
+}
+
+sub macro_append_text
+{
+    # Appends some text to the macro
+    my ($makefile, $macro_name, $text) = @_;
+
+    my $macro = $makefile->{macros}{$macro_name};
+    if (!defined ($macro)) {
+	# If the macro doesn't exist create it
+	&macro_create ($makefile, $macro_name, $text, &last_macro ($makefile));
+
+    } elsif ($macro->{atline} > 0) {
+	my ($last_line_num, $macro_style) =
+	    &analyze_macro_style ($makefile, $macro_name);
+	my $last_line = @{$makefile->{lines}}[$last_line_num - 1];
+	my @lines = &format_macro ($last_line, $text, $macro_style, 0);
+
+	$last_line = shift @lines;
+	@{$makefile->{lines}}[$last_line_num - 1] = $last_line;
+	$makefile->{dirty} = 1;
+	&file_insert_lines ($makefile, $last_line_num + 1, @lines);
+
+	# FIXME: this is not right, since maybe $text doesn't have backslashes 
+	# and newlines
+	$macro->{contents} .= " $text";
+    }
+}
+
+sub macro_create
+{
+    # hint is a macro name.  This macro should be created around it.
+    my ($makefile, $macro_name, $initial, $hint_near) = @_;
+
+    my $macros = $makefile->{macros};
+    if (!$hint_near) {
+	$hint_near = &last_macro ($makefile);
+    };
+
+    my ($line, $lead, $trail) = &get_line_from_hint ($makefile, $hint_near);
+
+    if (defined ($macros->{$macro_name})) {
+	$line = $macros->{$macro_name}{atline};
+	&report_warning (300, "Macro '$macro_name' already exists.  It will be deleted");
+	&macro_remove ($makefile, $macro_name);
+	$lead = 1;
+	$trail = 0;
+    };
+    
+    &debug ("macro_append: Using line $line");
+
+    &file_insert_lines ($makefile, $line, "\n") if (!$trail);
+    &file_insert_lines ($makefile, $line, 
+			&format_macro ("$macro_name = ", $initial));
+    &file_insert_lines ($makefile, $line++, "\n") if !$lead;
+    
+    # FIXME: this is not right, since maybe $initial doesn't have backslashes 
+    # and newlines
+    my %new_macro = ( contents => $initial,
+		      atline   => $line,
+		      used     => 0 );
+    $macros->{$macro_name} = \%new_macro;
+}
+
+sub macro_remove
+{
+    my $makefile = shift;
+
+    while (my $macro_name = shift) {
+	my $macro = $makefile->{macros}{$macro_name};
+	if (!defined ($macro)) {
+	    next;
+	}
+
+	my $line = $macro->{atline};
+	if ($line > 0) {
+	    &debug ("Removing macro $macro_name at line $line");
+	    
+	    my $count = 1;
+	    while (@{$makefile->{lines}}[$line + $count - 2] =~ /\\\s*$/) {
+		$count++;
+	    };
+	    
+	    &file_remove_lines ($makefile, $line, $count, 1);
+	    
+	    delete $makefile->{macros}{$macro_name};
+	};
+    }
+}
+
+sub macro_rewrite
+{
+    # Rewrites the macro with the new text, but trying to accomodate to the
+    # previous style
+    my ($makefile, $macro_name, $text, $hint_near) = @_;
+
+    my $macro = $makefile->{macros}{$macro_name};
+    if (!defined ($macro)) {
+    	if ($hint_near eq "") {
+	    $hint_near = &last_macro ($makefile);
+	}
+	# If the macro doesn't exist create it
+	&macro_create ($makefile, $macro_name, $text, $hint_near);
+
+    } elsif ($macro->{atline} > 0) {
+	my ($last_line_num, $macro_style) =
+	    &analyze_macro_style ($makefile, $macro_name);
+	my $first_line_num = $macro->{atline};
+
+	my @lines = &format_macro ("$macro_name = ", $text, $macro_style, 0);
+
+	&file_remove_lines ($makefile, $first_line_num, 
+			    $last_line_num - $first_line_num + 1);
+	&file_insert_lines ($makefile, $first_line_num, @lines);
+
+	# FIXME: this is not right, since maybe $text doesn't have backslashes 
+	# and newlines
+	$macro->{contents} = $text;
+    }
+}
+
+sub macro_remove_text
+{
+    # Removes some text from the macro definition
+    my ($makefile, $macro_name, $remove_text, $remove_macro_if_empty) = @_;
+
+    my $macro = $makefile->{macros}{$macro_name};
+    if (!defined ($macro) || $macro->{atline} <= 0) {
+	return;
+    };
+
+    $macro->{contents} =~ s/$remove_text//;
+    if (&trim ($macro->{contents}) eq "" && $remove_macro_if_empty) {
+	&macro_remove ($makefile, $macro_name);
+	return;
+    }
+
+    my ($last_line_num, $macro_style) = &analyze_macro_style ($makefile, $macro_name);
+    my $line_num = $macro->{atline};
+    while ($line_num <= $last_line_num) {
+	my $line = @{$makefile->{lines}}[$line_num - 1];
+	
+	if ($line =~ /(\s|=)$remove_text\s/) {
+	    &debug ("found the text to remove at line $line_num");
+	    # Found the text
+	    # Check if it's the only thing in the line
+	    if ($line =~ /^\s+$remove_text\s*\\?$/) {
+		&file_remove_lines ($makefile, $line_num, 1);
+		if ($line_num == $last_line_num) {
+		    # remove the trailing bs of the previous line
+		    $_ = @{$makefile->{lines}}[$line_num - 2];
+		    chomp; chop;
+		    @{$makefile->{lines}}[$line_num - 2] = $_ . "\n";
+		    $makefile->{dirty} = 1;
+		}
+	    } else {
+		# else, just remove the text, the trailing bs and reformat the line
+		$line =~ s/$remove_text\s//;
+		$line =~ s/\\\s*$//;
+		if ($line !~ /^\s+/ && $line_num > $macro->{atline}) {
+		    $line = $macro_style->{leading_space} . $line;
+		};
+		my @lines = &format_macro ($line, "", $macro_style,
+					   ($line_num != $last_line_num));
+		@{$makefile->{lines}}[$line_num - 1] = shift @lines;
+		$makefile->{dirty} = 1;
+		if (@lines) {
+		    &file_insert_lines ($makefile, $line_num + 1, @lines);
+		}
+	    }
+	    last;
+	};
+	$line_num++;
+    }
+}
+
+sub test_print_am_file
+{
+    my ($makefile, $message) = @_;
+
+    print "MAKEFILE CONTENTS $message\n" . "-" x 79 . "\n";
+    my $number = 1;
+    foreach my $line (@{$makefile->{lines}}) {
+	print "$number: $line";
+	$number++;
+    };
+}
+
+
+######################################################################
+# Rule editing functions
+######################################################################
+
+sub rule_remove
+{
+    my $makefile = shift;
+
+    while (my $rule_name = shift) {
+	my $rule = $makefile->{rules}{$rule_name};
+	if (!defined ($rule)) {
+	    next;
+	}
+
+	my $line = $rule->{atline};
+	if ($line > 0) {
+	    my $count = 1;
+	    my $maxcount = scalar @{$makefile->{lines}} - $line;
+	    # count dependency lines
+	    while ($count < $maxcount && 
+		   @{$makefile->{lines}}[$line + $count - 2] =~ /\\\s*$/) {
+		$count++;
+	    };
+	    # count action lines
+	    while ($count <= $maxcount &&
+		   @{$makefile->{lines}}[$line + $count - 1] =~ /^\t/) {
+		$count++;
+	    };
+	    
+	    &debug ("Removing rule $rule_name at line $line");
+	    
+	    &file_remove_lines ($makefile, $line, $count, 1);
+	    
+	    delete $makefile->{rules}{$rule_name};
+	};
+    }
+}
+
+
+######################################################################
+# Text editing functions
+######################################################################
+
+sub beginning_of_line ($$)
+{
+    my ($string, $offset) = @_;
+
+    # this automagically handles first lines, 
+    # since rindex will return -1 in that case
+    return rindex ($string, "\n", $offset - 1) + 1;
+}
+
+sub next_line_offset ($$)
+{
+    my ($string, $offset) = @_;
+
+    my $next_line = index ($string, "\n", $offset) + 1;
+    # make correction if at end of string and no terminating newline
+    $next_line = length $string if $next_line <= $offset;
+
+    return $next_line;
+}
+
+sub get_line_at_offset ($$)
+{
+    my ($string, $offset) = @_;
+
+    my $line_begin = beginning_of_line $string, $offset;
+    my $line_end = next_line_offset $string, $offset;
+
+    return substr $string, $line_begin, ($line_end - $line_begin);
+}
+
+sub stat_sample (@)
+{
+    my ($sum, $count, $max, $i) = (0,0,0,0);
+    my ($most_freq, $count_most_freq) = (0,0);
+    foreach my $data (@_) {
+	$sum += $i * $data;
+	$count += $data;
+	$max = $i if $data > 0;
+	if ($data >= $count_most_freq) {
+	    ($most_freq, $count_most_freq) = ($i, $data);
+	};
+	$i++;
+    };
+    return ($max, int ($sum / $count), $most_freq, $count_most_freq / $count);
+}
+
+# return values:
+#  0 means unordered
+# +1 means descending
+# -1 means ascending
+sub analyze_order (@)
+{
+    my $order = undef;
+    my $last = shift;
+    foreach my $item (@_) {
+	my $current = $last cmp $item;
+	if ($current != 0) {
+	    if (!defined $order) {
+		$order = $current;
+	    } elsif ($current != $order) {
+		$order = 0;
+		last;
+	    };
+	};
+	$last = $item;
+    };
+    return $order;
+}
+
+sub find_best_match ($$@)
+{
+    my $order = shift;
+    my $ref = shift;
+    my ($winner, $score) = (undef, 0);
+
+    foreach my $item (@_) {
+	my $this_score = 0;
+	if ($order == 0) {
+	    # find the nearest string
+	    while (substr ($ref, $this_score, 1) eq substr ($item, $this_score, 1) &&
+		   $this_score < length $ref) {
+		$this_score++;
+	    };
+	} elsif ($order > 0) {
+	    $this_score = $item cmp $ref;
+	} else {
+	    $this_score = $ref cmp $item;
+	};
+
+	if ($this_score >= $score) {
+	    ($winner, $score) = ($item, $this_score);
+	};
+    };
+    return $winner;
+}
+
+sub analyze_text ($$$)
+{
+    my ($string, $offset, $length) = @_;
+
+    # variables to keep information
+    my ($ignore_first_line, $ignore_last_line) = (0,0);
+    my (@widths, @left_margins, @right_margins, @item_counts);
+    my $backslashed = 0;
+
+    # acquire data to analyze
+    my $inner_text = substr $string, $offset, $length;
+    my $first_line_offset = beginning_of_line $string, $offset;
+    my $outer_text = substr ($string, $first_line_offset,
+			     next_line_offset ($string, $offset + $length) 
+			     - $first_line_offset);
+
+    my @lines = split "\n", $outer_text;
+    my @inner_lines = split "\n", $inner_text;
+
+    $ignore_first_line = 1 if trim (chop_backslash ($inner_lines[0])) eq "";
+    $ignore_last_line = 1 if trim (chop_backslash ($inner_lines[ inner_lines - 1])) eq "";
+
+    my ($line_count, $line_number) = (0, 0);
+    foreach my $line (@lines) {
+	my $expanded_line = expand_tabs $line;
+	my $line_length = length $expanded_line;
+
+	# skip ignored lines
+	next if $line_number == 0 && $ignore_first_line;
+	next if $line_number == @lines - 1 && $ignore_last_line;
+
+	# sample data
+	$widths[$line_length]++;
+	$backslashed = 1 if $expanded_line =~ m/\\\s*$/;
+	$expanded_line = chop_backslash $expanded_line;
+	if ($line_number != 0 && $line_number != @lines - 1) {
+	    $expanded_line =~ m/^(\s*)\S.*\S(\s*)$/;
+	    $left_margins[length $1]++;
+	    $right_margins[length $2]++;
+	};
+
+	my @items = split /\s+/, trim $expanded_line;
+	$item_counts[ items]++;
+
+	# finish this line
+	$line_count++;
+
+    } continue {	
+	$line_number++;
+    };
+
+    # get information
+    my ($max_width, $avg_width, $width, $width_prop) = stat_sample @widths;
+    my (undef, undef, $left_margin) = stat_sample @left_margins;
+    my (undef, undef, $right_margin) = stat_sample @right_margins;
+    my ($item_count) = stat_sample @item_counts;
+
+    # determine if text is somehow ordered
+    my @items = split /\s+/, trim $inner_text;
+    my $order = analyze_order @items;
+
+#     print "max_width => $max_width, avg_width => $avg_width, width => ".
+# 	($width_prop > .75 ? $width : -1) .", ignore_first_line => $ignore_first_line, ".
+# 	"ignore_last_line => $ignore_last_line, left_margin => $left_margin, ".
+# 	"right_margin => $right_margin, items_per_line => $item_count, ".
+# 	"order => $order, backslashed => $backslashed\n";
+
+    # width = -1 means every line's width is just what's needed for that line
+    # and in that case, attention is taken to right_margin
+    # otherwise, it indicates the desired width for any new line 
+    # and the right_margin adjusts to fit such width
+    return { max_width => $max_width,
+	     avg_width => $avg_width,
+	     width => $width_prop > .75 ? $width : -1,
+	     ignore_first_line => $ignore_first_line,
+	     ignore_last_line => $ignore_last_line,
+	     left_margin => $left_margin,
+	     right_margin => $right_margin,
+	     items_per_line => $item_count,
+	     order => $order,
+	     backslashed => $backslashed };
+}
+
+# FIXME: probably format_macro can be replaced with this new (possibly
+# better thought) function
+sub format_line ($$$)
+{
+    my ($line, $style, $start_column) = @_;
+
+    $start_column = 0 if !defined $start_column;
+    my $expanded_line = expand_tabs $line;
+    my @line_items = split /\s+/, trim (chop_backslash $line);
+    my $item_count = scalar @line_items;
+    my $width = $style->{width} > 0 ? $style->{width} : $style->{max_width};
+
+#     print "Formatting $line";
+
+    if ($item_count > 1 && 
+	(length ($expanded_line) + $start_column > $width ||
+	 $item_count > $style->{items_per_line})) {
+	
+	my $rest = $line;
+	my $new_line = "";
+	my @rest;
+	my ($at, $chunk);
+
+	# try to split the line as close to $style->{width} as possible
+	$item_count = 0;
+	$rest =~ m/^(\s*\S+)/;
+	$chunk = $1;
+	$at = $+[1];
+	do {
+	    # add the chunk
+	    $new_line .= "$chunk ";
+	    $item_count++;
+	    $rest = substr $rest, $at;
+	    
+	    # match next chunk
+	    $rest =~ m/^\s*(\S+)/;
+	    $chunk = $1;
+	    $at = $+[1];
+	} while (length (expand_tabs ("$new_line $chunk")) + $start_column < $width &&
+		 $item_count < $style->{items_per_line});
+
+	# format the reduced line
+	$new_line = &format_line ("$new_line\n", $style, $start_column);
+
+	# correct left margin in $rest
+	$rest =~ s/^\s*//;
+	$rest = (" " x $style->{left_margin}) . $rest;
+
+	# recurse on $rest, which should still be a single line
+	$line = $new_line . &format_line ($rest, $style, 0);
+	
+    } else {
+	# line fits and has a valid number of items... just close it
+	# remove trailing space
+	$line = chop_backslash $line;
+	$line =~ s/\s*$//;
+	
+	if ($style->{width} > 0) {
+	    # close line at a fixed length
+	    $line .= " " x (length (expand_tabs $line) - $style->{width});
+	} else {
+	    $line .= " " x $style->{right_margin};
+	};
+	if ($style->{backslashed}) {
+	    $line .= "\\";
+	};
+	$line .= "\n";
+
+    };
+
+    return $line;
+}
+
+sub insert_text ($$$$)
+{
+    my ($string, $start_column, $new_text, $style) = @_;
+    my $result = "";
+
+    # first of all we need to find a place to put the new text on
+    my @string_items = split /\s+/, trim $string;
+    my $insert_offset;
+    my $reference = find_best_match $style->{order}, $new_text, @string_items;
+
+    if ($reference) {
+	my $tmp = " $string ";
+	while ($tmp =~ m/\s+(\Q$reference\E)\s+/g) {
+	    $insert_offset = $+[1];
+	};
+
+    } else {
+	# append the text to the string if we couldn't find a reference
+	$insert_offset = beginning_of_line $string, length ($string);
+	$insert_offset = beginning_of_line $string, $insert_offset - 1 
+	    if $style->{ignore_last_line};
+    };
+
+    my ($before, $after) = (substr ($string, 0, $insert_offset),
+			    substr ($string, $insert_offset));
+
+    # chop spaces at both ends
+    $before =~ s/\s*$//;
+    $after =~ s/^\s*//;
+
+    $result = "$before $new_text $after";
+
+    my $start_of_line_to_format = beginning_of_line $result, length $before;
+    my $line_to_format = get_line_at_offset $result, length $before;
+    my $formatted_line = format_line ($line_to_format, $style, 
+				      $start_of_line_to_format == 0 ? 
+				      $start_column : 0);
+
+    # replace the formatted line
+    substr $result, $start_of_line_to_format, length $line_to_format, $formatted_line;
+
+    return $result;
+}
+
+
+######################################################################
+# configure.in file editing functions
+######################################################################
+
+sub get_column ($$)
+{
+    my ($string, $offset) = @_;
+
+    my $column0 = beginning_of_line $string, $offset;
+    return length expand_tabs substr $string, $column0, ($offset - $column0);
+}
+
+sub edit_config_files ($$$)
+{
+    my ($configure_in, $action, $operand) = @_;
+    my ($offset, $config_files) = get_config_files $configure_in->{contents};
+
+    return unless defined $config_files;
+
+    # get current style
+    my $style = analyze_text $configure_in->{contents}, $offset, length $config_files;
+    my $new_config_files = $config_files;
+    my $start_column = get_column $configure_in->{contents}, $offset;
+
+    if ($action eq "add") {
+	$new_config_files = insert_text ($config_files, $start_column,
+					 $operand, $style);
+
+    } elsif ($action eq "remove") {
+	$new_config_files =~ s/\Q$operand\E\s*//g;
+    };
+
+    # save result string
+    if ($new_config_files ne $config_files) {
+	substr $configure_in->{contents}, $offset, length $config_files, $new_config_files;
+	$configure_in->{dirty} = 1;
+    };
+}
+
+
+######################################################################
+# General Functions
+######################################################################
+
+sub am_file_reset
+{
+    my $makefile = shift;
+    foreach my $macro (values %{$makefile->{macros}}) {
+	$macro->{used} = 0;
+    }
+    foreach my $rule (values %{$makefile->{rules}}) {
+	$rule->{used} = 0;
+    }
+}
+
+sub use_macro
+{
+    my $macro = $_[0];
+    if (defined ($macro)) {
+	$macro->{used} = 1;
+	return &trim ($macro->{contents});
+    } else {
+	return undef ();
+    };
+}
+
+sub use_rule
+{
+    my $rule = $_[0];
+    if (defined ($rule)) {
+	$rule->{used} = 1;
+	return (&trim ($rule->{dependencies}), $rule->{actions});
+    } else {
+	return (undef, undef);
+    };
+}
+
+sub expand_one_var
+{
+    my ($source, %expansion_set) = @_;
+    my ($var_name, $repl);
+
+    # Expand normal macros
+    while ($source =~ /\$[({](\w*)[)}]/) {
+	$var_name = $1;
+	$repl = &empty_if_undef ($expansion_set{$var_name}{contents});
+	$source =~ s/\$[({]$var_name[)}]/$repl/;
+    }
+
+    # Expand substitution macros $(macro:search=replace)
+    while ($source =~ /\$\((\w*):([^=]+)=([^\)]*)\)/) {
+	$var_name = $1;
+	my ($search, $replace) = ($2, $3);
+
+	$repl = &empty_if_undef ($expansion_set{$var_name}{contents});
+	$repl =~ s/$search/$replace/g;
+
+	# Set sources list
+	$source =~ s/\$\($var_name:([^=]+)=([^\)]*)\)/$repl/;
+    }
+    return $source;
+}
+
+1;

Added: trunk/plugins/gbf-am/GBF/General.pm
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/GBF/General.pm	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,120 @@
+package GBF::General;
+
+use Exporter;
+use strict;
+
+our (@ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
+
+ ISA = qw (Exporter);
+
+ EXPORT = qw (&report_error &report_warning
+	      &trim &canonicalize_name &empty_if_undef &expand_tabs
+	      &base_name &dir_name &chop_backslash &max &min);
+ EXPORT_OK = qw ($debug);
+%EXPORT_TAGS = ();
+
+
+
+######################################################################
+####  SUBROUTINES AND HELPER FUNCTIONS  ##############################
+######################################################################
+
+sub report_error
+{
+    my ($code, $message) = @_;
+
+    print STDERR "ERROR($code): $message\n";
+
+    return $code;
+}
+
+sub report_warning
+{
+    my ($code, $message) = @_;
+
+    print STDERR "WARNING($code): $message\n";
+}
+
+sub canonicalize_name
+{
+    my ($name);
+
+    $name = $_[0];
+    $name =~ tr/\.-/_/;
+    
+    return $name;
+}
+
+sub trim
+{
+    $_ = $_[0];
+
+    s/^\s*//;
+    s/\s*$//;
+
+    return $_;
+}
+
+sub empty_if_undef
+{
+    return defined ($_[0]) ? $_[0] : "";
+}
+
+sub expand_tabs
+{
+    $_ = $_[0];
+    my $i;
+    while (/\t/) {
+	$i = index ($_, "\t");
+	$i = $i % 8;
+	if ($i == 0) {
+	    s/\t/        /;
+	} else {
+	    my $pad = " " x (8 - $i);
+	    s/\t/$pad/;
+	};
+    };
+    return $_;
+}
+
+sub base_name {
+    my ($name, $dir);
+    
+    $name = $_[0];
+    $dir = dir_name ($name);
+    $name =~ s/$dir//;
+
+    return $name;
+}
+
+sub dir_name {
+    my ($name);
+
+    $name = $_[0];
+    $name =~ s/[^\/]+$//;
+
+    return $name;
+}
+
+sub chop_backslash ($)
+{
+    $_ = shift;
+    s/\\\s*$//;
+    return $_;
+}
+
+sub max
+{
+    my $max = shift;
+    foreach my $x (@_) { $max = $x if $x > $max; };	
+    return $max;
+}
+
+sub min
+{
+    my $min = shift;
+    foreach my $x (@_) { $min = $x if $x < $min; };	
+    return $min;
+}
+
+1;

Added: trunk/plugins/gbf-am/GBF/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/GBF/Makefile.am	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,5 @@
+perlmodulesdir = $(pkgdatadir)/GBF
+perlmodules_DATA = AmFiles.pm General.pm
+
+EXTRA_DIST = $(perlmodules_DATA)
+

Added: trunk/plugins/gbf-am/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/Makefile.am	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,70 @@
+# Plugin UI file
+plugin_uidir = $(anjuta_ui_dir)
+plugin_ui_DATA = 
+
+# Plugin glade file
+plugin_gladedir = $(anjuta_glade_dir)
+plugin_glade_DATA = gbf-am-dialogs.glade
+
+# Plugin icon file
+plugin_pixmapsdir = $(anjuta_image_dir)
+plugin_pixmaps_DATA = gbf-am-plugin-48.png
+
+# Plugin scripts
+scriptsdir = $(bindir)
+scripts_SCRIPTS = gbf-am-parse
+
+# Plugin description file
+plugin_in_files = gbf-am.plugin.in
+%.plugin: %.plugin.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugindir = $(anjuta_plugin_dir)
+plugin_DATA = $(plugin_in_files:.plugin.in=.plugin)
+
+AM_CPPFLAGS = 					\
+	$(WARN_CFLAGS) \
+	$(DEPRECATED_FLAGS) \
+	$(LIBANJUTA_CFLAGS) \
+	-DSCRIPTS_DIR=\"$(scriptsdir)\"
+
+plugin_LTLIBRARIES = \
+	libgbf-am.la
+
+libgbf_am_la_SOURCES = \
+	plugin.c \
+	gbf-am-project.c \
+	gbf-am-project.h \
+	gbf-am-config.c \
+	gbf-am-config.h \
+	gbf-am-properties.c \
+	gbf-am-properties.h 
+
+libgbf_am_la_LDFLAGS = $(ANJUTA_PLUGIN_LDFLAGS)
+
+libgbf_am_la_LIBADD = \
+	$(LIBANJUTA_LIBS)
+
+# Test program
+
+noinst_PROGRAMS = test
+
+test_SOURCES = \
+	test.c
+
+test_LDADD = \
+	libgbf-am.la \
+	$(LIBANJUTA_LIBS)
+
+
+EXTRA_DIST = \
+	$(plugin_in_files) \
+	$(plugin_DATA) \
+	$(plugin_ui_DATA) \
+	$(plugin_pixmaps_DATA)
+
+DISTCLEANFILES = \
+	$(plugin_DATA) \
+	$(plugin_in_files)
+
+SUBDIRS = GBF
+

Added: trunk/plugins/gbf-am/gbf-am-config.c
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/gbf-am-config.c	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,385 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gbf-am-config.c
+ *
+ * This file is part of the Gnome Build framework
+ * Copyright (C) 2002  Gustavo Giráez
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include "gbf-am-config.h"
+
+typedef struct _GbfAmConfigEntry  GbfAmConfigEntry;
+
+struct _GbfAmConfigEntry {
+	gchar            *key;
+	GbfAmConfigValue *value;
+};
+
+struct _GbfAmConfigMapping {
+	GList *pairs;
+};
+
+
+GbfAmConfigValue *
+gbf_am_config_value_new (GbfAmValueType type)
+{
+	GbfAmConfigValue *new_value;
+
+	g_return_val_if_fail (type != GBF_AM_TYPE_INVALID, NULL);
+	
+	new_value = g_new0 (GbfAmConfigValue, 1);
+	new_value->type = type;
+
+	switch (type) {
+	    case GBF_AM_TYPE_STRING:
+		    new_value->string = NULL;
+		    break;
+	    case GBF_AM_TYPE_MAPPING:
+		    new_value->mapping = gbf_am_config_mapping_new ();
+		    break;
+	    case GBF_AM_TYPE_LIST:
+		    new_value->list = NULL;
+		    break;
+	    default:
+		    break;
+	}
+	
+	return new_value;
+}
+
+void 
+gbf_am_config_value_free (GbfAmConfigValue *value)
+{
+	if (value == NULL)
+		return;
+	
+	switch (value->type) {
+	    case GBF_AM_TYPE_STRING:
+		    g_free (value->string);
+		    value->string = NULL;
+		    break;
+	    case GBF_AM_TYPE_MAPPING:
+		    gbf_am_config_mapping_destroy (value->mapping);
+		    value->mapping = NULL;
+		    break;
+	    case GBF_AM_TYPE_LIST:
+		    if (value->list) {
+			    g_slist_foreach (value->list,
+					     (GFunc) gbf_am_config_value_free,
+					     NULL);
+			    g_slist_free (value->list);
+			    value->list = NULL;
+		    }
+		    break;
+	    default:
+		    g_warning ("%s", _("Invalid GbfAmConfigValue type"));
+		    break;
+	}
+	g_free (value);
+}
+
+GbfAmConfigValue *
+gbf_am_config_value_copy (const GbfAmConfigValue *source)
+{
+	GbfAmConfigValue *value;
+	GSList *l;
+	
+	if (source == NULL)
+		return NULL;
+	
+	value = gbf_am_config_value_new (source->type);
+	
+	switch (source->type) {
+	    case GBF_AM_TYPE_STRING:
+		    value->string = g_strdup (source->string);
+		    break;
+	    case GBF_AM_TYPE_MAPPING:
+		    value->mapping = gbf_am_config_mapping_copy (source->mapping);
+		    break;
+	    case GBF_AM_TYPE_LIST:
+		    value->list = NULL;
+		    for (l = source->list; l; l = l->next) {
+			    GbfAmConfigValue *new_value =
+				    gbf_am_config_value_copy ((GbfAmConfigValue *)l->data);
+			    value->list = g_slist_prepend (value->list, new_value);
+		    }
+		    value->list = g_slist_reverse (value->list);
+		    break;
+	    default:
+		    g_warning ("%s", _("Invalid GbfAmConfigValue type"));
+		    break;
+	}
+
+	return value;
+}
+
+void
+gbf_am_config_value_set_string (GbfAmConfigValue *value,
+				const gchar      *string)
+{
+	g_return_if_fail (value != NULL && value->type == GBF_AM_TYPE_STRING);
+
+	if (value->string)
+		g_free (value->string);
+	
+	value->string = g_strdup (string);
+}
+
+void
+gbf_am_config_value_set_list (GbfAmConfigValue *value,
+			      GSList           *list)
+{
+	GSList *l;
+	
+	g_return_if_fail (value != NULL && value->type == GBF_AM_TYPE_LIST);
+
+	if (value->list) {
+		g_slist_foreach (value->list, (GFunc) gbf_am_config_value_free, NULL);
+		g_slist_free (value->list);
+	}
+	
+	value->list = NULL;
+	for (l = list; l; l = l->next) {
+		GbfAmConfigValue *new_value = gbf_am_config_value_copy
+			((GbfAmConfigValue *)l->data);
+		value->list = g_slist_prepend (value->list, new_value);
+	}
+
+	value->list = g_slist_reverse (value->list);
+}
+
+void
+gbf_am_config_value_set_list_nocopy (GbfAmConfigValue *value,
+				     GSList           *list)
+{
+	g_return_if_fail (value != NULL && value->type == GBF_AM_TYPE_LIST);
+
+	if (value->list) {
+		g_slist_foreach (value->list, (GFunc) gbf_am_config_value_free, NULL);
+		g_slist_free (value->list);
+	}
+	value->list = list;
+}
+
+void
+gbf_am_config_value_set_mapping (GbfAmConfigValue   *value,
+				 GbfAmConfigMapping *mapping)
+{
+	g_return_if_fail (value != NULL && value->type == GBF_AM_TYPE_MAPPING);
+
+	gbf_am_config_mapping_destroy (value->mapping);
+
+	value->mapping = mapping;
+}
+
+GbfAmConfigMapping *
+gbf_am_config_mapping_new (void)
+{
+	GbfAmConfigMapping *new_map;
+
+	new_map = g_new0 (GbfAmConfigMapping, 1);
+	new_map->pairs = NULL;
+	
+	return new_map;
+}
+
+void 
+gbf_am_config_mapping_destroy (GbfAmConfigMapping *mapping)
+{
+	GbfAmConfigEntry *entry;
+	GList            *lp;
+
+	if (mapping == NULL)
+		return;
+
+	for (lp = mapping->pairs; lp; lp = lp->next) {
+		entry = (GbfAmConfigEntry *) lp->data;
+		gbf_am_config_value_free (entry->value);
+		g_free (entry->key);
+		g_free (entry);
+	}
+	g_list_free (mapping->pairs);
+	g_free (mapping);
+}
+
+GbfAmConfigMapping *
+gbf_am_config_mapping_copy (const GbfAmConfigMapping *source)
+{
+	GbfAmConfigMapping *new_map;
+	GList              *lp;
+
+	if (source == NULL)
+		return NULL;
+	
+	new_map = g_new0 (GbfAmConfigMapping, 1);
+	new_map->pairs = NULL;
+	
+	for (lp = source->pairs; lp; lp = lp->next) {
+		GbfAmConfigEntry *new_entry, *entry;
+
+		entry = (GbfAmConfigEntry *) lp->data;
+		if (entry == NULL)
+			continue;
+		
+		new_entry = g_new0 (GbfAmConfigEntry, 1);
+		new_entry->key = g_strdup (entry->key);
+		new_entry->value = gbf_am_config_value_copy (entry->value);
+		new_map->pairs = g_list_prepend (new_map->pairs, new_entry);
+	}
+
+	return new_map;
+}
+
+GbfAmConfigValue * 
+gbf_am_config_mapping_lookup (GbfAmConfigMapping *mapping,
+			      const gchar        *key)
+{
+	GbfAmConfigEntry *entry = NULL;
+	GList            *lp;
+
+	g_return_val_if_fail (mapping != NULL && key != NULL, NULL);
+
+	for (lp = mapping->pairs; lp; lp = lp->next) {
+		entry = (GbfAmConfigEntry *) lp->data;
+		if (!strcmp (entry->key, key))
+			break;
+	}
+	
+	return (lp ? entry->value : NULL);
+}
+
+gboolean 
+gbf_am_config_mapping_insert (GbfAmConfigMapping *mapping,
+			      const gchar        *key,
+			      GbfAmConfigValue   *value)
+{
+	GbfAmConfigEntry *entry = NULL;
+	GList            *lp;
+	gboolean          insert = TRUE;
+	
+	g_return_val_if_fail (mapping != NULL && key != NULL, FALSE);
+
+	for (lp = mapping->pairs; lp; lp = lp->next) {
+		entry = (GbfAmConfigEntry *) lp->data;
+		if (!strcmp (entry->key, key)) {
+			insert = FALSE;
+			break;
+		}
+	}
+	
+	if (insert) {
+		GbfAmConfigEntry *new_entry;
+
+		new_entry = g_new0 (GbfAmConfigEntry, 1);
+		new_entry->key = g_strdup (key);
+		new_entry->value = value;
+		mapping->pairs = g_list_prepend (mapping->pairs, new_entry);
+	}
+	
+	return insert;
+}
+
+gboolean 
+gbf_am_config_mapping_update (GbfAmConfigMapping *mapping,
+			      const gchar        *key,
+			      GbfAmConfigValue   *value)
+{
+	GbfAmConfigEntry *entry = NULL;
+	GList            *lp;
+	gboolean         update = TRUE;
+	gboolean 	 insert = TRUE;
+	
+	g_return_val_if_fail (mapping != NULL && key != NULL, FALSE);
+
+	for (lp = mapping->pairs; lp; lp = lp->next) {
+		entry = (GbfAmConfigEntry *) lp->data;
+		if (!strcmp (entry->key, key)) {
+			if (entry->value != value)
+			{
+				gbf_am_config_value_free (entry->value);
+				entry->value = value;
+				update = TRUE;	
+			}
+			insert = FALSE;
+			break;
+		}
+	}
+	
+	if (insert) {
+		GbfAmConfigEntry *new_entry;
+
+		new_entry = g_new0 (GbfAmConfigEntry, 1);
+		new_entry->key = g_strdup (key);
+		new_entry->value = value;
+		mapping->pairs = g_list_prepend (mapping->pairs, new_entry);
+	}
+	
+	return update;
+}
+
+gboolean 
+gbf_am_config_mapping_remove (GbfAmConfigMapping *mapping,
+			      const gchar        *key)
+{
+	GbfAmConfigEntry *entry = NULL;
+	GList            *lp;
+	gboolean          remove = FALSE;
+	
+	g_return_val_if_fail (mapping != NULL && key != NULL, FALSE);
+
+	for (lp = mapping->pairs; lp; lp = lp->next) {
+		entry = (GbfAmConfigEntry *) lp->data;
+		if (!strcmp (entry->key, key)) {
+			remove = TRUE;
+			break;
+		}
+	}
+	
+	if (remove) {
+		gbf_am_config_value_free (entry->value);
+		g_free (entry->key);
+		g_free (entry);
+		mapping->pairs = g_list_delete_link (mapping->pairs, lp);
+	}
+	
+	return remove;
+}
+
+void
+gbf_am_config_mapping_foreach (GbfAmConfigMapping *mapping,
+			       GbfAmConfigMappingFunc callback,
+			       gpointer user_data)
+{
+	GbfAmConfigEntry *entry = NULL;
+	GList            *lp;
+	
+	g_return_if_fail (mapping != NULL && callback != NULL);
+
+	for (lp = mapping->pairs; lp; lp = lp->next) {
+		entry = (GbfAmConfigEntry *) lp->data;
+		callback (entry->key, entry->value, user_data);
+	}
+}

Added: trunk/plugins/gbf-am/gbf-am-config.h
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/gbf-am-config.h	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,86 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gbf-am-config.h
+ *
+ * This file is part of the Gnome Build framework
+ * Copyright (C) 2002  Gustavo Giráez
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __GBF_AM_CONFIG_H__
+#define __GBF_AM_CONFIG_H__
+
+#include <glib.h>
+
+/* config data structures */
+typedef enum {
+	GBF_AM_TYPE_INVALID,
+	GBF_AM_TYPE_STRING,
+	GBF_AM_TYPE_MAPPING,
+	GBF_AM_TYPE_LIST
+} GbfAmValueType;
+
+typedef struct _GbfAmConfigValue    GbfAmConfigValue;
+typedef struct _GbfAmConfigMapping  GbfAmConfigMapping;
+
+struct _GbfAmConfigValue {
+	GbfAmValueType type;
+	gchar               *string;
+	GbfAmConfigMapping  *mapping;
+	GSList              *list;
+};
+
+typedef void (*GbfAmConfigMappingFunc) (const gchar *key,
+					GbfAmConfigValue *value,
+					gpointer user_data);
+/* ---------- public interface */
+
+GbfAmConfigValue   *gbf_am_config_value_new             (GbfAmValueType      type);
+void                gbf_am_config_value_free            (GbfAmConfigValue   *value);
+GbfAmConfigValue   *gbf_am_config_value_copy            (const GbfAmConfigValue *source);
+
+void                gbf_am_config_value_set_string      (GbfAmConfigValue   *value,
+							 const gchar        *string);
+void                gbf_am_config_value_set_list        (GbfAmConfigValue   *value,
+							 GSList             *list);
+void                gbf_am_config_value_set_list_nocopy (GbfAmConfigValue   *value,
+							 GSList             *list);
+void                gbf_am_config_value_set_mapping     (GbfAmConfigValue   *value,
+							 GbfAmConfigMapping *mapping);
+
+#define gbf_am_config_value_get_string(x)  ((const gchar *)(((GbfAmConfigValue *)x)->string))
+#define gbf_am_config_value_get_list(x)    ((GSList *)(((GbfAmConfigValue *)x)->list))
+#define gbf_am_config_value_get_mapping(x) ((GbfAmConfigMapping *)(((GbfAmConfigValue *)x)->mapping))
+
+GbfAmConfigMapping *gbf_am_config_mapping_new           (void);
+void                gbf_am_config_mapping_destroy       (GbfAmConfigMapping *mapping);
+GbfAmConfigMapping *gbf_am_config_mapping_copy          (const GbfAmConfigMapping *source);
+GbfAmConfigValue   *gbf_am_config_mapping_lookup        (GbfAmConfigMapping *mapping,
+							 const gchar        *key);
+gboolean            gbf_am_config_mapping_insert        (GbfAmConfigMapping *mapping,
+							 const gchar        *key,
+							 GbfAmConfigValue   *value);
+gboolean            gbf_am_config_mapping_update        (GbfAmConfigMapping *mapping,
+							 const gchar        *key,
+							 GbfAmConfigValue   *value);
+gboolean            gbf_am_config_mapping_remove        (GbfAmConfigMapping *mapping,
+							 const gchar        *key);
+void                gbf_am_config_mapping_foreach       (GbfAmConfigMapping *mapping,
+							 GbfAmConfigMappingFunc callback,
+							 gpointer user_data);
+
+#endif /* __GBF_AM_CONFIG_H__ */

Added: trunk/plugins/gbf-am/gbf-am-dialogs.glade
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/gbf-am-dialogs.glade	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,571 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd";>
+
+<glade-interface>
+
+<widget class="GtkWindow" id="project_properties_dialog">
+  <property name="title" translatable="yes">window1</property>
+  <property name="type">GTK_WINDOW_TOPLEVEL</property>
+  <property name="window_position">GTK_WIN_POS_NONE</property>
+  <property name="modal">False</property>
+  <property name="resizable">True</property>
+  <property name="destroy_with_parent">False</property>
+  <property name="decorated">True</property>
+  <property name="skip_taskbar_hint">False</property>
+  <property name="skip_pager_hint">False</property>
+  <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+  <property name="focus_on_map">True</property>
+
+  <child>
+    <widget class="GtkNotebook" id="top_level">
+      <property name="border_width">5</property>
+      <property name="visible">True</property>
+      <property name="can_focus">True</property>
+      <property name="show_tabs">True</property>
+      <property name="show_border">True</property>
+      <property name="tab_pos">GTK_POS_TOP</property>
+      <property name="scrollable">False</property>
+      <property name="enable_popup">False</property>
+
+      <child>
+	<widget class="GtkTable" id="general_properties_table">
+	  <property name="visible">True</property>
+	  <property name="n_rows">7</property>
+	  <property name="n_columns">2</property>
+	  <property name="homogeneous">False</property>
+	  <property name="row_spacing">0</property>
+	  <property name="column_spacing">0</property>
+	</widget>
+	<packing>
+	  <property name="tab_expand">False</property>
+	  <property name="tab_fill">True</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkLabel" id="label1">
+	  <property name="visible">True</property>
+	  <property name="label" translatable="yes">General</property>
+	  <property name="use_underline">False</property>
+	  <property name="use_markup">False</property>
+	  <property name="justify">GTK_JUSTIFY_LEFT</property>
+	  <property name="wrap">False</property>
+	  <property name="selectable">False</property>
+	  <property name="xalign">0.5</property>
+	  <property name="yalign">0.5</property>
+	  <property name="xpad">0</property>
+	  <property name="ypad">0</property>
+	  <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+	  <property name="width_chars">-1</property>
+	  <property name="single_line_mode">False</property>
+	  <property name="angle">0</property>
+	</widget>
+	<packing>
+	  <property name="type">tab</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkVBox" id="vbox1">
+	  <property name="height_request">300</property>
+	  <property name="visible">True</property>
+	  <property name="homogeneous">False</property>
+	  <property name="spacing">0</property>
+
+	  <child>
+	    <widget class="GtkHButtonBox" id="hbuttonbox1">
+	      <property name="border_width">5</property>
+	      <property name="visible">True</property>
+	      <property name="layout_style">GTK_BUTTONBOX_END</property>
+	      <property name="spacing">5</property>
+
+	      <child>
+		<widget class="GtkButton" id="add_module_button">
+		  <property name="visible">True</property>
+		  <property name="can_default">True</property>
+		  <property name="can_focus">True</property>
+		  <property name="relief">GTK_RELIEF_NORMAL</property>
+		  <property name="focus_on_click">True</property>
+
+		  <child>
+		    <widget class="GtkAlignment" id="alignment1">
+		      <property name="visible">True</property>
+		      <property name="xalign">0.5</property>
+		      <property name="yalign">0.5</property>
+		      <property name="xscale">0</property>
+		      <property name="yscale">0</property>
+		      <property name="top_padding">0</property>
+		      <property name="bottom_padding">0</property>
+		      <property name="left_padding">0</property>
+		      <property name="right_padding">0</property>
+
+		      <child>
+			<widget class="GtkHBox" id="hbox1">
+			  <property name="visible">True</property>
+			  <property name="homogeneous">False</property>
+			  <property name="spacing">2</property>
+
+			  <child>
+			    <widget class="GtkImage" id="image1">
+			      <property name="visible">True</property>
+			      <property name="stock">gtk-add</property>
+			      <property name="icon_size">4</property>
+			      <property name="xalign">0.5</property>
+			      <property name="yalign">0.5</property>
+			      <property name="xpad">0</property>
+			      <property name="ypad">0</property>
+			    </widget>
+			    <packing>
+			      <property name="padding">0</property>
+			      <property name="expand">False</property>
+			      <property name="fill">False</property>
+			    </packing>
+			  </child>
+
+			  <child>
+			    <widget class="GtkLabel" id="label3">
+			      <property name="visible">True</property>
+			      <property name="label" translatable="yes">Add _module</property>
+			      <property name="use_underline">True</property>
+			      <property name="use_markup">False</property>
+			      <property name="justify">GTK_JUSTIFY_LEFT</property>
+			      <property name="wrap">False</property>
+			      <property name="selectable">False</property>
+			      <property name="xalign">0.5</property>
+			      <property name="yalign">0.5</property>
+			      <property name="xpad">0</property>
+			      <property name="ypad">0</property>
+			      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+			      <property name="width_chars">-1</property>
+			      <property name="single_line_mode">False</property>
+			      <property name="angle">0</property>
+			    </widget>
+			    <packing>
+			      <property name="padding">0</property>
+			      <property name="expand">False</property>
+			      <property name="fill">False</property>
+			    </packing>
+			  </child>
+			</widget>
+		      </child>
+		    </widget>
+		  </child>
+		</widget>
+	      </child>
+
+	      <child>
+		<widget class="GtkButton" id="add_package_button">
+		  <property name="visible">True</property>
+		  <property name="can_default">True</property>
+		  <property name="can_focus">True</property>
+		  <property name="relief">GTK_RELIEF_NORMAL</property>
+		  <property name="focus_on_click">True</property>
+
+		  <child>
+		    <widget class="GtkAlignment" id="alignment2">
+		      <property name="visible">True</property>
+		      <property name="xalign">0.5</property>
+		      <property name="yalign">0.5</property>
+		      <property name="xscale">0</property>
+		      <property name="yscale">0</property>
+		      <property name="top_padding">0</property>
+		      <property name="bottom_padding">0</property>
+		      <property name="left_padding">0</property>
+		      <property name="right_padding">0</property>
+
+		      <child>
+			<widget class="GtkHBox" id="hbox2">
+			  <property name="visible">True</property>
+			  <property name="homogeneous">False</property>
+			  <property name="spacing">2</property>
+
+			  <child>
+			    <widget class="GtkImage" id="image2">
+			      <property name="visible">True</property>
+			      <property name="stock">gtk-add</property>
+			      <property name="icon_size">4</property>
+			      <property name="xalign">0.5</property>
+			      <property name="yalign">0.5</property>
+			      <property name="xpad">0</property>
+			      <property name="ypad">0</property>
+			    </widget>
+			    <packing>
+			      <property name="padding">0</property>
+			      <property name="expand">False</property>
+			      <property name="fill">False</property>
+			    </packing>
+			  </child>
+
+			  <child>
+			    <widget class="GtkLabel" id="label4">
+			      <property name="visible">True</property>
+			      <property name="label" translatable="yes">Add _Package</property>
+			      <property name="use_underline">True</property>
+			      <property name="use_markup">False</property>
+			      <property name="justify">GTK_JUSTIFY_LEFT</property>
+			      <property name="wrap">False</property>
+			      <property name="selectable">False</property>
+			      <property name="xalign">0.5</property>
+			      <property name="yalign">0.5</property>
+			      <property name="xpad">0</property>
+			      <property name="ypad">0</property>
+			      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+			      <property name="width_chars">-1</property>
+			      <property name="single_line_mode">False</property>
+			      <property name="angle">0</property>
+			    </widget>
+			    <packing>
+			      <property name="padding">0</property>
+			      <property name="expand">False</property>
+			      <property name="fill">False</property>
+			    </packing>
+			  </child>
+			</widget>
+		      </child>
+		    </widget>
+		  </child>
+		</widget>
+	      </child>
+
+	      <child>
+		<widget class="GtkButton" id="remove_button">
+		  <property name="visible">True</property>
+		  <property name="can_default">True</property>
+		  <property name="can_focus">True</property>
+		  <property name="label">gtk-remove</property>
+		  <property name="use_stock">True</property>
+		  <property name="relief">GTK_RELIEF_NORMAL</property>
+		  <property name="focus_on_click">True</property>
+		</widget>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <property name="expand">False</property>
+	      <property name="fill">True</property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkScrolledWindow" id="scrolledwindow1">
+	      <property name="border_width">5</property>
+	      <property name="visible">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+	      <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+	      <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
+	      <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+	      <child>
+		<widget class="GtkTreeView" id="packages_treeview">
+		  <property name="visible">True</property>
+		  <property name="can_focus">True</property>
+		  <property name="headers_visible">True</property>
+		  <property name="rules_hint">False</property>
+		  <property name="reorderable">False</property>
+		  <property name="enable_search">True</property>
+		  <property name="fixed_height_mode">False</property>
+		  <property name="hover_selection">False</property>
+		  <property name="hover_expand">False</property>
+		</widget>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <property name="expand">True</property>
+	      <property name="fill">True</property>
+	    </packing>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="tab_expand">False</property>
+	  <property name="tab_fill">True</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkLabel" id="label2">
+	  <property name="visible">True</property>
+	  <property name="label" translatable="yes">Packages</property>
+	  <property name="use_underline">False</property>
+	  <property name="use_markup">False</property>
+	  <property name="justify">GTK_JUSTIFY_LEFT</property>
+	  <property name="wrap">False</property>
+	  <property name="selectable">False</property>
+	  <property name="xalign">0.5</property>
+	  <property name="yalign">0.5</property>
+	  <property name="xpad">0</property>
+	  <property name="ypad">0</property>
+	  <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+	  <property name="width_chars">-1</property>
+	  <property name="single_line_mode">False</property>
+	  <property name="angle">0</property>
+	</widget>
+	<packing>
+	  <property name="type">tab</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkVBox" id="vbox3">
+	  <property name="visible">True</property>
+	  <property name="homogeneous">False</property>
+	  <property name="spacing">0</property>
+
+	  <child>
+	    <widget class="GtkHButtonBox" id="hbuttonbox2">
+	      <property name="visible">True</property>
+	      <property name="layout_style">GTK_BUTTONBOX_END</property>
+	      <property name="spacing">5</property>
+
+	      <child>
+		<widget class="GtkButton" id="add_variable_button">
+		  <property name="visible">True</property>
+		  <property name="can_default">True</property>
+		  <property name="can_focus">True</property>
+		  <property name="label">gtk-add</property>
+		  <property name="use_stock">True</property>
+		  <property name="relief">GTK_RELIEF_NORMAL</property>
+		  <property name="focus_on_click">True</property>
+		</widget>
+	      </child>
+
+	      <child>
+		<widget class="GtkButton" id="remove_variable_button">
+		  <property name="visible">True</property>
+		  <property name="can_default">True</property>
+		  <property name="can_focus">True</property>
+		  <property name="label">gtk-remove</property>
+		  <property name="use_stock">True</property>
+		  <property name="relief">GTK_RELIEF_NORMAL</property>
+		  <property name="focus_on_click">True</property>
+		</widget>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <property name="expand">False</property>
+	      <property name="fill">True</property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkScrolledWindow" id="scrolledwindow3">
+	      <property name="border_width">5</property>
+	      <property name="visible">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+	      <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+	      <property name="shadow_type">GTK_SHADOW_IN</property>
+	      <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+	      <child>
+		<widget class="GtkTreeView" id="variables_treeview">
+		  <property name="visible">True</property>
+		  <property name="can_focus">True</property>
+		  <property name="headers_visible">True</property>
+		  <property name="rules_hint">False</property>
+		  <property name="reorderable">False</property>
+		  <property name="enable_search">True</property>
+		  <property name="fixed_height_mode">False</property>
+		  <property name="hover_selection">False</property>
+		  <property name="hover_expand">False</property>
+		</widget>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <property name="expand">True</property>
+	      <property name="fill">True</property>
+	    </packing>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="tab_expand">False</property>
+	  <property name="tab_fill">True</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkLabel" id="label6">
+	  <property name="visible">True</property>
+	  <property name="label" translatable="yes">Variables</property>
+	  <property name="use_underline">False</property>
+	  <property name="use_markup">False</property>
+	  <property name="justify">GTK_JUSTIFY_LEFT</property>
+	  <property name="wrap">False</property>
+	  <property name="selectable">False</property>
+	  <property name="xalign">0.5</property>
+	  <property name="yalign">0.5</property>
+	  <property name="xpad">0</property>
+	  <property name="ypad">0</property>
+	  <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+	  <property name="width_chars">-1</property>
+	  <property name="single_line_mode">False</property>
+	  <property name="angle">0</property>
+	</widget>
+	<packing>
+	  <property name="type">tab</property>
+	</packing>
+      </child>
+    </widget>
+  </child>
+</widget>
+
+<widget class="GtkDialog" id="package_selection_dialog">
+  <property name="title" translatable="yes">Select package</property>
+  <property name="type">GTK_WINDOW_TOPLEVEL</property>
+  <property name="window_position">GTK_WIN_POS_NONE</property>
+  <property name="modal">False</property>
+  <property name="default_width">600</property>
+  <property name="default_height">500</property>
+  <property name="resizable">True</property>
+  <property name="destroy_with_parent">False</property>
+  <property name="decorated">True</property>
+  <property name="skip_taskbar_hint">False</property>
+  <property name="skip_pager_hint">False</property>
+  <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+  <property name="focus_on_map">True</property>
+  <property name="has_separator">True</property>
+
+  <child internal-child="vbox">
+    <widget class="GtkVBox" id="dialog-vbox1">
+      <property name="visible">True</property>
+      <property name="homogeneous">False</property>
+      <property name="spacing">0</property>
+
+      <child internal-child="action_area">
+	<widget class="GtkHButtonBox" id="dialog-action_area1">
+	  <property name="visible">True</property>
+	  <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+	  <child>
+	    <widget class="GtkButton" id="cancelbutton1">
+	      <property name="visible">True</property>
+	      <property name="can_default">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label">gtk-cancel</property>
+	      <property name="use_stock">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="focus_on_click">True</property>
+	      <property name="response_id">-6</property>
+	    </widget>
+	  </child>
+
+	  <child>
+	    <widget class="GtkButton" id="okbutton1">
+	      <property name="visible">True</property>
+	      <property name="can_default">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label">gtk-add</property>
+	      <property name="use_stock">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="focus_on_click">True</property>
+	      <property name="response_id">-3</property>
+	    </widget>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">True</property>
+	  <property name="pack_type">GTK_PACK_END</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkVBox" id="vbox2">
+	  <property name="visible">True</property>
+	  <property name="homogeneous">False</property>
+	  <property name="spacing">0</property>
+
+	  <child>
+	    <widget class="GtkFrame" id="frame1">
+	      <property name="visible">True</property>
+	      <property name="label_xalign">0</property>
+	      <property name="label_yalign">0.5</property>
+	      <property name="shadow_type">GTK_SHADOW_NONE</property>
+
+	      <child>
+		<widget class="GtkAlignment" id="alignment3">
+		  <property name="visible">True</property>
+		  <property name="xalign">0.5</property>
+		  <property name="yalign">0.5</property>
+		  <property name="xscale">1</property>
+		  <property name="yscale">1</property>
+		  <property name="top_padding">0</property>
+		  <property name="bottom_padding">0</property>
+		  <property name="left_padding">12</property>
+		  <property name="right_padding">0</property>
+
+		  <child>
+		    <widget class="GtkScrolledWindow" id="scrolledwindow2">
+		      <property name="border_width">5</property>
+		      <property name="visible">True</property>
+		      <property name="can_focus">True</property>
+		      <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+		      <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+		      <property name="shadow_type">GTK_SHADOW_IN</property>
+		      <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+		      <child>
+			<widget class="GtkTreeView" id="pkg_treeview">
+			  <property name="visible">True</property>
+			  <property name="can_focus">True</property>
+			  <property name="headers_visible">True</property>
+			  <property name="rules_hint">False</property>
+			  <property name="reorderable">False</property>
+			  <property name="enable_search">True</property>
+			  <property name="fixed_height_mode">False</property>
+			  <property name="hover_selection">False</property>
+			  <property name="hover_expand">False</property>
+			</widget>
+		      </child>
+		    </widget>
+		  </child>
+		</widget>
+	      </child>
+
+	      <child>
+		<widget class="GtkLabel" id="label5">
+		  <property name="visible">True</property>
+		  <property name="label" translatable="yes">&lt;b&gt;Select Package to add:&lt;/b&gt;</property>
+		  <property name="use_underline">False</property>
+		  <property name="use_markup">True</property>
+		  <property name="justify">GTK_JUSTIFY_LEFT</property>
+		  <property name="wrap">False</property>
+		  <property name="selectable">False</property>
+		  <property name="xalign">0.5</property>
+		  <property name="yalign">0.5</property>
+		  <property name="xpad">0</property>
+		  <property name="ypad">0</property>
+		  <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+		  <property name="width_chars">-1</property>
+		  <property name="single_line_mode">False</property>
+		  <property name="angle">0</property>
+		</widget>
+		<packing>
+		  <property name="type">label_item</property>
+		</packing>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <property name="expand">True</property>
+	      <property name="fill">True</property>
+	    </packing>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">True</property>
+	  <property name="fill">True</property>
+	</packing>
+      </child>
+    </widget>
+  </child>
+</widget>
+
+</glade-interface>

Added: trunk/plugins/gbf-am/gbf-am-parse.in
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/gbf-am-parse.in	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,2612 @@
+#!/usr/bin/perl -w
+# -*- perl -*-
+
+eval 'exec @PERL@ -S $0 ${1+"$@"}'
+    if 0;
+
+BEGIN
+{
+    my $prefix = "@prefix@";
+    my $perlmodulesdir = "@datadir@/@PACKAGE@";
+    unshift @INC, "$perlmodulesdir";
+}
+
+use strict;
+
+use GBF::General;
+use GBF::AmFiles;
+
+# I18n
+use POSIX;
+use Locale::gettext qw(textdomain gettext);
+
+setlocale(LC_MESSAGES, "");
+textdomain("@PACKAGE@");
+
+my $verbose = 0;
+my $dry_run = 0;
+
+# General FIXMEs:
+
+# - implement help for the command line
+
+# - Externalize intltool rule extractor, via a config file or something 
+#   (i.e. don't hardcode)
+
+# - Don't remove backslashes from read macros
+
+# - take makefile names from configure.{in,ac}, since they can also be
+#   named makefile (lowercase) and besides that's the way automake works
+
+# File index:
+# 1. CONSTANTS AND VARIABLES
+# 2. PARSER FUNCTIONS
+# 3. TARGET EXTRACT FUNCTIONS
+# 4. GROUP AND PROJECT CONSTRUCTION
+# 5. PROJECT MODIFICATION
+# 6. TARGET WRITER FUNCTIONS
+# 7. XML PRINTING FUNCTIONS
+# 8. XML SCANNING
+# 9. XML PROCESSING
+# 10. HELPER FUNCTIONS
+# 11. MAIN PROGRAM
+
+##########################################################################################
+#
+# BEGIN DOCUMENTATION
+#
+# Syntax:
+# --------------------------------------------------------------------------------------- 
+#
+#  gbf-am-parse [options] <operation> <argument>
+#
+#  Operations:
+#
+#     --get : argument is the project root
+#             analyzes the automake project and outputs an xml representation
+#
+#     --set : argument is a file (or - for stdin)
+#             reads a xml file which specifies operations to be performed
+#             outputs changed project groups affected by the operations
+#
+#  Options:
+#
+#     -v (--verbose) : output additional information during processing
+#     -n (--dry-run) : when in --set operation mode don't alter any files, just
+#                      parse the input
+#     -d (--debug)   : output debugging information
+#
+#  Output:
+# 
+#  In stdout the script outputs the xml representation of the project
+#  (or the changed groups).  Through stderr the script outputs errors and warnings
+#  using the following format:
+#
+#  DEBUG: message
+#  ERROR(code): message
+#  WARNING(code): message
+#
+#  Error codes:
+#
+#       0   Success
+#    1-99   General errors
+#       1:  Malformed project
+#       2:  Invalid directory
+#       3:  Cannot open output file
+#       4:  Cannot open input file
+#       5:  Syntax error
+# 100-199   configure.in/Makefile.am parser error
+#     100:  Can't open file
+#     101:  Invalid trailing backslash
+# 200-299   Extraction errors
+#     200:  Invalid primary prefix
+# 300-399   Target writer errors
+#     300:  Invalid operation
+#     301:  Invalid operand
+#     302:  Unimplemented target writer
+#     303:  Group not found
+#     304:  Can't write file
+#     305:  Target/group already exists
+#
+#  Warning codes:
+#
+#       0   Success
+#    1-99   General warnings
+# 100-199   configure.in/Makefile.am parser warning
+#     100:  Adding text to not previously declared macro
+#     101:  General error in configure.in (other things will not work)
+#     102:  Makefile.am for a subdirectory not found
+# 200-299   Extraction warning
+#     200:  General semantic error
+# 300-399   Target writer warning
+#     300:  Creating an already existing macro
+#     301:  Out of bounds line number
+#     302:  Target types don't match while creating a new simple primary target
+#
+#
+#
+# Output format (see output.dtd):
+# ---------------------------------------------------------------------------------------
+#
+# Sample output:
+# <?xml version='1.0' encoding='ISO-8859-1' standalone='yes'?>
+# <!DOCTYPE project []>
+# <project root="/home/gustavo/tmp/gdl">
+#
+# the 'root' attribute is a full path to the root of the project
+#
+#  <config>
+#    <param name="source_generators">
+#      <item name="glib_genmarshal" value="GLIB_GENMARSHAL"/>
+#      <item name="glib_mkenums" value="GLIB_MKENUMS"/>
+#      <item name="orbit_idl" value="ORBIT_IDL"/>
+#    </param>
+#  </config>
+#
+# the project itself can contain some 'config' parameters
+# the config is composed of 'param' elements
+# special characters in parameter values are escaped
+# \  (the backslash) -> '\\'
+# \n (newline)       -> '\n'
+# \t (tab character) -> '\t'
+#
+# there are three types of parameters: 
+#
+# 1) simple strings:
+# <param name="parameter" value="string"/>
+#
+# 2) lists:
+# <param name="list">
+#   <item value="item 1"/>
+#   <item value="item 2"/>
+# </param>
+#
+# 3) mappings:
+# <param name="mapping">
+#   <item name="item1" value="value 1"/>
+#   <item name="item2" value="value 2"/>
+# </param>
+#
+# Next in the project comes the root group
+# each group can have:
+# - one config section (defined as above)
+# - some targets
+# - other groups nested in
+#
+#  <group name="/" id="/">
+#
+# here the attribute 'name' is a printable name
+# and 'id' is a unique name to identify the group in the whole project
+# also 'id' is the path from the project root for the group
+# here's a nested group (nested groups appear before targets but after group config
+#
+#    <group name="idl" id="/idl/">
+#      <config>
+#        <param name="installdirs">
+#          <item name="idl" value="$(datadir)/idl/libgdl-1.0"/>
+#        </param>
+#      </config>
+#      <target name="idl" type="data" id="idl:data">
+#
+# each target has a printable 'name' a unique 'id' (within the group) and a 'type'
+# conventionally id is name:type but that is subject to change
+# the targets also can have a config section
+#
+#        <config>
+#          <param name="installdir" value="idl"/>
+#        </config>
+#
+# after the config section, comes the target sources and dependencies
+# the difference between a source and a dependency is that plain sources are
+# files available directly, and dependencies are files which are in turn
+# generated from other targets
+#
+#        <source uri="/idl/GDL.idl"/>
+#        <source uri="/idl/editor-buffer.idl"/>
+#        <source uri="/idl/editor-gutter.idl"/>
+#        <source uri="/idl/symbol-browser.idl"/>
+#      </target>
+#
+# an example of a target with dependencies:
+#
+#      <target name="libgdl-1.la" type="shared_lib" id="libgdl-1.la:shared_lib">
+#        <source uri="/gdl/default-icon.c"/>
+#        .
+#        .
+#        <source uri="/gdl/gdl-recent.c"/>
+#        <dependency file="/gdl/libgdlmarshal.c" target="libgdlmarshal.c:rule"/>
+#        .
+#        .
+#        <dependency file="/gdl/libgdltypebuiltins.c" target="libgdltypebuiltins.c:rule"/>
+#      </target>
+#
+# the 'uri' attribute contains a path absolute to the project root for the file
+# the 'file' is the same as 'uri' but for dependencies, and 'target' is the target
+# 'id' as explained before (the targets belong to the same group)
+#
+#
+# Internal architecture:
+# ---------------------------------------------------------------------------------------
+#
+# All data is kept in anonymous hashes to emulate a C struct
+#
+# Meta structure definitions
+#
+# +--------------
+# | project               : information about the project as a whole
+# +----------------
+# | configure_in          : configure_in hash (defined below)
+# | prefix                : string containing the root directory of the project
+# | config                : project_config hash (defined below)
+# | root_group            : root group hash (defined below)
+# | all_groups            : flat hash containing all the groups for faster access
+# |
+#
+# +-----------------
+# | configure_in          : information regarding the parsing of the configure.in file
+# +--------------------
+# | lines                 : list of text lines in configure.in file
+# | other_files           : hash of configure time generated files (target => source)
+# | vars                  : hash of AC_SUBSTed vars (varname => contents)
+# |
+#
+# +----------------
+# | project_config        : project configuration
+# +-----------------
+# | source_generators     : hash of defined source generators mapped to configure.in vars
+# |                         (source_gen_name => configure_in_variable)
+# | 
+#
+# +----------------
+# | group                 : group (or directory for this backend) information
+# +-----------------
+# | name                  : string, the name of the group
+# | makefile              : makefile hash (see GBF/AmFiles.pm)
+# | targets               : hash of target hashes (defined below) 
+# |                         (target_name => target_hash)
+# | config                : group_config hash for this directory
+# | groups                : hash of subgroups hashes
+# | changed               : the group_process procedure puts this flag to 1
+# | 
+#
+# +----------------
+# | group_config          : group configuration
+# +-----------------
+# | subdirs               : SUBDIRS macro (order is important!!)
+# | includes (*)          : INCLUDES macro
+# | ldadd (*)             : LDADD macro
+# | compile (*)           : COMPILE macro
+# | link (*)              : LINK macro
+# | options (*)           : AUTOMAKE_OPTIONS macro
+# | omit_deps (*)         : OMIT_DEPENDENCIES macro
+# | built_sources         : BUILT_SOURCES macro (expanded)
+# | extra_dist            : EXTRA_DIST macro (expanded)
+# | cleanfiles (*)        : CLEANFILES macro
+# | mostlyclean (*)       : MOSTLYCLEANFILES macro
+# | maintainclean (*)     : MAINTAINCLEANFILES macro
+# | distclean (*)         : DISTCLEANFILES macro
+# | install_exec_local(*) : install-exec-local rule actions
+# | install_data_local(*) : install-data-local rule actions
+# | dist_hook (*)         : dist-hook rule actions
+# | other_vars            : hash of unused remaining Makefile.am macros after target
+# |                         extraction (macro_name => macro_contents)
+# | installdirs           : hash containing user defined installation prefixes
+# |                         (prefix => installation_dir)
+# | 
+#
+# +----------------
+# | target                : target information
+# +-----------------
+# | id                    : id of the target (how it's identified in the group's hash
+# | name                  : string, name of the target
+# | type                  : type of the target
+# | sources               : list of the source files which this target depends on
+# | dependencies          : list of other targets this target depends on
+# | built_files           : list of files presumably this target generates
+# | config                : target_config hash (defined below)
+# | 
+#
+# +----------------
+# | target_config         : extra target configuration (these are all optional and 
+# |                         dependent of the target type)
+# +-----------------
+# | ldflags (*)           : for a primary target, target_LDFLAGS macro contents
+# | ldadd (*)             : for a primary target, target_LDADD macro contents
+# | libadd (*)						: for a primary target, target_LIBADD macro contents
+# | explicit_deps (*)     : for a primary target, target_DEPENDENCIES macro contents
+# | actions (*)           : rule actions if the target is derived from a makefile rule
+# | installdir            : installation prefix (or noinst, check, EXTRA)
+# | 
+#
+# (*)  These fields are optional, they can be empty or undef
+#
+#
+# END DOCUMENTATION  ------------------
+#
+##########################################################################################
+
+
+
+######################################################################
+####  1. CONSTANTS AND VARIABLES  ####################################
+######################################################################
+
+
+# These files are included automatically by automake, so they should be sources
+# of the "extra" target in the root group
+# Makefile.am's and configure.in should be only accessible through the
+# configuration controls of the backend
+
+my @auto_files  = qw ( ChangeLog README INSTALL COPYING AUTHORS NEWS );
+
+my %am_valid_prefixes = ( PROGRAMS    => [ qw ( bin sbin libexec pkglib check) ],
+			  LIBRARIES   => [ qw ( lib pkglib ) ],
+			  LTLIBRARIES => [ qw ( lib pkglib ) ],
+			  SCRIPTS     => [ qw ( bin sbin libexec pkgdata check) ],
+			  HEADERS     => [ qw ( include oldinclude pkginclude ) ],
+			  DATA        => [ qw ( data sysconf sharedstate localstate 
+						pkgdata ) ],
+			  LISP        => [ qw ( lisp ) ], 
+			  TEXINFOS    => [ qw ( info ) ],
+			  MANS        => [ qw ( man ) ] );
+
+my %automake_types = ( PROGRAMS    => "program",
+		       LIBRARIES   => "static_lib",
+		       LTLIBRARIES => "shared_lib",
+		       MANS        => "man",
+		       DATA        => "data",
+		       SCRIPTS     => "script",
+		       TEXINFOS    => "info",
+		       LISP        => "lisp",
+		       HEADERS     => "headers",
+		       JAVA        => "java",
+		       PYTHON      => "python" );
+
+my $PRIMARY_MATCHER = "^(\\w+)_(" . join ('|', keys %automake_types) . ")\\z";
+
+my %target_writers = ( program         => \&compiled_primary_target_writer,
+		       static_lib      => \&compiled_primary_target_writer,
+		       shared_lib      => \&compiled_primary_target_writer,
+		       man             => \&unimplemented_writer,
+		       data            => \&simple_primary_target_writer,
+		       script          => \&simple_primary_target_writer,
+		       info            => \&unimplemented_writer,
+		       lisp            => \&unimplemented_writer,
+		       headers         => \&simple_primary_target_writer,
+		       java            => \&simple_primary_target_writer,
+		       python          => \&simple_primary_target_writer,
+		       generic_rule    => \&unimplemented_writer,
+		       extra           => \&simple_extra_dist_target_writer,
+		       configure_generated_file 
+		                       => \&unimplemented_writer,
+		       orbit_idl       => \&unimplemented_writer,
+		       glib_mkenums    => \&unimplemented_writer,
+		       glib_genmarshal => \&unimplemented_writer,
+		       intltool_rule   => \&unimplemented_writer );
+
+
+# Other special variables
+
+my %intltool_rules = ( INTLTOOL_DESKTOP_RULE   => ".desktop",
+		       INTLTOOL_DIRECTORY_RULE => ".directory",
+		       INTLTOOL_KEYS_RULE      => ".keys",
+		       INTLTOOL_OAF_RULE       => ".oaf",
+		       INTLTOOL_PONG_RULE      => ".pong",
+		       INTLTOOL_SERVER_RULE    => ".server",
+		       INTLTOOL_SHEET_RULE     => ".sheet",
+		       INTLTOOL_SOUNDLIST_RULE => ".soundlist",
+		       INTLTOOL_UI_RULE        => ".ui",
+		       INTLTOOL_XML_RULE       => ".xml",
+		       INTLTOOL_CAVES_RULE     => ".caves" );
+
+
+######################################################################
+####  2. PARSER FUNCTIONS  ###########################################
+######################################################################
+
+
+
+######################################################################
+####  3. TARGET EXTRACT FUNCTIONS  ###################################
+######################################################################
+
+# (target extraction from makefile.am contents)
+
+sub extract_project_config
+{
+    my $project = $_[0];
+
+    $project->{config} = {};
+    use Data::Dumper;
+    ## print Dumper($project->{configure_in});
+
+    # Package, version and URL
+    my ($package_name, $package_version, $package_url);
+    
+    if ($project->{configure_in}{contents} =~ /AC_INIT\(([^\,\)]+)\)/ms)
+    {
+	$package_name = $1;
+	$package_name =~ s/^[\s\[]*//s;
+	$package_name =~ s/[\s\]]*$//s;
+	$project->{config}->{package_name} = $package_name;
+    }
+    elsif ($project->{configure_in}{contents} =~ /AC_INIT\(([^\,\)]+),([^\,\)]+)\)/ms)
+    {
+	$package_name = $1;
+	$package_version = $2;
+	$package_name =~ s/^[\s\[]*//s;
+	$package_name =~ s/[\s\]]*$//s;
+	$package_version =~ s/^[\s\[]*//s;
+	$package_version =~ s/[\s\]]*$//s;
+	$project->{config}->{package_name} = $package_name;
+	$project->{config}->{package_version} = $package_version;
+    }
+    elsif ($project->{configure_in}{contents} =~ /AC_INIT\(([^\,\)]+),([^\,\)]+),([^\,\)]+)\)/ms)
+    {
+	$package_name = $1;
+	$package_version = $2;
+	$package_url = $3;
+	$package_name =~ s/^[\s\[]*//s;
+	$package_name =~ s/[\s\]]*$//s;
+	$package_version =~ s/^[\s\[]*//s;
+	$package_version =~ s/[\s\]]*$//s;
+	$package_url =~ s/^[\s\[]*//s;
+	$package_url =~ s/[\s\]]*$//s;
+	$project->{config}->{package_name} = $package_name;
+	$project->{config}->{package_version} = $package_version;
+	$project->{config}->{package_url} = $package_url;
+    }
+
+    # Variables.
+    my %vars = %{$project->{configure_in}{vars}};
+    my @vars_order = @{$project->{configure_in}{vars_order}};
+    $project->{config}->{variables} = \%vars;
+    $project->{config}->{variables_order} = \ vars_order;
+    
+    # Substitutions.
+    my @substitutions = $project->{configure_in}{contents} =~ /AC_SUBST\(([^\)]+)\)/sg;
+    $project->{config}->{substitutions} = \ substitutions;
+    
+    # Get known source generators from variables
+    my %source_generators;
+    foreach my $var (keys %vars) {
+	$_ = $vars{$var};
+	if (/\$PKG_CONFIG.+--variable=orbit_idl/) {
+	    $source_generators{orbit_idl} = $var;
+	    &debug ("Found orbit-idl compiler in configure.in var $var");
+	}
+	elsif (/\$PKG_CONFIG.+--variable=glib_mkenums/) {
+	    $source_generators{glib_mkenums} = $var;
+	    &debug ("Found glib-mkenums in configure.in var $var");
+	}
+	elsif (/\$PKG_CONFIG.+--variable=glib_genmarshal/) {
+	    $source_generators{glib_genmarshal} = $var;
+	    &debug ("Found glib-genmarshal in configure.in var $var");
+	};
+    };
+    $project->{config}->{source_generators} = \%source_generators;
+    
+    # Extract pkg-config packages
+    my %pkgconfig_modules = $project->{configure_in}{contents}
+	=~ /PKG_CHECK_MODULES\([\s\[]*([^\,\)\]]+)[\s\]]*\,(.+?)\)/sg;
+    foreach my $one_module (keys(%pkgconfig_modules)) {
+	my $line = $pkgconfig_modules{$one_module};
+	my $packages = "";
+	my $yes_code = "";
+	my $no_code = "";
+	if ($line =~ /^[\s\]]*([^\,\]]+)[\s\]]*,([^\,]+),([^\,]+)$/s) {
+	    $packages = $1;
+	    $yes_code = $2;
+	    $no_code = $3;
+	    $packages =~ s/^[\s\[]*//s;
+	    $packages =~  s/[\s\]]*$//s;
+	} elsif ($line =~ /^[\s\]]*([^\,\]]+)[\s\]]*,([^\,]+)$/s) {
+	    $packages = $1;
+	    $yes_code = $2;
+	    $packages =~ s/^[\s\[]*//s;
+	    $packages =~  s/[\s\]]*$//s;
+	} else {
+	    $packages = $line;
+	    $packages =~ s/^[\s\[]*//s;
+	    $packages =~  s/[\s\]]*$//s;
+	}
+	$packages =~ s/\s+/ /sg;
+	$packages =~ s/^\s+//sg;
+	$packages =~ s/s+$//sg;
+	$packages =~ s/^\[//sg;
+	$packages =~ s/\]$//sg;
+	$yes_code =~ s/^\s+//sg;
+	$yes_code =~ s/s+$//sg;
+	$yes_code =~ s/^\[//sg;
+	$yes_code =~ s/\]$//sg;
+	$no_code  =~ s/^\s+//sg;
+	$no_code  =~ s/s+$//sg;
+	$no_code  =~ s/^\[//sg;
+	$no_code  =~ s/\]$//sg;
+	
+	$packages =~ s/ >= /_\$\$_>=_\$\$_/g;
+	$packages =~ s/ <= /_\$\$_<=_\$\$_/g;
+	$packages =~ s/ == /_\$\$_==_\$\$_/g;
+	$packages =~ s/ = /_\$\$_=_\$\$_/g;
+	
+	my @pkgs = split(/\s+/, $packages);
+	my @tmp;
+	foreach my $pkg (@pkgs) {
+	    $pkg =~ s/_\$\$_>=_\$\$_/ >= /g;
+	    $pkg =~ s/_\$\$_<=_\$\$_/ <= /g;
+	    $pkg =~ s/_\$\$_==_\$\$_/ == /g;
+	    $pkg =~ s/_\$\$_=_\$\$_/ = /g;
+	    push (@tmp, $pkg);
+	}
+	@pkgs = @tmp;
+	    
+	my %info;
+	$info{'packages'} = join (", ", @pkgs);
+	$info{'action-if'} = $yes_code;
+	$info{'action-not'} = $no_code;
+	
+	$project->{config}->{"pkg_check_modules_${one_module}"} = \%info;
+	if (!defined($project->{config}->{"pkg_check_modules"})) {
+	    $project->{config}->{"pkg_check_modules"} = "$one_module";
+	} else {
+	    $project->{config}->{"pkg_check_modules"} .= ", $one_module";
+	}
+    }
+}
+
+sub extract_group_config
+{
+    my ($group, $project) = @_;
+
+    my %macros = %{$group->{makefile}{macros}};
+    my %rules = %{$group->{makefile}{rules}};
+    my $config = $group->{config};
+
+    $config->{includes} = &use_macro ($macros{INCLUDES});
+    $config->{ldadd}    = &use_macro ($macros{LDADD});
+    $config->{libadd}    = &use_macro ($macros{LIBADD});
+    $config->{compile}  = &use_macro ($macros{COMPILE});
+    $config->{link}     = &use_macro ($macros{LINK});
+    $config->{amcflags} = &use_macro($macros{AM_CFLAGS});
+    $config->{amcppflags} = &use_macro($macros{AM_CPPFLAGS});
+    $config->{amcxxflags} = &use_macro($macros{AM_CXXFLAGS});
+    $config->{amgcjflags} = &use_macro($macros{AM_GCJFLAGS});
+    $config->{amjavaflags} = &use_macro($macros{AM_JAVAFLAGS});    
+    $config->{amfflags} = &use_macro($macros{AM_FFLAGS});
+    
+    # FIXME: extract inherited from autoconf: CC, CFLAGS, CPPFLAGS, DEFS, LDFLAGS, 
+    # LIBS, CXX, CXXFLAGS, CXXCOMPILE, CXXLINK, JAVAC
+    # FIXME: other automake flags (Fortran, etc.)
+    # FIXME: what is ELCFILES?
+
+    $config->{built_sources} = &empty_if_undef (&use_macro ($macros{BUILT_SOURCES}));
+    $config->{extra_dist}    = &empty_if_undef (&use_macro ($macros{EXTRA_DIST}));
+    $config->{subdirs}       = trim(join(' ', (
+        &empty_if_undef (&use_macro ($macros{SUBDIRS})),
+        &empty_if_undef (&use_macro ($macros{DIST_SUBDIRS})),
+        &empty_if_undef (&use_macro ($macros{SRC_SUBDIRS})),
+        )));
+
+    $config->{options}       = &use_macro ($macros{AUTOMAKE_OPTIONS});
+    $config->{omit_deps}     = &use_macro ($macros{OMIT_DEPENDENCIES});
+    $config->{cleanfiles}    = &use_macro ($macros{CLEANFILES});
+    $config->{mostlyclean}   = &use_macro ($macros{MOSTLYCLEANFILES});
+    $config->{distclean}     = &use_macro ($macros{DISTCLEANFILES});
+    $config->{maintainclean} = &use_macro ($macros{MAINTAINCLEANFILES});
+
+    (undef, $config->{install_data_local}) = &use_rule ($rules{'install-data-local'});
+    (undef, $config->{install_exec_local}) = &use_rule ($rules{'install-exec-local'});
+    (undef, $config->{dist_hook})          = &use_rule ($rules{'dist-hook'});
+
+    $config->{other_vars}  = {};
+    $config->{installdirs} = {};
+
+    my $top_srcdir = "$group->{prefix}";
+    $top_srcdir =~ s/\/([^\/]+)/\/\.\./g;     # replace all directory names by ..
+    $top_srcdir = substr $top_srcdir, 1, -1;  # strip leading slash 
+
+    # Add special macros for later expanding
+    $group->{makefile}{macros}{srcdir} = { contents => ".",
+					   atline   => 0,
+					   used     => 1 };
+    $group->{makefile}{macros}{top_srcdir} = { contents => $top_srcdir,
+					       atline   => 0,
+					       used     => 1 };
+}
+
+sub check_primary_prefix
+{
+    my ($prefix, $primary, $group) = @_;
+
+    if ($prefix eq "EXTRA" || $prefix eq "noinst" || $prefix eq "check") {
+	return $prefix;
+    };
+
+    my %macros = %{$group->{makefile}{macros}};
+
+    # get user supplied prefix (if there is one)
+    my @user_dir = grep (($_ eq $prefix . "dir"), keys %macros);
+
+    if (grep (($_ eq $prefix), @{$am_valid_prefixes{$primary}}) || @user_dir) {
+	if (@user_dir) {
+	    # Save the installation dir in the group configuration
+	    if (!defined ($group->{config}{installdirs}{$prefix})) {
+		$group->{config}{installdirs}{$prefix} = 
+		    &use_macro ($macros{$user_dir[0]});
+	    };
+	};
+	return $prefix;
+    }
+    else {
+	## Some projects have install dirs defined in configure and
+	## therefore they are not found in the Makefile.am and is not
+	## necessarily an error condition.
+	## &report_error (200, "Invalid prefix '$prefix' for primary '$primary'");
+	## return "";
+	
+	# Save the installation dir in the group configuration
+	if (!defined ($group->{config}{installdirs}{$prefix})) {
+	    $group->{config}{installdirs}{$prefix} = "";
+	};
+	return $prefix;
+    };
+}
+
+# Function extract_standard_targets
+
+# Extracts standard automake targets from parsed Makefile.am
+
+sub extract_standard_targets
+{
+    my $group = $_[0];
+    my $makefile = $group->{makefile};
+    my %macros = %{$makefile->{macros}};
+    my %rules = %{$makefile->{rules}};
+    
+    # Search for primary variables
+    MACRO: foreach my $macro_name (keys %macros) {
+	if ($macros{$macro_name}{used}) {
+	    next MACRO;
+	};
+
+	my ($prefix, $primary);
+
+	if ($macro_name =~ $PRIMARY_MATCHER) {
+	    $prefix = $1;
+	    $primary = $2;
+	} else {
+	    # we don't handle any other than primary automake types here
+	    next MACRO;
+	};
+
+	my $install_dir = &check_primary_prefix ($prefix, $primary, $group);
+	my $type = $automake_types{$primary};
+
+	my $tmp_value = &expand_one_var (&use_macro ($macros{$macro_name}), %macros);
+	my @split_macro = split /\s+/, &trim ($tmp_value);
+
+	# Handle programs, scripts and libraries
+	if ($primary eq "PROGRAMS" || $primary eq "LIBRARIES" || 
+	    $primary eq "LTLIBRARIES" || $primary eq "SCRIPTS") {
+	    
+	    foreach my $target (@split_macro) {
+		my %new_target;
+		my $canonical = &canonicalize_name ($target);
+
+		# Fill in the new target info
+		%new_target = ( id           => "$target:$type",
+				name         => $target,
+				type         => $type,
+				sources      => [],
+				dependencies => [],
+				built_files  => [ $target ],
+				config       => { installdir => $install_dir } );
+
+		my $sources = &use_macro ($macros{$canonical . "_SOURCES"});
+		if (defined ($sources)) {
+		    $new_target{sources} = [split /\s+/, $sources];
+		    $sources = &use_macro ($macros{"EXTRA_" . $canonical . "_SOURCES"});
+		    if (defined $sources) {
+			push @{$new_target{sources}}, split /\s+/, $sources;
+		    };
+		} else {
+		    # Default automake source
+		    $new_target{sources} = ["$target.c", ];
+		};
+
+		# LIBADD and LDADD parameters
+		$new_target{config}{ldadd} = 
+		    &empty_if_undef (&use_macro ($macros{$canonical . "_LDADD"}));
+		$new_target{config}{libadd} =	
+			&empty_if_undef (&use_macro ($macros{$canonical . "_LIBADD"}));
+		
+		$new_target{config}{ldflags} = 
+		    &use_macro ($macros{$canonical . "_LDFLAGS"});
+		$new_target{config}{explicit_deps} = 
+		    &use_macro ($macros{$canonical . "_DEPENDENCIES"});
+		$new_target{config}{cflags} = 
+		    &use_macro ($macros{$canonical . "_CFLAGS"});
+		$new_target{config}{cppflags} = 
+		    &use_macro ($macros{$canonical . "_CPPFLAGS"});
+		$new_target{config}{cxxflags} = 
+		    &use_macro ($macros{$canonical . "_CXXFLAGS"});
+		$new_target{config}{gcjflags} = 
+		    &use_macro ($macros{$canonical . "_GCJFLAGS"});
+		$new_target{config}{fflags} = 
+		    &use_macro ($macros{$canonical . "_FFLAGS"});		    
+		# If there is a rule for $(canonical_OBJECTS), then it should be 
+		# treated as defining extra dependencies for the target
+		my ($extra_deps, $extra_actions) = 
+		    &use_rule ($rules{"\$(${canonical}_OBJECTS)"});
+		if (defined ($extra_deps)) {
+		    $new_target{config}{explicit_deps} .= $extra_deps;
+		    if ($extra_actions ne "") {
+			&report_warning (200, gettext("Actions defined for \$(${canonical}_OBJECTS), and this should be left to automake alone"));
+		    };
+		};
+
+		# Add the target to the group
+		$group->{targets}{$new_target{id}} = \%new_target;
+	    };
+	}
+	elsif ($primary eq "SCRIPTS" || $primary eq "DATA" || $primary eq "HEADERS" ||
+	       $primary eq "PYTHON" || $primary eq "JAVA") {
+	    # for SCRIPTS, HEADERS, JAVA, PYTHON and DATA we generate a single
+		# target whose sources are all the files mentioned in the macro
+		# definition.
+		# FIXME: data files are not included in dist by default, 
+	    # whereas others are so, we need to make the distinction here
+	    my %new_target;
+
+	    my $target_name = "$prefix";
+
+	    %new_target = ( id           => "$prefix:$type",
+			    name         => $target_name,
+			    type         => $type,
+			    sources      => \ split_macro,
+			    dependencies => [],
+			    built_files  => [],
+			    config       => { installdir => $install_dir } );
+
+	    $group->{targets}{$new_target{id}} = \%new_target;
+	};
+
+	# FIXME: handle JAVA (they aren't included in dist), LISP
+	# FIXME: handle TEXINFOS (and dependencies, which are texi_TEXINFOS)
+	# FIXME: handle MANS (not included in dist)
+    };
+}
+
+# FIXME: maybe we should split this function in one or more subs, one for 
+# intltool rules and other for source generators
+sub extract_other_targets
+{
+    my ($group, %source_generators) = @_;
+
+    # Look for specific, known, rules
+    my @extra = @{$group->{makefile}{extra}};
+    my %rules = %{$group->{makefile}{rules}};
+    my %macros = %{$group->{makefile}{macros}};
+    
+    # Look for rules containing known source generators
+  RULE:
+    for my $rule (keys %rules) {
+	if ($rules{$rule}{used}) {
+	    next RULE;
+	};
+
+	my %new_target;
+
+	%new_target = ( id           => "$rule:rule",
+			name         => rule_name ($rule),
+			type         => "",
+			sources      => [],
+			dependencies => [],
+			built_files  => [],
+			config       => { installdir => "" } );
+
+	$_ = $rules{$rule}{actions};
+
+	if (/orbit-idl/ || (defined ($source_generators{orbit_idl}) && 
+			    /(\$\(|\@)$source_generators{orbit_idl}(\)|\@)/)) {
+	    &debug ("Found orbit-idl rule $rule");
+	    $new_target{type} = "orbit_idl";
+	    # FIXME: we should actually get the filename given to orbit-idl and
+	    # append -common.c, -skels.c, -stubs.c and .h to it
+	    # For now we assume the rule itself contains the generated files
+	    $new_target{built_files} = [ $rule ];
+	}
+	elsif (/glib-mkenums/ || (defined ($source_generators{glib_mkenums}) && 
+				  /(\$\(|\@)$source_generators{glib_mkenums}(\)|\@)/)) {
+	    &debug ("Found glib-mkenums rule $rule");
+	    $new_target{type} = "glib_mkenums";
+	    $new_target{built_files} = [ $rule ];
+	}
+	elsif (/glib-genmarshal/ || 
+	       (defined ($source_generators{glib_genmarshal}) && 
+		/(\$\(|\@)$source_generators{glib_genmarshal}(\)|\@)/)) {
+	    &debug ("Found glib-genmarshal rule $rule");
+	    $new_target{type} = "glib_genmarshal";
+	    $new_target{built_files} = [ $rule ];
+	}
+	else {
+	    # Next if we didn't match any known generator
+	    next RULE;
+	};
+
+	# If we got here, we matched a generator, and we already have its type set
+	my ($deps, $actions) = &use_rule ($rules{$rule});
+	
+	$new_target{sources} = [ split /\s+/, $deps ];
+	$new_target{config}{actions} = $actions;
+
+	$group->{targets}{$new_target{id}} = \%new_target;
+    };
+
+    # Look for INTLTOOL_*_RULES
+    foreach my $intltool_rule (keys %intltool_rules) {
+	if (grep /^\ $intltool_rule\@$/, @extra) {
+	    &debug ("\ $intltool_rule\@ found");
+	
+	    my %new_target;
+
+	    my $ext = $intltool_rules{$intltool_rule};
+
+	    # FIXME: we make a strong assumption here: all .ext files are already
+	    # generated as sources of other targets, and this may not be true, since
+	    # when we get here, there are still makefile rules unanalyzed
+	    my @built_files = &get_sources_by_extension ($group, $ext);
+	    my @sources = map "$_.in", @built_files;
+
+	    %new_target = ( id           => "$ext:intltool_rule",
+			    name         => "$ext files translation",
+			    type         => "intltool_rule",
+			    sources      => \ sources,
+			    dependencies => [],
+			    built_files  => \ built_files,
+			    config       => { installdir => "" } );
+
+	    $group->{targets}{$new_target{id}} = \%new_target;
+	};
+    };
+}
+
+sub rule_name
+{
+    my $rule = $_[0];
+
+    $rule =~ tr/a-zA-Z _//cd;
+
+    return "$rule";
+}
+
+sub make_targets_from_rules
+{
+    my $group = $_[0];
+    my %rules = %{$group->{makefile}{rules}};
+    my %macros = %{$group->{makefile}{macros}};
+
+  RULE: 
+    foreach my $rule (keys %rules) {
+	my %new_target;
+
+	if ($rules{$rule}{used}) {
+	    next RULE;
+	};
+
+	my ($deps, $actions) = &use_rule ($rules{$rule});
+
+	%new_target = ( id           => "$rule:rule",
+			name         => rule_name ($rule),
+			type         => "generic_rule",
+			sources      => [ split /\s+/, $deps ],
+			dependencies => [],
+			built_files  => [ $rule ],
+			config       => { actions    => $actions,
+					  installdir => "" } );
+
+	$group->{targets}{$new_target{id}} = \%new_target;
+    };
+}
+
+sub extract_remaining_macros
+{
+    my $group = $_[0];
+
+    my %macros = %{$group->{makefile}{macros}};
+    my %other_vars;
+
+    for my $macro (keys %macros) {
+	if (! $macros{$macro}{used}) {
+	    $other_vars{$macro} = &use_macro ($macros{$macro});
+	};
+    };
+
+    $group->{config}{other_vars} = \%other_vars;
+}
+
+sub expand_variables
+{
+    my ($group, $only_targets) = @_;
+
+    my %macros = %{$group->{makefile}{macros}};
+    my %targets = %{$group->{targets}};
+
+    for my $target (keys %targets) {
+	# Expand sources
+	my $sources = join ' ', @{$targets{$target}{sources}};
+	$sources = &expand_one_var ($sources, %macros);
+	# Remove any reference to the Makefile itself
+	$sources =~ s/\s+Makefile\s+//g;
+	$targets{$target}{sources} = [ split '\s+', &trim ($sources) ];
+
+	# Expand built_files
+	my $built_files = join ' ', @{$targets{$target}{built_files}};
+	$built_files = &expand_one_var ($built_files, %macros);
+	$targets{$target}{built_files} = [ split '\s+', &trim ($built_files) ];
+    };
+
+    if (! $only_targets) {
+	# Expand BUILT_SOURCES
+	$group->{config}{built_sources} = 
+	    &trim (&expand_one_var ($group->{config}{built_sources}, %macros));
+	&debug ("BUILT_SOURCES = $group->{config}{built_sources}");
+
+	# Expand EXTRA_DIST
+	$group->{config}{extra_dist} = 
+	    &trim (&expand_one_var ($group->{config}{extra_dist}, %macros));
+	
+	# Expand subdirs
+	$group->{config}{subdirs} =
+	    &trim (&expand_one_var ($group->{config}{subdirs}, %macros));
+    };
+}
+
+sub reduce_sources
+{
+    my $group = $_[0];
+
+    my %targets = %{$group->{targets}};
+
+    # FIXME: also remove all sources which don't exist as files
+    foreach my $target (values %targets) {
+	my @targets_found;
+	my $sources = join ' ', @{$target->{sources}};
+	
+	($sources, @targets_found) = &remove_files_from_built_files ($sources, $group);
+
+	$target->{sources} = [ split /\s+/, $sources ];
+	if (@targets_found) {
+	    # FIXME: there must be a more direct way to do this
+	    my @dependencies = @{$target->{dependencies}};
+	    push @dependencies, @targets_found;
+	    $target->{dependencies} = \ dependencies;
+	};
+    };
+}
+
+
+######################################################################
+####  4. GROUP AND PROJECT CONSTRUCTION  #############################
+######################################################################
+
+sub group_process
+{
+    my $group = $_[0];
+    my $project = $group->{project};
+
+    # unuse all macros and rules
+    &am_file_reset ($group->{makefile});
+
+    # Standard automake targets
+    &extract_group_config ($group, $project);
+    &extract_standard_targets ($group);
+
+    # We need to make a first expansion here so the intltool rules
+    # gets sources correctly
+    &expand_variables ($group, 1);
+
+    # Extract known target types from rules
+    &extract_other_targets ($group, %{$project->{config}->{source_generators}});
+
+    # Get remaining rules and get them as generic rules in the group's targets
+    &make_targets_from_rules ($group);
+
+    # Get remaining macros and put them in the group's config
+    &extract_remaining_macros ($group);
+
+    # Create a target for each file generated at configure time
+    my $relative_path = substr "$group->{prefix}", 1;
+    my %other_files = %{$project->{configure_in}{other_files}};
+    foreach my $file (keys %other_files) {
+	# Strip relative path from generated file and source
+	if ($file =~ /^$relative_path[^\/]+$/) {
+	    my $source = $other_files{$file};
+	    $source =~ s/^$relative_path//;
+	    $file =~ s/^$relative_path//;
+	    my %new_target = ( id           => "$file:configure_generated_file",
+			       name         => $file,
+			       type         => "configure_generated_file",
+			       sources      => [ $source ],
+			       dependencies => [],
+			       built_files  => [ $file ],
+			       config       => { installdir => "" } );
+	    $group->{targets}{$new_target{id}} = \%new_target;
+	};
+    };
+
+    # Expand variables in target sources, built_files, EXTRA_DIST and BUILT_SOURCES
+    &expand_variables ($group, 0);
+    
+    # Create the extra target
+    my @sources = split /\s+/, &remove_files_from_sources 
+	($group->{config}{extra_dist}, $group);
+
+    if ($group->{name} eq "/") {
+	foreach my $auto_file (@auto_files) {
+	    my $duplicate = 0;
+	    foreach my $source (@sources) {
+		if ($source eq $auto_file) {
+		    $duplicate = 1;
+		    last;
+		};
+	    };
+	    if (!$duplicate) {
+		push @sources, $auto_file;
+	    };
+	};
+    };
+
+    if (@sources) {
+	$group->{targets}{"other:extra"} = { id           => "other:extra",
+					     name         => "other files",
+					     type         => "extra",
+					     sources      => \ sources,
+					     dependencies => [],
+					     built_files  => [],
+					     config       => { installdir => "" } };
+    };
+
+    # Remove files from sources when those files are generated by
+    # other targets 
+    &reduce_sources ($group);
+
+    # FIXME: since we are maybe called for reprocess of the group
+    # after a modification, we need to check if the user added/deleted
+    # any subgroups and act accordingly.  Some of that is currently
+    # done in create_group
+
+    $group->{changed} = 1;
+}
+
+
+sub create_group
+{
+    my ($prefix, $name, $project) = @_;
+
+    my $group = { name     => $name,
+		  targets  => {},
+		  config   => { 
+	          installdirs => {} },
+		  groups   => {},
+		  prefix   => $prefix,
+		  project  => $project };
+
+    my $full_prefix = $project->{prefix} . $prefix;
+    $group->{makefile} = &parse_am_file ("${full_prefix}Makefile.am");
+
+    if (! $group->{makefile}) {
+	return undef;
+    };
+
+    &group_process ($group);
+
+    # Recurse
+    my $subdirs = $group->{config}{subdirs};
+    if ($subdirs) {
+	my @subgroups = split /\s+/, $subdirs;
+	foreach my $subgroup (@subgroups) {
+	    if ($subgroup ne ".") {
+		&debug ("${full_prefix}: Recursing into '$subgroup'");
+		my $makefile = "${full_prefix}$subgroup/Makefile.am";
+		if (-f $makefile ) {
+		    my $new_prefix = $prefix ne "" ? "$prefix$subgroup/" : $subgroup;
+		    $group->{groups}{$subgroup} = &create_group ($new_prefix, 
+								 "$subgroup", 
+								 $project);
+		} else {
+		    &report_warning (102, gettext("file $makefile doesn't exist"));
+		}
+	    };
+	};
+    };
+
+    return $group;
+}
+
+###
+# project_update_all_groups (project)
+#   regenerates the flat hash which contains all groups for fast lookup
+sub project_update_all_groups
+{
+    my $project = shift;
+
+    # create flat groups hash
+    my %flat_hash = ();
+    my @helper_list = $project->{root_group};
+    while (@helper_list) {
+	my $current_group = shift @helper_list;
+	push @helper_list, values %{$current_group->{groups}};
+	$flat_hash{$current_group->{prefix}} = $current_group;
+    };
+    $project->{all_groups} = \%flat_hash;
+}
+
+sub process_project
+{
+    my $project_dir = $_[0];
+
+    &debug ("Using $project_dir as the project root");
+    if ( ! -f "$project_dir/configure.in" && ! -f "$project_dir/configure.ac" ) {
+	&report_error (1, gettext("Root directory doesn't look like the root of an " .
+		       "automake package"));
+	return undef;
+    };
+
+    my $project = { prefix       => $project_dir,
+		    config       => {},
+		    configure_in => {},
+		    root_group   => {} };
+    
+    # Parse configure.in
+    $project->{configure_in} = &parse_configure_in ($project_dir);
+    # and extract useful information
+    &extract_project_config ($project);
+
+    # Recursively parse the Makefile.am files
+    $project->{root_group} = &create_group ("/", "/", $project);
+    
+    &project_update_all_groups ($project);
+
+    return $project;
+};
+
+
+######################################################################
+####  5. PROJECT MODIFICATION  #######################################
+######################################################################
+
+###
+# project_reset_changed (project)
+#   reset groups' changed flag
+sub project_reset_changed
+{
+    my $project = shift;
+    foreach my $group (values %{$project->{all_groups}}) {
+	$group->{changed} = 0;
+    }
+}
+
+###
+# project_operate (project, operations)
+#   perform indicated operations on project
+#   returns: true if the project changed
+sub project_operate
+{
+    my ($project, $ops) = @_;
+    my $project_dirty = 0;
+
+  OP:
+    foreach my $op (@$ops) {
+	&debug ("($op->{op}, $op->{group_name}, ".
+		"$op->{target_name}, $op->{operand})");
+	my ($group, $target, $operand, $group_name, $result);
+	
+	if ($op->{op} eq "add_group" || $op->{op} eq "remove_group") {
+	    $operand = $op->{group_name};
+	    $group_name = $operand;
+	    $group_name =~ s/[^\/]+\/\z//;
+	    &debug ("Adding a group $operand on group $group_name");
+	} else {
+	    # Get the group from the project
+	    $group_name = $op->{group_name};
+	}
+	
+	# if no group name is defined, it is probably a project op.
+	if ($group_name eq "") {
+	    if (!defined($operand)) {
+	    	$operand = $op->{operand};
+	    }
+	    $result = &project_op_handler ($project, $op->{op}, $operand);
+	    $project_dirty = 1;
+	    next OP;
+	}
+	
+	# else it is group op.
+	$group = $project->{all_groups}{$group_name};
+	
+	if (!$group) { 
+	    &report_error (303, gettext("The group $group_name doesn't exist"));
+	    next OP; 
+	}
+	&debug ("Using group $group->{name}");
+	
+	# If the operation is on a group, do it now
+	## if ($op->{op} =~ /group/) {
+	if ($op->{target_name} eq "") {
+	    if (!defined($operand)) {
+	    	$operand = $op->{operand};
+	    }
+	    $result = &group_op_handler ($project, $group, $op->{op}, $operand);
+	    $project_dirty = 1;
+	    next OP;
+	}
+
+	# If not, continue with the parameters preparation
+	$operand = $op->{operand};
+	$target = $op->{target_name};
+	my $target_type = $operand;
+	if ($op->{op} ne "add_target" && $op->{op} !~ /group/) {
+	    # Get the target hash from the group
+	    $target = $group->{targets}{$target};
+	    if (! defined ($target)) {
+		&report_error (304, gettext("The target $op->{target_name} doesn't exist"));
+		next OP;
+	    }
+	    $target_type = $target->{type};
+	}
+	
+	# Get the target writer
+	my $writer = $target_writers{$target_type};
+	$result = &$writer ($project, $group, $op->{op}, $target, $operand);
+	if ($result == 0) {
+	    $project_dirty = 1;
+	}
+    }
+
+    if ($project_dirty) {
+	# update flat hash first
+	&project_update_all_groups ($project);
+
+	# write configure.in
+	if ($project->{configure_in}{dirty}) {
+	    output_configure_in $project->{configure_in};
+	    $project->{configure_in} =
+		&parse_configure_in_buffer ($project->{configure_in}{filename},
+		    $project->{configure_in}{contents});
+	    &extract_project_config ($project);
+	    # FIXME: possibly more reprocess configure.in information
+	};
+
+	foreach my $group (values %{$project->{all_groups}}) {
+	    if ($group->{makefile}{dirty}) {
+		my $makefile = $group->{makefile};
+		&debug ("$makefile->{filename} is dirty");
+
+		# output modified makefile.am if not running in test mode
+		&output_am_file ($makefile, $makefile->{filename}) unless ($dry_run);
+
+		# reprocess group
+		&group_process ($group);
+
+		$makefile->{dirty} = 0;
+	    };
+	};
+    };
+
+    return $project_dirty;
+}
+
+
+######################################################################
+####  6. TARGET WRITER FUNCTIONS  ####################################
+######################################################################
+
+sub group_op_handler
+{
+    my ($project, $group, $op, $operand) = @_;
+    my $makefile = $group->{makefile};
+
+    # group is the group hash in which to do the operation
+    # op is: "add_group" or "remove_group"
+    # operand is full group name for add_group, and ignored for remove_group
+
+    if ($op eq "add_group") {
+	my @components = split '/', $operand;
+	my $group_name = "";
+	$group_name = pop @components while ($group_name eq "");
+
+	# check that the group doesn't exist already
+	if (defined $group->{groups}{$group_name}) {
+	    return &report_error (305, gettext("Group $operand already exists"));
+	};
+
+	# create the directory and an empty Makefile.am
+	my $local_new_dir = $group->{prefix} . $group_name;
+	my $new_dir = $project->{prefix} . $local_new_dir;
+	
+	unless (-d $new_dir) {
+	    mkdir $new_dir || return &report_error (304, gettext("Can't mkdir $new_dir"));
+	};
+	unless (-f "$new_dir/Makefile.am") {
+	    if (open (NEWFILE, ">$new_dir/Makefile.am")) {
+		print NEWFILE "## File created by the gnome-build tools\n\n\n";
+		close NEWFILE;
+	    } else {
+		return &report_error (304, gettext("Can't write $new_dir/Makefile.am"));
+	    };
+	};
+
+	# add new directory to the SUBDIRS macro in the parent group
+	&macro_append_text ($makefile, "SUBDIRS", $group_name);
+
+	# create the internal representation of the group
+	$group->{groups}{$group_name} = &create_group ($operand, "$group_name", $project);
+
+	# add Makefile to list of configure generated files
+	$local_new_dir =~ s/^\///;
+	edit_config_files $project->{configure_in}, "add", "$local_new_dir/Makefile";
+    }
+    elsif ($op eq "remove_group") {
+	my @components = split '/', $operand;
+	my $group_name = "";
+
+	$group_name = pop @components while ($group_name eq "");
+	
+	# check that the group doesn't exist already
+	if (!defined $group->{groups}{$group_name}) {
+	    return &report_error (305, gettext("Group $operand does not exists"));
+	};
+	
+	# remove directory from the SUBDIRS macro in the parent group
+	&macro_remove_text ($makefile, "SUBDIRS", $group_name, 1);
+	
+	# add Makefile to list of configure generated files
+	my $local_new_dir = $group->{prefix} . $group_name;
+	$local_new_dir =~ s/^\///;
+	edit_config_files $project->{configure_in}, "remove", "$local_new_dir/Makefile";
+	
+	## Remove group from internal hashes
+	delete $project->{all_groups}->{$operand};
+	delete $group->{groups}->{$group_name};
+    }
+    elsif ($op eq "set_config") {
+      KEY:
+	foreach my $key (keys %$operand) {
+	    my $value = $operand->{$key};
+
+	    if ($key eq "includes") {
+		&macro_rewrite ($makefile, "INCLUDES", $value);
+	    } elsif ($key eq "amcflags") {
+		&macro_rewrite ($makefile, "AM_CFLAGS", $value);
+		} elsif ($key eq "amcppflags") {
+		&macro_rewrite ($makefile, "AM_CPPFLAGS", $value);
+		} elsif ($key eq "amcxxflags") {
+		&macro_rewrite ($makefile, "AM_CXXFLAGS", $value);
+		} elsif ($key eq "amgcjflags") {
+		&macro_rewrite ($makefile, "AM_GCJFLAGS", $value);
+		} elsif ($key eq "amjavaflags") {
+		&macro_rewrite ($makefile, "AM_JAVAFLAGS", $value);
+		} elsif ($key eq "amfflags") {
+		&macro_rewrite ($makefile, "AM_FFLAGS", $value);
+	  } elsif ($key eq "installdirs") {
+	    	foreach my $item (keys %$value) {
+		    if ($value->{$item} =~ /^\s*$/) {
+			&macro_remove ($makefile, $item."dir");
+		    } else {
+			&macro_rewrite ($makefile, $item."dir", $value->{$item});
+		    }
+		}
+	    }    
+	}
+    }
+    else {
+    
+	return &report_error (300, gettext("Invalid operation '$op' to group_op_handler"));
+    }
+    # success!
+    return 0;
+}
+
+sub project_op_handler
+{
+    my ($project, $op, $operand) = @_;
+    my $configure_in = $project->{"configure_in"};
+
+    if ($verbose) {
+        print "Operation = $op\n";
+        print "operand start\n";
+        print Dumper $operand;
+        print "operand end\n";
+    }
+    
+    # op is: "set_config"
+    # operand is full group name for add_group, and ignored for remove_group
+    my $package_name = $project->{config}->{package_name};
+    my $package_version = $project->{config}->{package_version};
+    my $package_url = $project->{config}->{package_url};
+    
+    if ($op eq "set_config") {
+      KEY:
+	foreach my $key (keys %$operand) {
+	    my $value = $operand->{$key};
+
+	    if ($key eq "variables") {
+	    	foreach my $var (keys %$value) {
+		    if ($value->{$var} =~ /^\s*$/) {
+			&configure_remove_variable ($configure_in, $var);
+		    } else {
+			&configure_rewrite_variable ($configure_in, $var,
+						     $value->{$var});
+		    }
+		}
+	    } elsif ($key =~ /^pkg_check_modules_(.*)$/) {
+		my $module = $1;
+	    	foreach my $subkey (keys %$value) {
+		    if ($subkey eq "packages") {
+			my $packages = $value->{$subkey};
+			&configure_rewrite_packages ($configure_in, $module,
+						     $packages);
+		    }
+		}
+	    } elsif ($key eq "package_name") {
+	        $package_name = $value;
+		configure_rewrite_package_info ($configure_in, $package_name,
+						$package_version, $package_url);
+	    } elsif ($key eq "package_version") {
+	        $package_version = $value;
+		configure_rewrite_package_info ($configure_in, $package_name,
+						$package_version, $package_url);
+	    } elsif ($key eq "package_url") {
+	        $package_url = $value;
+		configure_rewrite_package_info ($configure_in, $package_name,
+						$package_version, $package_url);
+	    }
+	}
+    }
+    else {
+    
+	return &report_error (300, gettext("Invalid operation '$op' to group_op_handler"));
+    }
+    # success!
+    return 0;
+}
+
+sub unimplemented_writer
+{
+    my ($project, $group, $op, $target, $operand) = @_;
+
+    # group is the group hash in which to do the operation
+    # op is: "add_target", "remove_target", "set_config", "add_source", "remove_source"
+    # target is:
+    #  - the new target name when add_target
+    #  - a ref to the target hash to operate on otherwise
+    # operand is:
+    #  - ignored for remove_target
+    #  - target type for add_target
+    #  - the source uri to add/remove on {add,remove}_source
+    #  - a config hash on set_config containing any/all fields in group config
+    # FIXME: handle dependencies
+
+    return &report_error (302, gettext("Unimplemented target type writer"));
+}
+
+sub compiled_primary_target_writer
+{
+    my ($project, $group, $op, $target, $operand) = @_;
+    my $makefile = $group->{makefile};
+
+    my %primaries = ( program    => "PROGRAMS",
+		      static_lib => "LIBRARIES",
+		      shared_lib => "LTLIBRARIES" );
+
+    &debug ("compiled primary writer: $op on group $group with $target($operand)");
+ 
+    # FIXME: Modify the $group variable as well as the makefile (worth it?...
+    # only if we have to do a sequence of operations on the same target)
+
+    if ($op eq "add_target") {
+	# $target contains the target name, which in this case is the name of
+	# the compiled objects (program or library)
+	# $operand contains the target type
+	my $canonical = &canonicalize_name ($target);
+	my ($primary, $prefix);
+
+	my %default_prefixes = ( program    => "bin",
+				 static_lib => "lib",
+				 shared_lib => "lib" );
+
+	$primary = $primaries{$operand};
+	$prefix = $default_prefixes{$operand};
+
+	if ($target eq "") {
+	    return &report_error (301, gettext("Invalid empty target name"));
+	}
+
+	if (!defined $primary) {
+	    return &report_error (301, gettext("Invalid target type '$operand' to compiled_primary_target_writer"));
+	}
+
+	# FIXME: verify that the target doesn't yet exist
+	&macro_append_text ($makefile, "${prefix}_${primary}", $target);
+	&macro_create ($makefile, "${canonical}_SOURCES", "", "${prefix}_${primary}");
+	
+    }
+    elsif ($op eq "remove_target") {
+	my $canonical = &canonicalize_name ($target->{name});
+	my $prefix = $target->{config}{installdir};
+	my $primary = $primaries{$target->{type}};
+
+	# FIXME: what if the target is in a macro which is used in prefix_PRIMARY
+	&macro_remove_text ($makefile, "${prefix}_${primary}", $target->{name}, 1);
+	&macro_remove_text ($makefile, "EXTRA_${primary}", $target->{name}, 1);
+	&macro_remove ($makefile, 
+		       "${canonical}_SOURCES", 
+		       "EXTRA_${canonical}_SOURCES",
+		       "${canonical}_LDADD",
+		       "${canonical}_LIBADD",
+		       "${canonical}_LDFLAGS",
+		       "${canonical}_DEPENDENCIES",
+		       "${canonical}_CFLAGS",
+		       "${canonical}_CPPFLAGS",
+		       "${canonical}_CXXFLAGS",
+		       "${canonical}_GCJFLAGS",
+		       "${canonical}_FFLAGS",
+		       );
+	## FIXME: The line below reports error.
+	## &rule_remove ($makefile, "\$(${canonical}_OBJECTS)");
+	
+	## Remove the target from internal hash
+	my $target_id = $target->{id};
+	delete $group->{targets}->{$target_id};
+    }
+    elsif ($op eq "set_config") {
+	my $canonical = &canonicalize_name ($target->{name});
+	my $primary = $primaries{$target->{type}};
+	
+      KEY:
+	foreach my $key (keys %$operand) {
+	    my $value = $operand->{$key};
+
+	    if ($key eq "installdir") {
+		my $old_prefix = $target->{config}{installdir};
+		my $new_prefix = &check_primary_prefix ($value, $primary, $group);
+		if (!$new_prefix) {
+		    next KEY;
+		}
+
+		# FIXME: handle the EXTRA prefix
+		&debug ("Removing $target->{name} from ${old_prefix}_${primary}");
+		&macro_remove_text ($makefile, "${old_prefix}_${primary}", 
+				    $target->{name}, 1);
+		&macro_append_text ($makefile, "${new_prefix}_${primary}",
+				    $target->{name});
+	    } elsif ($key eq "ldflags") {
+	    	if ($value !~ /^\s*$/) {
+		    &macro_rewrite ($makefile, "${canonical}_LDFLAGS",
+				    $value, "${canonical}_SOURCES");
+		} else {
+		    &macro_remove ($makefile, "${canonical}_LDFLAGS");
+		};
+	    } elsif ($key eq "cflags") {
+	    	if ($value !~ /^\s*$/) {
+		    &macro_rewrite ($makefile, "${canonical}_CFLAGS",
+				    $value, "${canonical}_SOURCES");
+		} else {
+		    &macro_remove ($makefile, "${canonical}_CFLAGS");
+		};
+			    } elsif ($key eq "cppflags") {
+	    	if ($value !~ /^\s*$/) {
+		    &macro_rewrite ($makefile, "${canonical}_CPPFLAGS",
+				    $value, "${canonical}_SOURCES");
+		} else {
+		    &macro_remove ($makefile, "${canonical}_CPPFLAGS");
+		};
+			    } elsif ($key eq "cxxflags") {
+	    	if ($value !~ /^\s*$/) {
+		    &macro_rewrite ($makefile, "${canonical}_CXXFLAGS",
+				    $value, "${canonical}_SOURCES");
+		} else {
+		    &macro_remove ($makefile, "${canonical}_CXXFLAGS");
+		};
+			    } elsif ($key eq "gcjflags") {
+	    	if ($value !~ /^\s*$/) {
+		    &macro_rewrite ($makefile, "${canonical}_GCJFLAGS",
+				    $value, "${canonical}_SOURCES");
+		} else {
+		    &macro_remove ($makefile, "${canonical}_GCJFLAGS");
+		};
+			    } elsif ($key eq "fflags") {
+	    	if ($value !~ /^\s*$/) {
+		    &macro_rewrite ($makefile, "${canonical}_FFLAGS",
+				    $value, "${canonical}_SOURCES");
+		} else {
+		    &macro_remove ($makefile, "${canonical}_FFLAGS");
+		};
+	    } elsif ($key eq "ldadd") {
+	    	if ($value !~ /^\s*$/) {
+		    &macro_rewrite ($makefile, "${canonical}_LDADD",
+				    $value, "${canonical}_SOURCES");
+		} else {
+		    &macro_remove ($makefile, "${canonical}_LDADD");
+		};
+	    } elsif ($key eq "libadd") {
+	    	if ($value !~ /^\s*$/) {
+		    &macro_rewrite ($makefile, "${canonical}_LIBADD",
+				    $value, "${canonical}_SOURCES");
+		} else {
+		    &macro_remove ($makefile, "${canonical}_LIBADD");
+		};
+			} elsif ($key eq "explicit_deps") {
+		# FIXME: this should perhaps be handled via sources add/remove
+	    	if ($value !~ /^\s*$/) {
+		    &macro_rewrite ($makefile, "${canonical}_DEPENDENCIES",
+				    $value, "${canonical}_SOURCES");
+		} else {
+		    &macro_remove ($makefile, "${canonical}_DEPENDENCIES");
+		};
+	    }
+	}
+    }
+    elsif ($op eq "add_source") {
+	my $canonical = &canonicalize_name ($target->{name});
+	my $var = "${canonical}_SOURCES";
+	my $rel_source;
+
+	# Note: we expect the operand to be the absolute to the root of the project
+	# i.e. for group /src/, the operand will be /src/file.c
+	$rel_source = path_absolute_to_relative ($group->{prefix}, $operand);
+
+	&debug ("Will modify $var using source $rel_source");
+	my %macros = %{$makefile->{macros}};
+	if (!exists ($macros{$var})) {
+	    # Need to add the default source the the new var
+	    $operand = "$target->{name}.c " . $rel_source;
+	}
+	&macro_append_text ($makefile, $var, $rel_source);
+	
+    }
+    elsif ($op eq "remove_source") {
+	my $canonical = &canonicalize_name ($target->{name});
+	my $var = "${canonical}_SOURCES";
+	my $rel_source;
+
+	# Note: we expect the operand to be the absolute to the root of the project
+	# i.e. for group /src/, the operand will be /src/file.c
+
+	# FIXME: this needs lot of work, since only works for literal filnanmes
+	# we need to identify where is this filename in expanded from a macro or 
+	# if it has some variable like $(srcdir)
+	$rel_source = path_absolute_to_relative ($group->{prefix}, $operand);
+
+	# FIXME: check for duplicate sources
+
+	&debug ("Will modify $var using source $rel_source");
+	if (!exists ($makefile->{macros}{$var}) &&
+	    $rel_source eq "${$target->{name}}.c") {
+	    # Create the empty macro if the removed source is the default one
+	    &macro_create ($makefile, $var, "");
+	} else {
+	    &macro_remove_text ($makefile, $var, $rel_source);
+	}
+
+    }
+    else {
+	return &report_error (300, gettext("Invalid operation '$op' to program_target_writer"));
+    };
+
+    # Success!
+    return 0;
+}
+
+sub hidden_in_another_var {
+	my ($makefile, $macro_name, $file) = @_;
+	
+	my @parts = split (/\s+/, $makefile->{macros}->{$macro_name}->{contents});
+	
+	foreach my $part (@parts) {
+		# FIXME: Are there more characters allowed in macro names than a-zA-Z0-9_?
+		if ($part =~ m/^\$\(([a-zA-Z0-9_]*)\)$/) {
+			my $var = $1;
+			my $content = $makefile->{macros}->{$var}->{contents};
+			my @sub_parts = split (/\s+/, $content);
+			foreach my $sub_part (@sub_parts) {
+				if ($sub_part eq $file) {
+					return $var;
+				}
+			}
+		}
+	}
+	
+	return undef;
+}
+
+sub simple_extra_dist_target_writer
+{
+    my ($project, $group, $op, $target, $operand) = @_;
+    my $makefile = $group->{makefile};
+
+    &debug ("simple extra target writer: $op on group $group with $target($operand)");
+
+	if ($op eq "add_target") {
+		if (exists($makefile->{macros}->{EXTRA_DIST}) and $makefile->{macros}->{EXTRA_DIST}->{content} !~ m/^\s*$/) {
+			return &report_error (305, gettext("An extra target already exists for ") . $group->{prefix})
+		}
+	}
+	elsif ($op eq "remove_target") {
+		&macro_remove ($makefile, "EXTRA_DIST");
+	}
+	elsif ($op eq "add_source") {
+		my $rel_source;
+	
+		# Note: we expect the operand to be the absolute to the root of the project
+		# i.e. for group /src/, the operand will be /src/file.c
+		$rel_source = path_absolute_to_relative ($group->{prefix}, $operand);
+	
+		&debug ("adding $rel_source to EXTRA_DIST");
+		# Note: we expect the uri to be relative to the group
+		&macro_append_text ($makefile, "EXTRA_DIST", $rel_source);
+    }
+    elsif ($op eq "remove_source") {
+		my $rel_source;
+	
+		# Note: we expect the operand to be the absolute to the root of the project
+		# i.e. for group /src/, the operand will be /src/file.c
+		$rel_source = path_absolute_to_relative ($group->{prefix}, $operand);
+	
+		if (defined(my $original_pkg = hidden_in_another_var($makefile, "EXTRA_DIST", $rel_source))) {
+			return &report_error (305, gettext("Could not remove file because it is part of group '$original_pkg'. Please delete it there."));
+		}
+			
+		&debug ("removing $rel_source from EXTRA_DIST");
+		# Note: we expect the uri to be relative to the group
+		&macro_remove_text ($makefile, "EXTRA_DIST", $rel_source);
+    }
+    else {
+		return &report_error (300, gettext("Invalid operation '$op' to program_target_writer"));
+    };
+
+    # Success!
+    return 0;
+}
+
+sub simple_primary_target_writer
+{
+    my ($project, $group, $op, $target, $operand) = @_;
+    my $makefile = $group->{makefile};
+
+    my %primaries = ( data    => "DATA",
+		      script => "SCRIPTS",
+		      headers => "HEADERS",
+		      java    => "JAVA",
+		      python  => "PYTHON");
+
+    &debug ("simple primary writer: $op on group $group with $target($operand)");
+ 
+    # FIXME: Modify the $group variable as well as the makefile (worth it?...
+    # only if we have to do a sequence of operations on the same target)
+
+    if ($op eq "add_target") {
+	# WARNING, Convention: $target for simple primaries is actually $prefix:$type
+	# $operand also contains the target type
+	my ($primary, $prefix, $check_primary);
+	
+	($prefix, $check_primary) = split /:/, $target;
+
+	$primary = $primaries{$operand};
+	if (!defined $primary) {
+	    return &report_error (301, gettext("Invalid target type '$operand' to simple_primary_target_writer"));
+	}
+
+	if ($check_primary ne $operand) {
+	    &report_warning (302, gettext("The target type supplied in the target name $target and the given target type '$operand' don't match.  Will use the one provided in the name"));
+	}
+
+	if (!&check_primary_prefix ($prefix, $primary, $group)) {
+	    return;
+	}
+
+	&macro_create ($makefile, "${prefix}_${primary}", "");
+	
+	## Add installation dir
+	if ($primary eq "DATA") {
+		&macro_create ($makefile, "${prefix}dir", '$(pkgdatadir)', "${prefix}_${primary}");
+	}
+	elsif ($primary eq "HEADERS") {
+		&macro_create ($makefile, "${prefix}dir", '$(pkgincludedir)', "${prefix}_${primary}");
+	}		
+	
+	## For data primary target DATA and HEADERS, also add it to EXTRA_DIST
+	if ($primary eq "DATA" || $primary eq "HEADERS")
+	{
+	    &macro_append_text ($makefile, "EXTRA_DIST",
+				"\$\(${prefix}_${primary}\)");
+	}
+    }
+    elsif ($op eq "remove_target") {
+	my $prefix = $target->{config}{installdir};
+	my $primary = $primaries{$target->{type}};
+
+	&macro_remove ($makefile, "${prefix}_${primary}");
+	
+	## Remove from EXTRA_DIST too.
+	&macro_remove_text ($makefile, "EXTRA_DIST",
+			    "\$\(${prefix}_${primary}\)");
+	
+	&macro_remove ($makefile, "${prefix}dir");
+	
+    }
+    elsif ($op eq "set_config") {
+	# Nothing to do here, since these kind of targets are not relocatable
+    }
+    elsif ($op eq "add_source") {
+	my $prefix = $target->{config}{installdir};
+	my $primary = $primaries{$target->{type}};
+	my $var = "${prefix}_${primary}";
+	my $rel_source;
+
+	# Note: we expect the operand to be the absolute to the root of the project
+	# i.e. for group /src/, the operand will be /src/file.c
+	$rel_source = path_absolute_to_relative ($group->{prefix}, $operand);
+
+	# Note: we expect the uri to be relative to the group
+	&macro_append_text ($makefile, $var, $rel_source);
+    }
+    elsif ($op eq "remove_source") {
+	my $prefix = $target->{config}{installdir};
+	my $primary = $primaries{$target->{type}};
+	my $var = "${prefix}_${primary}";
+	my $rel_source;
+
+	# Note: we expect the operand to be the absolute to the root of the project
+	# i.e. for group /src/, the operand will be /src/file.c
+	$rel_source = path_absolute_to_relative ($group->{prefix}, $operand);
+	
+	&debug ("removing $rel_source from $var macro");
+	# Note: we expect the uri to be relative to the group
+	&macro_remove_text ($makefile, $var, $rel_source);
+    }
+    else {
+	return &report_error (300, gettext("Invalid operation '$op' to program_target_writer"));
+    };
+
+    # Success!
+    return 0;
+}
+
+
+######################################################################
+####  7. XML PRINTING FUNCTIONS  #####################################
+######################################################################
+
+my ($indent_level, $have_vspace);
+
+$indent_level = 0;
+$have_vspace = 0;
+
+sub xml_enter  { $indent_level += 2; }
+sub xml_leave  { $indent_level -= 2; }
+sub xml_indent { print " " x $indent_level; $have_vspace = 0; }
+sub xml_vspace { if (not $have_vspace) { print "\n"; $have_vspace = 1; } }
+sub xml_print { &xml_indent; print @_; }
+
+sub xml_escape
+{
+    $_ = $_[0];
+    s/\&/\&amp;/g;
+    s/\</\&lt;/g;
+    s/\>/\&gt;/g;
+    s/\"/\&quot;/g;
+    s/\'/\&apos;/g;
+    ## s/\\/\\\\/g;
+    ## s/\n/\\n/g;
+    ## s/\t/\\t/g;
+    return $_;
+}
+
+sub xml_unescape
+{
+    $_ = $_[0];
+    s/\&amp;/\&/g;
+    s/\&lt;/\</g;
+    s/\&gt;/\>/g;
+    s/\&quot;/\"/g;
+    s/\&apos;/\'/g;
+    ## s/\\\\/\\/g;
+    ## s/\\n/\n/g;
+    ## s/\\t/\t/g;
+    return $_;
+}
+
+sub xml_print_source
+{
+    my ($source, $prefix) = @_;
+
+    &xml_enter ();
+    &xml_print ("<source uri='" . xml_escape(reduce_path ($prefix . $source)) . "'/>\n");
+    &xml_leave ();
+}
+
+sub xml_print_dep
+{
+    my ($dep, $prefix) = @_;
+
+    my ($target, $file) = split ';', $dep;
+    &xml_enter ();
+    &xml_print ("<dependency file='" . 
+		xml_escape(reduce_path ($prefix . $file)) . 
+		"' target='" . xml_escape($target) . "'/>\n");
+    &xml_leave ();
+}
+
+sub xml_print_target
+{
+    my ($target, $group) = @_;
+    my $sr;
+
+    &xml_enter ();
+    
+    &xml_print ("<target name='" . xml_escape($target->{name} ) . "' type='" . xml_escape($target->{type}) .
+		"' id='" . xml_escape($target->{id}) . "'>\n");
+    # Print the target config
+    &xml_print_config ($target->{config}, ());
+
+    foreach $sr (@{$target->{sources}}) {
+	&xml_print_source ($sr, $group->{prefix});
+    }
+    foreach $sr (@{$target->{dependencies}}) {
+	&xml_print_dep ($sr, $group->{prefix});
+    };
+    &xml_print ("</target>\n");
+
+    &xml_leave ();
+}
+
+###
+# group_print_xml (group [, which])
+#   recursively prints the xml representation of the group
+#   if which is "changed" only prints the group if it's flaged as changed
+sub group_print_xml
+{
+    my $group = shift;
+    my $which = shift || "all";
+    
+    if ($which ne "changed" || $group->{changed}) {
+	&xml_enter ();
+	&xml_print ("<group name='" . xml_escape($group->{name}) . "' id='" . xml_escape($group->{prefix}) . "' ".
+		    "source='" . xml_escape($group->{makefile}{filename}) . "'>\n");
+    }
+
+    # Print each subgroup
+    foreach my $gr (values %{$group->{groups}}) {
+	&group_print_xml ($gr, $which);
+    }
+
+    if ($which ne "changed" || $group->{changed}) {
+	# Print the group config
+	&xml_print_config ($group->{config}, ());
+	
+	# Print the targets
+	foreach my $tr (values %{$group->{targets}}) {
+	    &xml_print_target ($tr, $group);
+	}
+	&xml_print ("</group>\n");
+
+	&xml_leave ();
+    }
+}
+
+sub xml_print_config
+{
+    my ($config, @except) = @_;
+    my $value;
+
+    &xml_enter ();
+    &xml_print ("<config>\n");
+    foreach my $key (keys %{$config}) {
+	if (($key =~ /_order$/) || (grep $_ eq $key, @except)) {
+	    next;
+	};
+
+	&xml_enter ();
+	$value = &empty_if_undef ($config->{$key});
+	if (ref ($value) eq "ARRAY" && @$value) {
+	    # It's a list
+	    &xml_print ("<param name=\"$key\">\n");
+	    &xml_enter ();
+	    foreach (@$value) {
+	    	## Remove white spaces from config parameters.
+	    	$_ =~ s/\s/ /gs;
+		$_ =~ s/\s+/ /gs;
+		$_ = &xml_escape ($_);
+		&xml_print ("<item value=\"$_\"/>\n");
+	    };
+	    &xml_leave ();
+	    &xml_print ("</param>\n");
+	} elsif (ref ($value) eq "HASH" && %$value) {
+	    # It's a hash
+	    my @value_order;
+	    if (defined ($config->{"${key}_order"}) &&
+	    	ref ($config->{"${key}_order"}) eq 'ARRAY') {
+	    	@value_order = @{$config->{"${key}_order"}};
+	    }
+	    if (@value_order <= 0) {
+	    	@value_order = keys %$value;
+	    }
+	    &xml_print ("<param name=\"$key\">\n");
+	    &xml_enter ();
+	    foreach my $item (@value_order) {
+	    	## Remove white spaces from config parameters.
+		my $val = $value->{$item};
+	    	$val =~ s/\s/ /gs;
+	    	$val =~ s/\s+/ /gs;
+		$_ = &xml_escape ($val);
+		&xml_print ("<item name=\"$item\" value=\"$_\"/>\n");
+	    };
+	    &xml_leave ();
+	    &xml_print ("</param>\n");
+	} elsif (! ref ($value) && $value) {
+	    ## Remove white spaces from config parameters.
+	    $value =~ s/\s/ /gs;
+	    $value =~ s/\s+/ /gs;
+	    $value = &xml_escape ($value);
+	    &xml_print ("<param name=\"$key\" value=\"$value\"/>\n");
+	};
+	&xml_leave ();
+    };
+    &xml_print ("</config>\n");
+    &xml_leave ();
+}
+
+###
+# project_print_xml (project [, which])
+#   print xml representation of the project
+#   if which is "changed" only prints changed groups in last modification
+sub project_print_xml
+{
+    my $project = shift;
+    my $which = shift || "all";
+
+    print "<?xml version='1.0' encoding='ISO-8859-1' standalone='yes'?>\n";
+    print "<!DOCTYPE project []>\n\n";
+    print "<project root=\"$project->{prefix}\" report=\"" . 
+	($which eq "changed" ? "partial" : "full") . 
+	"\" source=\"$project->{configure_in}{filename}\">\n";
+    
+    # Print project config.
+    &xml_print_config ($project->{config}, ());
+
+    # Print the groups
+    &group_print_xml ($project->{root_group}, $which);
+    
+    print "</project>\n";
+}
+
+sub recursive_print
+{
+    my ($level, $el) = @_;
+    my $pad = " " x (2 * $level);
+    if (!ref ($el)) {
+	print $pad, $el, "\n";
+    } elsif (ref ($el) eq "ARRAY") {
+	foreach my $sel (@$el) {
+	    &recursive_print ($level + 1, $sel);
+	}
+    } elsif (ref ($el) eq "HASH") {
+	foreach my $sel (keys %$el) {
+	    print $pad, $sel, " => ", $el->{$sel}, "\n";
+	}
+    }
+}
+
+
+######################################################################
+####  8. XML SCANNING  ###############################################
+######################################################################
+
+sub xml_scan_make_kid_array
+{
+    my %hash = ();
+    my (@sublist, @attr);
+
+    @attr = $_[0] =~ /[^\s]+\s*([a-zA-Z_-]+)\s*\=\s*\"([^\"]*)/g;
+    %hash = @attr;
+    
+    push @sublist, \%hash;
+    return \ sublist;
+}
+
+sub xml_scan_recurse
+{
+    my ($tree, $scan_ref) = @_;
+    
+    my @list = @$tree;
+    my ($el, $sublist);
+    
+    while (@$scan_ref) {
+	$el = shift @$scan_ref;
+
+        # Empty strings, PI and DTD must go.
+	if (($el eq "") || $el =~ /^\<[!?].*\>$/s) { next; }
+
+	if ($el =~ /^\<.*\/\>$/s) {
+	    # Empty.
+	    $el =~ /^\<([a-zA-Z_-]+).*\/\>$/s;
+	    push @list, $1;
+	    push @list, &xml_scan_make_kid_array ($el);
+
+	} elsif ($el =~ /^\<\/.*\>$/s) {
+	    # End.
+	    last;
+
+	} elsif ($el =~ /^\<.*\>$/s) {
+	    # Start.
+	    $el =~ /^\<([a-zA-Z_-]+).*\>$/s;
+	    push @list, $1;
+	    $sublist = &xml_scan_make_kid_array ($el);
+	    push @list, &xml_scan_recurse ($sublist, $scan_ref);
+	    next;
+
+	} elsif ($el ne "") {
+	    # PCDATA.
+	    push @list, 0;
+	    push @list, "$el";
+	}
+    }
+    
+    return \ list;
+}
+
+sub xml_scan
+{
+    my $input_file = $_[0];
+    my ($doc, $tree, $i);
+    
+    if (!$input_file || $input_file eq "-") {
+	$doc .= $i while ($i = <STDIN>);
+
+    } else {
+	if (open INPUT_FILE, $input_file) {
+	    $doc .= $i while ($i = <INPUT_FILE>);
+	    close INPUT_FILE;
+	}
+	else {
+	    &report_error (4, gettext("Can't open input file '$input_file'"));
+	    return [];
+	}
+    }
+
+    &debug ("Got input: $doc");
+
+    my @xml_scan_list = ($doc =~ /([^\<]*)(\<[^\>]*\>)[ \t\n\r]*/mg); # pcdata, tag, pcdata, tag, ...
+    
+    $tree = &xml_scan_recurse ([], \ xml_scan_list);
+    
+    return $tree;
+}
+
+
+######################################################################
+####  9. XML PROCESSING  #############################################
+######################################################################
+
+sub xml_parse_params
+{
+    my $tree = $_[0];
+    my %params;
+    
+    shift @$tree;
+    
+    while (@$tree && $$tree[0] eq "param") {
+    	shift @$tree;
+	my $child = $$tree[0];
+	if (defined($$child[0]->{value})) {
+		my $str = $$child[0]->{value};
+        	$params{$$child[0]->{name}} = xml_unescape ($str);
+	} else {
+		my %items;
+		$params{$$child[0]->{name}} = \%items;
+		shift @$child;
+		while (@$child && $$child[0] eq "item") {
+			shift @$child;
+			my $str = $$child[0][0]->{value};
+			$items{$$child[0][0]->{name}} = xml_unescape ($str);
+			shift @$child;
+		}
+	}
+	shift @$tree;
+    }
+    return \%params;
+}
+
+sub xml_parse_add
+{
+    my $tree = $_[0];
+    my $type;
+    my ($parent, $curr);
+
+    my %new_op = ( op          => "",
+		   group_name  => "",
+		   target_name => "",
+		   operand     => "" );
+
+    $type = $$tree[0]->{type};
+    $new_op{op} = "add_$type";
+
+    shift @$tree;
+
+    $parent = $$tree[0];
+    $curr = $$tree[1];
+
+    while (1) {
+	if ($parent eq "group") {
+	    # The group id is unique
+	    $new_op{group_name} = $$curr[0]->{id};
+	} elsif ($parent eq "target") { 
+	    $new_op{target_name} = $$curr[0]->{id};
+	    if ($type eq "target") {
+		$new_op{operand} = $$curr[0]->{type};
+	    }
+	} elsif ($parent eq "source") { 
+	    $new_op{operand} = $$curr[0]->{uri};
+	}
+
+	if ($parent eq $type) {
+	    last;
+	}
+	
+	shift @$curr;
+	$parent = $$curr[0];
+	$curr = $$curr[1];
+    }
+
+    return \%new_op;
+}
+
+sub xml_parse_change
+{
+}
+
+sub xml_parse_set
+{
+    my $tree = $_[0];
+    my $type;
+    my ($parent, $curr);
+
+    my %new_op = ( op          => "",
+		   group_name  => "",
+		   target_name => "",
+		   operand     => "" );
+    $new_op{operand} = {};
+
+    $type = $$tree[0]->{type};
+    $new_op{op} = "set_$type";
+
+    shift @$tree;
+
+    $parent = $$tree[0];
+    $curr = $$tree[1];
+
+    while (1) {
+	if ($parent eq "group") {
+	    $new_op{group_name} = $$curr[0]->{id};
+	} elsif ($parent eq "target") { 
+	    $new_op{target_name} = $$curr[0]->{id};
+	} elsif ($parent eq "config") {
+	    $new_op{operand} = xml_parse_params($curr);
+	}
+
+	if ($parent eq $type) {
+	    last;
+	}
+	
+	shift @$curr;
+	$parent = $$curr[0];
+	$curr = $$curr[1];
+    }
+
+    return \%new_op;
+}
+
+sub xml_parse_remove
+{
+    my $tree = $_[0];
+    my $type;
+    my ($parent, $curr);
+
+    my %new_op = ( op          => "",
+		   group_name  => "",
+		   target_name => "",
+		   operand     => "" );
+
+    $type = $$tree[0]->{type};
+    $new_op{op} = "remove_$type";
+
+    shift @$tree;
+
+    $parent = $$tree[0];
+    $curr = $$tree[1];
+
+    while (1) {
+	if ($parent eq "group") {
+	    # The group name is unique
+	    $new_op{group_name} = $$curr[0]->{id};
+	} elsif ($parent eq "target") { 
+	    $new_op{target_name} = $$curr[0]->{id};
+	} elsif ($parent eq "source") { 
+	    $new_op{operand} = $$curr[0]->{uri};
+	}
+
+	if ($parent eq $type) {
+	    last;
+	}
+	
+	shift @$curr;
+	$parent = $$curr[0];
+	$curr = $$curr[1];
+    }
+    return \%new_op;
+}
+
+sub xml_parse_ops
+{
+    my $tree = $_[0];
+    my @ops;
+
+    shift @$tree;  # Skip attributes.
+
+    while (@$tree) {
+	if ($$tree[0] eq "add") { 
+	    push @ops, &xml_parse_add ($$tree[1]); 
+	}
+	elsif ($$tree[0] eq "change") { 
+	    push @ops, &xml_parse_change ($$tree[1]); 
+	}
+	elsif ($$tree[0] eq "set") { 
+	    push @ops, &xml_parse_set ($$tree[1]); 
+	}
+	elsif ($$tree[0] eq "remove") { 
+	    push @ops, &xml_parse_remove ($$tree[1]); 
+	}
+
+	shift @$tree;
+	shift @$tree;
+    }
+
+    return \ ops;
+}
+
+
+######################################################################
+####  10. HELPER FUNCTIONS  ##########################################
+######################################################################
+
+sub path_relative_to_absolute
+{
+    my ($prefix, $rel) = @_;
+
+    if (substr ($prefix, -1) ne "/") {
+	$prefix .= "/";
+    };
+
+    return reduce_path ($prefix . $rel);
+}
+
+sub path_absolute_to_relative
+{
+    my ($prefix, $absolute) = @_;
+
+    my @prefix_parts = split '/', $prefix;
+    my @absolute_parts = split '/', $absolute;
+    my @result = ();
+    my $append_parent = 0;
+
+    # If prefix is root, assume single empty field so that leading '/'
+    # is stripped from the given abosulte path to form the relative path.
+    @prefix_parts = ("") if ($prefix eq "/");
+    
+    foreach my $part (@prefix_parts) {
+	my $abs_part = shift @absolute_parts;
+
+	if ($part eq $abs_part && !$append_parent) {
+	    next;
+	} else {
+	    $append_parent = 1;
+	    unshift @result, "..";
+	    push @result, $abs_part;
+	};
+	    
+    };
+    push @result, @absolute_parts;
+
+    return join ('/', @result);
+}
+
+sub reduce_path
+{
+    my ($uri) = @_;
+
+    my @result = ();
+
+    foreach my $part (split '/', $uri) {
+	if ($part eq "..") {
+	    # Preserve a leading /../
+	    if (@result > 0 && $result[$#result] ne "") {
+		pop @result;
+	    } else {
+		push @result, $part;
+	    }
+	} elsif ($part eq ".") {
+	    next;
+	} else {
+	    push @result, $part;
+	};
+    };
+
+    return (join '/', @result);
+}
+
+sub remove_files_from_sources
+{
+    my ($var, $group) = @_;
+
+    my %targets = %{$group->{targets}};
+
+    # Strips from $var all those files already present in some target's sources
+    $var = " $var ";
+    foreach my $target (values %targets) {
+	foreach my $source (@{$target->{sources}}) {
+	    $var =~ s/\s\Q$source\s/ /g;
+	};
+    };
+    return &trim ($var);
+}
+
+sub remove_files_from_built_files
+{
+    my ($var, $group) = @_;
+
+    my %targets = %{$group->{targets}};
+
+    # Strips from $var all those files already present in some target's built_file,
+    # returning the target name
+    my @targets_found;
+    $var = " $var ";
+    foreach my $target (keys %targets) {
+	foreach my $built_file (@{$targets{$target}{built_files}}) {
+	    if ($var =~ /\s\Q$built_file\E\s/) {
+		$var =~ s/\s\Q$built_file\E\s/ /g;
+		push @targets_found, "$target;$built_file";
+	    };
+	};
+    };
+    # Now remove those files present in BUILT_SOURCES
+    foreach my $built_file (split /\s+/, $group->{config}{built_sources}) {
+	$var =~ s/\s$built_file\s/ /g;
+    }
+	
+    return (&trim ($var), @targets_found);
+}
+
+sub get_sources_by_extension
+{
+    my ($group, $ext) = @_;
+
+    my @result;
+
+    foreach my $target (values %{$group->{targets}}) {
+	my @sources = @{$target->{sources}};
+	push @result, grep /$ext\z/, @sources;
+    };
+    return @result;
+}
+
+sub make_absolute_path
+{
+    my $path = $_[0];
+
+    if (substr ($path, 0, 1) ne "/") {
+	# the path is not absolute
+	my $cwd = `pwd`;
+	chomp $cwd;
+	$path = "$cwd/$path";
+    };
+
+    return reduce_path ($path);
+}
+
+######################################################################
+####  11. MAIN PROGRAM  ##############################################
+######################################################################
+
+my ($op, $arg, $newop);
+
+$op = "";
+while (@ARGV) {
+    $_ = shift @ARGV;
+    if    ($_ eq "--get" || $_ eq "-g") { $newop = "get"; }
+    elsif ($_ eq "--set" || $_ eq "-s") { $newop = "set"; }
+    elsif ($_ eq "--verbose" || $_ eq "-v") { $verbose = 1; }
+    elsif ($_ eq "--dry-run" || $_ eq "-n") { $dry_run = 1; }
+    elsif ($_ eq "--debug" || $_ eq "-d") { &enable_debug(); }
+
+    elsif ($_ eq "--test-scan") { $newop = "test-scan"; }
+    else {
+	if ($arg) {
+	    &report_error (5, gettext("You can't specify more than one project dir/file"));
+	    exit 5;
+	}
+	$arg = $_;
+    }
+    if ($newop) {
+	if ($op) {
+	    &report_error (5, gettext("You can't specify more than one operation"));
+	    exit 5;
+	}
+	$op = $newop;
+	$newop = "";
+    }
+}
+
+if ($op eq 'get') {
+    #########################################
+    # Get the project XML view
+    #########################################
+
+    my $project_dir = make_absolute_path ($arg);
+
+    if (!$project_dir || ! -d $project_dir || ! -e $project_dir) {
+	&report_error (2, gettext("Project root directory doesn't exist"));
+	exit 2;
+    };
+
+    my $project = &process_project ($project_dir);
+
+    if ($project) {
+	&project_print_xml ($project);
+    };
+
+}
+elsif ($op eq "test-scan") {
+    #########################################
+    # Test XML scanning
+    #########################################
+
+    my $tree = &xml_scan ($arg);
+    &recursive_print (-1, $tree);
+
+}
+elsif ($op eq "set") {
+    ########################################
+    # Project modification
+    ########################################
+
+    my $tree = &xml_scan ($arg);
+
+    # Walk the tree recursively and extract configuration parameters.
+
+    while (@$tree) {
+	if ($$tree[0] eq "project") { 
+	    # Get the project and execute
+	    my $project_dir = make_absolute_path ($$tree [1][0]->{root});
+	    my $project = &process_project ($project_dir);
+	    my $ops = &xml_parse_ops ($$tree[1]);
+	    if ($project && $ops) {
+		# reset groups changed flag so we later know what
+		# groups changed and need to be fed back to the user
+		&project_reset_changed ($project);
+
+		# perform the operations
+		if (&project_operate ($project, $ops)) {
+		    # print changed groups
+		    &project_print_xml ($project, "changed");
+		}
+	    };
+	}
+	shift @$tree;
+	shift @$tree;
+    }
+}
+else {
+    &report_warning (0, gettext("Nothing to do"));
+}

Added: trunk/plugins/gbf-am/gbf-am-plugin-48.png
==============================================================================
Binary file. No diff available.

Added: trunk/plugins/gbf-am/gbf-am-project.c
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/gbf-am-project.c	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,3796 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8; coding: utf-8 -*- */
+/* gbf-am-project.c
+ *
+ * Copyright (C) 2000  JP Rosevear
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: JP Rosevear
+ * Author: Naba Kumar
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <glib/gi18n.h>
+#include <libgnome/gnome-macros.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libgnomevfs/gnome-vfs-monitor.h>
+#include <libgnomevfs/gnome-vfs-ops.h>
+#include <libgnomevfs/gnome-vfs-utils.h>
+#include <libgnomevfs/gnome-vfs-xfer.h>
+#include "gbf-am-project.h"
+#include "gbf-am-config.h"
+#include "gbf-am-properties.h"
+
+/* With debugging enable, the perl script gbf-am-parse outputs
+ * debugging messages not in xml format and the parser
+ * return an error */
+/*#define ENABLE_DEBUG*/
+
+#ifdef ENABLE_DEBUG
+#define DEBUG(x) x
+#else
+#define DEBUG(x)
+#endif
+
+
+#define UNIMPLEMENTED  G_STMT_START { g_warning (G_STRLOC": unimplemented"); } G_STMT_END
+
+/* Constant strings for parsing perl script error output */
+#define ERROR_PREFIX      "ERROR("
+#define WARNING_PREFIX    "WARNING("
+#define MESSAGE_DELIMITER ": "
+
+
+/* ----- Queue data types ----- */
+
+/* FIXME: extend and make most operations asynchronous */
+typedef enum {
+	BUILD
+} GbfAmProjectOpType;
+
+typedef struct {
+	GbfAmProject       *project;
+	GbfAmProjectOpType  type;
+	gchar 		   *build_id;
+} GbfAmProjectOp;
+
+
+/* ----- Change sets data types ----- */
+
+typedef struct _GbfAmChange GbfAmChange;
+
+typedef enum {
+	GBF_AM_CHANGE_ADDED,
+	GBF_AM_CHANGE_REMOVED
+} GbfAmChangeType;
+
+struct _GbfAmChange {
+	GbfAmChangeType  change;
+	GbfAmNodeType    type;
+	gchar           *id;
+};
+
+
+/* ----- XML output parser data types ----- */
+
+typedef struct _GbfAmProjectParseData GbfAmProjectParseData;
+
+struct _GbfAmProjectParseData {
+	GbfAmProject       *project;
+
+	/* For tracking state */
+	GNode              *current_node;
+	gint                depth;          /* group depth only */
+	GbfAmConfigMapping *config;
+	gchar              *param_key;
+	gboolean            full_report;
+
+	/* parser state */
+	enum {
+		PARSE_INITIAL,
+		PARSE_DONE,
+		
+		PARSE_PROJECT,
+		PARSE_GROUP,
+		PARSE_TARGET,
+		PARSE_SOURCE,
+		PARSE_DEPENDENCY,
+		PARSE_CONFIG,
+		PARSE_PARAM,
+		PARSE_ITEM,
+		PARSE_PARAM_DONE,
+		
+		PARSE_ERROR
+	} state, save_state;
+
+	/* list of GbfAmChange with changed project elements */
+	gboolean            compute_change_set;
+	GSList             *change_set;
+
+	/* hash table to keep track of possibly removed nodes */
+	GHashTable         *nodes;
+	
+	/* Errors accumulation */
+	GString            *error;
+};
+
+
+/* ----- Script spawning data types and constants ----- */
+
+#define GBF_AM_PARSE       SCRIPTS_DIR "/gbf-am-parse"
+/* timeout in milliseconds */
+#define SCRIPT_TIMEOUT     30000
+
+/* buffer size tuning parameters */
+#define READ_BUFFER_SIZE   32768
+#define READ_BUFFER_DELTA  16384
+
+typedef struct _GbfAmSpawnData GbfAmSpawnData;
+typedef struct _GbfAmChannel   GbfAmChannel;
+
+struct _GbfAmChannel {
+	GIOChannel *channel;
+	gchar      *buffer;
+	gsize       size;      /* allocated buffer size */
+	gsize       length;    /* real buffer length/index into buffer */
+	guint       tag;       /* main loop source tag for this channel's watch */
+};
+	
+struct _GbfAmSpawnData {
+	GMainLoop   *main_loop;
+	
+	/* child PID to kill it on timeout */
+	gint         child_pid;
+	
+	/* buffers and related channels */
+	GbfAmChannel input;
+	GbfAmChannel output;
+	GbfAmChannel error;
+	gint         open_channels;
+};
+
+
+/* ----- Standard GObject types and variables ----- */
+
+enum {
+	PROP_0,
+	PROP_PROJECT_DIR
+};
+
+static GbfProject *parent_class;
+
+
+/* ----------------------------------------------------------------------
+   Private prototypes
+   ---------------------------------------------------------------------- */
+
+static gboolean        uri_is_equal                 (const gchar       *uri1,
+						     const gchar       *uri2);
+static gboolean        uri_is_parent                (const gchar       *parent_uri,
+						     const gchar       *child_uri);
+static gboolean        uri_is_local_path            (const gchar       *uri);
+static gchar          *uri_to_path                  (const gchar       *uri);
+static gchar          *uri_normalize                (const gchar       *uri,
+						     const gchar       *base_uri);
+
+static gboolean        xml_write_add_source         (GbfAmProject      *project,
+						     xmlDocPtr          doc,
+						     GNode             *g_node,
+						     const gchar       *uri);
+static gboolean        xml_write_remove_source      (GbfAmProject      *project,
+						     xmlDocPtr          doc,
+						     GNode             *g_node);
+static gboolean        xml_write_add_target         (GbfAmProject      *project,
+						     xmlDocPtr          doc,
+						     GNode             *g_node,
+						     const gchar       *name,
+						     const gchar       *type);
+static gboolean        xml_write_add_group          (GbfAmProject      *project,
+						     xmlDocPtr          doc,
+						     GNode             *g_node,
+						     const gchar       *new_group);
+static xmlDocPtr       xml_new_change_doc           (GbfAmProject      *project);
+
+static GbfAmChange    *change_new                   (GbfAmChangeType    ch,
+						     GbfAmNode         *node);
+static void            change_free                  (GbfAmChange       *change);
+static GbfAmChange    *change_set_find              (GSList            *change_set,
+						     GbfAmChangeType    ch,
+						     GbfAmNodeType      type);
+static void            change_set_destroy           (GSList            *change_set);
+
+static void            error_set                    (GError            **error,
+						     gint               code,
+						     const gchar       *message);
+static GError         *parse_errors                 (GbfAmProject      *project,
+						     const gchar       *error_buffer);
+static gboolean        parse_output_xml             (GbfAmProject      *project,
+						     gchar             *xml,
+						     gint               length,
+						     GSList           **change_set,
+						     gchar            **error_message);
+
+static void            spawn_shutdown               (GbfAmSpawnData    *data);
+static void            spawn_data_destroy           (GbfAmSpawnData    *data);
+static gboolean        spawn_write_child            (GIOChannel        *ioc,
+						     GIOCondition       condition,
+						     gpointer           user_data);
+static gboolean        spawn_read_output            (GIOChannel        *ioc,
+						     GIOCondition       condition,
+						     gpointer           user_data);
+static gboolean        spawn_read_error             (GIOChannel        *ioc,
+						     GIOCondition       condition,
+						     gpointer           user_data);
+static gboolean        spawn_kill_child             (GbfAmSpawnData    *data);
+static GbfAmSpawnData *spawn_script                 (gchar             **argv,
+						     gint               timeout,
+						     gchar             *input,
+						     gint               input_size,
+						     GIOFunc            input_cb,
+						     GIOFunc            output_cb,
+						     GIOFunc            error_cb);
+
+static gboolean        project_reload               (GbfAmProject      *project,
+						     GError           **err);
+static gboolean        project_update               (GbfAmProject      *project,
+						     xmlDocPtr          doc,
+						     GSList           **change_set,
+						     GError           **err);
+
+static void            gbf_am_node_free             (GbfAmNode         *node);
+static GNode          *project_node_new             (GbfAmNodeType      type);
+static void            project_node_destroy         (GbfAmProject      *project,
+						     GNode             *g_node);
+static void            project_data_destroy         (GbfAmProject      *project);
+static void            project_data_init            (GbfAmProject      *project);
+
+static void            gbf_am_project_class_init    (GbfAmProjectClass *klass);
+static void            gbf_am_project_instance_init (GbfAmProject      *project);
+static void            gbf_am_project_dispose       (GObject           *object);
+static void            gbf_am_project_get_property  (GObject           *object,
+						     guint              prop_id,
+						     GValue            *value,
+						     GParamSpec        *pspec);
+
+
+/*
+ * Queue operations ------------------------
+ *
+ * - Operations are added to the head and retrieved from the tail
+ */
+
+/*
+ * URI and path manipulation functions -----------------------------
+ */
+
+static gboolean 
+uri_is_equal (const gchar *uri1,
+	      const gchar *uri2)
+{
+	gboolean retval = FALSE;
+	GnomeVFSURI *vfs_uri1, *vfs_uri2;
+
+	vfs_uri1 = gnome_vfs_uri_new (uri1);
+	vfs_uri2 = gnome_vfs_uri_new (uri2);
+	if (vfs_uri1 != NULL && vfs_uri2 != NULL)
+		retval = gnome_vfs_uri_equal (vfs_uri1, vfs_uri2);
+	gnome_vfs_uri_unref (vfs_uri1);
+	gnome_vfs_uri_unref (vfs_uri2);
+
+	return retval;
+}
+
+static gboolean 
+uri_is_parent (const gchar *parent_uri,
+	       const gchar *child_uri)
+{
+	gboolean retval = FALSE;
+	GnomeVFSURI *parent, *child;
+
+	parent = gnome_vfs_uri_new (parent_uri);
+	child = gnome_vfs_uri_new (child_uri);
+	if (parent != NULL && child != NULL)
+		retval = gnome_vfs_uri_is_parent (parent, child, TRUE);
+	gnome_vfs_uri_unref (parent);
+	gnome_vfs_uri_unref (child);
+
+	return retval;
+}
+
+static gboolean
+uri_is_local_path (const gchar *uri)
+{
+	const gchar *p;
+	
+	for (p = uri;
+	     g_ascii_isalnum (*p) || *p == '+' || *p == '-' || *p == '.';
+	     p++)
+		;
+
+	if (*p == ':')
+		return FALSE;
+	else
+		return TRUE;
+}
+
+static gchar *
+uri_to_path (const gchar *uri)
+{
+	return gnome_vfs_get_local_path_from_uri (uri);
+}
+
+static gchar *
+uri_normalize (const gchar *uri, const gchar *base_uri)
+{
+	gchar *normalized_uri = NULL;
+
+	if (uri_is_local_path (uri)) {
+		gchar *absolute_path;
+		
+		absolute_path = gnome_vfs_expand_initial_tilde (uri);
+		if (!g_path_is_absolute (uri)) {
+			gchar *tmp = absolute_path;
+			gchar *base_dir;
+			if (base_uri != NULL)
+				base_dir = uri_to_path (base_uri);
+			else
+				base_dir = g_get_current_dir ();
+
+			absolute_path = g_build_filename (base_dir, tmp, NULL);
+			g_free (base_dir);
+			g_free (tmp);
+		}
+		normalized_uri = gnome_vfs_make_uri_canonical (absolute_path);
+		g_free (absolute_path);
+	} else {
+		GnomeVFSURI *vfs_uri;
+
+		vfs_uri = gnome_vfs_uri_new (uri);
+		/* FIXME: this should probably check that the method is file: */
+		if (gnome_vfs_uri_is_local (vfs_uri))
+			normalized_uri = gnome_vfs_make_uri_canonical (uri);
+		gnome_vfs_uri_unref (vfs_uri);
+	}
+
+	/* strip trailing slash */
+	if (normalized_uri) {
+		gint length = strlen (normalized_uri);
+		gchar *p;
+		if (length > 0) {
+			p = normalized_uri + length - 1;
+			if (*p == '/')
+				*p = '\0';
+		}
+	}
+
+	return normalized_uri;
+}
+
+/**
+ * uri_get_chrooted_path:
+ * @root_uri: the root uri (or NULL)
+ * @uri: the uri which must be inside @root_uri for which the
+ * root-changed path is wanted
+ *
+ * E.g.: uri_get_chrooted_path ("file:///foo/bar", "file:///foo/bar/baz/winkle") -> "/baz/winkle"
+ *
+ * Return value: a newly allocated chrooted path
+ **/
+static gchar *
+uri_get_chrooted_path (const gchar *root_uri, const gchar *uri)
+{
+	gchar *root_path, *path;
+	gint root_length;
+	gchar *retval = NULL;
+	
+	path = uri_to_path (uri);
+	if (root_uri == NULL)
+		return path;
+	
+	root_path = uri_to_path (root_uri);
+	
+	root_length = strlen (root_path);
+	if (strncmp (root_path, path, root_length) == 0) {
+		/* check for trailing path separator in root... we need it to
+		 * make the returned path absolute */
+		if (root_path [root_length - 1] == '/')
+			root_length--;
+		retval = g_strdup (path + root_length);
+	}
+
+	g_free (root_path);
+	g_free (path);
+	
+	return retval;
+}
+
+/*
+ * Project modification functions -----------------------------
+ */
+
+static xmlNodePtr 
+xml_new_source_node (GbfAmProject *project,
+		     xmlDocPtr     doc,
+		     const gchar  *uri)
+{
+	xmlNodePtr source;
+	gchar *filename;
+	
+	source = xmlNewDocNode (doc, NULL, BAD_CAST "source", NULL);
+	filename = uri_get_chrooted_path (project->project_root_uri, uri);
+	xmlSetProp (source, BAD_CAST "uri", BAD_CAST filename);
+	g_free (filename);
+	
+	return source;
+}
+
+static xmlNodePtr 
+xml_write_location_recursive (GbfAmProject *project,
+			      xmlDocPtr     doc,
+			      xmlNodePtr    cur,
+			      GNode        *g_node)
+{
+	xmlNodePtr result, xml_node, last_node;
+	gboolean finished = FALSE;
+
+	result = NULL;
+	last_node = NULL;
+	xml_node = NULL;
+	while (g_node && !finished) {
+		GbfAmNode *node = GBF_AM_NODE (g_node);
+		switch (node->type) {
+			case GBF_AM_NODE_GROUP:
+				xml_node = xmlNewDocNode (doc, NULL, BAD_CAST "group", NULL);
+				xmlSetProp (xml_node, BAD_CAST "id", BAD_CAST node->id);
+				finished = TRUE;
+				break;
+			case GBF_AM_NODE_TARGET:
+				xml_node = xmlNewDocNode (doc, NULL, BAD_CAST "target", NULL);
+				/* strip the group id from the target
+				   id, since the script identifies
+				   targets only within the group */
+				xmlSetProp (xml_node, BAD_CAST "id",
+					    BAD_CAST node->id +
+					    strlen (GBF_AM_NODE (g_node->parent)->id));
+				break;
+			case GBF_AM_NODE_SOURCE:
+				xml_node = xml_new_source_node (project, doc, node->uri);
+				break;
+			default:
+				g_assert_not_reached ();
+				break;
+		}
+
+		/* set returned node */
+		if (result == NULL)
+			result = xml_node;
+
+		/* link the previously created node to the new node */
+		if (last_node != NULL)
+			xmlAddChild (xml_node, last_node);
+
+		last_node = xml_node;
+		g_node = g_node->parent;
+	}
+	/* link the last created node to the current node */
+	xmlAddChild (cur, last_node);
+	
+	return result;
+}
+
+static gboolean 
+xml_write_add_source (GbfAmProject *project,
+		      xmlDocPtr     doc,
+		      GNode        *g_node,
+		      const gchar  *uri)
+{
+	xmlNodePtr cur, source;
+	
+	cur = xmlNewDocNode (doc, NULL, BAD_CAST "add", NULL);
+	xmlSetProp (cur, BAD_CAST "type", BAD_CAST "source");
+	xmlAddChild (doc->children, cur);
+
+	cur = xml_write_location_recursive (project, doc, cur, g_node);
+	source = xml_new_source_node (project, doc, uri);
+	xmlAddChild (cur, source);
+	
+	return (cur != NULL);
+}
+
+static gboolean
+xml_write_remove_source (GbfAmProject *project,
+			 xmlDocPtr     doc,
+			 GNode        *g_node)
+{
+	xmlNodePtr cur;
+
+	cur = xmlNewDocNode (doc, NULL, BAD_CAST "remove", NULL);
+	xmlSetProp (cur, BAD_CAST "type", BAD_CAST "source");
+	xmlAddChild (doc->children, cur);
+	
+	cur = xml_write_location_recursive (project, doc, cur, g_node);
+	
+	return (cur != NULL);
+}
+
+static gboolean 
+xml_write_add_target (GbfAmProject *project,
+		      xmlDocPtr     doc,
+		      GNode        *g_node,
+		      const gchar  *name,
+		      const gchar  *type)
+{
+	xmlNodePtr cur, target;
+	
+	g_assert (GBF_AM_NODE (g_node)->type == GBF_AM_NODE_GROUP);
+
+	cur = xmlNewDocNode (doc, NULL, BAD_CAST "add", NULL);
+	xmlSetProp (cur, BAD_CAST "type", BAD_CAST "target");
+	xmlAddChild (doc->children, cur);
+
+	cur = xml_write_location_recursive (project, doc, cur, g_node);
+	
+	target = xmlNewDocNode (doc, NULL, BAD_CAST "target", NULL);
+	xmlSetProp (target, BAD_CAST "id", BAD_CAST name);
+	xmlSetProp (target, BAD_CAST "type", BAD_CAST type);
+	xmlAddChild (cur, target);
+
+	return TRUE;
+}
+
+typedef struct {
+	GbfAmConfigMapping *old_config;
+	xmlDocPtr doc;
+	xmlNodePtr curr_xml_node;
+} GbfXmlWriteData;
+
+static void
+xml_write_set_item_config_cb (const gchar *param, GbfAmConfigValue *value,
+			      gpointer user_data)
+{
+	xmlNodePtr param_node;
+	GbfXmlWriteData *data = (GbfXmlWriteData*)user_data;
+	
+	if (value->type == GBF_AM_TYPE_STRING) {
+		GbfAmConfigValue *old_value = NULL;
+		const gchar *old_str = "", *new_str = "";
+		
+		new_str = gbf_am_config_value_get_string (value);
+		if (new_str == NULL)
+			new_str = "";
+		if (data->old_config)
+			old_value = gbf_am_config_mapping_lookup (data->old_config, param);
+		if (old_value) {
+			old_str = gbf_am_config_value_get_string (old_value);
+			if (old_str == NULL)
+				old_str = "";
+		}
+		if (strcmp (new_str, old_str) != 0)
+		{
+			param_node = xmlNewDocNode (data->doc, NULL,
+						    BAD_CAST "item", NULL);
+			xmlSetProp (param_node, BAD_CAST "name", BAD_CAST param);
+			xmlSetProp (param_node, BAD_CAST "value", BAD_CAST new_str);
+			xmlAddChild (data->curr_xml_node, param_node);
+		}
+	}
+}
+
+static void
+xml_write_set_param_config_cb (const gchar *param, GbfAmConfigValue *value,
+			       gpointer user_data)
+{
+	xmlNodePtr param_node;
+	GbfXmlWriteData *data = (GbfXmlWriteData*)user_data;
+	
+	if (value->type == GBF_AM_TYPE_STRING) {
+		GbfAmConfigValue *old_value;
+		const gchar *old_str = "", *new_str = "";
+		
+		new_str = gbf_am_config_value_get_string (value);
+		if (new_str == NULL)
+			new_str = "";
+		
+		old_value = gbf_am_config_mapping_lookup (data->old_config, param);
+		if (old_value) {
+			old_str = gbf_am_config_value_get_string (old_value);
+			if (old_str == NULL)
+				old_str = "";
+		}
+		if (strcmp (new_str, old_str) != 0)
+		{
+			param_node = xmlNewDocNode (data->doc, NULL,
+						    BAD_CAST "param", NULL);
+			xmlSetProp (param_node, BAD_CAST "name", BAD_CAST param);
+			xmlSetProp (param_node, BAD_CAST "value", BAD_CAST new_str);
+			xmlAddChild (data->curr_xml_node, param_node);
+		}
+	} else if (value->type == GBF_AM_TYPE_LIST) {
+		param_node = xmlNewDocNode (data->doc, NULL,
+					    BAD_CAST "param", NULL);
+		xmlSetProp (param_node, BAD_CAST "name", BAD_CAST param);
+		/* FIXME */
+	} else if (value->type == GBF_AM_TYPE_MAPPING) {
+		
+		GbfXmlWriteData write_data;
+		GbfAmConfigValue *old_value = NULL;
+		GbfAmConfigMapping *new_mapping = NULL, *old_mapping = NULL;
+		
+		new_mapping = gbf_am_config_value_get_mapping (value);
+		old_value = gbf_am_config_mapping_lookup (data->old_config, param);
+		if (old_value)
+			old_mapping = gbf_am_config_value_get_mapping (old_value);
+		
+		param_node = xmlNewDocNode (data->doc, NULL, BAD_CAST "param", NULL);
+		
+		xmlSetProp (param_node, BAD_CAST "name", BAD_CAST param);
+		
+		write_data.doc = data->doc;
+		write_data.curr_xml_node = param_node;
+		write_data.old_config = old_mapping;
+		
+		gbf_am_config_mapping_foreach (new_mapping,
+					       xml_write_set_item_config_cb,
+					       &write_data);
+		if (param_node->children)
+			xmlAddChild (data->curr_xml_node, param_node);
+		else
+			xmlFreeNode (param_node);
+	} else {
+		g_warning ("Should not be here");
+	}
+}
+
+static gboolean 
+xml_write_set_config (GbfAmProject       *project,
+		      xmlDocPtr           doc,
+		      GNode              *g_node,
+		      GbfAmConfigMapping *new_config)
+{
+	xmlNodePtr cur, config;
+	GbfXmlWriteData user_data;
+	
+	cur = xmlNewDocNode (doc, NULL, BAD_CAST "set", NULL);
+	xmlSetProp (cur, BAD_CAST "type", BAD_CAST "config");
+	xmlAddChild (doc->children, cur);
+
+	if (g_node)
+		cur = xml_write_location_recursive (project, doc, cur, g_node);
+	
+	config = xmlNewDocNode (doc, NULL, BAD_CAST "config", NULL);
+	xmlAddChild (cur, config);
+	
+	user_data.doc = doc;
+	user_data.curr_xml_node = config;
+	user_data.old_config = g_node? GBF_AM_NODE (g_node)->config : project->project_config;
+	
+	gbf_am_config_mapping_foreach (new_config,
+				       xml_write_set_param_config_cb,
+				       &user_data);
+	if (config->children)
+		return TRUE;
+	else
+		return FALSE;
+}
+
+static gboolean
+xml_write_remove_target (GbfAmProject *project,
+			 xmlDocPtr     doc,
+			 GNode        *g_node)
+{
+	xmlNodePtr cur;
+
+	cur = xmlNewDocNode (doc, NULL, BAD_CAST "remove", NULL);
+	xmlSetProp (cur, BAD_CAST "type", BAD_CAST "target");
+	xmlAddChild (doc->children, cur);
+	
+	cur = xml_write_location_recursive (project, doc, cur, g_node);
+	
+	return (cur != NULL);
+}
+
+static gboolean 
+xml_write_add_group (GbfAmProject *project,
+		     xmlDocPtr     doc,
+		     GNode        *g_node,
+		     const gchar  *new_group)
+{
+	xmlNodePtr cur, group;
+	gchar *new_id;
+	
+	g_assert (GBF_AM_NODE (g_node)->type == GBF_AM_NODE_GROUP);
+
+	cur = xmlNewDocNode (doc, NULL, BAD_CAST "add", NULL);
+	xmlSetProp (cur, BAD_CAST "type", BAD_CAST "group");
+	xmlAddChild (doc->children, cur);
+
+	/* calculate new id needed by the script */
+	new_id = g_strdup_printf ("%s%s/", GBF_AM_NODE (g_node)->id, new_group);
+	
+	group = xmlNewDocNode (doc, NULL, BAD_CAST "group", NULL);
+	xmlSetProp (group, BAD_CAST "id", BAD_CAST new_id);
+	xmlAddChild (cur, group);
+	g_free (new_id);
+	
+	return TRUE;
+}
+
+static gboolean
+xml_write_remove_group (GbfAmProject *project,
+			 xmlDocPtr     doc,
+			 GNode        *g_node)
+{
+	xmlNodePtr cur;
+
+	cur = xmlNewDocNode (doc, NULL, BAD_CAST "remove", NULL);
+	xmlSetProp (cur, BAD_CAST "type", BAD_CAST "group");
+	xmlAddChild (doc->children, cur);
+	
+	cur = xml_write_location_recursive (project, doc, cur, g_node);
+	
+	return (cur != NULL);
+}
+
+static xmlDocPtr
+xml_new_change_doc (GbfAmProject *project)
+{
+	xmlDocPtr doc;
+	
+	doc = xmlNewDoc (BAD_CAST "1.0");
+	if (doc != NULL) {
+		gchar *root_path;
+		root_path = uri_to_path (project->project_root_uri);
+		doc->children = xmlNewDocNode (doc, NULL, BAD_CAST "project", NULL);
+		xmlSetProp (doc->children, BAD_CAST "root", BAD_CAST root_path);
+		g_free (root_path);
+	}
+
+	return doc;
+}
+
+
+/*
+ * File monitoring support --------------------------------
+ * FIXME: review these
+ */
+
+static void
+monitor_cb (GnomeVFSMonitorHandle   *handle,
+	    const gchar             *monitor_uri,
+	    const gchar             *info_uri,
+	    GnomeVFSMonitorEventType event_type,
+	    gpointer                 data)
+{
+	GbfAmProject *project = data;
+
+	g_return_if_fail (project != NULL && GBF_IS_AM_PROJECT (project));
+
+	switch (event_type) {
+		case GNOME_VFS_MONITOR_EVENT_CHANGED:
+		case GNOME_VFS_MONITOR_EVENT_DELETED:
+			/* monitor will be removed here... is this safe? */
+			DEBUG (g_message ("File changed"));
+			project_reload (project, NULL);
+			g_signal_emit_by_name (G_OBJECT (project), "project-updated");
+			break;
+		default:
+			break;
+	}
+}
+
+static void
+monitor_add (GbfAmProject *project, const gchar *uri)
+{
+	GnomeVFSMonitorHandle *handle = NULL;
+	GnomeVFSResult result;
+
+	g_return_if_fail (project != NULL);
+	g_return_if_fail (project->monitors != NULL);
+
+	if (!uri)
+		return;
+	
+	handle = g_hash_table_lookup (project->monitors, uri);
+	if (!handle) {
+		GnomeVFSURI *vfs_uri;
+		gboolean exists;
+		
+		vfs_uri = gnome_vfs_uri_new (uri);
+		exists = gnome_vfs_uri_exists (vfs_uri);
+		gnome_vfs_uri_unref (vfs_uri);
+
+		if (exists) {
+			result = gnome_vfs_monitor_add (&handle,
+							uri,
+							GNOME_VFS_MONITOR_FILE,
+							monitor_cb,
+							project);
+			if (result == GNOME_VFS_OK) {
+				g_hash_table_insert (project->monitors,
+						     g_strdup (uri),
+						     handle);
+			}
+		}
+	}
+}
+
+static void
+monitors_remove (GbfAmProject *project)
+{
+	g_return_if_fail (project != NULL);
+
+	if (project->monitors)
+		g_hash_table_destroy (project->monitors);
+	project->monitors = NULL;
+}
+
+static void
+group_hash_foreach_monitor (gpointer key,
+			    gpointer value,
+			    gpointer user_data)
+{
+	GNode *group_node = value;
+	GbfAmProject *project = user_data;
+
+	monitor_add (project, GBF_AM_NODE (group_node)->uri);
+}
+
+static void
+monitors_setup (GbfAmProject *project)
+{
+	g_return_if_fail (project != NULL);
+
+	monitors_remove (project);
+	
+	/* setup monitors hash */
+	project->monitors = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+						   (GDestroyNotify) gnome_vfs_monitor_cancel);
+
+	monitor_add (project, project->project_file);
+	g_hash_table_foreach (project->groups, group_hash_foreach_monitor, project);
+}
+
+
+/*
+ * Change sets functions --------------------------------
+ */
+
+static GbfAmChange *
+change_new (GbfAmChangeType ch, GbfAmNode *node)
+{
+	GbfAmChange *change;
+
+	change = g_new0 (GbfAmChange, 1);
+	change->change = ch;
+	change->type = node->type;
+	change->id = g_strdup (node->id);
+
+	return change;
+}
+
+static void
+change_free (GbfAmChange *change)
+{
+	if (change) {
+		g_free (change->id);
+		g_free (change);
+	}
+}
+
+static GbfAmChange *
+change_set_find (GSList *change_set, GbfAmChangeType ch, GbfAmNodeType type)
+{
+	GSList *iter = change_set;
+
+	while (iter) {
+		GbfAmChange *change = iter->data;
+		if (change->change == ch && change->type == type)
+			return change;
+		iter = g_slist_next (iter);
+	}
+
+	return NULL;
+}
+
+static void
+change_set_destroy (GSList *change_set)
+{
+	GSList *l;
+	for (l = change_set; l; l = g_slist_next (l))
+		change_free (l->data);
+	g_slist_free (change_set);
+}
+
+#ifdef ENABLE_DEBUG
+static void
+change_set_debug_print (GSList *change_set)
+{
+	GSList *iter = change_set;
+
+	g_print ("Change set:\n");
+	while (iter) {
+		GbfAmChange *change = iter->data;
+
+		switch (change->change) {
+			case GBF_AM_CHANGE_ADDED:
+				g_print ("added   ");
+				break;
+			case GBF_AM_CHANGE_REMOVED:
+				g_print ("removed ");
+				break;
+			default:
+				g_assert_not_reached ();
+				break;
+		}
+		switch (change->type) {
+			case GBF_AM_NODE_GROUP:
+				g_print ("group  ");
+				break;
+			case GBF_AM_NODE_TARGET:
+				g_print ("target ");
+				break;
+			case GBF_AM_NODE_SOURCE:
+				g_print ("source ");
+				break;
+			default:
+				g_assert_not_reached ();
+				break;
+		}
+		g_print ("%s\n", change->id);
+
+		iter = g_slist_next (iter);
+	}
+}
+#endif
+
+/*
+ * Perl script output parser ------------------------------
+ */
+
+#define PARSER_ASSERT(x)  G_STMT_START {						\
+	if (!(x)) {									\
+		DEBUG (g_warning ("Parser assertion at " G_STRLOC " failed: " #x));	\
+		data->state = PARSE_ERROR; return;					\
+	}										\
+											\
+	} G_STMT_END 
+ 
+static void
+sax_start_element (void *ctxt, const xmlChar *name, const xmlChar **attrs)
+{
+	GbfAmProjectParseData *data;
+	GbfAmProject *project;
+	GNode *g_node;
+	GbfAmNode *node;
+	
+	data = ctxt;
+	project = data->project;
+	
+	PARSER_ASSERT (data->state != PARSE_ERROR && data->state != PARSE_DONE);
+	
+	if (xmlStrEqual (name, BAD_CAST "project")) {
+		const xmlChar *project_file = NULL;
+
+		/* project node */
+		PARSER_ASSERT (data->state == PARSE_INITIAL);
+		data->state = PARSE_PROJECT;
+
+		/* process attributes: lookup project file and check
+		 * if we're getting the whole project or just changed
+		 * groups */
+		while (attrs && *attrs != NULL) {
+			const xmlChar **val = attrs;
+			
+			val++;
+			if (xmlStrEqual (*attrs, BAD_CAST "root")) {
+				/* FIXME: check that the root is the
+				 * same as the project's root */
+			}
+			else if (xmlStrEqual (*attrs, BAD_CAST "report"))
+				data->full_report = (xmlStrEqual (*val, BAD_CAST "full") != 0);
+			else if (xmlStrEqual (*attrs, BAD_CAST "source"))
+				project_file = *val;
+			
+			attrs = ++val;
+		}
+
+		/* clear project if we are getting a full report */
+		if (data->full_report) {
+			/* FIXME: will this be correct in all cases? */
+			data->compute_change_set = FALSE;
+			project_data_init (project);
+		}
+		
+		/* assign here, since we destroy the data in project_data_init() */
+		if (project_file) {
+			g_free (project->project_file);
+			project->project_file = g_strdup ((char *) project_file);
+		}
+		
+	} else if (xmlStrEqual (name, BAD_CAST "group")) {
+		const xmlChar *group_name = NULL;
+		const xmlChar *group_id = NULL;
+		const xmlChar *group_source = NULL;
+
+		/* group node */
+		PARSER_ASSERT (data->state == PARSE_PROJECT ||
+			       data->state == PARSE_GROUP);
+		data->state = PARSE_GROUP;
+
+		/* process attributes: lookup name, id and source file
+		 * (i.e. Makefile.am) */
+		while (attrs && *attrs != NULL) {
+			const xmlChar **val = attrs;
+			
+			val++;
+			if (xmlStrEqual (*attrs, BAD_CAST "name"))
+				group_name = *val;
+			else if (xmlStrEqual (*attrs, BAD_CAST "id"))
+				group_id = *val;
+			else if (xmlStrEqual (*attrs, BAD_CAST "source"))
+				group_source = *val;
+			
+			attrs = ++val;
+		}
+		PARSER_ASSERT (group_name != NULL && group_id != NULL);
+
+		/* lookup the node first, as it could already exist */
+		g_node = g_hash_table_lookup (project->groups, group_id);
+		if (g_node == NULL) {
+			g_node = project_node_new (GBF_AM_NODE_GROUP);
+			node = GBF_AM_NODE (g_node);
+			
+			node->id = g_strdup ((char *) group_id);
+			
+			/* set parent group */
+			if (data->current_node != NULL) {
+				g_node_prepend (data->current_node, g_node);
+			} else {
+				/* node is project root */
+				g_assert (project->root_node == NULL);
+				project->root_node = g_node;
+			}
+
+			if (data->compute_change_set)
+				data->change_set = g_slist_prepend (
+					data->change_set,
+					change_new (GBF_AM_CHANGE_ADDED, node));
+			
+			/* save the group in the hash for quick access */
+			g_hash_table_insert (project->groups, g_strdup (node->id), g_node);
+
+		} else {
+			GNode *child;
+			
+			node = GBF_AM_NODE (g_node);
+
+			/* node found so remove it from the tracking hash */
+			g_hash_table_remove (data->nodes, g_node);
+			
+			/* re-set data */
+			g_free (node->name);
+			g_free (node->uri);
+
+			/* recreate the configuration */
+			gbf_am_config_mapping_destroy (node->config);
+			
+			/* track all child nodes */
+			child = g_node_first_child (g_node);
+			while (child != NULL) {
+				/* only track groups if getting a full
+				 * report, otherwise non-changed
+				 * groups are later removed from the
+				 * project because the script doesn't
+				 * report them */
+				if (data->full_report ||
+				    GBF_AM_NODE (child)->type != GBF_AM_NODE_GROUP)
+					g_hash_table_insert (data->nodes, child, child);
+				child = g_node_next_sibling (child);
+			}
+		}
+		node->name = g_strdup ((char *) group_name);
+		node->uri = g_strdup ((char *) group_source);
+		node->config = gbf_am_config_mapping_new ();
+		
+		/* set working node */
+		data->depth++;
+		data->current_node = g_node;
+
+	} else if (xmlStrEqual (name, BAD_CAST "target")) {
+		const xmlChar *id = NULL;
+		const xmlChar *target_name = NULL;
+		const xmlChar *target_type = NULL;
+		gchar *group_id;
+		
+		/* target node */
+		PARSER_ASSERT (data->state == PARSE_GROUP);
+		data->state = PARSE_TARGET;
+
+		/* process attributes: lookup name, type and id */
+		while (attrs && *attrs != NULL) {
+			const xmlChar **val = attrs;
+			
+			val++;
+			if (xmlStrEqual (*attrs, BAD_CAST "name"))
+				target_name = *val;
+			else if (xmlStrEqual (*attrs, BAD_CAST "type"))
+				target_type = *val;
+			else if (xmlStrEqual (*attrs, BAD_CAST "id"))
+				id = *val;
+
+			attrs = ++val;
+		}
+		PARSER_ASSERT (target_name != NULL && target_type != NULL);
+		
+		/* compute the id using the given by the script if possible */
+		group_id = g_strdup_printf ("%s%s",
+					    GBF_AM_NODE (data->current_node)->id,
+					    id != NULL ? id : target_name);
+		
+		/* lookup the node first, as it could already exist */
+		g_node = g_hash_table_lookup (project->targets, group_id);
+		if (g_node == NULL) {
+			g_node = project_node_new (GBF_AM_NODE_TARGET);
+			node = GBF_AM_NODE (g_node);
+			
+			node->id = group_id;
+
+			/* set target's parent */
+			g_node_prepend (data->current_node, g_node);
+			
+			if (data->compute_change_set)
+				data->change_set = g_slist_prepend (
+					data->change_set,
+					change_new (GBF_AM_CHANGE_ADDED, node));
+			
+			/* save the target in the hash for quick access */
+			g_hash_table_insert (project->targets, g_strdup (node->id), g_node);
+
+		} else {
+			GNode *child;
+			
+			node = GBF_AM_NODE (g_node);
+			g_free (group_id);
+
+			/* node found so remove it from the tracking hash */
+			g_hash_table_remove (data->nodes, g_node);
+			
+			/* re-set data */
+			g_free (node->name);
+			g_free (node->detail);
+
+			/* recreate the configuration */
+			gbf_am_config_mapping_destroy (node->config);
+			
+			/* track all child nodes */
+			child = g_node_first_child (g_node);
+			while (child != NULL) {
+				g_hash_table_insert (data->nodes, child, child);
+				child = g_node_next_sibling (child);
+			}
+		}
+		node->name = g_strdup ((char *) target_name);
+		node->detail = g_strdup ((char *) target_type);
+		node->config = gbf_am_config_mapping_new ();
+
+		/* set working node */
+		data->current_node = g_node;
+
+	} else if (xmlStrEqual (name, BAD_CAST "source")) {
+		const xmlChar *uri = NULL;
+		gchar *source_uri, *source_id;
+		
+		/* source node */
+		PARSER_ASSERT (data->state == PARSE_TARGET);
+		data->state = PARSE_SOURCE;
+
+		/* process attributes: lookup uri */
+		while (attrs && *attrs != NULL) {
+			const xmlChar **val = attrs;
+			
+			val++;
+			if (xmlStrEqual (*attrs, BAD_CAST "uri"))
+				uri = *val;
+
+			attrs = ++val;
+		}
+		PARSER_ASSERT (uri != NULL);
+		
+		source_uri = g_build_filename (project->project_root_uri, uri, NULL);
+		source_id = g_strdup_printf ("%s:%s",
+					     GBF_AM_NODE (data->current_node)->id,
+					     source_uri);
+
+		/* lookup the node first, as it could already exist */
+		g_node = g_hash_table_lookup (project->sources, source_id);
+		if (g_node == NULL) {
+			g_node = project_node_new (GBF_AM_NODE_SOURCE);
+			node = GBF_AM_NODE (g_node);
+			
+			node->id = source_id;
+			
+			/* set source's parent */
+			g_node_prepend (data->current_node, g_node);
+			
+			if (data->compute_change_set)
+				data->change_set = g_slist_prepend (
+					data->change_set,
+					change_new (GBF_AM_CHANGE_ADDED, node));
+			
+			/* save the source in the hash for quick access */
+			g_hash_table_insert (project->sources, g_strdup (node->id), g_node);
+
+		} else {
+			/* node found so remove it from the tracking hash */
+			g_hash_table_remove (data->nodes, g_node);
+			
+			node = GBF_AM_NODE (g_node);
+			g_free (source_id);
+			
+			/* re-set data */
+			g_free (node->uri);
+		}
+		node->uri = source_uri;
+			
+		/* set working node */
+		data->current_node = g_node;
+		
+	} else if (xmlStrEqual (name, BAD_CAST "dependency")) {
+		const xmlChar *uri = NULL;
+		const xmlChar *target_dep = NULL;
+		gchar *source_uri, *source_id;
+		
+		/* dependency node */
+		PARSER_ASSERT (data->state == PARSE_TARGET);
+		data->state = PARSE_DEPENDENCY;
+
+		/* process attributes: lookup file and dependency target */
+		while (attrs && *attrs != NULL) {
+			const xmlChar **val = attrs;
+			
+			val++;
+			if (xmlStrEqual (*attrs, BAD_CAST "file"))
+				uri = *val;
+			else if (xmlStrEqual (*attrs, BAD_CAST "target"))
+				target_dep = *val;
+
+			attrs = ++val;
+		}
+		PARSER_ASSERT (uri != NULL && target_dep != NULL);
+		
+		/* Compute the id */
+		source_uri = g_build_filename (project->project_root_uri, uri, NULL);
+		source_id = g_strdup_printf ("%s:%s",
+					     GBF_AM_NODE (data->current_node)->id,
+					     source_uri);
+
+		/* lookup the node first, as it could already exist */
+		g_node = g_hash_table_lookup (project->sources, source_id);
+		if (g_node == NULL) {
+			g_node = project_node_new (GBF_AM_NODE_SOURCE);
+			node = GBF_AM_NODE (g_node);
+			
+			node->id = source_id;
+			
+			/* set source's parent */
+			g_node_prepend (data->current_node, g_node);
+			
+			if (data->compute_change_set)
+				data->change_set = g_slist_prepend (
+					data->change_set,
+					change_new (GBF_AM_CHANGE_ADDED, node));
+			
+			/* save the source in the hash for quick access */
+			g_hash_table_insert (project->sources, g_strdup (node->id), g_node);
+
+		} else {
+			/* node found so remove it from the tracking hash */
+			g_hash_table_remove (data->nodes, g_node);
+			
+			g_free (source_id);
+			node = GBF_AM_NODE (g_node);
+			
+			/* re-set data */
+			g_free (node->uri);
+			g_free (node->detail);
+		}
+		node->uri = source_uri;
+		node->detail = g_strdup ((char *) target_dep);
+
+		/* set working node */
+		data->current_node = g_node;
+		
+	} else if (xmlStrEqual (name, BAD_CAST "config")) {
+		/* config node */
+		PARSER_ASSERT (data->state == PARSE_PROJECT ||
+			       data->state == PARSE_GROUP ||
+			       data->state == PARSE_TARGET);
+
+		switch (data->state) {
+		    case PARSE_PROJECT:
+			    data->config = project->project_config;
+			    break;
+		    case PARSE_GROUP:
+		    case PARSE_TARGET:
+			    g_assert (data->current_node != NULL);
+			    data->config = GBF_AM_NODE (data->current_node)->config;
+			    break;
+		    default:
+			    g_assert_not_reached ();
+			    break;
+		}
+
+		data->save_state = data->state;
+		data->state = PARSE_CONFIG;
+
+	} else if (xmlStrEqual (name, BAD_CAST "param")) {
+		const xmlChar *param_name = NULL;
+		const xmlChar *param_value = NULL;
+		
+		/* config param node */
+		PARSER_ASSERT (data->state == PARSE_CONFIG);
+
+		/* process attributes: lookup parameter name and value (if scalar) */
+		while (attrs && *attrs != NULL) {
+			const xmlChar **val = attrs;
+			
+			val++;
+			if (xmlStrEqual (*attrs, BAD_CAST "name"))
+				param_name = *val;
+			else if (xmlStrEqual (*attrs, BAD_CAST "value"))
+				param_value = *val;
+			
+			attrs = ++val;
+		}
+		PARSER_ASSERT (param_name != NULL);
+
+		if (param_value != NULL) {
+			GbfAmConfigValue *param;
+
+			/* save scalar string parameter */
+			param = gbf_am_config_value_new (GBF_AM_TYPE_STRING);
+			gbf_am_config_value_set_string (param, (char *) param_value);
+
+			/* try to save the parameter in the config mapping */
+			if (!gbf_am_config_mapping_update (data->config,
+							   (char *) param_name, param)) {
+				gbf_am_config_value_free (param);
+			}
+
+			/* we are finished with the parameter */
+			data->state = PARSE_PARAM_DONE;
+
+		} else {
+			/* we expect a list or a hash of values */
+			data->param_key = g_strdup ((char *) param_name);
+			data->state = PARSE_PARAM;
+		}
+		
+	} else if (xmlStrEqual (name, BAD_CAST "item")) {
+		GbfAmConfigValue *param, *item;
+		const xmlChar *item_name = NULL;
+		const xmlChar *item_value = NULL;
+
+		/* parameter item node */
+		PARSER_ASSERT (data->state == PARSE_PARAM);
+		g_assert (data->param_key != NULL);
+		data->state = PARSE_ITEM;
+		
+		/* process attributes: lookup parameter name and value (if scalar) */
+		while (attrs && *attrs != NULL) {
+			const xmlChar **val = attrs;
+			
+			val++;
+			if (xmlStrEqual (*attrs, BAD_CAST "name"))
+				item_name = *val;
+			else if (xmlStrEqual (*attrs, BAD_CAST "value"))
+				item_value = *val;
+			
+			attrs = ++val;
+		}
+		PARSER_ASSERT (item_value != NULL);
+		
+		/* create item value */
+		item = gbf_am_config_value_new (GBF_AM_TYPE_STRING);
+		gbf_am_config_value_set_string (item, (char *) item_value);
+		
+		/* get current configuration parameter */
+		param = gbf_am_config_mapping_lookup (data->config, data->param_key);
+		
+		/* if this is the first item, we must decide whether
+		   it's a list or a mapping.  if it's not the first
+		   one, we must check that the items are of the same
+		   type */
+		if (param == NULL) {
+			if (item_name != NULL) {
+				param = gbf_am_config_value_new (GBF_AM_TYPE_MAPPING);
+			} else {
+				param = gbf_am_config_value_new (GBF_AM_TYPE_LIST);
+			}
+			gbf_am_config_mapping_insert (data->config,
+						      data->param_key,
+						      param);
+		}
+		
+		switch (param->type) {
+		    case GBF_AM_TYPE_LIST:
+			    param->list = g_slist_prepend (param->list, item);
+			    break;
+			    
+		    case GBF_AM_TYPE_MAPPING:
+			    if (item_name == NULL ||
+				!gbf_am_config_mapping_update (param->mapping,
+							       (char *) item_name,
+							       item)) {
+				    gbf_am_config_value_free (item);
+			    }
+			    break;
+			    
+		    default:
+			    g_assert_not_reached ();
+			    break;
+		}
+	}
+}
+
+static void
+sax_end_element (void *ctx, const xmlChar *name)
+{
+	GbfAmProjectParseData *data = ctx;
+
+	PARSER_ASSERT (data->state != PARSE_ERROR && data->state != PARSE_DONE);
+	
+	if (xmlStrEqual (name, BAD_CAST "project")) {
+		PARSER_ASSERT (data->state == PARSE_PROJECT);
+		g_assert (data->current_node == NULL);
+		data->state = PARSE_DONE;
+		
+	} else if (xmlStrEqual (name, BAD_CAST "group")) {
+		PARSER_ASSERT (data->state == PARSE_GROUP);
+		g_assert (data->current_node != NULL);
+		data->depth--;
+		if (data->depth == 0) {
+			data->current_node = NULL;
+			data->state = PARSE_PROJECT;
+		} else {
+			data->current_node = data->current_node->parent;
+		}
+		
+		/* FIXME: resolve all target inter-dependencies here */
+
+	} else if (xmlStrEqual (name, BAD_CAST "target")) {
+		PARSER_ASSERT (data->state == PARSE_TARGET);
+		g_assert (data->current_node != NULL);
+		data->current_node = data->current_node->parent;
+		data->state = PARSE_GROUP;
+
+	} else if (xmlStrEqual (name, BAD_CAST "source")) {
+		PARSER_ASSERT (data->state == PARSE_SOURCE);
+		g_assert (data->current_node != NULL);
+		data->current_node = data->current_node->parent;
+		data->state = PARSE_TARGET;
+
+	} else if (xmlStrEqual (name, BAD_CAST "dependency")) {
+		PARSER_ASSERT (data->state == PARSE_DEPENDENCY);
+		g_assert (data->current_node != NULL);
+		data->current_node = data->current_node->parent;
+		data->state = PARSE_TARGET;
+
+	} else if (xmlStrEqual (name, BAD_CAST "config")) {
+		PARSER_ASSERT (data->state == PARSE_CONFIG);
+		data->state = data->save_state;
+		data->save_state = PARSE_INITIAL;
+		data->config = NULL;
+		
+	} else if (xmlStrEqual (name, BAD_CAST "param")) {
+		PARSER_ASSERT (data->state == PARSE_PARAM ||
+			       data->state == PARSE_PARAM_DONE);
+		data->state = PARSE_CONFIG;
+		g_free (data->param_key);
+		data->param_key = NULL;
+		
+	} else if (xmlStrEqual (name, BAD_CAST "item")) {
+		PARSER_ASSERT (data->state == PARSE_ITEM);
+		data->state = PARSE_PARAM;
+
+	}
+}
+
+static void
+sax_error (void *ctx, const char *format, ...)
+{
+	va_list args;
+	char buffer[256];
+	
+	va_start (args, format);
+	vsnprintf (buffer, 256, format, args);
+	va_end (args);
+	
+	GbfAmProjectParseData *data = ctx;
+	g_string_append (data->error, buffer);
+}
+
+#undef PARSER_ASSERT
+
+static void 
+hash_foreach_add_removed (GNode        *g_node,
+			  gpointer      value,
+			  GSList      **change_set)
+{
+	*change_set = g_slist_prepend (*change_set,
+				       change_new (GBF_AM_CHANGE_REMOVED,
+						   GBF_AM_NODE (g_node)));
+}
+
+static void 
+hash_foreach_destroy_node (GNode        *g_node,
+			   gpointer      value,
+			   GbfAmProject *project)
+{
+	project_node_destroy (project, g_node);
+}
+
+static gboolean 
+parse_output_xml (GbfAmProject *project,
+		  gchar        *xml_text,
+		  gint          length,
+		  GSList      **change_set,
+		  gchar **      error_message)
+{
+	xmlSAXHandler handler;
+	GbfAmProjectParseData data;
+	int retval;
+	
+	memset (&handler, 0, sizeof (xmlSAXHandler));
+	handler.startElement = sax_start_element;
+	handler.endElement = sax_end_element;
+	handler.error = sax_error;
+	handler.initialized = 0;
+	
+	data.state = PARSE_INITIAL;
+	data.save_state = PARSE_INITIAL;
+	data.project = project;
+	data.current_node = NULL;
+	data.depth = 0;
+	data.config = NULL;
+	data.param_key = NULL;
+	data.full_report = TRUE;
+
+	data.change_set = NULL;
+	data.nodes = g_hash_table_new (g_direct_hash, g_direct_equal);
+	data.compute_change_set = (change_set != NULL);
+	data.error = g_string_new (NULL);
+	
+	xmlSubstituteEntitiesDefault (TRUE);
+
+	/* Parse document */
+	retval = xmlSAXUserParseMemory (&handler, &data, xml_text, length);
+
+	if (retval != 0)
+	{
+		if (error_message) {
+			if (data.error->len > 0)
+				*error_message = g_strdup (data.error->str);
+		}
+	}
+	g_string_free (data.error, TRUE);
+	
+	if (data.state != PARSE_DONE)
+	{
+		retval = -1;
+	}
+
+	/* construct the change set */
+	if (retval >= 0 && data.compute_change_set) {
+		g_hash_table_foreach (data.nodes,
+				      (GHFunc) hash_foreach_add_removed,
+				      &data.change_set);
+		*change_set = data.change_set;
+		data.change_set = NULL;
+	}
+	
+	/* free up any remaining parsing data */
+	change_set_destroy (data.change_set);
+	if (data.nodes) {
+		g_hash_table_foreach (data.nodes,
+				      (GHFunc) hash_foreach_destroy_node,
+				      project);
+		g_hash_table_destroy (data.nodes);
+	}
+	g_free (data.param_key);
+	
+	return (retval == 0);
+}
+
+
+/*
+ * Perl script error parsing ------------------------
+ */
+
+/* FIXME: set correct error codes in all the places we call this function */
+static void
+error_set (GError **error, gint code, const gchar *message)
+{
+	if (error != NULL) {
+		if (*error != NULL) {
+			gchar *tmp;
+			
+			/* error already created, just change the code
+			 * and prepend the string */
+			(*error)->code = code;
+			tmp = (*error)->message;
+			(*error)->message = g_strconcat (message, "\n\n", tmp, NULL);
+			g_free (tmp);
+			
+		} else {
+			*error = g_error_new_literal (GBF_PROJECT_ERROR,
+						      code, 
+						      message);
+		}
+	}
+}
+
+static GError *
+parse_errors (GbfAmProject *project,
+	      const gchar  *error_buffer)
+{
+	const gchar *line_ptr, *next_line, *p;
+	GError *err = NULL;
+	GString *message;
+	
+	message = g_string_new (NULL);
+	line_ptr = error_buffer;
+	while (line_ptr) {
+		/* FIXME: maybe use constants or enums for the error type */
+		gint error_type = 0;
+		gint error_code;
+		gint line_length;
+		
+		next_line = g_strstr_len (line_ptr, strlen (line_ptr), "\n");
+		/* skip newline */
+		next_line = next_line ? next_line + 1 : NULL;
+		line_length = next_line ? next_line - line_ptr : strlen (line_ptr);
+		p = line_ptr;
+		
+		if (g_str_has_prefix (line_ptr, ERROR_PREFIX)) {
+			/* get the error */
+			p = line_ptr + strlen (ERROR_PREFIX);
+			error_type = 1;
+		}
+#if 0
+		/* FIXME: skip warnings for now */
+		else if (g_str_has_prefix (line_ptr, WARNING_PREFIX)) {
+			/* get the warning */
+			p = line_ptr + strlen (WARNING_PREFIX);
+			error_type = 2;
+		}
+#endif
+
+		if (error_type != 0) {
+			/* get the error/warning code */
+			error_code = strtol (p, (char **) &p, 10);
+			if (error_code != 0) {
+				p = g_strstr_len (p, line_length, MESSAGE_DELIMITER);
+				if (p != NULL) {
+					gchar *msg;
+
+					/* FIXME: think another
+					 * solution for getting the
+					 * messages, since this has
+					 * problems with i18n as it's
+					 * very difficult to get
+					 * translated strings from the
+					 * perl script */
+					p += strlen (MESSAGE_DELIMITER);
+					if (next_line != NULL)
+						msg = g_strndup (p, next_line - p - 1);
+					else
+						msg = g_strdup (p);
+
+					if (message->len > 0)
+						g_string_append (message, "\n");
+					g_string_append (message, msg);
+					g_free (msg);
+				}
+			}
+		}
+		
+		line_ptr = next_line;
+	}
+
+	if (message->len > 0) {
+		err = g_error_new (GBF_PROJECT_ERROR,
+				   GBF_PROJECT_ERROR_GENERAL_FAILURE,
+				   "%s", message->str);
+	}
+	g_string_free (message, TRUE);
+	
+	return err;
+}
+
+
+/*
+ * Process spawning ------------------------
+ */
+
+static void
+shutdown_channel (GbfAmSpawnData *data, GbfAmChannel *channel)
+{
+	if (channel->channel) {
+		g_io_channel_shutdown (channel->channel, TRUE, NULL);
+		g_io_channel_unref (channel->channel);
+		channel->channel = NULL;
+	}
+	if (channel->tag != 0) {
+		GMainContext *context = NULL;
+		GSource *source;
+		if (data->main_loop != NULL)
+			context = g_main_loop_get_context (data->main_loop);
+		source = g_main_context_find_source_by_id (context, channel->tag);
+		if (source != NULL)
+			g_source_destroy (source);
+		channel->tag = 0;
+	}
+}
+
+static void
+spawn_shutdown (GbfAmSpawnData *data)
+{
+	g_return_if_fail (data != NULL);
+	
+	if (data->child_pid) {
+		DEBUG (g_message ("Killing child"));
+		kill (data->child_pid, SIGKILL);
+		data->child_pid = 0;
+	}
+
+	/* close channels and remove sources */
+	shutdown_channel (data, &data->input);
+	shutdown_channel (data, &data->output);
+	shutdown_channel (data, &data->error);
+	data->open_channels = 0;
+	
+	if (data->output.buffer) {
+		/* shrink buffer and add terminator */
+		data->output.buffer = g_realloc (data->output.buffer,
+						 ++data->output.length);
+		data->output.buffer [data->output.length - 1] = 0;
+	}
+
+	if (data->error.buffer) {
+		/* shrink buffer and add terminator */
+		data->error.buffer = g_realloc (data->error.buffer,
+						++data->error.length);
+		data->error.buffer [data->error.length - 1] = 0;
+	}
+	
+	if (data->main_loop)
+		g_main_loop_quit (data->main_loop);
+}
+
+static void
+spawn_data_destroy (GbfAmSpawnData *data)
+{
+	g_return_if_fail (data != NULL);
+
+	spawn_shutdown (data);
+
+	if (data->input.buffer) {
+		/* input buffer is provided by the user, so it's not freed here */
+		data->input.buffer = NULL;
+		data->input.size = 0;
+		data->input.length = 0;
+	}
+	if (data->output.buffer) {
+		g_free (data->output.buffer);
+		data->output.buffer = NULL;
+		data->output.size = 0;
+		data->output.length = 0;
+	}
+	if (data->error.buffer) {
+		g_free (data->error.buffer);
+		data->error.buffer = NULL;
+		data->error.size = 0;
+		data->error.length = 0;
+	}
+	g_free (data);
+}
+
+static gboolean
+spawn_write_child (GIOChannel *ioc, GIOCondition condition, gpointer user_data)
+{
+	GbfAmSpawnData *data = user_data;
+	gboolean retval = FALSE;
+
+	g_assert (data != NULL);
+	g_assert (data->input.channel == ioc);
+	
+	if (condition & G_IO_OUT) {
+		gsize bytes_written = 0;
+		GIOStatus status;
+		GError *error = NULL;
+
+		/* try to write all data remaining */
+		status = g_io_channel_write_chars (ioc,
+						   data->input.buffer + data->input.length,
+						   data->input.size - data->input.length,
+						   &bytes_written, &error);
+		data->input.length += bytes_written;
+
+		switch (status) {
+		    case G_IO_STATUS_NORMAL:
+			    DEBUG (g_message ("wrote %" G_GSIZE_FORMAT " bytes", bytes_written));
+			    
+			    if (data->input.length < data->input.size) {
+				    /* don't remove the source */
+				    retval = TRUE;
+			    }
+			    break;
+
+		    default:
+			    if (error) {
+				    g_warning ("Error while writing to stdin: %s",
+					       error->message);
+				    g_error_free (error);
+			    }
+			    break;
+		}
+	}
+
+	if (!retval) {
+		/* finished writing or some error ocurred */
+		g_io_channel_shutdown (data->input.channel, TRUE, NULL);
+		g_io_channel_unref (data->input.channel);
+		data->input.channel = NULL;
+		/* returning false will remove the source */
+		data->input.tag = 0;
+
+		data->open_channels--;
+		if (data->open_channels == 0) {
+			/* need to signal the end of the operation */
+			if (data->main_loop)
+				/* executing synchronously */
+				g_main_loop_quit (data->main_loop);
+			/* FIXME: what to do in the async case? */
+		}
+	}
+
+	return retval;
+}
+
+static gboolean
+read_channel (GbfAmChannel *channel, GIOCondition condition, GbfAmSpawnData *data)
+{
+	gboolean retval = FALSE;
+	
+	if (condition & (G_IO_IN | G_IO_PRI)) {
+		gsize bytes_read = 0;
+		GIOStatus status;
+		GError *error = NULL;
+
+		/* allocate buffer */
+		if (channel->buffer == NULL) {
+			channel->size = READ_BUFFER_SIZE;
+			channel->buffer = g_malloc (channel->size);
+			channel->length = 0;
+		}
+
+		status = g_io_channel_read_chars (channel->channel,
+						  channel->buffer + channel->length,
+						  channel->size - channel->length,
+						  &bytes_read, &error);
+		channel->length += bytes_read;
+
+		switch (status) {
+		    case G_IO_STATUS_NORMAL:
+			    /* grow buffer if necessary */
+			    if (channel->size - channel->length < READ_BUFFER_DELTA) {
+				    channel->size += READ_BUFFER_DELTA;
+				    channel->buffer = g_realloc (channel->buffer,
+								 channel->size);
+			    }
+			    retval = TRUE;
+			    break;
+			    
+		    case G_IO_STATUS_EOF:
+			    /* will close the channel */
+			    break;
+			    
+		    default:
+			    if (error) {
+				    g_warning ("Error while reading stderr: %s",
+					       error->message);
+				    g_error_free (error);
+			    }
+			    break;
+		}
+	}
+	
+	if (!retval) {
+		/* eof was reached or some error ocurred */
+		g_io_channel_shutdown (channel->channel, FALSE, NULL);
+		g_io_channel_unref (channel->channel);
+		channel->channel = NULL;
+		/* returning false will remove the source */
+		channel->tag = 0;
+
+		data->open_channels--;
+		if (data->open_channels == 0) {
+			/* need to signal the end of the operation */
+			if (data->main_loop)
+				/* executing synchronously */
+				g_main_loop_quit (data->main_loop);
+			/* FIXME: what to do in the async case? */
+		}
+	}
+
+	return retval;
+}
+
+static gboolean
+spawn_read_output (GIOChannel *ioc, GIOCondition condition, gpointer user_data)
+{
+	GbfAmSpawnData *data = user_data;
+	
+	/* some checks first */
+	g_assert (data != NULL);
+	g_assert (ioc == data->output.channel);
+	
+	return read_channel (&data->output, condition, data);
+}
+
+static gboolean
+spawn_read_error (GIOChannel *ioc, GIOCondition condition, gpointer user_data)
+{
+	GbfAmSpawnData *data = user_data;
+	
+	/* some checks first */
+	g_assert (data != NULL);
+	g_assert (ioc == data->error.channel);
+	
+	return read_channel (&data->error, condition, data);
+}
+
+static gboolean
+spawn_kill_child (GbfAmSpawnData *data)
+{
+	/* we can't wait longer */
+	DEBUG (g_message ("Timeout: sending SIGTERM to child process"));
+	
+	kill (data->child_pid, SIGTERM);
+	
+	if (data->main_loop)
+		g_main_loop_quit (data->main_loop);
+
+	return FALSE;
+}
+
+static guint 
+context_io_add_watch (GMainContext *context,
+		      GIOChannel   *channel,
+		      GIOCondition  condition,
+		      GSourceFunc   func,
+		      gpointer      user_data)
+{
+	GSource *source;
+	guint id;
+  
+	g_return_val_if_fail (channel != NULL, 0);
+
+	source = g_io_create_watch (channel, condition);
+	g_source_set_callback (source, func, user_data, NULL);
+	id = g_source_attach (source, context);
+	g_source_unref (source);
+	
+	return id;
+}
+
+static GbfAmSpawnData * 
+spawn_script (gchar  **argv,
+	      gint     timeout,
+	      gchar   *input,
+	      gint     input_size,
+	      GIOFunc  input_cb,
+	      GIOFunc  output_cb,
+	      GIOFunc  error_cb)
+{
+	GbfAmSpawnData *data;
+	gint child_in, child_out, child_err;
+	GError *error = NULL;
+	gboolean async;
+	
+	data = g_new0 (GbfAmSpawnData, 1);
+
+	/* we consider timeout < 0 to mean asynchronous request */
+	async = (timeout <= 0);
+
+	/* setup default callbacks */
+	if (input_cb == NULL) input_cb = spawn_write_child;
+	if (output_cb == NULL) output_cb = spawn_read_output;
+	if (error_cb == NULL) error_cb = spawn_read_error;
+	
+	/* set input buffer */
+	if (input) {
+		data->input.buffer = input;
+		data->input.size = input_size;
+		data->input.length = 0;  /* for input buffer length acts as an index */
+	}
+
+	DEBUG (g_message ("Spawning script"));
+	
+	if (!g_spawn_async_with_pipes (NULL,             /* working dir */
+				       argv,
+				       NULL,             /* environment */
+				       0,                /* flags */
+				       NULL, NULL,       /* child setup func */
+				       &data->child_pid,
+				       &child_in, &child_out, &child_err,
+				       &error)) {
+		g_warning ("Unable to fork: %s", error->message);
+		g_error_free (error);
+		g_free (data);
+		return NULL;
+
+	} else {
+		GMainContext *context = NULL;
+		
+		if (!async) {
+			/* we need a new context to do the i/o
+			 * otherwise we could have re-entrancy
+			 * problems since gtk events will be processed
+			 * while we iterate the inner main loop */
+			context = g_main_context_new ();
+			data->main_loop = g_main_loop_new (context, FALSE);
+		}
+
+		fcntl (child_in, F_SETFL, O_NONBLOCK);
+		fcntl (child_out, F_SETFL, O_NONBLOCK);
+		fcntl (child_err, F_SETFL, O_NONBLOCK);
+
+		data->open_channels = 3;
+		if (input != NULL && input_size > 0) {
+			data->input.channel = g_io_channel_unix_new (child_in);
+			data->input.tag = context_io_add_watch (context,
+								data->input.channel,
+								G_IO_OUT | G_IO_ERR |
+								G_IO_HUP | G_IO_NVAL,
+								(GSourceFunc) input_cb, data);
+		} else {
+			/* we are not interested in stdin */
+			close (child_in);
+			data->open_channels--;
+		}
+		
+		/* create watches for stdout and stderr */
+		data->output.channel = g_io_channel_unix_new (child_out);
+		data->output.tag = context_io_add_watch (context,
+							 data->output.channel,
+							 G_IO_ERR | G_IO_HUP |
+							 G_IO_NVAL | G_IO_IN,
+							 (GSourceFunc) output_cb, data);
+		data->error.channel = g_io_channel_unix_new (child_err);
+		data->error.tag = context_io_add_watch (context,
+							data->error.channel,
+							G_IO_ERR | G_IO_HUP |
+							G_IO_NVAL | G_IO_IN,
+							(GSourceFunc) error_cb, data);
+		
+		if (!async) {
+			GSource *source;
+
+			/* add the timeout */
+			source = g_timeout_source_new (timeout);
+			g_source_set_callback (source, (GSourceFunc) spawn_kill_child, data, NULL);
+			g_source_attach (source, context);
+			g_source_unref (source);
+		
+			g_main_loop_run (data->main_loop);
+
+			/* continue iterations until all channels have been closed */
+			while (data->open_channels > 0 && g_main_context_pending (context))
+				g_main_context_iteration (context, FALSE);
+			
+			/* close channels and remove io watches */
+			if (data->open_channels == 0)
+				/* normal shutdown */
+				data->child_pid = 0;
+			spawn_shutdown (data);
+
+			/* destroy main loop & context */
+			g_main_loop_unref (data->main_loop);
+			data->main_loop = NULL;
+			g_main_context_unref (context);
+		}
+
+		return data;
+	}
+}
+
+/*
+ * Script execution control ----------------------------
+ */
+
+static gboolean
+project_reload (GbfAmProject *project, GError **err) 
+{
+	GbfAmSpawnData *data;
+	gchar *argv [5], *project_path;
+	gboolean retval;
+	gint i;
+	
+	project_path = uri_to_path (project->project_root_uri);
+		
+	i = 0;
+	argv [i++] = GBF_AM_PARSE;
+	DEBUG (argv [i++] = "-d");
+	argv [i++] = "--get";
+	argv [i++] = project_path;
+	argv [i++] = NULL;
+	g_assert (i <= G_N_ELEMENTS (argv));
+
+	data = spawn_script (argv, SCRIPT_TIMEOUT,
+			     NULL, 0,            /* input buffer */
+			     NULL, NULL, NULL);  /* i/o callbacks */
+
+	g_free (project_path);
+
+	retval = FALSE;
+	if (data != NULL) {
+		if (data->error.length > 0 && err != NULL) {
+			/* the buffer is zero terminated */
+			*err = parse_errors (project, data->error.buffer);
+		}
+		
+		if (data->output.length > 0) {
+			gchar *xml_error = NULL;
+			retval = parse_output_xml (project,
+						   data->output.buffer,
+						   data->output.length,
+						   NULL, &xml_error);
+			if (err != NULL && *err == NULL &&
+			    !retval && xml_error) {
+				/* xml parse error */
+				g_set_error (err,
+					     GBF_PROJECT_ERROR,
+					     GBF_PROJECT_ERROR_GENERAL_FAILURE,
+					     "XML parse error: %s",
+					     xml_error);
+			}
+			g_free (xml_error);
+		} else {
+			/* FIXME: generate some kind of error here */
+			g_warning ("Child process returned no data");
+		}
+
+		spawn_data_destroy (data);
+	}
+
+	monitors_setup (project);
+	
+	return retval;
+}
+
+static gboolean 
+project_update (GbfAmProject *project,
+		xmlDocPtr     doc,
+		GSList      **change_set,
+		GError      **err) 
+{
+	GbfAmSpawnData *data;
+	gchar *argv [5];
+	gboolean retval;
+	gint i;
+	xmlChar *xml_doc;
+	gint xml_size;
+	
+	/* remove file monitors */
+	monitors_remove (project);
+
+	i = 0;
+	argv [i++] = GBF_AM_PARSE;
+	DEBUG (argv [i++] = "-d");
+	argv [i++] = "--set";
+	argv [i++] = "-";
+	argv [i++] = NULL;
+	g_assert (i <= G_N_ELEMENTS (argv));
+
+	/* dump the document to memory */
+	xmlSubstituteEntitiesDefault (TRUE);
+	xmlDocDumpMemory (doc, &xml_doc, &xml_size);
+
+	/* execute the script */
+	data = spawn_script (argv, SCRIPT_TIMEOUT,
+			     (char *) xml_doc, xml_size,  /* input buffer */
+			     NULL, NULL, NULL);  /* i/o callbacks */
+	xmlFree (xml_doc);
+
+	retval = FALSE;
+	if (data != NULL) {
+		if (data->error.length > 0 && err != NULL) {
+			/* the buffer is zero terminated */
+			*err = parse_errors (project, data->error.buffer);
+		}
+		
+		if (data->output.length > 0) {
+			gchar *xml_error = NULL;
+			/* process the xml output for changed groups */
+			retval = parse_output_xml (project,
+						   data->output.buffer,
+						   data->output.length,
+						   change_set, &xml_error);
+			if (err != NULL && *err == NULL &&
+			    !retval && xml_error) {
+				/* xml parse error */
+				g_set_error (err,
+					     GBF_PROJECT_ERROR,
+					     GBF_PROJECT_ERROR_GENERAL_FAILURE,
+					     "XML parse error: %s",
+					     xml_error);
+			}
+			g_free (xml_error);
+			
+			/* FIXME: emit this only if the project has indeed changed */
+			g_signal_emit_by_name (G_OBJECT (project), "project-updated");
+		}
+		
+		spawn_data_destroy (data);
+	}
+
+	monitors_setup (project);
+	
+	return retval;
+}
+
+/*
+ * ---------------- Data structures managment
+ */
+
+static void
+gbf_am_node_free (GbfAmNode *node)
+{
+	if (node) {
+		g_free (node->id);
+		g_free (node->name);
+		g_free (node->detail);
+		g_free (node->uri);
+		gbf_am_config_mapping_destroy (node->config);
+
+		g_free (node);
+	}
+}
+
+static GNode *
+project_node_new (GbfAmNodeType type)
+{
+	GbfAmNode *node;
+
+	node = g_new0 (GbfAmNode, 1);
+	node->type = type;
+
+	return g_node_new (node);
+}
+
+static gboolean 
+foreach_node_destroy (GNode    *g_node,
+		      gpointer  data)
+{
+	GbfAmProject *project = data;
+	
+	switch (GBF_AM_NODE (g_node)->type) {
+		case GBF_AM_NODE_GROUP:
+			g_hash_table_remove (project->groups, GBF_AM_NODE (g_node)->id);
+			break;
+		case GBF_AM_NODE_TARGET:
+			g_hash_table_remove (project->targets, GBF_AM_NODE (g_node)->id);
+			break;
+		case GBF_AM_NODE_SOURCE:
+			g_hash_table_remove (project->sources, GBF_AM_NODE (g_node)->id);
+			break;
+		default:
+			g_assert_not_reached ();
+			break;
+	}
+	gbf_am_node_free (GBF_AM_NODE (g_node));
+
+	return FALSE;
+}
+
+static void
+project_node_destroy (GbfAmProject *project, GNode *g_node)
+{
+	g_return_if_fail (project != NULL);
+	g_return_if_fail (GBF_IS_AM_PROJECT (project));
+	
+	if (g_node) {
+		/* free each node's data first */
+		g_node_traverse (g_node,
+				 G_IN_ORDER, G_TRAVERSE_ALL, -1,
+				 foreach_node_destroy, project);
+
+		/* now destroy the tree itself */
+		g_node_destroy (g_node);
+	}
+}
+
+static void
+project_data_destroy (GbfAmProject *project)
+{
+	g_return_if_fail (project != NULL);
+	g_return_if_fail (GBF_IS_AM_PROJECT (project));
+	
+	monitors_remove (project);
+	
+	/* project data */
+	project_node_destroy (project, project->root_node);
+	project->root_node = NULL;
+	g_free (project->project_file);
+	project->project_file = NULL;
+	gbf_am_config_mapping_destroy (project->project_config);
+	project->project_config = NULL;
+	
+	/* shortcut hash tables */
+	if (project->groups) g_hash_table_destroy (project->groups);
+	if (project->targets) g_hash_table_destroy (project->targets);
+	if (project->sources) g_hash_table_destroy (project->sources);
+	project->groups = NULL;
+	project->targets = NULL;
+	project->sources = NULL;
+}
+
+static void
+project_data_init (GbfAmProject *project)
+{
+	g_return_if_fail (project != NULL);
+	g_return_if_fail (GBF_IS_AM_PROJECT (project));
+	
+	/* free data if necessary */
+	project_data_destroy (project);
+
+	/* FIXME: initialize monitors here, since monitors' lifecycle
+	 * are bound to source files from the project */
+	
+	/* project data */
+	project->project_file = NULL;
+	project->project_config = gbf_am_config_mapping_new ();
+	project->root_node = NULL;
+	
+	/* shortcut hash tables */
+	project->groups = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+	project->targets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+	project->sources = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+}
+
+GbfAmConfigMapping *
+gbf_am_project_get_config (GbfAmProject *project, GError **error)
+{
+	g_return_val_if_fail (GBF_IS_AM_PROJECT (project), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+	
+	return gbf_am_config_mapping_copy (project->project_config);
+}
+
+GbfAmConfigMapping *
+gbf_am_project_get_group_config (GbfAmProject *project, const gchar *group_id,
+				 GError **error)
+{
+	GbfAmNode *node;
+	GNode *g_node;
+	
+	g_return_val_if_fail (GBF_IS_AM_PROJECT (project), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+	
+	g_node = g_hash_table_lookup (project->groups, group_id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Group doesn't exist"));
+		return NULL;
+	}
+	node = GBF_AM_NODE (g_node);
+	return gbf_am_config_mapping_copy (node->config);
+}
+
+GbfAmConfigMapping *
+gbf_am_project_get_target_config (GbfAmProject *project, const gchar *target_id,
+				  GError **error)
+{
+	GbfAmNode *node;
+	GNode *g_node;
+	
+	g_return_val_if_fail (GBF_IS_AM_PROJECT (project), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+	
+	g_node = g_hash_table_lookup (project->targets, target_id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Target doesn't exist"));
+		return NULL;
+	}
+	node = GBF_AM_NODE (g_node);
+	return gbf_am_config_mapping_copy (node->config);
+}
+
+void
+gbf_am_project_set_config (GbfAmProject *project,
+			   GbfAmConfigMapping *new_config, GError **error)
+{
+	xmlDocPtr doc;
+	GSList *change_set = NULL;
+	
+	g_return_if_fail (GBF_IS_AM_PROJECT (project));
+	g_return_if_fail (new_config != NULL);
+	g_return_if_fail (error == NULL || *error == NULL);
+	
+	/* Create the update xml */
+	doc = xml_new_change_doc (project);
+	
+	if (!xml_write_set_config (project, doc, NULL, new_config)) {
+		xmlFreeDoc (doc);
+		return;
+	}
+
+	DEBUG ({
+		xmlSetDocCompressMode (doc, 0);
+		xmlSaveFile ("/tmp/set-config.xml", doc);
+	});
+
+	/* Update the project */
+	if (!project_update (project, doc, &change_set, error)) {
+		error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+			   _("Unable to update project"));
+		xmlFreeDoc (doc);
+		return;
+	}
+	xmlFreeDoc (doc);
+	change_set_destroy (change_set);
+}
+
+void
+gbf_am_project_set_group_config (GbfAmProject *project, const gchar *group_id,
+				 GbfAmConfigMapping *new_config, GError **error)
+{
+	GbfAmNode *node;
+	xmlDocPtr doc;
+	GNode *g_node;
+	GSList *change_set = NULL;
+	
+	g_return_if_fail (GBF_IS_AM_PROJECT (project));
+	g_return_if_fail (new_config != NULL);
+	g_return_if_fail (error == NULL || *error == NULL);
+	
+	g_node = g_hash_table_lookup (project->groups, group_id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Group doesn't exist"));
+		return;
+	}
+	node = GBF_AM_NODE (g_node);
+	
+	/* Create the update xml */
+	doc = xml_new_change_doc (project);
+	if (!xml_write_set_config (project, doc, g_node, new_config)) {
+		xmlFreeDoc (doc);
+		return;
+	}
+
+	DEBUG ({
+		xmlSetDocCompressMode (doc, 0);
+		xmlSaveFile ("/tmp/set-config.xml", doc);
+	});
+	
+	/* Update the project */
+	if (!project_update (project, doc, &change_set, error)) {
+		error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+			   _("Unable to update project"));
+		xmlFreeDoc (doc);
+		return;
+	}
+	xmlFreeDoc (doc);
+	change_set_destroy (change_set);
+}
+
+void
+gbf_am_project_set_target_config (GbfAmProject *project,
+				  const gchar *target_id,
+				  GbfAmConfigMapping *new_config,
+				  GError **error)
+{
+	xmlDocPtr doc;
+	GNode *g_node;
+	GSList *change_set = NULL;
+	
+	g_return_if_fail (GBF_IS_AM_PROJECT (project));
+	g_return_if_fail (new_config != NULL);
+	g_return_if_fail (error == NULL || *error == NULL);
+	
+	g_node = g_hash_table_lookup (project->targets, target_id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Target doesn't exist"));
+	}
+	
+	/* Create the update xml */
+	doc = xml_new_change_doc (project);
+	if (!xml_write_set_config (project, doc, g_node, new_config)) {
+		xmlFreeDoc (doc);
+		return;
+	}
+
+	DEBUG ({
+		xmlSetDocCompressMode (doc, 0);
+		xmlSaveFile ("/tmp/set-config.xml", doc);
+	});
+
+	/* Update the project */
+	if (!project_update (project, doc, &change_set, error)) {
+		error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+			   _("Unable to update project"));
+		xmlFreeDoc (doc);
+		return;
+	}
+	xmlFreeDoc (doc);
+	change_set_destroy (change_set);
+}
+
+/*
+ * GbfProjectIface methods ------------------------------------------
+ */
+
+static void 
+impl_load (GbfProject  *_project,
+	   const gchar *uri,
+	   GError     **error)
+{
+	GbfAmProject *project;
+	gchar *root_path;
+	
+	g_return_if_fail (GBF_IS_AM_PROJECT (_project));
+
+	project = GBF_AM_PROJECT (_project);
+	if (project->project_root_uri) {
+		/* FIXME:
+		 * - do we really want to allow object reutilization
+		 * - cancel some pending operations in the queue?
+		 */
+		project_data_destroy (project);
+		g_free (project->project_root_uri);
+		project->project_root_uri = NULL;
+
+		project_data_init (project);
+	}
+
+	/* allow this? */
+	if (uri == NULL)
+		return;
+	
+	/* check that the uri is in the filesystem */
+	project->project_root_uri = uri_normalize (uri, NULL);
+	if (project->project_root_uri == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Invalid or remote path (only local paths supported)"));
+		return;
+	}
+		
+	/* some basic checks */
+	root_path = uri_to_path (project->project_root_uri);
+	if (root_path == NULL || !g_file_test (root_path, G_FILE_TEST_IS_DIR)) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Project doesn't exist or invalid path"));
+		g_free (root_path);
+		g_free (project->project_root_uri);
+		project->project_root_uri = NULL;
+		return;
+	}
+	g_free (root_path);
+
+	/* now try loading the project */
+	if (!project_reload (project, error)) {
+		error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+			   _("Malformed project"));
+		g_free (project->project_root_uri);
+		project->project_root_uri = NULL;
+	}
+}
+
+static gboolean
+file_exists (const gchar *path, const gchar *filename)
+{
+	gchar *full_path;
+	gboolean retval;
+	
+	full_path = g_build_filename (path, filename, NULL);
+	retval = g_file_test (full_path, G_FILE_TEST_EXISTS);
+	g_free (full_path);
+
+	return retval;
+}
+
+static gboolean 
+impl_probe (GbfProject  *_project,
+	    const gchar *path,
+	    GError     **error)
+{
+	gchar *normalized_uri, *root_path;
+	gboolean retval = FALSE;
+	
+	g_return_val_if_fail (GBF_IS_AM_PROJECT (_project), FALSE);
+
+	normalized_uri = uri_normalize (path, NULL);
+	if (normalized_uri != NULL) {
+		root_path = uri_to_path (normalized_uri);
+		if (root_path != NULL && g_file_test (root_path, G_FILE_TEST_IS_DIR)) {
+			retval = (file_exists (root_path, "Makefile.am") &&
+				  (file_exists (root_path, "configure.in") ||
+				   file_exists (root_path, "configure.ac")));
+			g_free (root_path);
+		}
+		g_free (normalized_uri);
+	}
+
+	return retval;
+}
+
+static void
+impl_refresh (GbfProject *_project,
+	      GError    **error)
+{
+	GbfAmProject *project;
+
+	g_return_if_fail (GBF_IS_AM_PROJECT (_project));
+
+	project = GBF_AM_PROJECT (_project);
+
+	if (project_reload (project, error))
+		g_signal_emit_by_name (G_OBJECT (project), "project-updated");
+}
+
+static GbfProjectCapabilities
+impl_get_capabilities (GbfProject *_project, GError    **error)
+{
+	g_return_val_if_fail (GBF_IS_AM_PROJECT (_project),
+			      GBF_PROJECT_CAN_ADD_NONE);
+	return (GBF_PROJECT_CAN_ADD_GROUP |
+		GBF_PROJECT_CAN_ADD_TARGET |
+		GBF_PROJECT_CAN_ADD_SOURCE);
+}
+
+static GbfProjectGroup * 
+impl_get_group (GbfProject  *_project,
+		const gchar *id,
+		GError     **error)
+{
+	GbfAmProject *project;
+	GbfProjectGroup *group;
+	GNode *g_node;
+	GbfAmNode *node;
+	
+	g_return_val_if_fail (GBF_IS_AM_PROJECT (_project), NULL);
+
+	project = GBF_AM_PROJECT (_project);
+	g_node = g_hash_table_lookup (project->groups, id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Group doesn't exist"));
+		return NULL;
+	}
+	node = GBF_AM_NODE (g_node);
+
+	group = g_new0 (GbfProjectGroup, 1);
+	group->id = g_strdup (node->id);
+	group->name = g_strdup (node->name);
+	if (g_node->parent)
+		group->parent_id = g_strdup (GBF_AM_NODE (g_node->parent)->id);
+	else
+		group->parent_id = NULL;
+	group->groups = NULL;
+	group->targets = NULL;
+
+	/* add subgroups and targets of the group */
+	g_node = g_node_first_child (g_node);
+	while (g_node) {
+		node = GBF_AM_NODE (g_node);
+		switch (node->type) {
+			case GBF_AM_NODE_GROUP:
+				group->groups = g_list_prepend (group->groups,
+								g_strdup (node->id));
+				break;
+			case GBF_AM_NODE_TARGET:
+				group->targets = g_list_prepend (group->targets,
+								 g_strdup (node->id));
+				break;
+			default:
+				break;
+		}
+		g_node = g_node_next_sibling (g_node);
+	}
+	group->groups = g_list_reverse (group->groups);
+	group->targets = g_list_reverse (group->targets);
+	
+	return group;
+}
+
+static void
+foreach_group (gpointer key, gpointer value, gpointer data)
+{
+	GList **groups = data;
+
+	*groups = g_list_prepend (*groups, g_strdup (key));
+}
+
+
+static GList *
+impl_get_all_groups (GbfProject *_project,
+		     GError    **error)
+{
+	GbfAmProject *project;
+	GList *groups = NULL;
+
+	g_return_val_if_fail (GBF_IS_AM_PROJECT (_project), NULL);
+
+	project = GBF_AM_PROJECT (_project);
+	g_hash_table_foreach (project->groups, foreach_group, &groups);
+
+	return groups;
+}
+
+static GtkWidget *
+impl_configure_new_group (GbfProject *_project,
+			  GError    **error)
+{
+	UNIMPLEMENTED;
+
+	return NULL;
+}
+
+static GtkWidget * 
+impl_configure_group (GbfProject  *_project,
+		      const gchar *id,
+		      GError     **error)
+{
+	GtkWidget *wid = NULL;
+	GError *err = NULL;
+	
+	g_return_val_if_fail (GBF_IS_PROJECT (_project), NULL);
+	g_return_val_if_fail (id != NULL, NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+	
+	wid = gbf_am_properties_get_group_widget (GBF_AM_PROJECT (_project),
+						  id, &err);
+	if (err) {
+		g_propagate_error (error, err);
+	}
+	return wid;
+}
+
+static gchar * 
+impl_add_group (GbfProject  *_project,
+		const gchar *parent_id,
+		const gchar *name,
+		GError     **error)
+{
+	GbfAmProject *project;
+	GNode *g_node, *iter_node;
+	xmlDocPtr doc;
+	GSList *change_set = NULL;
+	GbfAmChange *change;
+	gchar *retval;
+	
+	g_return_val_if_fail (GBF_IS_AM_PROJECT (_project), NULL);
+
+	project = GBF_AM_PROJECT (_project);
+	
+	/* Validate group name */
+	if (!name || strlen (name) <= 0)
+	{
+		error_set (error, GBF_PROJECT_ERROR_VALIDATION_FAILED,
+			   _("Please specify group name"));
+		return NULL;
+	}
+	{
+		gboolean failed = FALSE;
+		const gchar *ptr = name;
+		while (*ptr) {
+			if (!isalnum (*ptr) && *ptr != '.' && *ptr != '-' &&
+			    *ptr != '_')
+				failed = TRUE;
+			ptr++;
+		}
+		if (failed) {
+			error_set (error, GBF_PROJECT_ERROR_VALIDATION_FAILED,
+				   _("Group name can only contain alphanumeric, '_', '-' or '.' characters"));
+			return NULL;
+		}
+	}
+	
+	/* find the parent group */
+	g_node = g_hash_table_lookup (project->groups, parent_id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Parent group doesn't exist"));
+		return NULL;
+	}
+
+	/* check that the new group doesn't already exist */
+	iter_node = g_node_first_child (g_node);
+	while (iter_node) {
+		GbfAmNode *node = GBF_AM_NODE (iter_node);
+		if (node->type == GBF_AM_NODE_GROUP &&
+		    !strcmp (node->name, name)) {
+			error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+				   _("Group already exists"));
+			return NULL;
+		}
+		iter_node = g_node_next_sibling (iter_node);
+	}
+			
+	/* Create the update xml */
+	doc = xml_new_change_doc (project);
+	if (!xml_write_add_group (project, doc, g_node, name)) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Group couldn't be created"));
+		xmlFreeDoc (doc);
+		return NULL;
+	}
+
+	DEBUG ({
+		xmlSetDocCompressMode (doc, 0);
+		xmlSaveFile ("/tmp/add-group.xml", doc);
+	});
+
+	/* Update the project */
+	if (!project_update (project, doc, &change_set, error)) {
+		error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+			   _("Unable to update project"));
+		xmlFreeDoc (doc);
+		return NULL;
+	}
+	xmlFreeDoc (doc);
+
+	/* get newly created group id */
+	retval = NULL;
+	DEBUG (change_set_debug_print (change_set));
+	change = change_set_find (change_set, GBF_AM_CHANGE_ADDED, GBF_AM_NODE_GROUP);
+	if (change) {
+		retval = g_strdup (change->id);
+	} else {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Group couldn't be created"));
+	}
+	change_set_destroy (change_set);
+
+	return retval;
+}
+
+static void 
+impl_remove_group (GbfProject  *_project,
+		   const gchar *id,
+		   GError     **error)
+{
+	GbfAmProject *project;
+	GNode *g_node;
+	xmlDocPtr doc;
+	GSList *change_set = NULL;
+	
+	g_return_if_fail (GBF_IS_AM_PROJECT (_project));
+
+	project = GBF_AM_PROJECT (_project);
+	
+	/* Find the target */
+	g_node = g_hash_table_lookup (project->groups, id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Group doesn't exist"));
+		return;
+	}
+
+	/* Create the update xml */
+	doc = xml_new_change_doc (project);
+	if (!xml_write_remove_group (project, doc, g_node)) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Group couldn't be removed"));
+		xmlFreeDoc (doc);
+		return;
+	}
+
+	DEBUG ({
+		xmlSetDocCompressMode (doc, 0);
+		xmlSaveFile ("/tmp/remove-group.xml", doc);
+	});
+
+	/* Update the project */
+	if (!project_update (project, doc, &change_set, error)) {
+		error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+			   _("Unable to update project"));
+	}
+	xmlFreeDoc (doc);
+	change_set_destroy (change_set);
+}
+
+static GbfProjectTarget * 
+impl_get_target (GbfProject  *_project,
+		 const gchar *id,
+		 GError     **error)
+{
+	GbfAmProject *project;
+	GbfProjectTarget *target;
+	GNode *g_node;
+	GbfAmNode *node;
+
+	g_return_val_if_fail (GBF_IS_AM_PROJECT (_project), NULL);
+
+	project = GBF_AM_PROJECT (_project);
+	g_node = g_hash_table_lookup (project->targets, id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Target doesn't exist"));
+		return NULL;
+	}
+	node = GBF_AM_NODE (g_node);
+
+	target = g_new0 (GbfProjectTarget, 1);
+	target->id = g_strdup (node->id);
+	target->name = g_strdup (node->name);
+	target->type = g_strdup (node->detail);
+	target->group_id = g_strdup (GBF_AM_NODE (g_node->parent)->id);
+	target->sources = NULL;
+
+	/* add sources to the target */
+	g_node = g_node_first_child (g_node);
+	while (g_node) {
+		node = GBF_AM_NODE (g_node);
+		switch (node->type) {
+			case GBF_AM_NODE_SOURCE:
+				target->sources = g_list_prepend (target->sources,
+								  g_strdup (node->id));
+				break;
+			default:
+				break;
+		}
+		g_node = g_node_next_sibling (g_node);
+	}
+	target->sources = g_list_reverse (target->sources);
+
+	return target;
+}
+
+static void
+foreach_target (gpointer key, gpointer value, gpointer data)
+{
+	GList **targets = data;
+
+	*targets = g_list_prepend (*targets, g_strdup (key));
+}
+
+static GList *
+impl_get_all_targets (GbfProject *_project,
+		      GError    **error)
+{
+	GbfAmProject *project;
+	GList *targets = NULL;
+
+	g_return_val_if_fail (GBF_IS_AM_PROJECT (_project), NULL);
+
+	project = GBF_AM_PROJECT (_project);
+	g_hash_table_foreach (project->targets, foreach_target, &targets);
+
+	return targets;
+}
+
+static GtkWidget *
+impl_configure_new_target (GbfProject *_project,
+			   GError    **error)
+{
+	UNIMPLEMENTED;
+
+	return NULL;
+}
+
+static GtkWidget * 
+impl_configure_target (GbfProject  *_project,
+		       const gchar *id,
+		       GError     **error)
+{
+	GtkWidget *wid = NULL;
+	GError *err = NULL;
+	
+	g_return_val_if_fail (GBF_IS_PROJECT (_project), NULL);
+	g_return_val_if_fail (id != NULL, NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+	
+	wid = gbf_am_properties_get_target_widget (GBF_AM_PROJECT (_project),
+						   id, &err);
+	if (err) {
+		g_propagate_error (error, err);
+	}
+	return wid;
+}
+
+static char * 
+impl_add_target (GbfProject  *_project,
+		 const gchar *group_id,
+		 const gchar *name,
+		 const gchar *type,
+		 GError     **error)
+{
+	GbfAmProject *project;
+	GNode *g_node, *iter_node;
+	xmlDocPtr doc;
+	GSList *change_set = NULL;
+	GbfAmChange *change;
+	gchar *retval;
+
+	g_return_val_if_fail (GBF_IS_AM_PROJECT (_project), NULL);
+	g_return_val_if_fail (type != NULL, NULL);
+	g_return_val_if_fail (name != NULL, NULL);
+
+	project = GBF_AM_PROJECT (_project);
+	
+	/* find the group */
+	g_node = g_hash_table_lookup (project->groups, group_id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Group doesn't exist"));
+		return NULL;
+	}
+	
+	/* Validate target name */
+	if (!name || strlen (name) <= 0)
+	{
+		error_set (error, GBF_PROJECT_ERROR_VALIDATION_FAILED,
+			   _("Please specify target name"));
+		return NULL;
+	}
+	{
+		gboolean failed = FALSE;
+		const gchar *ptr = name;
+		while (*ptr) {
+			if (!isalnum (*ptr) && *ptr != '.' && *ptr != '-' &&
+			    *ptr != '_')
+				failed = TRUE;
+			ptr++;
+		}
+		if (failed) {
+			error_set (error, GBF_PROJECT_ERROR_VALIDATION_FAILED,
+				   _("Target name can only contain alphanumeric, '_', '-' or '.' characters"));
+			return NULL;
+		}
+	}
+	if (!strcmp (type, "shared_lib")) {
+		if (strlen (name) < 7 ||
+		    strncmp (name, "lib", strlen("lib")) != 0 ||
+		    strcmp (&name[strlen(name) - 3], ".la") != 0) {
+			error_set (error, GBF_PROJECT_ERROR_VALIDATION_FAILED,
+				   _("Shared library target name must be of the form 'libxxx.la'"));
+			return NULL;
+		}
+	}
+	else if (!strcmp (type, "static_lib")) {
+		if (strlen (name) < 6 ||
+		    strncmp (name, "lib", strlen("lib")) != 0 ||
+		    strcmp (&name[strlen(name) - 2], ".a") != 0) {
+			error_set (error, GBF_PROJECT_ERROR_VALIDATION_FAILED,
+				   _("Static library target name must be of the form 'libxxx.a'"));
+			return NULL;
+		}
+	}
+	
+	/* check that the target doesn't already exist */
+	iter_node = g_node_first_child (g_node);
+	while (iter_node) {
+		GbfAmNode *node = GBF_AM_NODE (iter_node);
+		if (node->type == GBF_AM_NODE_TARGET &&
+		    !strcmp (node->name, name)) {
+			error_set (error, GBF_PROJECT_ERROR_ALREADY_EXISTS,
+				   _("Target already exists"));
+			return NULL;
+		}
+		iter_node = g_node_next_sibling (iter_node);
+	}
+			
+	/* Create the update xml */
+	doc = xml_new_change_doc (project);
+	if (!xml_write_add_target (project, doc, g_node, name, type)) {
+		error_set (error, GBF_PROJECT_ERROR_GENERAL_FAILURE,
+			   _("General failure in target creation"));
+		xmlFreeDoc (doc);
+		return NULL;
+	}
+
+	DEBUG ({
+		xmlSetDocCompressMode (doc, 0);
+		xmlSaveFile ("/tmp/add-target.xml", doc);
+	});
+
+	/* Update the project */
+	if (!project_update (project, doc, &change_set, error)) {
+		error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+			   _("Unable to update project"));
+		xmlFreeDoc (doc);
+		return NULL;
+	}
+	xmlFreeDoc (doc);
+	
+	/* get newly created target id */
+	retval = NULL;
+	DEBUG (change_set_debug_print (change_set));
+	change = change_set_find (change_set, GBF_AM_CHANGE_ADDED,
+				  GBF_AM_NODE_TARGET);
+	if (change) {
+		retval = g_strdup (change->id);
+	} else {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Newly created target could not be identified"));
+	}
+	change_set_destroy (change_set);
+
+	return retval;
+}
+
+static void 
+impl_remove_target (GbfProject  *_project,
+		    const gchar *id,
+		    GError     **error)
+{
+	GbfAmProject *project;
+	GNode *g_node;
+	xmlDocPtr doc;
+	GSList *change_set = NULL;
+	
+	g_return_if_fail (GBF_IS_AM_PROJECT (_project));
+
+	project = GBF_AM_PROJECT (_project);
+	
+	/* Find the target */
+	g_node = g_hash_table_lookup (project->targets, id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Target doesn't exist"));
+		return;
+	}
+
+	/* Create the update xml */
+	doc = xml_new_change_doc (project);
+	if (!xml_write_remove_target (project, doc, g_node)) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Target couldn't be removed"));
+		xmlFreeDoc (doc);
+		return;
+	}
+
+	DEBUG ({
+		xmlSetDocCompressMode (doc, 0);
+		xmlSaveFile ("/tmp/remove-target.xml", doc);
+	});
+
+	/* Update the project */
+	if (!project_update (project, doc, &change_set, error)) {
+		error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+			   _("Unable to update project"));
+	}
+	xmlFreeDoc (doc);
+	change_set_destroy (change_set);
+}
+
+static const gchar * 
+impl_name_for_type (GbfProject  *_project,
+		    const gchar *type)
+{
+	if (!strcmp (type, "static_lib")) {
+		return _("Static Library");
+	} else if (!strcmp (type, "shared_lib")) {
+		return _("Shared Library");
+	} else if (!strcmp (type, "headers")) {
+		return _("Header Files");
+	} else if (!strcmp (type, "man")) {
+		return _("Man Documentation");
+	} else if (!strcmp (type, "data")) {
+		return _("Miscellaneous Data");
+	} else if (!strcmp (type, "program")) {
+		return _("Program");
+	} else if (!strcmp (type, "script")) {
+		return _("Script");
+	} else if (!strcmp (type, "info")) {
+		return _("Info Documentation");
+	} else if (!strcmp (type, "java")) {
+		return _("Java Module");
+	} else if (!strcmp (type, "python")) {
+		return _("Python Module");
+	} else {
+		return _("Unknown");
+	}
+}
+
+static const gchar * 
+impl_mimetype_for_type (GbfProject  *_project,
+			const gchar *type)
+{
+	if (!strcmp (type, "static_lib")) {
+		return "application/x-archive";
+	} else if (!strcmp (type, "shared_lib")) {
+		return "application/x-sharedlib";
+	} else if (!strcmp (type, "headers")) {
+		return "text/x-chdr";
+	} else if (!strcmp (type, "man")) {
+		return "text/x-troff-man";
+	} else if (!strcmp (type, "data")) {
+		return "application/octet-stream";
+	} else if (!strcmp (type, "program")) {
+		return "application/x-executable";
+	} else if (!strcmp (type, "script")) {
+		return "text/x-shellscript";
+	} else if (!strcmp (type, "info")) {
+		return "application/x-tex-info";
+	} else if (!strcmp (type, "java")) {
+		return "application/x-java";
+	} else if (!strcmp (type, "python")) {
+		return "application/x-python";
+	} else {
+		return "text/plain";
+	}
+}
+
+static gchar **
+impl_get_types (GbfProject *_project)
+{
+	return g_strsplit ("program:shared_lib:static_lib:headers:"
+			   "man:data:script:info:java:python", ":", 0);
+}
+
+static GbfProjectTargetSource * 
+impl_get_source (GbfProject  *_project,
+		 const gchar *id,
+		 GError     **error)
+{
+	GbfAmProject *project;
+	GbfProjectTargetSource *source;
+	GNode *g_node;
+	GbfAmNode *node;
+
+	g_return_val_if_fail (GBF_IS_AM_PROJECT (_project), NULL);
+
+	project = GBF_AM_PROJECT (_project);
+	g_node = g_hash_table_lookup (project->sources, id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Source doesn't exist"));
+		return NULL;
+	}
+	node = GBF_AM_NODE (g_node);
+
+	source = g_new0 (GbfProjectTargetSource, 1);
+	source->id = g_strdup (node->id);
+	source->source_uri = g_strdup (node->uri);
+	source->target_id = g_strdup (GBF_AM_NODE (g_node->parent)->id);
+
+	return source;
+}
+
+static void
+foreach_source (gpointer key, gpointer value, gpointer data)
+{
+	GList **sources = data;
+
+	*sources = g_list_prepend (*sources, g_strdup (key));
+}
+
+static GList *
+impl_get_all_sources (GbfProject *_project,
+		      GError    **error)
+{
+	GbfAmProject *project;
+	GList *sources = NULL;
+
+	g_return_val_if_fail (GBF_IS_AM_PROJECT (_project), NULL);
+
+	project = GBF_AM_PROJECT (_project);
+	g_hash_table_foreach (project->sources, foreach_source, &sources);
+
+	return sources;
+}
+
+static GtkWidget *
+impl_configure_new_source (GbfProject *_project,
+			   GError    **error)
+{
+	UNIMPLEMENTED;
+
+	return NULL;
+}
+
+static GtkWidget * 
+impl_configure_source (GbfProject  *_project,
+		       const gchar *id,
+		       GError     **error)
+{
+	UNIMPLEMENTED;
+
+	return NULL;
+}
+
+/**
+ * impl_add_source:
+ * @project: 
+ * @target_id: the target ID to where to add the source
+ * @uri: an uri to the file, which can be absolute or relative to the target's group
+ * @error: 
+ * 
+ * Add source implementation.  The uri must have the project root as its parent.
+ * 
+ * Return value: 
+ **/
+static gchar * 
+impl_add_source (GbfProject  *_project,
+		 const gchar *target_id,
+		 const gchar *uri,
+		 GError     **error)
+{
+	GbfAmProject *project;
+	GNode *g_node, *iter_node;
+	xmlDocPtr doc;
+	gboolean abort_action = FALSE;
+	gchar *full_uri = NULL;
+	gchar *group_uri;
+	GSList *change_set = NULL;
+	GbfAmChange *change;
+	gchar *retval;
+	gchar *filename;
+	const gchar *ptr;
+	gboolean failed = FALSE;
+	
+	g_return_val_if_fail (GBF_IS_AM_PROJECT (_project), NULL);
+	g_return_val_if_fail (uri != NULL, NULL);
+	g_return_val_if_fail (target_id != NULL, NULL);
+	
+	project = GBF_AM_PROJECT (_project);
+	
+	filename = g_path_get_basename (uri);
+	
+	/* Validate target name */
+	ptr = filename;
+	while (*ptr) {
+		if (!isalnum (*ptr) && *ptr != '.' && *ptr != '-' &&
+		    *ptr != '_')
+			failed = TRUE;
+		ptr++;
+	}
+	if (failed) {
+		error_set (error, GBF_PROJECT_ERROR_VALIDATION_FAILED,
+			   _("Source file name can only contain alphanumeric, '_', '-' or '.' characters"));
+		g_free (filename);
+		return NULL;
+	}
+	
+	/* check target */
+	g_node = g_hash_table_lookup (project->targets, target_id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Target doesn't exist"));
+		return NULL;
+	}
+
+	/* if the uri is relative, resolve it against the target's
+	 * group directory; we need to compute the group's uri
+	 * first */
+	group_uri = uri_normalize (g_path_skip_root (GBF_AM_NODE (g_node->parent)->id),
+				   project->project_root_uri);
+	
+	full_uri = uri_normalize (uri, group_uri);
+	
+	/* Check that the source uri is inside the project root */
+	if (!uri_is_parent (project->project_root_uri, full_uri)) {
+		/* If not, copy it to target's group directory */
+		GnomeVFSURI *group_vuri, *dest_vuri, *src_vuri;
+		GnomeVFSResult res;
+		
+		src_vuri = gnome_vfs_uri_new (uri);
+		group_vuri = gnome_vfs_uri_new (group_uri);
+		dest_vuri = gnome_vfs_uri_append_file_name (group_vuri,
+							    filename);
+		
+		res = gnome_vfs_xfer_uri (src_vuri, dest_vuri,
+					  GNOME_VFS_XFER_DEFAULT,
+					  GNOME_VFS_XFER_ERROR_MODE_ABORT,
+					  GNOME_VFS_XFER_OVERWRITE_MODE_ABORT,
+					  NULL, NULL);
+		if (res != GNOME_VFS_OK && res != GNOME_VFS_ERROR_FILE_EXISTS) {
+			GbfProjectError gbf_err;
+			gchar *error_str =
+				g_strdup_printf ("Failed to copy source file inside project: %s",
+						 gnome_vfs_result_to_string (res));
+			switch (res) {
+			case GNOME_VFS_ERROR_NOT_FOUND:
+				gbf_err = GBF_PROJECT_ERROR_DOESNT_EXIST;
+				break;
+			default:
+				gbf_err = GBF_PROJECT_ERROR_GENERAL_FAILURE;
+				break;
+			}
+			error_set (error, gbf_err, error_str);
+			g_free (error_str);
+			abort_action = TRUE;
+		} else {
+			g_free (full_uri);
+			full_uri = gnome_vfs_uri_to_string (dest_vuri,
+							    GNOME_VFS_URI_HIDE_NONE);
+		}
+		gnome_vfs_uri_unref (src_vuri);
+		gnome_vfs_uri_unref (group_vuri);
+		gnome_vfs_uri_unref (dest_vuri);
+	}
+	
+	g_free (group_uri);
+	g_free (filename);
+	
+	/* check for source duplicates */
+	iter_node = g_node_first_child (g_node);
+	while (!abort_action && iter_node) {
+		GbfAmNode *node = GBF_AM_NODE (iter_node);
+		
+		if (node->type == GBF_AM_NODE_SOURCE &&
+		    uri_is_equal (full_uri, node->uri)) {
+			error_set (error, GBF_PROJECT_ERROR_ALREADY_EXISTS,
+				   _("Source file is already in given target"));
+			abort_action = TRUE;
+		}
+		iter_node = g_node_next_sibling (iter_node);
+	}
+
+	/* have there been any errors? */
+	if (abort_action) {
+		g_free (full_uri);
+		return NULL;
+	}
+	
+	/* Create the update xml */
+	doc = xml_new_change_doc (project);
+
+	if (!xml_write_add_source (project, doc, g_node, full_uri)) {
+		error_set (error, GBF_PROJECT_ERROR_GENERAL_FAILURE,
+			   _("General failure in adding source file"));
+		abort_action = TRUE;
+	}
+
+	g_free (full_uri);
+	if (abort_action) {
+		xmlFreeDoc (doc);
+		return NULL;
+	}
+	
+	DEBUG ({
+		xmlSetDocCompressMode (doc, 0);
+		xmlSaveFile ("/tmp/add-source.xml", doc);
+	});
+
+	/* Update the project */
+	if (!project_update (project, doc, &change_set, error)) {
+		error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+			   _("Unable to update project"));
+		xmlFreeDoc (doc);
+		return NULL;
+	}
+	xmlFreeDoc (doc);
+
+	/* get newly created source id */
+	retval = NULL;
+	DEBUG (change_set_debug_print (change_set));
+	change = change_set_find (change_set, GBF_AM_CHANGE_ADDED,
+				  GBF_AM_NODE_SOURCE);
+	if (change) {
+		retval = g_strdup (change->id);
+	} else {
+		error_set (error, GBF_PROJECT_ERROR_GENERAL_FAILURE,
+			   _("Newly added source file could not be identified"));
+	}
+	change_set_destroy (change_set);
+	return retval;
+}
+
+static void 
+impl_remove_source (GbfProject  *_project,
+		    const gchar *id,
+		    GError     **error)
+{
+	GbfAmProject *project;
+	GNode *g_node;
+	xmlDocPtr doc;
+	
+	g_return_if_fail (GBF_IS_AM_PROJECT (_project));
+
+	project = GBF_AM_PROJECT (_project);
+	
+	/* Find the source */
+	g_node = g_hash_table_lookup (project->sources, id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Source doesn't exist"));
+		return;
+	}
+
+	/* Create the update xml */
+	doc = xml_new_change_doc (project);
+	if (!xml_write_remove_source (project, doc, g_node)) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Source couldn't be removed"));
+		xmlFreeDoc (doc);
+		return;
+	}
+
+	DEBUG ({
+		xmlSetDocCompressMode (doc, 0);
+		xmlSaveFile ("/tmp/remove-source.xml", doc);
+	});
+
+	/* Update the project */
+	/* FIXME: should get and process the change set to verify that
+	 * the source has been removed? */
+	if (!project_update (project, doc, NULL, error)) {
+		error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+			   _("Unable to update project"));
+	}
+	xmlFreeDoc (doc);
+}
+
+static GtkWidget *
+impl_configure (GbfProject *_project, GError **error)
+{
+	GtkWidget *wid = NULL;
+	GError *err = NULL;
+	
+	g_return_val_if_fail (GBF_IS_PROJECT (_project), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+	
+	wid = gbf_am_properties_get_widget (GBF_AM_PROJECT (_project), &err);
+	if (err) {
+		g_propagate_error (error, err);
+	}
+	return wid;
+}
+
+static GList *
+impl_get_config_modules   (GbfProject *project, GError **error)
+{
+	GbfAmConfigMapping *config;
+	GbfAmConfigValue *value;
+	GError* err = NULL;
+	const gchar* pkg_modules;
+	GList* result = NULL;
+	
+	config = gbf_am_project_get_config (GBF_AM_PROJECT(project), &err);
+	if (err) 
+	{
+		g_propagate_error (error, err);
+		return NULL;
+	}
+	if ((value = gbf_am_config_mapping_lookup (config, "pkg_check_modules")) &&
+	    (pkg_modules = gbf_am_config_value_get_string (value)))
+	{
+		gchar **module;
+		gchar **modules = g_strsplit (pkg_modules, ", ", -1);
+		for (module = modules; *module != NULL; ++module) 
+		{
+			result = g_list_prepend (result, g_strdup (*module));
+		}
+		g_strfreev (modules);
+	}
+	return result;
+}
+
+static gboolean
+package_is_valid (const gchar* package)
+{
+	const gchar* c = package;
+	while (*c != '\0')
+	{
+		if (!g_ascii_isalnum (*c) &&
+		    (*c != '_') && (*c != '-') && (*c != '.') && (*c != '+'))
+		{
+			return FALSE;
+		}
+		c++;
+	}
+	return TRUE;
+}
+
+static GList *
+impl_get_config_packages  (GbfProject *project,
+			   const gchar* module,
+			   GError **error)
+{
+	GbfAmConfigMapping *config;
+	GbfAmConfigValue *module_info;
+	GbfAmConfigMapping *module_info_map;
+	GError* err = NULL;
+	GList* result = NULL;
+	
+	config = gbf_am_project_get_config (GBF_AM_PROJECT(project), &err);
+	if (err) 
+	{
+		g_propagate_error (error, err);
+		return NULL;
+	}
+			
+	gchar *module_key = g_strconcat ("pkg_check_modules_",
+					 module,
+					 NULL);
+	
+	if ((module_info = gbf_am_config_mapping_lookup (config, module_key)) &&
+	    (module_info_map = gbf_am_config_value_get_mapping (module_info))) 
+	{
+		GbfAmConfigValue *pkgs_val;
+		const gchar *packages;
+		
+		if ((pkgs_val = gbf_am_config_mapping_lookup (module_info_map, "packages")) &&
+		    (packages = gbf_am_config_value_get_string (pkgs_val))) 
+		{
+			gchar **pkgs, **pkg;
+			pkgs = g_strsplit (packages, ", ", -1);
+			for (pkg = pkgs; *pkg != NULL; ++pkg) 
+			{
+				gchar* version;
+				if ((version = strchr (*pkg, ' ')))
+					*version = '\0';
+				if (package_is_valid (*pkg))
+				{
+					result = g_list_append (result, 
+								g_strdup (*pkg));
+				}
+			}			
+			g_strfreev (pkgs);
+		}		
+	}
+	g_free (module_key);
+	return result;
+}
+
+static void
+gbf_am_project_class_init (GbfAmProjectClass *klass)
+{
+	GObjectClass *object_class;
+	GbfProjectClass *project_class;
+
+	object_class = G_OBJECT_CLASS (klass);
+	project_class = GBF_PROJECT_CLASS (klass);
+	parent_class = g_type_class_peek_parent (klass);
+
+	object_class->dispose = gbf_am_project_dispose;
+	object_class->get_property = gbf_am_project_get_property;
+
+	project_class->load = impl_load;
+	project_class->probe = impl_probe;
+	project_class->refresh = impl_refresh;
+	project_class->get_capabilities = impl_get_capabilities;
+
+	project_class->add_group = impl_add_group;
+	project_class->remove_group = impl_remove_group;
+	project_class->get_group = impl_get_group;
+	project_class->get_all_groups = impl_get_all_groups;
+	project_class->configure_group = impl_configure_group;
+	project_class->configure_new_group = impl_configure_new_group;
+
+	project_class->add_target = impl_add_target;
+	project_class->remove_target = impl_remove_target;
+	project_class->get_target = impl_get_target;
+	project_class->get_all_targets = impl_get_all_targets;
+	project_class->configure_target = impl_configure_target;
+	project_class->configure_new_target = impl_configure_new_target;
+
+	project_class->add_source = impl_add_source;
+	project_class->remove_source = impl_remove_source;
+	project_class->get_source = impl_get_source;
+	project_class->get_all_sources = impl_get_all_sources;
+	project_class->configure_source = impl_configure_source;
+	project_class->configure_new_source = impl_configure_new_source;
+
+	project_class->configure = impl_configure;
+	project_class->get_config_modules = impl_get_config_modules;
+	project_class->get_config_packages = impl_get_config_packages;
+	
+	project_class->name_for_type = impl_name_for_type;
+	project_class->mimetype_for_type = impl_mimetype_for_type;
+	project_class->get_types = impl_get_types;
+	
+	/* default signal handlers */
+	project_class->project_updated = NULL;
+
+	/* FIXME: shouldn't we use '_' instead of '-' ? */
+	g_object_class_install_property 
+		(object_class, PROP_PROJECT_DIR,
+		 g_param_spec_string ("project-dir", 
+				      _("Project directory"),
+				      _("Project directory"),
+				      NULL,
+				      G_PARAM_READABLE));
+}
+
+static void
+gbf_am_project_instance_init (GbfAmProject *project)
+{
+	/* initialize data & monitors */
+	project->project_root_uri = NULL;
+	project_data_init (project);
+
+	/* setup queue */
+	project->queue_ops = g_queue_new ();
+	project->queue_handler_tag = 0;
+	
+	/* initialize build callbacks */
+	project->callbacks = NULL;
+
+	/* FIXME: those path should be configurable */
+	project->make_command = g_strdup ("/usr/bin/make");
+	project->configure_command = g_strdup ("./configure");
+	project->autogen_command = g_strdup ("./autogen.sh");
+	project->install_prefix = g_strdup ("/gnome");
+}
+
+static void
+gbf_am_project_dispose (GObject *object)
+{
+	GbfAmProject *project;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (GBF_IS_AM_PROJECT (object));
+
+	project = GBF_AM_PROJECT (object);
+
+	/* project data & monitors */
+	project_data_destroy (project);
+	g_free (project->project_root_uri);
+	project->project_root_uri = NULL;
+
+	g_free (project->make_command);
+	g_free (project->configure_command);
+	g_free (project->autogen_command);
+	g_free (project->install_prefix);
+	
+	GNOME_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
+}
+
+static void
+gbf_am_project_get_property (GObject    *object,
+			     guint       prop_id,
+			     GValue     *value,
+			     GParamSpec *pspec)
+{
+	GbfAmProject *project = GBF_AM_PROJECT (object);
+
+	switch (prop_id) {
+		case PROP_PROJECT_DIR:
+			g_value_set_string (value, project->project_root_uri);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+GbfProject *
+gbf_am_project_new (void)
+{
+	return GBF_PROJECT (g_object_new (GBF_TYPE_AM_PROJECT, NULL));
+}
+
+GBF_BACKEND_BOILERPLATE (GbfAmProject, gbf_am_project);

Added: trunk/plugins/gbf-am/gbf-am-project.h
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/gbf-am-project.h	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,131 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8; coding: utf-8 -*- */
+/* gbf-am-project.h
+ *
+ * Copyright (C) 2000  JP Rosevear
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: JP Rosevear
+ */
+
+#ifndef _GBF_AM_PROJECT_H_
+#define _GBF_AM_PROJECT_H_
+
+#include <glib-object.h>
+#include <libanjuta/gbf-project.h>
+#include <libanjuta/anjuta-plugin.h>
+#include "gbf-am-config.h"
+
+G_BEGIN_DECLS
+
+#define GBF_TYPE_AM_PROJECT		(gbf_am_project_get_type (NULL))
+#define GBF_AM_PROJECT(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), GBF_TYPE_AM_PROJECT, GbfAmProject))
+#define GBF_AM_PROJECT_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass), GBF_TYPE_AM_PROJECT, GbfAmProjectClass))
+#define GBF_IS_AM_PROJECT(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GBF_TYPE_AM_PROJECT))
+#define GBF_IS_AM_PROJECT_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((obj), GBF_TYPE_AM_PROJECT))
+
+typedef struct _GbfAmProject        GbfAmProject;
+typedef struct _GbfAmProjectClass   GbfAmProjectClass;
+typedef struct _GbfAmBuildCallback  GbfAmBuildCallback;
+typedef struct _GbfAmNode           GbfAmNode;
+
+typedef enum {
+	GBF_AM_NODE_GROUP,
+	GBF_AM_NODE_TARGET,
+	GBF_AM_NODE_SOURCE
+} GbfAmNodeType;
+	
+struct _GbfAmNode {
+	GbfAmNodeType       type;
+	gchar              *id;        /* unique id among nodes of the same type */
+	gchar              *name;      /* user visible string */
+	GbfAmConfigMapping *config;
+	gchar              *uri;       /* groups: path to Makefile.am;
+					  targets: NULL;
+					  sources: file uri */
+	gchar              *detail;    /* groups: NULL;
+					  targets: target type;
+					  sources: NULL or target dependency for built sources */
+};
+
+struct _GbfAmProject {
+	GbfProject          parent;
+
+	/* uri of the project; this can be a full uri, even though we
+	 * can only work with native local files */
+	gchar              *project_root_uri;
+
+	/* project data */
+	gchar              *project_file;      /* configure.in uri */
+	GbfAmConfigMapping *project_config;    /* project configuration
+						* (i.e. from configure.in) */
+	GNode              *root_node;         /* tree containing project data;
+						* each GNode's data is a
+						* GbfAmNode, and the root of
+						* the tree is the root group. */
+
+	/* shortcut hash tables, mapping id -> GNode from the tree above */
+	GHashTable         *groups;
+	GHashTable         *targets;
+	GHashTable         *sources;
+	
+	/* project files monitors */
+	GHashTable         *monitors;
+
+	/* operations queue */
+	GQueue             *queue_ops;
+        guint               queue_handler_tag;
+
+	/* build callbacks */
+	GList              *callbacks;
+
+	/* build config */
+	gchar	 	   *make_command;
+	gchar 		   *configure_command;
+	gchar 		   *autogen_command;
+	gchar 		   *install_prefix;
+};
+
+struct _GbfAmProjectClass {
+	GbfProjectClass parent_class;
+};
+
+/* convenient shortcut macro the get the GbfAmNode from a GNode */
+#define GBF_AM_NODE(g_node)  ((g_node) != NULL ? (GbfAmNode *)((g_node)->data) : NULL)
+
+GType         gbf_am_project_get_type (GTypeModule *plugin);
+GbfProject   *gbf_am_project_new      (void);
+
+/* FIXME: The config infrastructure should probably be made part of GbfProject
+ * so that other backend implementations could use them directly and we don't
+ * have to create separate configuration widgets. But then different back end
+ * implementations could have significantly different config management
+ * warranting separate implementations.
+ */
+/* These functions returns a copy of the config. It should be free with
+ * gbf_am_config_value_free() when no longer required
+ */
+GbfAmConfigMapping *gbf_am_project_get_config (GbfAmProject *project, GError **error);
+GbfAmConfigMapping *gbf_am_project_get_group_config (GbfAmProject *project, const gchar *group_id, GError **error);
+GbfAmConfigMapping *gbf_am_project_get_target_config (GbfAmProject *project, const gchar *target_id, GError **error);
+
+void gbf_am_project_set_config (GbfAmProject *project, GbfAmConfigMapping *new_config, GError **error);
+void gbf_am_project_set_group_config (GbfAmProject *project, const gchar *group_id, GbfAmConfigMapping *new_config, GError **error);
+void gbf_am_project_set_target_config (GbfAmProject *project, const gchar *target_id, GbfAmConfigMapping *new_config, GError **error);
+
+G_END_DECLS
+
+#endif /* _GBF_AM_PROJECT_H_ */

Added: trunk/plugins/gbf-am/gbf-am-properties.c
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/gbf-am-properties.c	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,1577 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8; coding: utf-8 -*- */
+/* gbf-am-properties.c
+ *
+ * Copyright (C) 2005  Naba Kumar
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Naba Kumar
+ */
+
+#include <config.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <gtk/gtk.h>
+#include <glade/glade-xml.h>
+
+#include <glib/gi18n.h>
+
+#include "gbf-am-config.h"
+#include "gbf-am-properties.h"
+
+#define GLADE_FILE  PACKAGE_DATA_DIR "/glade/gbf-am-dialogs.glade"
+
+typedef enum {
+	GBF_AM_CONFIG_LABEL,
+	GBF_AM_CONFIG_ENTRY,
+	GBF_AM_CONFIG_TEXT,
+	GBF_AM_CONFIG_LIST,
+} GbfConfigPropertyType;
+
+enum {
+	COL_PACKAGE,
+	COL_VERSION,
+	N_COLUMNS
+};
+
+enum {
+	COL_PKG_PACKAGE,
+	COL_PKG_DESCRIPTION,
+	N_PKG_COLUMNS
+};
+
+enum {
+	COL_VAR_NAME,
+	COL_VAR_VALUE,
+	N_VAR_COLUMNS
+};
+
+static void
+on_property_entry_changed (GtkEntry *entry, GbfAmConfigValue *value)
+{
+	gbf_am_config_value_set_string (value, gtk_entry_get_text (entry));
+}
+
+static void
+add_configure_property (GbfAmProject *project, GbfAmConfigMapping *config,
+			GbfConfigPropertyType prop_type,
+			const gchar *display_name, const gchar *direct_value,
+			const gchar *config_key, GtkWidget *table,
+			gint position)
+{
+	GtkWidget *label;
+	const gchar *value;
+	GtkWidget *widget;
+	GbfAmConfigValue *config_value = NULL;
+	
+	value = "";
+	if (direct_value)
+	{
+		value = direct_value;
+	} else {
+		config_value = gbf_am_config_mapping_lookup (config,
+							     config_key);
+		if (!config_value) {
+			config_value = gbf_am_config_value_new (GBF_AM_TYPE_STRING);
+			gbf_am_config_value_set_string (config_value, "");
+			gbf_am_config_mapping_insert (config, config_key,
+						      config_value);
+		}
+		if (config_value && config_value->type == GBF_AM_TYPE_STRING) {
+			const gchar *val_str;
+			val_str = gbf_am_config_value_get_string (config_value);
+			if (val_str)
+				value = val_str;
+		}
+	}
+	
+	label = gtk_label_new (display_name);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, -1);
+	gtk_widget_show (label);
+	gtk_table_attach (GTK_TABLE (table), label, 0, 1, position, position+1,
+			  GTK_FILL, GTK_FILL, 5, 3);
+	switch (prop_type) {
+		case GBF_AM_CONFIG_LABEL:
+			widget = gtk_label_new (value);
+			gtk_misc_set_alignment (GTK_MISC (widget), 0, -1);
+			break;
+		case GBF_AM_CONFIG_ENTRY:
+			widget = gtk_entry_new ();
+			gtk_entry_set_text (GTK_ENTRY (widget), value);
+			if (config_value) {
+				g_signal_connect (widget, "changed",
+						  G_CALLBACK (on_property_entry_changed),
+						  config_value);
+			}
+			break;
+		default:
+			g_warning ("Should not reach here");
+			widget = gtk_label_new (_("Unknown"));
+			gtk_misc_set_alignment (GTK_MISC (widget), 0, -1);
+	}
+	gtk_widget_show (widget);
+	gtk_table_attach (GTK_TABLE (table), widget, 1, 2, position, position+1,
+			  GTK_FILL | GTK_EXPAND, GTK_FILL, 5, 3);
+}
+
+static void
+recursive_config_foreach_cb (const gchar *key, GbfAmConfigValue *value,
+			     gpointer user_data)
+{
+	GtkWidget *table;
+	GtkWidget *label;
+	GtkWidget *widget;
+	gint position;
+	
+	table = GTK_WIDGET (user_data);
+	position = g_list_length (GTK_TABLE (table)->children);
+	
+	label = gtk_label_new (key);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, -1);
+	gtk_widget_show (label);
+	gtk_table_attach (GTK_TABLE (table), label, 0, 1,
+			  position, position+1,
+			  GTK_FILL, GTK_FILL, 5, 3);
+		
+	if (value->type == GBF_AM_TYPE_STRING) {
+		widget = gtk_entry_new ();
+		gtk_entry_set_text (GTK_ENTRY (widget),
+				    gbf_am_config_value_get_string (value));
+		g_signal_connect (widget, "changed",
+				  G_CALLBACK (on_property_entry_changed),
+				  value);
+	} else if (value->type == GBF_AM_TYPE_LIST) {
+		/* FIXME: */
+		widget = gtk_label_new ("FIXME");
+		gtk_misc_set_alignment (GTK_MISC (widget), 0, -1);
+	} else if (value->type == GBF_AM_TYPE_MAPPING) {
+		/* FIXME: */
+		widget = gtk_label_new ("FIXME");
+		gtk_misc_set_alignment (GTK_MISC (widget), 0, -1);
+	} else {
+		g_warning ("Should not be here");
+		widget = gtk_label_new (_("Unknown"));
+		gtk_misc_set_alignment (GTK_MISC (widget), 0, -1);
+	}
+	gtk_widget_show (widget);
+	gtk_table_attach (GTK_TABLE (table), widget, 1, 2,
+			  position, position+1,
+			  GTK_FILL | GTK_EXPAND, GTK_FILL, 5, 3);
+}
+
+static GtkListStore *
+packages_get_pkgconfig_list (void)
+{
+	GtkListStore *store;
+	GtkTreeIter iter;
+	gchar line[1024];
+	gchar *tmpfile, *pkg_cmd;
+	FILE *pkg_fd;
+	
+	store = gtk_list_store_new (N_PKG_COLUMNS, G_TYPE_STRING,
+				    G_TYPE_STRING);
+	
+	/* Now get all the pkg-config info */
+	tmpfile = g_strdup_printf ("%s%cpkgmodules--%d", g_get_tmp_dir (),
+				   G_DIR_SEPARATOR, getpid());
+	pkg_cmd = g_strconcat ("pkg-config --list-all 2>/dev/null | sort > ",
+			       tmpfile, NULL);
+	if (system (pkg_cmd) == -1)
+		return store;
+	pkg_fd = fopen (tmpfile, "r");
+	if (!pkg_fd) {
+		g_warning ("Can not open %s for reading", tmpfile);
+		g_free (tmpfile);
+		return store;
+	}
+	while (fgets (line, 1024, pkg_fd)) {
+		gchar *name_end;
+		gchar *desc_start;
+		gchar *description;
+		gchar *name;
+		
+		if (line[0] == '\0')
+			continue;
+		
+		name_end = line;
+		while (!isspace(*name_end))
+			name_end++;
+		desc_start = name_end;
+		while (isspace(*desc_start))
+			desc_start++;
+		
+		name = g_strndup (line, name_end-line);
+		description = g_strndup (desc_start, strlen (desc_start)-1);
+		
+		gtk_list_store_append (store, &iter);
+		gtk_list_store_set (store, &iter,
+				    COL_PKG_PACKAGE, name,
+				    COL_PKG_DESCRIPTION, description,
+				    -1);
+	}
+	fclose (pkg_fd);
+	unlink (tmpfile);
+	g_free (tmpfile);
+	return store;
+}
+
+static void
+save_packages_list (GbfAmProject *project, GbfAmConfigMapping *config,
+		    GtkTreeModel *model, GtkTreeIter *parent)
+{
+	gchar *key_name;
+	gchar *module_name;
+	GbfAmConfigValue *value;
+	GtkTreeIter child;
+	gboolean has_children;
+	GString *packages_list = g_string_new (NULL);
+	
+	gtk_tree_model_get (model, parent, COL_PACKAGE, &module_name, -1);
+	has_children = gtk_tree_model_iter_children (model, &child, parent);
+	if (has_children)
+	{
+	    do
+	    {
+		    gchar *package, *version;
+		    gtk_tree_model_get (model, &child, COL_PACKAGE,
+					&package, COL_VERSION,
+					&version, -1);
+		    if (strlen (packages_list->str) > 0)
+			g_string_append (packages_list, ", ");
+		    g_string_append (packages_list, package);
+		    if (version)
+		    {
+			    g_string_append (packages_list, " ");
+			    g_string_append (packages_list, version);
+		    }
+		    g_free (package);
+		    g_free (version);
+	    }
+	    while (gtk_tree_model_iter_next (model, &child));
+	}
+	
+	if (strlen (packages_list->str) > 0)
+	{
+		GbfAmConfigMapping *pkgmodule;
+		key_name = g_strconcat ("pkg_check_modules_",
+					module_name,
+					NULL);
+	
+		value = gbf_am_config_mapping_lookup (config, key_name);
+		if (!value)
+		{
+			pkgmodule = gbf_am_config_mapping_new ();
+			value = gbf_am_config_value_new (GBF_AM_TYPE_MAPPING);
+			gbf_am_config_value_set_mapping (value, pkgmodule);
+			gbf_am_config_mapping_insert (config,
+						      key_name,
+						      value);
+		}
+		pkgmodule = gbf_am_config_value_get_mapping (value);
+		value = gbf_am_config_mapping_lookup (pkgmodule,
+						      "packages");
+		if (!value)
+		{
+			value = gbf_am_config_value_new (GBF_AM_TYPE_STRING);
+			gbf_am_config_value_set_string (value, packages_list->str);
+			gbf_am_config_mapping_insert (pkgmodule,
+						      "packages",
+						      value);
+		}
+		else
+		{
+			gbf_am_config_value_set_string (value, packages_list->str);
+		}
+		g_free (key_name);
+	}
+	g_free (module_name);
+}
+
+static void
+package_edited_cb (GtkCellRendererText *cellrenderertext, gchar *arg1,
+		   gchar *arg2, GtkWidget *top_level)
+{
+	GtkTreeView *treeview;
+	GtkTreeSelection *selection;
+	GtkTreeIter iter, parent;
+	GtkTreeModel *model;
+	GbfAmProject *project;
+	GbfAmConfigMapping *config;
+	gboolean has_parent;
+
+	if (strcmp (arg1, arg2) == 0)
+		return;
+	project = g_object_get_data (G_OBJECT (top_level), "__project");
+	config = g_object_get_data (G_OBJECT (top_level), "__config");
+	treeview = g_object_get_data (G_OBJECT (project),
+				      "__packages_treeview");
+	selection = gtk_tree_view_get_selection (treeview);
+	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+		return;
+	
+	/* If the selected node is module name and the module has children,
+	 * do not alow editing it
+	 */
+	has_parent = gtk_tree_model_iter_parent (model, &parent, &iter);
+	if (!has_parent &&
+	    gtk_tree_model_iter_n_children (model, &iter) > 0)
+		return;
+	if (strcmp (arg2, _("Enter new module")) == 0 ||
+	    strcmp (arg2, "") == 0)
+		gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
+	else
+		gtk_tree_store_set (GTK_TREE_STORE (model), &iter, COL_PACKAGE,
+				    arg2, -1);
+	if (has_parent)
+	    save_packages_list (project, config, model, &parent);
+}
+
+static void
+package_version_edited_cb (GtkCellRendererText *cellrenderertext, gchar *arg1,
+			   gchar *arg2, GtkWidget *top_level)
+{
+	GtkTreeView *treeview;
+	GtkTreeSelection *selection;
+	GtkTreeIter iter, parent;
+	GtkTreeModel *model;
+	GbfAmProject *project;
+	GbfAmConfigMapping *config;
+	
+	if (strcmp (arg1, arg2) == 0)
+		return;
+	project = g_object_get_data (G_OBJECT (top_level), "__project");
+	config = g_object_get_data (G_OBJECT (top_level), "__config");
+	
+	treeview = g_object_get_data (G_OBJECT (project),
+				      "__packages_treeview");
+	selection = gtk_tree_view_get_selection (treeview);
+	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+		return;
+	if (!gtk_tree_model_iter_parent (model, &parent, &iter))
+		return;
+	gtk_tree_store_set (GTK_TREE_STORE (model), &iter, COL_VERSION,
+			    arg2, -1);
+	save_packages_list (project, config, model, &parent);
+}
+
+static void
+add_package_module_clicked_cb (GtkWidget *button, GbfAmProject *project)
+{
+	GtkTreeView *treeview;
+	GtkTreeSelection *selection;
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	
+	treeview = g_object_get_data (G_OBJECT (project),
+				      "__packages_treeview");
+	model = gtk_tree_view_get_model (treeview);
+	gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL);
+	gtk_tree_store_set (GTK_TREE_STORE (model), &iter, COL_PACKAGE,
+			    _("Enter new module"), -1);
+	selection = gtk_tree_view_get_selection (treeview);
+	gtk_tree_selection_select_iter (selection, &iter);
+	
+	path = gtk_tree_model_get_path (model, &iter);
+	gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(treeview), path,
+				      NULL, FALSE, 0, 0);
+	gtk_tree_view_set_cursor (GTK_TREE_VIEW(treeview), path,
+				  gtk_tree_view_get_column(GTK_TREE_VIEW(treeview),
+							   COL_PACKAGE), TRUE);
+	gtk_tree_path_free (path);
+}
+
+static void
+add_package_clicked_cb (GtkWidget *button, GbfAmProject *project)
+{
+	GladeXML *gxml;
+	GtkTreeView *treeview;
+	GtkTreeSelection *selection;
+	GtkTreeIter iter, parent;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	GtkWidget *dlg;
+	GtkWidget *pkg_treeview;
+	GtkListStore *store;
+	GtkCellRenderer *renderer;
+	GtkTreeViewColumn *col;
+	gchar *pkg_to_add = NULL;
+	GbfAmConfigMapping *config;
+	
+	/* Let user select a package */
+	gxml = glade_xml_new (GLADE_FILE, "package_selection_dialog",
+			      GETTEXT_PACKAGE);
+	dlg = glade_xml_get_widget (gxml, "package_selection_dialog");
+	pkg_treeview = glade_xml_get_widget (gxml, "pkg_treeview");
+	renderer = gtk_cell_renderer_text_new ();
+	col = gtk_tree_view_column_new_with_attributes (_("Module/Packages"),
+							renderer,
+							"text", COL_PKG_PACKAGE,
+							NULL);
+	gtk_tree_view_append_column (GTK_TREE_VIEW (pkg_treeview), col);
+	renderer = gtk_cell_renderer_text_new ();
+	col = gtk_tree_view_column_new_with_attributes (_("Version"),
+							renderer,
+							"text",
+							COL_PKG_DESCRIPTION,
+							NULL);
+	gtk_tree_view_append_column (GTK_TREE_VIEW (pkg_treeview), col);
+	
+	store = packages_get_pkgconfig_list ();
+	gtk_tree_view_set_model (GTK_TREE_VIEW (pkg_treeview),
+				 GTK_TREE_MODEL (store));
+	if (gtk_dialog_run (GTK_DIALOG (dlg)) == GTK_RESPONSE_ACCEPT)
+	{
+		GtkTreeSelection *sel;
+		GtkTreeIter pkg_iter;
+		
+		sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (pkg_treeview));
+		if (gtk_tree_selection_get_selected (sel, NULL, &pkg_iter))
+		{
+			gtk_tree_model_get (GTK_TREE_MODEL (store),
+					    &pkg_iter, COL_PKG_PACKAGE,
+					    &pkg_to_add, -1);
+		}
+	}
+	gtk_widget_destroy (dlg);
+	
+	if (!pkg_to_add)
+		return;
+	
+	treeview = g_object_get_data (G_OBJECT (project),
+				      "__packages_treeview");
+	config = g_object_get_data (G_OBJECT (project), "__config");
+	
+	selection = gtk_tree_view_get_selection (treeview);
+	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+	{
+		g_free (pkg_to_add);
+		return;
+	}
+	if (!gtk_tree_model_iter_parent (model, &parent, &iter))
+		gtk_tree_selection_get_selected (selection, &model, &parent);
+	gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &parent);
+	gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
+			    COL_PACKAGE, pkg_to_add, -1);
+	save_packages_list (project, config, model, &parent);
+	
+	g_free (pkg_to_add);
+	
+	path = gtk_tree_model_get_path (model, &parent);
+	gtk_tree_view_expand_row (GTK_TREE_VIEW (treeview), path, TRUE);
+	gtk_tree_path_free (path);
+	
+	gtk_tree_selection_select_iter (selection, &iter);
+	
+	path = gtk_tree_model_get_path (model, &iter);
+	gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(treeview), path,
+				      NULL, FALSE, 0, 0);
+	gtk_tree_view_set_cursor (GTK_TREE_VIEW(treeview), path,
+				  gtk_tree_view_get_column(GTK_TREE_VIEW(treeview),
+							   COL_PACKAGE),
+				  FALSE);
+	gtk_tree_path_free (path);
+}
+
+static void
+remove_package_clicked_cb (GtkWidget *button, GbfAmProject *project)
+{
+	GtkTreeView *treeview;
+	GtkTreeSelection *selection;
+	GtkTreeIter iter, parent;
+	GtkTreeModel *model;
+	const gchar *msg;
+	gchar *name;
+	GtkWidget *dlg;
+	gboolean has_parent;
+	GbfAmConfigMapping *config;
+	
+	treeview = g_object_get_data (G_OBJECT (project),
+				      "__packages_treeview");
+	config = g_object_get_data (G_OBJECT (project),
+				      "__config");
+	selection = gtk_tree_view_get_selection (treeview);
+	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+		return;
+	gtk_tree_model_get (model, &iter, COL_PACKAGE, &name, -1);
+	has_parent = gtk_tree_model_iter_parent (model, &parent, &iter);
+	if (!has_parent)
+		msg = _("Are you sure you want to remove module \"%s\" and all its associated packages?");
+	else
+		msg = _("Are you sure you want to remove package \"%s\"?");
+	dlg = gtk_message_dialog_new_with_markup (NULL,
+						  GTK_DIALOG_DESTROY_WITH_PARENT,
+						  GTK_MESSAGE_QUESTION,
+						  GTK_BUTTONS_YES_NO,
+						  msg, name);
+	if (gtk_dialog_run (GTK_DIALOG (dlg)) == GTK_RESPONSE_YES)
+		gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
+	gtk_widget_destroy (dlg);
+	g_free (name);
+	if (has_parent)
+		save_packages_list (project, config, model, &parent);
+}
+
+static void
+packages_treeview_selection_changed_cb (GtkTreeSelection *selection,
+					GbfAmProject *project)
+{
+	GtkTreeIter iter/*, parent*/;
+	GtkTreeModel *model;
+	GtkWidget *add_module_button, *add_package_button, *remove_button;
+	
+	add_module_button = g_object_get_data (G_OBJECT (project),
+					       "__add_module_button");
+	add_package_button = g_object_get_data (G_OBJECT (project),
+					       "__add_package_button");
+	remove_button = g_object_get_data (G_OBJECT (project),
+					       "__remove_button");
+	
+	gtk_widget_set_sensitive (add_module_button, TRUE);
+	gtk_widget_set_sensitive (add_package_button, TRUE);
+	gtk_widget_set_sensitive (remove_button, TRUE);
+	
+	if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
+		gtk_widget_set_sensitive (add_package_button, FALSE);
+		gtk_widget_set_sensitive (remove_button, FALSE);
+	}		
+}
+
+static void
+variable_name_edited_cb (GtkCellRendererText *cellrenderertext, gchar *arg1,
+			 gchar *arg2, GtkWidget *top_level)
+{
+	GtkTreeView *treeview;
+	GtkTreeSelection *selection;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	GbfAmProject *project;
+	
+	project = g_object_get_data (G_OBJECT (top_level), "__project");
+	treeview = g_object_get_data (G_OBJECT (project),
+				      "__variables_treeview");
+	selection = gtk_tree_view_get_selection (treeview);
+	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+		return;
+	g_message ("Var name = %s", arg2);
+	if (strcmp (arg2, _("Enter new variable")) == 0 ||
+	    strcmp (arg2, "") == 0)
+	{
+		gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+	}
+	else if (strcmp (arg1, arg2) != 0 && strlen (arg2) != 0)
+	{
+		gtk_list_store_set (GTK_LIST_STORE (model), &iter, COL_VAR_NAME,
+				    arg2, -1);
+	}
+}
+
+static void
+variable_value_edited_cb (GtkCellRendererText *cellrenderertext, gchar *arg1,
+			 gchar *arg2, GtkWidget *top_level)
+{
+	GtkTreeView *treeview;
+	GtkTreeSelection *selection;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	GbfAmConfigMapping *config;
+	GbfAmConfigMapping *variables;
+	GbfAmConfigValue *value;
+	GbfAmProject *project;
+	gchar *variable_name;
+	
+	project = g_object_get_data (G_OBJECT (top_level), "__project");
+	config = g_object_get_data (G_OBJECT (top_level), "__config");
+	
+	if (strcmp (arg1, arg2) == 0)
+		return;
+	treeview = g_object_get_data (G_OBJECT (project),
+				      "__variables_treeview");
+	selection = gtk_tree_view_get_selection (treeview);
+	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+		return;
+
+	gtk_list_store_set (GTK_LIST_STORE (model), &iter, COL_VAR_VALUE,
+			    arg2, -1);
+	gtk_tree_model_get (model, &iter, COL_VAR_NAME, &variable_name, -1);
+
+	value = gbf_am_config_mapping_lookup (config, "variables");
+	if (!value)
+	{
+		variables = gbf_am_config_mapping_new ();
+		value = gbf_am_config_value_new (GBF_AM_TYPE_MAPPING);
+		gbf_am_config_value_set_mapping (value, variables);
+		gbf_am_config_mapping_insert (config, "variables", value);
+					      
+	}
+	else
+	{
+		variables = gbf_am_config_value_get_mapping (value);
+	}
+	value = gbf_am_config_mapping_lookup (variables, variable_name);
+
+	if (!value)
+	{
+		value = gbf_am_config_value_new (GBF_AM_TYPE_STRING);
+		gbf_am_config_value_set_string (value, arg2);
+		gbf_am_config_mapping_insert (variables, variable_name, value);
+	}
+	else
+	{
+		gbf_am_config_value_set_string (value, arg2);
+	}
+	g_free (variable_name);
+}
+
+static void
+add_variable_clicked_cb (GtkWidget *button, GbfAmProject *project)
+{
+	GtkTreeView *treeview;
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	
+	treeview = g_object_get_data (G_OBJECT (project),
+				      "__variables_treeview");
+	model = gtk_tree_view_get_model (treeview);
+	gtk_list_store_append (GTK_LIST_STORE (model), &iter);
+	gtk_list_store_set (GTK_LIST_STORE (model), &iter, COL_VAR_NAME,
+			    _("Enter new variable"), -1);
+	
+	path = gtk_tree_model_get_path (model, &iter);
+	gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(treeview), path,
+				      NULL, FALSE, 0, 0);
+	gtk_tree_view_set_cursor (GTK_TREE_VIEW(treeview), path,
+				  gtk_tree_view_get_column(GTK_TREE_VIEW(treeview),
+							   COL_VAR_NAME),
+				  TRUE);
+	gtk_tree_path_free (path);
+}
+
+static void
+remove_variable_clicked_cb (GtkWidget *button, GtkWidget *top_level)
+{
+	GtkTreeView *treeview;
+	GtkTreeSelection *selection;
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+	const gchar *msg;
+	gchar *name;
+	GtkWidget *dlg;
+	GbfAmProject *project = g_object_get_data (G_OBJECT (top_level), "__project");
+	GbfAmConfigMapping *config = g_object_get_data (G_OBJECT (top_level), "__config");
+	
+	treeview = g_object_get_data (G_OBJECT (project),
+				      "__variables_treeview");
+	selection = gtk_tree_view_get_selection (treeview);
+	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+		return;
+	gtk_tree_model_get (model, &iter, COL_VAR_NAME, &name, -1);
+	msg = _("Are you sure you want to remove variable \"%s\"?");
+	dlg = gtk_message_dialog_new_with_markup (NULL,
+						  GTK_DIALOG_DESTROY_WITH_PARENT,
+						  GTK_MESSAGE_QUESTION,
+						  GTK_BUTTONS_YES_NO,
+						  msg, name);
+	if (gtk_dialog_run (GTK_DIALOG (dlg)) == GTK_RESPONSE_YES)
+	{
+		GbfAmConfigMapping *variables;
+		GbfAmConfigValue *value;
+		
+		value = gbf_am_config_mapping_lookup (config, "variables");
+		if (value)
+		{
+			variables = gbf_am_config_value_get_mapping (value);
+			value = gbf_am_config_value_new (GBF_AM_TYPE_STRING);
+			gbf_am_config_value_set_string (value, "");
+			gbf_am_config_mapping_insert (variables, name, value);
+		}
+		gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+	}
+	gtk_widget_destroy (dlg);
+	g_free (name);
+}
+
+static void
+variables_treeview_selection_changed_cb (GtkTreeSelection *selection,
+					 GbfAmProject *project)
+{
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+	GtkWidget *remove_variable_button;
+	
+	remove_variable_button = g_object_get_data (G_OBJECT (project),
+					       "__remove_variable_button");
+	
+	
+	if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
+		gtk_widget_set_sensitive (remove_variable_button, FALSE);
+	}
+	else
+	{
+		gtk_widget_set_sensitive (remove_variable_button, TRUE);
+	}
+}
+
+static void
+on_variables_hash_foreach (const gchar* variable_name, GbfAmConfigValue *value,
+                           gpointer user_data)
+{
+	GtkTreeIter iter;
+	GtkListStore *store = (GtkListStore*) user_data;
+	const gchar *variable_value = gbf_am_config_value_get_string (value);
+	gtk_list_store_append (store, &iter);
+	gtk_list_store_set (store, &iter, COL_VAR_NAME, variable_name,
+			    COL_VAR_VALUE, variable_value, -1);
+}
+
+static void
+on_project_widget_destroy (GtkWidget *wid, GtkWidget *top_level)
+{
+	GError *err = NULL;
+	
+	GbfAmProject *project = g_object_get_data (G_OBJECT (top_level), "__project");
+	GbfAmConfigMapping *new_config = g_object_get_data (G_OBJECT (top_level), "__config");
+	gbf_am_project_set_config (project, new_config, &err);
+	if (err) {
+		g_warning ("%s", err->message);
+		g_error_free (err);
+	}
+	g_object_unref (top_level);
+}
+
+GtkWidget*
+gbf_am_properties_get_widget (GbfAmProject *project, GError **error)
+{
+	GladeXML *gxml;
+	GbfAmConfigMapping *config;
+	GbfAmConfigValue *value;
+	GtkWidget *top_level;
+	GtkWidget *table;
+	GtkWidget *treeview;
+	GtkWidget *add_module_button;
+	GtkWidget *add_package_button;
+	GtkWidget *remove_button;
+	GtkWidget *add_variable_button;
+	GtkWidget *remove_variable_button;
+	GtkTreeStore *store;
+	GtkListStore *variables_store;
+	GtkTreeSelection *selection;
+	GtkTreeViewColumn *col;
+	GtkCellRenderer *renderer;
+	const gchar *pkg_modules;
+	GError *err = NULL;
+	GbfAmConfigMapping *variables;
+	
+	g_return_val_if_fail (GBF_IS_AM_PROJECT (project), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+	
+	config = gbf_am_project_get_config (project, &err);
+	if (err) {
+		g_propagate_error (error, err);
+		return NULL;
+	}
+	
+	gxml = glade_xml_new (GLADE_FILE, "project_properties_dialog",
+			      GETTEXT_PACKAGE);
+	top_level = glade_xml_get_widget (gxml, "top_level");
+        
+	g_object_set_data (G_OBJECT (top_level), "__project", project);
+	g_object_set_data_full (G_OBJECT (top_level), "__config", config,
+				(GDestroyNotify)gbf_am_config_mapping_destroy);
+	g_signal_connect (top_level, "destroy",
+			  G_CALLBACK (on_project_widget_destroy), top_level);
+	g_object_ref (top_level);
+        
+	add_module_button = glade_xml_get_widget (gxml, "add_module_button");
+	g_object_set_data (G_OBJECT (project), "__add_module_button",
+			   add_module_button);
+	
+	add_package_button = glade_xml_get_widget (gxml, "add_package_button");
+	g_object_set_data (G_OBJECT (project), "__add_package_button",
+			   add_package_button);
+	
+	remove_button = glade_xml_get_widget (gxml, "remove_button");
+	g_object_set_data (G_OBJECT (project), "__remove_button",
+			   remove_button);
+	
+	gtk_widget_set_sensitive (add_module_button, TRUE);
+	gtk_widget_set_sensitive (add_package_button, FALSE);
+	gtk_widget_set_sensitive (remove_button, FALSE);
+	
+	table = glade_xml_get_widget (gxml, "general_properties_table");
+	
+	g_object_ref (top_level);
+	gtk_container_remove (GTK_CONTAINER(top_level->parent), top_level);
+	
+	g_signal_connect (add_module_button, "clicked",
+			  G_CALLBACK (add_package_module_clicked_cb),
+			  project);
+	g_signal_connect (add_package_button, "clicked",
+			  G_CALLBACK (add_package_clicked_cb),
+			  project);
+	g_signal_connect (remove_button, "clicked",
+			  G_CALLBACK (remove_package_clicked_cb),
+			  project);
+	
+	/* Project Info */
+	add_configure_property (project, config, GBF_AM_CONFIG_LABEL,
+				_("Project:"), project->project_root_uri,
+				NULL, table, 0);
+	add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+				_("Package name:"), NULL, "package_name",
+				table, 1);
+	add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+				_("Version:"), NULL, "package_version",
+				table, 2);
+	add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+				_("Url:"), NULL, "package_url",
+				table, 3);
+	
+	/* pkg config packages */
+	store = gtk_tree_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING);
+	if ((value = gbf_am_config_mapping_lookup (config, "pkg_check_modules")) &&
+	    (pkg_modules = gbf_am_config_value_get_string (value))) {
+		GtkTreeIter module_iter;
+		gchar **module;
+		gchar **modules = g_strsplit (pkg_modules, ", ", -1);
+		module = modules;
+		while (*module) {
+			GbfAmConfigValue *module_info;
+			GbfAmConfigMapping *module_info_map;
+			
+			gchar *module_key = g_strconcat ("pkg_check_modules_",
+							 *module,
+							 NULL);
+			
+			if ((module_info = gbf_am_config_mapping_lookup (config, module_key)) &&
+			    (module_info_map = gbf_am_config_value_get_mapping (module_info))) {
+				GbfAmConfigValue *pkgs_val;
+				const gchar *packages;
+				
+				gtk_tree_store_append (store, &module_iter, NULL);
+				gtk_tree_store_set (store, &module_iter, COL_PACKAGE, *module, -1);
+				
+				if ((pkgs_val = gbf_am_config_mapping_lookup (module_info_map, "packages")) &&
+				    (packages = gbf_am_config_value_get_string (pkgs_val))) {
+					gchar **pkgs, **pkg;
+					pkgs = g_strsplit (packages, ", ", -1);
+					pkg = pkgs;
+					while (*pkg) {
+						GtkTreeIter iter;
+						gchar *version;
+						
+						gtk_tree_store_append (store, &iter, &module_iter);
+						if ((version = strchr (*pkg, ' '))) {
+							*version = '\0';
+							version++;
+							gtk_tree_store_set (store, &iter, COL_PACKAGE, *pkg,
+									    COL_VERSION, version, -1);
+						} else {
+							gtk_tree_store_set (store, &iter, COL_PACKAGE, *pkg, -1);
+						}
+						pkg++;
+					}
+					g_strfreev (pkgs);
+				}
+			}
+			g_free (module_key);
+			module++;
+		}
+		g_strfreev (modules);
+	}
+	
+	treeview = glade_xml_get_widget (gxml, "packages_treeview");
+	
+	g_object_set_data (G_OBJECT (project), "__packages_treeview", treeview);
+	g_object_set_data (G_OBJECT (project), "__config", config);
+	
+	gtk_tree_view_set_model (GTK_TREE_VIEW(treeview),
+				 GTK_TREE_MODEL (store));
+	renderer = gtk_cell_renderer_text_new ();
+	g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
+	g_signal_connect (G_OBJECT (renderer), "edited",
+			  G_CALLBACK (package_edited_cb), top_level);
+	col = gtk_tree_view_column_new_with_attributes (_("Module/Packages"),
+							renderer,
+							"text", 0, NULL);
+	gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), col);
+	renderer = gtk_cell_renderer_text_new ();
+	g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
+	g_signal_connect (G_OBJECT (renderer), "edited",
+			  G_CALLBACK (package_version_edited_cb), top_level);
+	col = gtk_tree_view_column_new_with_attributes (_("Version"),
+							renderer,
+							"text", 1, NULL);
+	gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), col);
+	gtk_tree_view_expand_all (GTK_TREE_VIEW (treeview));
+	
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
+	g_signal_connect (selection, "changed",
+			  G_CALLBACK (packages_treeview_selection_changed_cb),
+			  project);
+	
+	/* Set up variables list */
+	variables_store = gtk_list_store_new (N_VAR_COLUMNS,
+					      G_TYPE_STRING,
+					      G_TYPE_STRING,
+					      G_TYPE_BOOLEAN);
+	if ((value = gbf_am_config_mapping_lookup (config, "variables")) &&
+	    (variables = gbf_am_config_value_get_mapping (value))) {
+		gbf_am_config_mapping_foreach (variables,
+					       on_variables_hash_foreach,
+					       variables_store);
+	}
+	
+	treeview = glade_xml_get_widget (gxml, "variables_treeview");
+	g_object_set_data (G_OBJECT (project), "__variables_treeview",
+			   treeview);
+	gtk_tree_view_set_model (GTK_TREE_VIEW(treeview),
+				 GTK_TREE_MODEL (variables_store));
+	renderer = gtk_cell_renderer_text_new ();
+	g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
+	g_signal_connect (G_OBJECT (renderer), "edited",
+			  G_CALLBACK (variable_name_edited_cb), top_level);
+	col = gtk_tree_view_column_new_with_attributes (_("Variable"),
+							renderer,
+							"text", 
+							COL_VAR_NAME, NULL);
+	gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), col);
+	renderer = gtk_cell_renderer_text_new ();
+	g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
+	g_signal_connect (G_OBJECT (renderer), "edited",
+			  G_CALLBACK (variable_value_edited_cb), top_level);
+	col = gtk_tree_view_column_new_with_attributes (_("Value"),
+							renderer,
+							"text", COL_VAR_VALUE,
+							NULL);
+	gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), col);
+	gtk_tree_view_expand_all (GTK_TREE_VIEW (treeview));
+
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
+	g_signal_connect (selection, "changed",
+			  G_CALLBACK (variables_treeview_selection_changed_cb),
+			  project);
+
+	add_variable_button = glade_xml_get_widget (gxml, "add_variable_button");
+	g_object_set_data (G_OBJECT (project), "__add_variable_button",
+			   add_variable_button);
+	
+	remove_variable_button = glade_xml_get_widget (gxml, "remove_variable_button");
+	g_object_set_data (G_OBJECT (project), "__remove_variable_button",
+			   remove_variable_button);
+	gtk_widget_set_sensitive (add_variable_button, TRUE);
+	gtk_widget_set_sensitive (remove_variable_button, FALSE);
+	
+	g_signal_connect (add_variable_button, "clicked",
+			  G_CALLBACK (add_variable_clicked_cb),
+			  project);
+	g_signal_connect (remove_variable_button, "clicked",
+			  G_CALLBACK (remove_variable_clicked_cb),
+			  top_level);
+	
+	gtk_widget_show_all (top_level);
+	
+	g_object_unref (variables_store);
+	g_object_unref (store);
+	g_object_unref (gxml);
+	return top_level;
+}
+
+enum
+{
+	COLUMN_CHECK,
+	COLUMN_MODULE_NAME
+};
+
+static const gchar* get_libs_key(GbfProjectTarget *target)
+{
+	g_return_val_if_fail (target != NULL, "ldadd");
+	/* We need to care here if it is ldadd or libadd #476315 */
+	if (g_str_equal (target->type, "shared_lib"))
+	{
+		return "libadd";
+	}
+	else
+	{
+		return "ldadd";
+	}
+}
+
+static void
+on_module_activate (GtkCellRendererToggle* cell_renderer,
+		    const gchar* tree_path,
+		    GtkTreeView* tree)
+{
+	GtkTreeIter iter;
+	GtkTreeModel* model = gtk_tree_view_get_model (tree);
+	gchar* module_name;
+	gchar* module_cflags;
+	gchar* module_libs;
+	gboolean use;
+	GtkTreePath* path = gtk_tree_path_new_from_string (tree_path);
+	GbfProjectTarget* target = g_object_get_data (G_OBJECT(tree), "target");
+	GbfAmConfigMapping* config = g_object_get_data (G_OBJECT(tree), "config");
+	GbfAmConfigMapping* group_config = g_object_get_data (G_OBJECT(tree), "group_config");	
+	GbfAmConfigValue* am_cpp_value = gbf_am_config_mapping_lookup(group_config, "amcppflags");
+	GbfAmConfigValue* cpp_value = gbf_am_config_mapping_lookup(config, "cppflags");
+	GbfAmConfigValue* libs_value = gbf_am_config_mapping_lookup(config, 
+								    get_libs_key(target));
+	
+	if (!cpp_value)
+	{
+		cpp_value = gbf_am_config_value_new (GBF_AM_TYPE_STRING);
+	}
+	if (!libs_value)
+	{
+		libs_value = gbf_am_config_value_new (GBF_AM_TYPE_STRING);
+	}
+	if (!am_cpp_value)
+	{
+		am_cpp_value = gbf_am_config_value_new (GBF_AM_TYPE_STRING);
+	}
+	
+	gtk_tree_model_get_iter (model, &iter, path);
+	gtk_tree_model_get (model, &iter,
+			    COLUMN_MODULE_NAME, &module_name,
+			    COLUMN_CHECK, &use,
+			    -1);
+	
+	module_cflags = g_strdup_printf ("$(%s_CFLAGS)", (gchar*) module_name);
+	module_libs = g_strdup_printf ("$(%s_LIBS)", (gchar*) module_name);
+	g_free (module_name);
+	gtk_tree_path_free (path);
+	
+	if (!use)
+	{
+		GString* cppflags;
+		GString* am_cpp_flags;
+		GString* libs;
+		cppflags = g_string_new (gbf_am_config_value_get_string (cpp_value));
+		am_cpp_flags = g_string_new (gbf_am_config_value_get_string (am_cpp_value));
+		libs = g_string_new (gbf_am_config_value_get_string (libs_value));
+
+		if (strlen (cppflags->str) && !strstr (cppflags->str, module_cflags))
+		{
+			g_string_append_printf (cppflags, " %s", module_cflags);
+			gbf_am_config_value_set_string (cpp_value, cppflags->str);
+			gbf_am_config_mapping_insert (config, "cppflags", cpp_value);
+		}
+		else if (!strstr (am_cpp_flags->str, module_cflags))
+		{
+			g_string_append_printf (am_cpp_flags, " %s", module_cflags);
+			gbf_am_config_value_set_string (am_cpp_value, am_cpp_flags->str);
+			gbf_am_config_mapping_insert (group_config, "amcppflags", am_cpp_value);
+		}
+		if (!strstr (libs->str, module_libs))
+		{
+			g_string_append_printf (libs, " %s", module_libs);		
+			gbf_am_config_value_set_string (libs_value, libs->str);
+			gbf_am_config_mapping_insert (config, get_libs_key (target), libs_value);
+		}
+
+		g_string_free (libs, TRUE);
+		g_string_free (cppflags, TRUE);
+		g_string_free (am_cpp_flags, TRUE);
+		
+	}
+	else
+	{
+		const gchar* cpp_flags = cpp_value ? gbf_am_config_value_get_string(cpp_value) : NULL;
+		const gchar* am_cpp_flags = am_cpp_value ? gbf_am_config_value_get_string(am_cpp_value) : NULL;
+		const gchar* libs_flags = libs_value ? gbf_am_config_value_get_string(libs_value) : NULL;
+
+		if (cpp_flags && strlen (cpp_flags))
+		{
+			const gchar* start = strstr (cpp_flags, module_cflags);
+			GString* str = g_string_new (cpp_flags);
+			if (start)
+				g_string_erase (str, start - cpp_flags, strlen (module_cflags));
+			gbf_am_config_value_set_string (cpp_value, str->str);
+			g_string_free (str, TRUE);
+			gbf_am_config_mapping_insert (config, "cppflags", cpp_value);
+		}
+		else if (am_cpp_flags)
+		{
+			const gchar* start = strstr (am_cpp_flags, module_cflags);
+			GString* str = g_string_new (am_cpp_flags);
+			if (start)
+				g_string_erase (str, start - am_cpp_flags, strlen (module_cflags));
+			gbf_am_config_value_set_string (am_cpp_value, str->str);
+			g_string_free (str, TRUE);
+			gbf_am_config_mapping_insert (group_config, "amcppflags", am_cpp_value);
+		}
+		if (libs_flags)
+		{
+			const gchar* start = strstr (libs_flags, module_libs);
+			GString* str = g_string_new (libs_flags);
+			if (start)
+				g_string_erase (str, start - libs_flags, strlen (module_libs));
+			gbf_am_config_value_set_string (libs_value, str->str);
+			g_string_free (str, TRUE);
+			gbf_am_config_mapping_insert (config, get_libs_key (target), libs_value);
+		}
+	}
+	gtk_list_store_set (GTK_LIST_STORE (model), &iter, COLUMN_CHECK, !use, -1);
+	g_free (module_cflags);
+	g_free (module_libs);	
+}
+
+static gboolean
+update_tree_foreach (GtkTreeModel* model,
+		     GtkTreePath* path,
+		     GtkTreeIter* iter,
+		     GtkWidget* view)
+{
+	gchar* module;
+	gboolean use = FALSE;
+	GbfAmConfigMapping* config = g_object_get_data (G_OBJECT(view), "config");
+	GbfAmConfigMapping* group_config = g_object_get_data (G_OBJECT(view), "group_config");
+	GbfProjectTarget* target = g_object_get_data (G_OBJECT(view), "target");
+	GbfAmConfigValue* am_cpp_value = gbf_am_config_mapping_lookup(group_config, "amcppflags");
+	GbfAmConfigValue* cpp_value = gbf_am_config_mapping_lookup(config, "cppflags");
+	GbfAmConfigValue* libs_value = gbf_am_config_mapping_lookup(config, get_libs_key(target));
+	const gchar* cpp_flags = cpp_value ? gbf_am_config_value_get_string(cpp_value) : NULL;
+	const gchar* am_cpp_flags = am_cpp_value ? gbf_am_config_value_get_string(am_cpp_value) : NULL;
+	const gchar* libs_flags = libs_value ? gbf_am_config_value_get_string(libs_value) : NULL;
+
+	gchar* config_cflags; 
+	gchar* config_libs;
+	
+	gtk_tree_model_get (model, iter,
+			    COLUMN_MODULE_NAME, &module,
+			    -1);
+	
+	config_cflags = g_strdup_printf ("$(%s_CFLAGS)", (gchar*) module);
+	config_libs = g_strdup_printf ("$(%s_LIBS)", (gchar*) module);
+	g_free (module);
+	
+	if ((cpp_flags && strstr (cpp_flags, config_cflags)) || 
+	    (am_cpp_flags && strstr (am_cpp_flags, config_cflags)))
+	{
+		if (libs_flags && strstr(libs_flags, config_libs))
+			use = TRUE;
+	}
+	
+	gtk_list_store_set (GTK_LIST_STORE(model), iter,
+			    COLUMN_CHECK, use,
+			    -1);
+	g_free (config_cflags);
+	g_free (config_libs);
+	
+	return FALSE; /* continue iteration */
+}
+
+static GtkWidget*
+create_module_list (GbfAmProject *project,
+		    GbfProjectTarget *target,
+		    GbfAmConfigMapping *config,
+		    GbfAmConfigMapping *group_config)
+{
+	GtkWidget* view;
+	GtkListStore* list;
+	GList* modules;
+	GList* node;
+	GtkCellRenderer* renderer_text;
+	GtkCellRenderer* renderer_toggle;
+	GtkTreeViewColumn* column_text;
+	GtkTreeViewColumn* column_toggle;
+	
+	g_return_val_if_fail (GBF_IS_AM_PROJECT (project), NULL);
+	
+	list = gtk_list_store_new (2, G_TYPE_BOOLEAN, G_TYPE_STRING);
+	view = gtk_tree_view_new_with_model (GTK_TREE_MODEL(list));
+	g_object_set_data (G_OBJECT(view), "config", config);
+	g_object_set_data (G_OBJECT(view), "group_config", group_config);
+	g_object_set_data (G_OBJECT(view), "target", target);	
+	renderer_text = gtk_cell_renderer_text_new();
+	renderer_toggle = gtk_cell_renderer_toggle_new();
+	g_signal_connect (renderer_toggle, "toggled", G_CALLBACK (on_module_activate),
+			  view);
+	
+	column_toggle = gtk_tree_view_column_new_with_attributes (_("Use"),
+								  renderer_toggle,
+								  "active", COLUMN_CHECK,
+								  NULL);
+	
+	column_text = gtk_tree_view_column_new_with_attributes (_("Module"),
+								renderer_text,
+								"text", COLUMN_MODULE_NAME,
+								NULL);
+	
+	gtk_tree_view_append_column (GTK_TREE_VIEW (view), column_toggle);
+	gtk_tree_view_append_column (GTK_TREE_VIEW (view), column_text);
+	gtk_widget_set_size_request (view, -1, 200);
+	
+	modules = gbf_project_get_config_modules (GBF_PROJECT (project), NULL);
+	
+	for (node = modules; node != NULL; node = g_list_next (node))
+	{
+		GtkTreeIter iter;		
+		
+		gtk_list_store_append (list, &iter);
+		gtk_list_store_set (list, &iter,
+				    COLUMN_CHECK, FALSE,
+				    COLUMN_MODULE_NAME, node->data,
+				    -1);
+	}
+	gtk_tree_model_foreach (GTK_TREE_MODEL(list),
+				(GtkTreeModelForeachFunc) update_tree_foreach,
+				view);
+	
+	return view;
+}
+
+static void
+on_group_widget_destroy (GtkWidget *wid, GtkWidget *table)
+{
+	GError *err = NULL;
+	
+	GbfAmProject *project = g_object_get_data (G_OBJECT (table), "__project");
+	GbfAmConfigMapping *new_config = g_object_get_data (G_OBJECT (table), "__config");
+	const gchar *group_id = g_object_get_data (G_OBJECT (table), "__group_id");
+	gbf_am_project_set_group_config (project, group_id, new_config, &err);
+	if (err) {
+		g_warning ("%s", err->message);
+		g_error_free (err);
+	}
+	g_object_unref (table);
+}
+
+GtkWidget*
+gbf_am_properties_get_group_widget (GbfAmProject *project,
+				    const gchar *group_id,
+				    GError **error)
+{
+	GbfProjectGroup *group;
+	GbfAmConfigMapping *config;
+	GbfAmConfigValue *value;
+	GtkWidget *table;
+	GtkWidget *table_cflags;
+	GtkWidget *expander_cflags;
+	GError *err = NULL;
+	
+	g_return_val_if_fail (GBF_IS_AM_PROJECT (project), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	group = gbf_project_get_group (GBF_PROJECT (project), group_id, &err);
+	if (err) {
+		g_propagate_error (error, err);
+		return NULL;
+	}
+	config = gbf_am_project_get_group_config (project, group_id, &err);
+	if (err) {
+		g_propagate_error (error, err);
+		return NULL;
+	}
+	
+	g_return_val_if_fail (group != NULL, NULL);
+	g_return_val_if_fail (config != NULL, NULL);
+	
+	table = gtk_table_new (7, 2, FALSE);
+	g_object_ref (table);
+	g_object_set_data (G_OBJECT (table), "__project", project);
+	g_object_set_data_full (G_OBJECT (table), "__config", config,
+				(GDestroyNotify)gbf_am_config_mapping_destroy);
+	g_object_set_data_full (G_OBJECT (table), "__group_id",
+				g_strdup (group_id),
+				(GDestroyNotify)g_free);
+	g_signal_connect (table, "destroy",
+			  G_CALLBACK (on_group_widget_destroy), table);
+	
+	/* Group name */
+	add_configure_property (project, config, GBF_AM_CONFIG_LABEL,
+				_("Group name:"), group->name, NULL, table, 0);
+	/* CFlags */
+	table_cflags = gtk_table_new(7,2, FALSE);
+	expander_cflags = gtk_expander_new(_("Advanced"));
+	gtk_table_attach (GTK_TABLE (table), expander_cflags, 0, 2, 2, 3,
+				  GTK_FILL | GTK_EXPAND, GTK_FILL, 5, 3);
+	gtk_container_add(GTK_CONTAINER(expander_cflags), table_cflags);
+	add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+				_("C compiler flags:"), NULL, "amcflags", table_cflags, 0);
+	add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+				_("C preprocessor flags:"), NULL, "amcppflags", table_cflags, 1);
+	add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+				_("C++ compiler flags:"), NULL, "amcxxflags", table_cflags, 2);
+	add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+				_("gcj compiler flags (ahead-of-time):"), NULL, "amgcjflags", table_cflags, 3);
+	add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+				_("Java compiler flags (just-in-time):"), NULL, "amjavaflags", table_cflags, 4);
+	add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+				_("Fortran compiler flags:"), NULL, "amfflags", table_cflags, 5);
+	/* Includes */
+	add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+				_("Includes (deprecated):"), NULL, "includes",
+				table_cflags, 6);
+				
+	/* Install directories */
+	value = gbf_am_config_mapping_lookup (config, "installdirs");
+	if (value) {
+		GtkWidget *table2, *frame, *lab;
+		char *text;
+		frame = gtk_frame_new ("");
+		
+		lab = gtk_frame_get_label_widget (GTK_FRAME(frame));
+		text = g_strdup_printf ("<b>%s</b>", _("Install directories:"));
+		gtk_label_set_markup (GTK_LABEL(lab), text);
+		g_free (text);
+				      
+		gtk_frame_set_shadow_type (GTK_FRAME (frame),
+					   GTK_SHADOW_NONE);
+		gtk_widget_show (frame);
+		gtk_table_attach (GTK_TABLE (table), frame, 0, 2, 3, 4,
+				  GTK_FILL | GTK_EXPAND, GTK_FILL, 5, 3);
+		table2 = gtk_table_new (0, 0, FALSE);
+		gtk_widget_show (table2);
+		gtk_container_set_border_width (GTK_CONTAINER (table2), 5);
+		gtk_container_add (GTK_CONTAINER(frame), table2);
+		gbf_am_config_mapping_foreach (value->mapping,
+					       recursive_config_foreach_cb,
+					       table2);
+	}
+	gtk_widget_show_all (table);
+	gbf_project_group_free (group);
+	return table;
+}
+
+static void
+on_target_widget_destroy (GtkWidget *wid, GtkWidget *table)
+{
+	GError *err = NULL;
+	
+	GbfAmProject *project = g_object_get_data (G_OBJECT (table), "__project");
+	GbfAmConfigMapping *new_config = g_object_get_data (G_OBJECT (table), "__config");
+	GbfAmConfigMapping *new_group_config = g_object_get_data (G_OBJECT (table), "__group_config");
+	const gchar *target_id = g_object_get_data (G_OBJECT (table), "__target_id");
+	const gchar *group_id = g_object_get_data (G_OBJECT (table), "__group_id");
+	gbf_am_project_set_target_config (project, target_id, new_config, &err);
+	if (err) {
+		g_warning ("%s", err->message);
+		g_error_free (err);
+	}
+	err = NULL;
+	gbf_am_project_set_group_config (project, group_id, new_group_config, &err);
+	if (err) {
+		g_warning ("%s", err->message);
+		g_error_free (err);
+	}
+	g_object_unref (table);
+}
+
+static void
+on_advanced_clicked (GtkButton* button,
+		     GtkWidget* target_widget)
+{
+	GtkWidget* dialog;
+	GtkWidget* table_cflags;
+	const gchar* lib_type;
+	GbfAmProject* project;
+	GbfAmConfigMapping* config;
+	GbfProjectTarget* target;
+	GtkWidget* view;
+	GtkTreeModel* model;
+	
+	project = g_object_get_data (G_OBJECT(target_widget), "__project");
+	config = g_object_get_data (G_OBJECT(target_widget), "__config");
+	target = g_object_get_data (G_OBJECT(target_widget), "__target");
+	view = g_object_get_data (G_OBJECT(target_widget), "__view");
+		
+	table_cflags = gtk_table_new(9,2, FALSE);
+	add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+				_("C compiler flags:"), NULL, "cflags", table_cflags, 0);
+	add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+				_("C preprocessor flags"), NULL, "cppflags", table_cflags, 1);
+	add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+				_("C++ compiler flags"), NULL, "cxxflags", table_cflags, 2);
+	add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+				_("gcj compiler flags (ahead-of-time)"), NULL, "gcjflags", table_cflags, 3);
+	add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+				_("Fortran compiler flags:"), NULL, "amfflags", table_cflags, 4);
+	/* LDFLAGS */
+	add_configure_property (project, config,
+				GBF_AM_CONFIG_ENTRY,
+				_("Linker flags:"), NULL,
+				"ldflags", table_cflags, 6);
+	
+	lib_type = get_libs_key (target);			     
+	add_configure_property (project, config,
+				GBF_AM_CONFIG_ENTRY,
+				_("Libraries:"), NULL,
+				lib_type, table_cflags, 7);
+	
+	/* DEPENDENCIES */
+	add_configure_property (project, config,
+				GBF_AM_CONFIG_ENTRY,
+				_("Dependencies:"), NULL,
+				"explicit_deps", table_cflags, 8);
+	
+	dialog = gtk_dialog_new_with_buttons (_("Advanced options"),
+					      NULL,
+					      GTK_DIALOG_MODAL,
+					      GTK_STOCK_CLOSE,
+					      GTK_RESPONSE_CLOSE,
+					      NULL);
+	gtk_container_add (GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), table_cflags);
+	gtk_widget_show_all (dialog);
+	
+	gtk_dialog_run (GTK_DIALOG (dialog));
+	gtk_widget_destroy (dialog);
+	
+	/* Little hack to reuse func... */
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
+	gtk_tree_model_foreach (model,
+				(GtkTreeModelForeachFunc) update_tree_foreach,
+				view);
+}
+
+GtkWidget*
+gbf_am_properties_get_target_widget (GbfAmProject *project,
+				     const gchar *target_id, GError **error)
+{
+	GbfProjectGroup *group;
+	GbfAmConfigMapping *group_config;
+	GbfProjectTarget *target;
+	GbfAmConfigMapping *config;
+	GbfAmConfigValue *value;
+	GbfAmConfigMapping *installdirs;
+	GbfAmConfigValue *installdirs_val, *dir_val;
+	GtkWidget *table;
+	GError *err = NULL;
+	
+	g_return_val_if_fail (GBF_IS_AM_PROJECT (project), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+	
+	target = gbf_project_get_target (GBF_PROJECT (project), target_id, &err);
+	if (err) {
+		g_propagate_error (error, err);
+		return NULL;
+	}
+	config = gbf_am_project_get_target_config (project, target_id, &err);
+	if (err) {
+		g_propagate_error (error, err);
+		return NULL;
+	}
+	g_return_val_if_fail (target != NULL, NULL);
+	g_return_val_if_fail (config != NULL, NULL);
+	
+	group = gbf_project_get_group (GBF_PROJECT (project),
+				       target->group_id, NULL);
+	group_config = gbf_am_project_get_group_config (project,
+							target->group_id,
+							NULL);
+	table = gtk_table_new (9, 2, FALSE);
+	g_object_ref (table);
+	g_object_set_data (G_OBJECT (table), "__project", project);
+	g_object_set_data_full (G_OBJECT (table), "__config", config,
+				(GDestroyNotify)gbf_am_config_mapping_destroy);
+	g_object_set_data_full (G_OBJECT (table), "__group_config", group_config,
+				(GDestroyNotify)gbf_am_config_mapping_destroy);
+	g_object_set_data_full (G_OBJECT (table), "__target_id",
+				g_strdup (target_id),
+				(GDestroyNotify)g_free);
+	g_object_set_data_full (G_OBJECT (table), "__group_id",
+				g_strdup(group->id),
+				(GDestroyNotify)g_free);
+	g_object_set_data_full (G_OBJECT (table), "__target", 
+				target,
+				(GDestroyNotify)gbf_project_target_free);
+	g_signal_connect (table, "destroy",
+			  G_CALLBACK (on_target_widget_destroy), table);
+	
+	/* Target name */
+	add_configure_property (project, config, GBF_AM_CONFIG_LABEL,
+				_("Target name:"), target->name, NULL, table, 0);
+	/* Target type */
+	add_configure_property (project, config, GBF_AM_CONFIG_LABEL,
+				_("Type:"),
+				gbf_project_name_for_type (GBF_PROJECT (project),
+							   target->type),
+				NULL, table, 1);
+	/* Target group */
+	add_configure_property (project, config, GBF_AM_CONFIG_LABEL,
+				_("Group:"), group->name,
+				NULL, table, 2);
+	
+	/* Target primary */
+	/* FIXME: Target config 'installdir' actually stores target primary,
+	 * and not what it really seems to mean, i.e the primary prefix it
+	 * belongs to. The actual install directory of a target is the
+	 * install directory of primary prefix it belongs to and is
+	 * configured in group properties.
+	 */
+	value = gbf_am_config_mapping_lookup (config, "installdir");
+	installdirs_val = gbf_am_config_mapping_lookup (group_config,
+							"installdirs");
+	if (installdirs_val)
+		installdirs = gbf_am_config_value_get_mapping (installdirs_val);
+	
+	if (!value || !installdirs_val) {
+		add_configure_property (project, config, GBF_AM_CONFIG_LABEL,
+					_("Install directory:"), NULL, "installdir",
+					table, 3);
+	} else {
+		const gchar *primary_prefix;
+		gchar *installdir;
+		
+		primary_prefix = gbf_am_config_value_get_string (value);
+		installdirs = gbf_am_config_value_get_mapping (installdirs_val);
+		dir_val = gbf_am_config_mapping_lookup (installdirs,
+							primary_prefix);
+		if (dir_val) {
+			installdir = g_strconcat (primary_prefix, " = ",
+						  gbf_am_config_value_get_string (dir_val),
+						  NULL);
+			add_configure_property (project, config,
+						GBF_AM_CONFIG_LABEL,
+						_("Install directory:"),
+						installdir, NULL,
+						table, 3);
+			g_free (installdir);
+		} else {
+			add_configure_property (project, config,
+						GBF_AM_CONFIG_LABEL,
+						_("Install directory:"),
+						NULL, "installdir",
+						table, 3);
+		}
+	}
+
+	if (target->type && (strcmp (target->type, "program") == 0 ||
+			     strcmp (target->type, "shared_lib") == 0 ||
+			     strcmp (target->type, "static_lib") == 0)) {
+		GtkWidget* scrolled_window;
+		GtkWidget* view =  create_module_list(project, target, 
+						      config, group_config);
+		GtkWidget* button = gtk_button_new_with_label (_("Advanced..."));
+		scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+		gtk_container_add (GTK_CONTAINER(scrolled_window), view);
+		gtk_table_attach (GTK_TABLE (table), scrolled_window, 0, 2, 4, 5,
+				  GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 5, 3);
+		gtk_table_attach (GTK_TABLE (table), button, 0, 2, 5, 6,
+				  GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 5, 3);	
+		g_object_set_data (G_OBJECT (table), "__view", view);
+		g_signal_connect (button, "clicked", G_CALLBACK(on_advanced_clicked), table);
+	}
+	gtk_widget_show_all (table);
+	return table;
+}

Added: trunk/plugins/gbf-am/gbf-am-properties.h
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/gbf-am-properties.h	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,38 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8; coding: utf-8 -*- */
+/* gbf-am-properties.h
+ *
+ * Copyright (C) 2005  Naba Kumar
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Naba Kumar
+ */
+
+#ifndef _GBF_AM_PROPERTIES_H_
+#define _GBF_AM_PROPERTIES_H_
+
+#include <gtk/gtkwidget.h>
+#include "gbf-am-project.h"
+
+GtkWidget *gbf_am_properties_get_widget (GbfAmProject *project, GError **error);
+GtkWidget *gbf_am_properties_get_group_widget (GbfAmProject *project,
+					       const gchar *group_id,
+					       GError **error);
+GtkWidget *gbf_am_properties_get_target_widget (GbfAmProject *project,
+						const gchar *target_id,
+						GError **error);
+
+#endif

Added: trunk/plugins/gbf-am/gbf-am.plugin.in
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/gbf-am.plugin.in	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,11 @@
+[Anjuta Plugin]
+_Name=Autotools backend
+_Description=Autotools backend for project manager
+Location=gbf-am:GbfAmPlugin
+Icon=gbf-am-plugin-48.png
+UserActivatable=no
+Interfaces=IAnjutaProjectBackend
+Dependencies=anjuta-project-manager:ProjectManagerPlugin
+
+[Project]
+Supported-Project-Types=automake

Added: trunk/plugins/gbf-am/output.dtd
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/output.dtd	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,39 @@
+<!DOCTYPE project [
+
+<!ELEMENT project (config?,group+)>
+<!ATTLIST project
+  root CDATA #REQUIRED>
+
+<!ELEMENT config (param*)>
+
+<!ELEMENT param (item*)>
+<!ATTLIST param 
+  name  CDATA #REQUIRED
+  value CDATA #IMPLIED>
+
+<!ELEMENT item EMPTY>
+<!ATTLIST item
+  name  CDATA #IMPLIED
+  value CDATA #REQUIRED>
+
+<!ELEMENT group (config?,group*,target*)>
+<!ATTLIST group
+  id   CDATA #REQUIRED
+  name CDATA #REQUIRED>
+
+<!ELEMENT target (config?,source*,dependency*)>
+<!ATTLIST target
+  id   CDATA #REQUIRED
+  name CDATA #REQUIRED
+  type CDATA #REQUIRED>
+
+<!ELEMENT source EMPTY>
+<!ATTLIST source
+  uri CDATA #REQUIRED>
+
+<!ELEMENT dependency EMPTY>
+<!ATTLIST dependency
+  file   CDATA #REQUIRED
+  target CDATA #REQUIRED>
+
+]>

Added: trunk/plugins/gbf-am/plugin.c
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/plugin.c	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,123 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+    plugin.c
+    Copyright (C) 2008 SÃbastien Granjoux
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#define DEBUG
+#include <config.h>
+#include <libanjuta/anjuta-debug.h>
+#include <libanjuta/gbf-project.h>
+#include <libanjuta/interfaces/ianjuta-project-backend.h>
+
+#include "plugin.h"
+#include "gbf-am-project.h"
+
+
+#define ICON_FILE "gfb-am-plugin-48.png"
+
+/* AnjutaPlugin functions
+ *---------------------------------------------------------------------------*/
+
+static gboolean
+activate_plugin (AnjutaPlugin *plugin)
+{
+	DEBUG_PRINT ("GbfAmPlugin: Activating Gnome build am backend Plugin ...");
+	
+	return TRUE;
+}
+
+static gboolean
+deactivate_plugin (AnjutaPlugin *plugin)
+{
+	DEBUG_PRINT ("GbfAmPlugin: Deacctivating Gnome build am backend Plugin ...");
+	return TRUE;
+}
+
+
+/* IAnjutaProjectBackend implementation
+ *---------------------------------------------------------------------------*/
+
+static GbfProject*
+iproject_backend_new_project (IAnjutaProjectBackend* backend, GError** err)
+{
+	GbfProject *project;
+	
+	project = gbf_am_project_new ();
+		
+	return project;
+}
+
+static void
+iproject_backend_iface_init(IAnjutaProjectBackendIface *iface)
+{
+	iface->new_project = iproject_backend_new_project;
+}
+
+/* GObject functions
+ *---------------------------------------------------------------------------*/
+
+/* Used in dispose and finalize */
+static gpointer parent_class;
+
+static void
+gbf_am_plugin_instance_init (GObject *obj)
+{
+}
+
+/* dispose is used to unref object created with instance_init */
+
+static void
+dispose (GObject *obj)
+{
+	G_OBJECT_CLASS (parent_class)->dispose (obj);
+}
+
+/* finalize used to free object created with instance init */
+
+static void
+finalize (GObject *obj)
+{
+	G_OBJECT_CLASS (parent_class)->finalize (obj);
+}
+
+static void
+gbf_am_plugin_class_init (GObjectClass *klass) 
+{
+	AnjutaPluginClass *plugin_class = ANJUTA_PLUGIN_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	plugin_class->activate = activate_plugin;
+	plugin_class->deactivate = deactivate_plugin;
+	klass->dispose = dispose;
+	klass->finalize = finalize;
+}
+
+/* AnjutaPlugin declaration
+ *---------------------------------------------------------------------------*/
+
+ANJUTA_PLUGIN_BEGIN (GbfAmPlugin, gbf_am_plugin);
+ANJUTA_PLUGIN_ADD_INTERFACE (iproject_backend, IANJUTA_TYPE_PROJECT_BACKEND);
+ANJUTA_PLUGIN_END;
+
+G_MODULE_EXPORT void
+anjuta_glue_register_components (GTypeModule *module)
+{
+	gbf_am_plugin_get_type (module);
+	gbf_am_project_get_type (module);
+}                     

Added: trunk/plugins/gbf-am/plugin.h
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/plugin.h	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,47 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+    plugin.h
+    Copyright (C) 2008 SÃbastien Granjoux
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#ifndef __PLUGIN_H__
+#define __PLUGIN_H__
+
+#include <libanjuta/anjuta-plugin.h>
+
+extern GType gbf_am_plugin_get_type (GTypeModule *module);
+#define GBF_TYPE_PLUGIN_AM         (gbf_am_plugin_get_type (NULL))
+#define GBF_PLUGIN_AM(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GBF_TYPE_PLUGIN_AM, GbfAmPlugin))
+#define GBF_PLUGIN_AM_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), GBF_TYPE_PLUGIN_AM, GbfAmPluginClass))
+#define GBF_IS_PLUGIN_AM(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GBF_TYPE_PLUGIN_AM))
+#define GBF_IS_PLUGIN_AM_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GBF_TYPE_PLUGIN_AM))
+#define GBF_PLUGIN_AM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GBF_TYPE_PLUGIN_AM, GbfAmPluginClass))
+
+typedef struct _GbfAmPlugin GbfAmPlugin;
+typedef struct _GbfAmPluginClass GbfAmPluginClass;
+
+struct _GbfAmPlugin 
+{
+	AnjutaPlugin parent;
+};
+
+struct _GbfAmPluginClass
+{
+	AnjutaPluginClass parent_class;
+};
+
+#endif

Added: trunk/plugins/gbf-am/program.xpm
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/program.xpm	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,56 @@
+/* XPM */
+static char * program_xpm[] = {
+"14 14 39 1",
+" 	c None",
+".	c #000000",
+"+	c #E4E3E1",
+"@	c #E4E4E4",
+"#	c #B3B3B1",
+"$	c #484641",
+"%	c #9F9D96",
+"&	c #888781",
+"*	c #B0AFAD",
+"=	c #A8A7A1",
+"-	c #908E86",
+";	c #97958E",
+">	c #807D74",
+",	c #595854",
+"'	c #605E57",
+")	c #898883",
+"!	c #76746B",
+"~	c #43423F",
+"{	c #282724",
+"]	c #363430",
+"^	c #6D6B63",
+"/	c #E2E2E1",
+"(	c #B6B5AF",
+"_	c #21201E",
+":	c #0A0908",
+"<	c #181816",
+"[	c #E6E6E4",
+"}	c #65635C",
+"|	c #161614",
+"1	c #8C8B89",
+"2	c #DFDEDC",
+"3	c #B0AFA9",
+"4	c #D5D4D1",
+"5	c #93918B",
+"6	c #D6D5D2",
+"7	c #ABA9A3",
+"8	c #5D5C55",
+"9	c #494943",
+"0	c #42413C",
+"              ",
+"      ...     ",
+"   .. .+. ..  ",
+"    # $%$ &.. ",
+"  .*=%%%-;>$. ",
+"   .%%,')>!.  ",
+" ..$%'~{]%^$..",
+" ./(%,_:<[>}'.",
+" ..$%)]|12}$..",
+"   .3>([45}.  ",
+"  .67%>>!!8$. ",
+"  ..,.$!$.9.. ",
+"   .. .0. ..  ",
+"      ...     "};

Added: trunk/plugins/gbf-am/run-test.sh
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/run-test.sh	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,82 @@
+#!/bin/sh
+
+project_root=`mktemp -d /tmp/test-am-XXXXXX`
+
+echo Creating project in $project_root
+
+if ! pushd $project_root > /dev/null; then 
+    echo Failed to cd to the project directory
+fi
+
+# Create mandatory automake files
+echo -n Creating NEWS AUTHORS INSTALL README COPYING ChangeLog
+touch NEWS AUTHORS INSTALL README COPYING ChangeLog
+
+echo -n configure.in
+cat > configure.in <<EOF
+AC_PREREQ(2.52)
+AC_INIT(test-am, 0.1)
+AM_CONFIG_HEADER(config.h)
+AC_CONFIG_SRCDIR(src/Makefile.am)
+AM_INIT_AUTOMAKE(AC_PACKAGE_NAME, AC_PACKAGE_VERSION)
+
+AC_OUTPUT([
+Makefile
+src/Makefile
+])
+
+EOF
+
+echo -n Makefile.am
+cat > Makefile.am <<EOF
+SUBDIRS = src
+
+EOF
+
+echo
+
+# Create at least one directory
+echo -n Creating src/
+mkdir src
+cd src
+
+echo -n src/test.c src/test.h src/foo.c
+touch test.c test.h foo.c
+
+echo -n src/Makefile.am
+cat > Makefile.am <<EOF
+INCLUDES = -DFOO
+
+lib_LTLIBRARIES = libfoo.la
+
+libfoo_la_SOURCES = test.c test.h
+
+include_HEADERS = test.h
+
+bin_PROGRAMS = foo
+
+foo_SOURCES = \
+	foo.c
+
+foo_LDADD = libfoo.la
+
+EOF
+
+echo
+if test "$1" = "-n" -o "$1" = "--no-test"; then
+    exit 0;
+fi
+
+echo Now running test program...
+
+popd > /dev/null
+echo ./test $project_root
+libtool --mode=execute ./test $project_root
+
+if [ $? -eq 0 ]; then
+    echo Removing test project
+    rm -Rf $project_root
+    echo DONE!
+    echo
+fi
+

Added: trunk/plugins/gbf-am/shared.xpm
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/shared.xpm	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,70 @@
+/* XPM */
+static char * shared_xpm[] = {
+"16 16 51 1",	    
+" 	c None",
+".	c #000000",
+"+	c #B2A97E",
+"@	c #B6AD81",
+"#	c #7A7356",
+"$	c #B0A77C",
+"%	c #B5AC80",
+"&	c #BAB184",
+"*	c #BBB284",
+"=	c #B6AD80",
+"-	c #ADA57B",
+";	c #B7AE81",
+">	c #BDB486",
+",	c #837C5C",
+"'	c #E9E9D6",
+")	c #B4AC80",
+"!	c #7C7557",
+"~	c #C0B687",
+"{	c #BEB586",
+"]	c #050504",
+"^	c #EAEADA",
+"/	c #B1A87D",
+"(	c #B9B082",
+"_	c #C0B788",
+":	c #6B6B4F",
+"<	c #E9E9D5",
+"[	c #BEBE9C",
+"}	c #1C1C15",
+"|	c #0E0E0B",
+"1	c #E9E9D7",
+"2	c #E8E8D5",
+"3	c #BAB183",
+"4	c #808065",
+"5	c #E6E6D1",
+"6	c #E4E4CC",
+"7	c #E8E8D4",
+"8	c #CDCDB2",
+"9	c #6A6A4E",
+"0	c #E0E0C7",
+"a	c #A8A87E",
+"b	c #EAEAD8",
+"c	c #E7E7D2",
+"d	c #C5C5A4",
+"e	c #E3E3CE",
+"f	c #5D5D44",
+"g	c #E7E7D3",
+"h	c #434343",
+"i	c #141414",
+"j	c #E7E7D4",
+"k	c #E5E5D1",
+"l	c #E0E0CA",
+"                ",
+"         ....   ",
+"         +  #   ",
+"       .$%&*=-. ",
+"     ....;>,$.. ",
+"     .'.-)!~{   ",
+" ..] .^../({_.. ",
+".:<[}|12.#.3#.4.",
+".'566'''7.2..68.",
+".90a..b5c7..def.",
+" ... .22g2.h..i ",
+"     .gjkl.     ",
+"     ......     ",
+"                ",
+"                ",
+"                "};

Added: trunk/plugins/gbf-am/static.xpm
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/static.xpm	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,57 @@
+/* XPM */
+static char * static_xpm[] = {
+"16 16 38 1",
+" 	c None",
+".	c #000000",
+"+	c #E9E9D6",
+"@	c #E8E8D5",
+"#	c #050504",
+"$	c #EAEADA",
+"%	c #484848",
+"&	c #0B0B0A",
+"*	c #050505",
+"=	c #0D0D0D",
+"-	c #6B6B4F",
+";	c #E9E9D5",
+">	c #BEBE9C",
+",	c #1C1C15",
+"'	c #0E0E0B",
+")	c #E9E9D7",
+"!	c #0E0E0C",
+"~	c #070706",
+"{	c #9F9F86",
+"]	c #E8E8D4",
+"^	c #808065",
+"/	c #E6E6D1",
+"(	c #E4E4CC",
+"_	c #E7E7D3",
+":	c #CDCDB2",
+"<	c #6A6A4E",
+"[	c #E0E0C7",
+"}	c #A8A87E",
+"|	c #EAEAD8",
+"1	c #E7E7D2",
+"2	c #C5C5A4",
+"3	c #E3E3CE",
+"4	c #5D5D44",
+"5	c #434343",
+"6	c #141414",
+"7	c #E7E7D4",
+"8	c #E5E5D1",
+"9	c #E0E0CA",
+"                ",
+"                ",
+"                ",
+"                ",
+"     ......     ",
+"     .+@@@.     ",
+" ..# .$+++.%&*= ",
+".-;>,')@+ !~{]^ ",
+".+/((+++]_ ++(:.",
+".<[}..|/1]..234.",
+" ... .@@_  5  6 ",
+"     ._789.     ",
+"     ......     ",
+"                ",
+"                ",
+"                "};

Added: trunk/plugins/gbf-am/test.c
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/test.c	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,221 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <unistd.h>
+#include <gtk/gtk.h>
+#include <libgnome/gnome-init.h>
+#include "gbf-am-project.h"
+
+static void
+source_print (GbfProject *project, GbfProjectTargetSource *source, gint level)
+{
+	gchar *indent = g_strnfill (level * 3, ' ');
+
+	g_assert (source != NULL);
+	
+	g_print ("%s%s\n", indent, source->source_uri);
+	
+	g_free (indent);
+}
+
+static void
+target_print (GbfProject *project, GbfProjectTarget *target, gint level)
+{
+	GList *iter;
+	gchar *indent = g_strnfill (level * 3, ' ');
+
+	g_assert (target != NULL);
+	
+	g_print ("%s%s [%s]\n", indent, target->name, target->type);
+	for (iter = target->sources; iter; iter = g_list_next (iter)) {
+		GbfProjectTargetSource *source;
+		source = gbf_project_get_source (project, iter->data, NULL);
+		source_print (project, source, level + 1);
+		gbf_project_target_source_free (source);
+	}
+	
+	g_free (indent);
+}
+
+static void
+group_print (GbfProject *project, GbfProjectGroup *group, gint level)
+{
+	GList *iter;
+	gchar *indent = g_strnfill (level * 3, ' ');
+
+	g_assert (group != NULL);
+	
+	g_print ("%s%s\n", indent, group->name);
+	for (iter = group->groups; iter; iter = g_list_next (iter)) {
+		GbfProjectGroup *subgroup;
+		subgroup = gbf_project_get_group (project, iter->data, NULL);
+		group_print (project, subgroup, level + 1);
+		gbf_project_group_free (subgroup);
+	}
+	
+	for (iter = group->targets; iter; iter = g_list_next (iter)) {
+		GbfProjectTarget *target;
+		target = gbf_project_get_target (project, iter->data, NULL);
+		target_print (project, target, level + 1);
+		gbf_project_target_free (target);
+	}
+	
+	g_free (indent);
+}
+
+static void
+project_print (GbfProject *project)
+{
+	GbfProjectGroup *group;
+
+	group = gbf_project_get_group (project, "/", NULL);
+	group_print (project, group, 0);
+	gbf_project_group_free (group);
+}
+
+static void
+project_updated_cb (GbfProject *project, gpointer user_data)
+{
+	g_print ("----------========== PROJECT UPDATED ==========----------\n");
+	project_print (project);
+	g_print ("----------=====================================----------\n");
+}
+
+#define ERROR_CHECK(err)  G_STMT_START {					\
+	if ((err) != NULL) {							\
+		g_print ("! Last operation failed: %s\n", (err)->message);	\
+		g_error_free (err);						\
+		(err) = NULL;							\
+		goto out;							\
+	}									\
+} G_STMT_END;
+
+int
+main (int argc, char *argv[])
+{
+	GbfProject *project;
+	gboolean valid;
+	GList *list;
+	gchar *dir;
+	gchar *new_group_id, *new_target_id, *new_source_id;
+	GError *error = NULL;
+	
+	/* Bootstrap */
+	gnome_program_init ("libgbf-am-test", VERSION, LIBGNOME_MODULE, 
+			    argc, argv, NULL,
+			    NULL);  /* Avoid GCC sentinel warning */
+
+	if (argc < 2) {
+		g_print ("! You need to specify a project path\n");
+		return 0;
+	}
+	dir = argv [1];
+	
+	g_print ("+ Creating new gbf-am project\n");
+	project = gbf_am_project_new ();
+	if (!project) {
+		g_print ("! Project creation failed\n");
+		return 0;
+	}
+	g_signal_connect (project, "project-updated",
+			  G_CALLBACK (project_updated_cb), NULL);
+	
+	g_print ("+ Probing location: ");
+	valid = gbf_project_probe (project, dir, &error);
+	g_print ("%s\n", valid ? "ok" : "not an automake project");
+	ERROR_CHECK (error);
+	
+	if (!valid)
+		goto out;
+	
+	g_print ("+ Loading project %s\n\n", dir);
+	gbf_project_load (project, dir, &error);
+	ERROR_CHECK (error);
+	
+	/* Show the initial project structure */
+	g_print ("+ Project structure:\n\n");
+	project_print (project);
+	g_print ("\n");
+
+	/* Test remaining elements accessors */
+	g_print ("+ Testing getters:\n\n");
+
+	g_print ("- All targets:\n");
+	list = gbf_project_get_all_targets (project, NULL);
+	while (list) {
+		g_print ("%s\n", (gchar *) list->data);
+		g_free (list->data);
+		list = g_list_delete_link (list, list);
+	}
+	g_print ("\n");
+	
+	g_print ("- All sources:\n");
+	list = gbf_project_get_all_sources (project, NULL);
+	while (list) {
+		g_print ("%s\n", (gchar *) list->data);
+		g_free (list->data);
+		list = g_list_delete_link (list, list);
+	}
+	g_print ("\n");
+
+	/* Test adders and removers */
+	g_print ("+ Adding a group \"test\" in \"/\"\n");
+	new_group_id = gbf_project_add_group (project, "/", "test", &error);
+	ERROR_CHECK (error);
+	g_print ("+ Got group id \"%s\"\n\n", new_group_id);
+
+	g_print ("+ Adding a \"program\" target \"test\" in \"%s\"\n", new_group_id);
+	new_target_id = gbf_project_add_target (project, new_group_id,
+						"test", "program", &error);
+	ERROR_CHECK (error);
+	g_print ("+ Got target id \"%s\"\n\n", new_target_id);
+
+	g_print ("+ Adding a source \"test.c\" to target \"%s\"\n", new_target_id);
+	new_source_id = gbf_project_add_source (project, new_target_id, "test.c", &error);
+	ERROR_CHECK (error);
+	g_print ("+ Got source id \"%s\"\n\n", new_source_id);
+
+	g_print ("+ Removing the source \"%s\"\n\n", new_source_id);
+	gbf_project_remove_source (project, new_source_id, &error);
+	ERROR_CHECK (error);
+
+#if 0
+	g_print ("+ Removing the target \"%s\"\n\n", new_target_id);
+	gbf_project_remove_target (project, new_target_id, &error);
+	ERROR_CHECK (error);
+#endif
+	
+#if 0
+	g_print ("+ Removing the group \"%s\"\n\n", new_group_id);
+	gbf_project_remove_group (project, new_group_id, &error);
+	ERROR_CHECK (error);
+#endif
+	g_free (new_group_id);
+	g_free (new_target_id);
+	g_free (new_source_id);
+	
+	/* Test file monitoring: we need to enter the main loop for
+	 * the signal to be emitted */
+	sleep (1);
+	g_print ("+ Testing file monitoring:\n\n");
+	{
+		gchar *file, *cmd;
+		file = g_build_filename (dir, "configure.in", NULL);
+		cmd = g_strdup_printf ("touch %s", file);
+		g_spawn_command_line_sync (cmd, NULL, NULL, NULL, NULL);
+		g_free (cmd);
+		g_free (file);
+	}
+
+	g_timeout_add (3000, (GSourceFunc) gtk_main_quit, NULL);
+	gtk_main ();
+	
+  out:
+	g_print ("+ Unreffing project\n\n");
+	g_object_unref (project);
+
+	return 0;
+}
+

Added: trunk/plugins/gbf-am/unknown.xpm
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/unknown.xpm	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,119 @@
+/* XPM */
+static char * unknown_xpm[] = {
+"16 16 100 2",
+"  	c None",
+". 	c #B4A889",
+"+ 	c #B2A688",
+"@ 	c #A89D80",
+"# 	c #9B9176",
+"$ 	c #9A9075",
+"% 	c #8F866D",
+"& 	c #B6AA8B",
+"* 	c #FBF9EB",
+"= 	c #E8E4D5",
+"- 	c #E3DECF",
+"; 	c #EEE9D8",
+"> 	c #ECE5D4",
+", 	c #938A71",
+"' 	c #AFA486",
+") 	c #E8E3D5",
+"! 	c #E2DECE",
+"~ 	c #DDD8C8",
+"{ 	c #D8D3C2",
+"] 	c #D3CDBC",
+"^ 	c #BAB39F",
+"/ 	c #E2DDCE",
+"( 	c #DCD8C8",
+"_ 	c #D7D2C1",
+": 	c #D2CDBB",
+"< 	c #CDC7B5",
+"[ 	c #DCD7C7",
+"} 	c #D7D1C1",
+"| 	c #D1CCBB",
+"1 	c #CCC6B4",
+"2 	c #C7C1AE",
+"3 	c #C2BBA8",
+"4 	c #BCB6A2",
+"5 	c #A9A390",
+"6 	c #837B64",
+"7 	c #D6D1C0",
+"8 	c #D1CBBA",
+"9 	c #CBC6B4",
+"0 	c #C6C0AE",
+"a 	c #C1BBA7",
+"b 	c #BCB5A1",
+"c 	c #B6B09B",
+"d 	c #A29B88",
+"e 	c #D0CAB9",
+"f 	c #CBC5B3",
+"g 	c #C6BFAD",
+"h 	c #C0BAA7",
+"i 	c #BBB4A1",
+"j 	c #B6AF9A",
+"k 	c #B1A994",
+"l 	c #9C9682",
+"m 	c #CAC4B2",
+"n 	c #C5BFAC",
+"o 	c #C0B9A6",
+"p 	c #BAB4A0",
+"q 	c #B5AE9A",
+"r 	c #B0A994",
+"s 	c #ABA38D",
+"t 	c #97907B",
+"u 	c #C4BEAB",
+"v 	c #BFB9A5",
+"w 	c #B5AE99",
+"x 	c #AFA893",
+"y 	c #AAA28D",
+"z 	c #A59D87",
+"A 	c #928A75",
+"B 	c #BEB8A4",
+"C 	c #B9B29E",
+"D 	c #B4AD98",
+"E 	c #AFA792",
+"F 	c #A9A28C",
+"G 	c #A49C86",
+"H 	c #9F9780",
+"I 	c #8D8570",
+"J 	c #B8B29D",
+"K 	c #B3AC97",
+"L 	c #AEA791",
+"M 	c #A9A18B",
+"N 	c #A39C85",
+"O 	c #9E967F",
+"P 	c #999179",
+"Q 	c #877F69",
+"R 	c #B3AB97",
+"S 	c #ADA690",
+"T 	c #A8A08A",
+"U 	c #A39B84",
+"V 	c #9E957E",
+"W 	c #8F8871",
+"X 	c #918971",
+"Y 	c #837B63",
+"Z 	c #9B9377",
+"` 	c #9E9784",
+" .	c #99927E",
+"..	c #948D78",
+"+.	c #908872",
+"@.	c #8B836D",
+"#.	c #857E67",
+"$.	c #888068",
+"%.	c #847C65",
+"&.	c #7F7760",
+"                                ",
+"        . + @ @ # $ %           ",
+"      & * = - ; > , = %         ",
+"      ' ) ! ~ { ] % ^ ^ %       ",
+"      @ / ( _ : < % % % %       ",
+"      @ [ } | 1 2 3 4 5 6       ",
+"      @ 7 8 9 0 a b c d 6       ",
+"      @ e f g h i j k l 6       ",
+"      @ m n o p q r s t 6       ",
+"      @ u v ^ w x y z A 6       ",
+"      @ B C D E F G H I 6       ",
+"      @ J K L M N O P Q 6       ",
+"      @ R S T U V W X Y 6       ",
+"      Z `     +   # $ Y 6       ",
+"        6 6 6 6 6 6 %.&.        ",
+"                                "};

Added: trunk/plugins/gbf-mkfile/.cvsignore
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/.cvsignore	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,6 @@
+Makefile.in
+Makefile
+.deps
+.libs
+test
+gbf-mkfile-parse

Added: trunk/plugins/gbf-mkfile/GBF/.cvsignore
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/GBF/.cvsignore	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in

Added: trunk/plugins/gbf-mkfile/GBF/General.pm
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/GBF/General.pm	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,120 @@
+package GBF::General;
+
+use Exporter;
+use strict;
+
+our (@ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
+
+ ISA = qw (Exporter);
+
+ EXPORT = qw (&report_error &report_warning
+	      &trim &canonicalize_name &empty_if_undef &expand_tabs
+	      &base_name &dir_name &chop_backslash &max &min);
+ EXPORT_OK = qw ($debug);
+%EXPORT_TAGS = ();
+
+
+
+######################################################################
+####  SUBROUTINES AND HELPER FUNCTIONS  ##############################
+######################################################################
+
+sub report_error
+{
+    my ($code, $message) = @_;
+
+    print STDERR "ERROR($code): $message\n";
+
+    return $code;
+}
+
+sub report_warning
+{
+    my ($code, $message) = @_;
+
+    print STDERR "WARNING($code): $message\n";
+}
+
+sub canonicalize_name
+{
+    my ($name);
+
+    $name = $_[0];
+    $name =~ tr/\.-/_/;
+    
+    return $name;
+}
+
+sub trim
+{
+    $_ = $_[0];
+
+    s/^\s*//;
+    s/\s*$//;
+
+    return $_;
+}
+
+sub empty_if_undef
+{
+    return defined ($_[0]) ? $_[0] : "";
+}
+
+sub expand_tabs
+{
+    $_ = $_[0];
+    my $i;
+    while (/\t/) {
+	$i = index ($_, "\t");
+	$i = $i % 8;
+	if ($i == 0) {
+	    s/\t/        /;
+	} else {
+	    my $pad = " " x (8 - $i);
+	    s/\t/$pad/;
+	};
+    };
+    return $_;
+}
+
+sub base_name {
+    my ($name, $dir);
+    
+    $name = $_[0];
+    $dir = dir_name ($name);
+    $name =~ s/$dir//;
+
+    return $name;
+}
+
+sub dir_name {
+    my ($name);
+
+    $name = $_[0];
+    $name =~ s/[^\/]+$//;
+
+    return $name;
+}
+
+sub chop_backslash ($)
+{
+    $_ = shift;
+    s/\\\s*$//;
+    return $_;
+}
+
+sub max
+{
+    my $max = shift;
+    foreach my $x (@_) { $max = $x if $x > $max; };	
+    return $max;
+}
+
+sub min
+{
+    my $min = shift;
+    foreach my $x (@_) { $min = $x if $x < $min; };	
+    return $min;
+}
+
+1;

Added: trunk/plugins/gbf-mkfile/GBF/Make.pm
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/GBF/Make.pm	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,1321 @@
+package Make::Rule::Vars;
+use Carp;
+use strict;
+my $generation = 0; # lexical cross-package scope used!
+
+# Package to handle 'magic' variables pertaining to rules e.g. $@ $* $^ $?
+# by using tie to this package 'subsvars' can work with array of 
+# hash references to possible sources of variable definitions.
+
+sub TIEHASH
+{
+ my ($class,$rule) = @_;
+ return bless \$rule,$class;
+}
+
+sub FETCH
+{
+ my $self = shift;
+ local $_ = shift;
+ my $rule = $$self;
+ return undef unless (/^[\ ^<?*]$/);
+ # print STDERR "FETCH $_ for ",$rule->Name,"\n";
+ return $rule->Name if ($_ eq '@');
+ return $rule->Base if ($_ eq '*');
+ return join(' ',$rule->exp_depend)  if ($_ eq '^');
+ return join(' ',$rule->out_of_date) if ($_ eq '?');
+ # Next one is dubious - I think $< is really more subtle ...
+ return ($rule->exp_depend)[0] if ($_ eq '<');
+ return undef;
+}
+
+package Make::Rule;
+use Carp;
+use strict;
+
+# Bottom level 'rule' package 
+# An instance exists for each ':' or '::' rule in the makefile.
+# The commands and dependancies are kept here.
+
+sub target
+{
+ return shift->{TARGET};
+}
+
+sub Name
+{
+ return shift->target->Name;
+}
+
+sub Base
+{
+ my $name = shift->target->Name;
+ $name =~ s/\.[^.]+$//;
+ return $name;
+}
+
+sub Info
+{
+ return shift->target->Info;
+}       
+
+sub depend
+{
+ my $self = shift;
+ if (@_)
+  {            
+   my $name = $self->Name;
+   my $dep = shift;
+   confess "dependants $dep are not an array reference" unless ('ARRAY' eq ref $dep); 
+   my $file;
+   foreach $file (@$dep)
+    {
+     unless (exists $self->{DEPHASH}{$file})
+      {    
+       $self->{DEPHASH}{$file} = 1;
+       push(@{$self->{DEPEND}},$file);
+      }
+    }
+  }
+ return (wantarray) ? @{$self->{DEPEND}} : $self->{DEPEND};
+}
+
+sub command
+{
+ my $self = shift;
+ if (@_)
+  {
+   my $cmd = shift;
+   confess "commands $cmd are not an array reference" unless ('ARRAY' eq ref $cmd); 
+   if (@$cmd)
+    {
+     if (@{$self->{COMMAND}})
+      {
+      # warn "Command for ".$self->Name," redefined";
+      # print STDERR "Was:",join("\n",@{$self->{COMMAND}}),"\n";
+      # print STDERR "Now:",join("\n",@$cmd),"\n";
+      }
+     $self->{COMMAND} = $cmd;
+    }
+   else
+    {
+     if (@{$self->{COMMAND}})
+      { 
+       # warn "Command for ".$self->Name," retained";
+       # print STDERR "Was:",join("\n",@{$self->{COMMAND}}),"\n";
+      }
+    } 
+  }
+ return (wantarray) ? @{$self->{COMMAND}} : $self->{COMMAND};
+}
+
+#
+# The key make test - is target out-of-date as far as this rule is concerned
+# In scalar context - boolean value of 'do we need to apply the rule'
+# In list context the things we are out-of-date with e.g. magic $? variable
+#
+sub out_of_date
+{
+ my $array = wantarray;
+ my $self  = shift;
+ my $info  = $self->Info;
+ my @dep = ();
+ my $tdate  = $self->target->date;
+ my $dep;
+ my $count = 0;
+ foreach $dep ($self->exp_depend)
+  {
+   my $date = $info->date($dep);
+   $count++;
+   if (!defined($date) || !defined($tdate) || $date < $tdate)
+    {       
+     # warn $self->Name." ood wrt ".$dep."\n";
+     return 1 unless $array;
+     push(@dep,$dep);
+    }
+  }
+ return @dep if $array;
+ # Note special case of no dependencies means it is always  out-of-date!
+ return !$count;
+}
+
+#
+# Return list of things rule depends on with variables expanded
+# - May need pathname and vpath processing as well
+#
+sub exp_depend
+{
+ my $self = shift;
+ my $info = $self->Info;
+ my @dep = map(split(/\s+/,$info->subsvars($_)),$self->depend);
+ return (wantarray) ? @dep : \ dep;
+}
+
+#
+# Return commands to apply rule with variables expanded
+# - No pathname processing needed, commands should always chdir()
+#   to logical place (at least till we get very clever at bourne shell parsing).
+# - May need vpath processing
+#
+sub exp_command
+{
+ my $self   = shift;
+ my $info   = $self->Info;
+ my $base   = $self->Name;
+ my %var;
+ tie %var,'Make::Rule::Vars',$self;
+ my @cmd  = map($info->subsvars($_,\%var),$self->command);
+ return (wantarray) ? @cmd : \ cmd;
+}
+
+#
+# clone creates a new rule derived from an existing rule, but 
+# with a different target. Used when left hand side was a variable.
+# perhaps should be used for dot/pattern rule processing too.
+#
+sub clone
+{
+ my ($self,$target) = @_;
+ my %hash = %$self;
+ $hash{TARGET} = $target;
+ $hash{DEPEND} = [ {$self->{DEPEND}}];
+ $hash{DEPHASH} = {%{$self->{DEPHASH}}};
+ my $obj = bless \%hash,ref $self;
+ return $obj;
+}
+
+sub new
+{
+ my $class = shift;
+ my $target = shift;
+ my $kind   = shift;
+ my $self = bless { TARGET => $target,             # parent target (left hand side)
+                    KIND => $kind,                 # : or ::
+                    DEPEND => [], DEPHASH => {},   # right hand args
+                    COMMAND => []                  # command(s)  
+                  },$class;        
+ $self->depend(shift) if (@_);
+ $self->command(shift) if (@_);
+ return $self;
+}
+
+#
+# This code has to go somewhere but no good home obvious yet.
+#  - only applies to ':' rules, but needs top level database
+#  - perhaps in ->commands of derived ':' class?
+#
+sub find_commands
+{
+ my ($self) = @_;
+ if (! {$self->{COMMAND}} && @{$self->{DEPEND}})
+  {
+   my $info = $self->Info;
+   my $name = $self->Name;
+   my @dep  = $self->depend;
+   my @rule = $info->patrule($self->Name);
+   if (@rule)
+    {
+     $self->depend($rule[0]);
+     $self->command($rule[1]);
+    }
+  }
+}
+
+#
+# Spew a shell script to perfom the 'make' e.g. make -n 
+#
+sub Script
+{
+ my $self = shift;
+ return unless $self->out_of_date;
+ my @cmd = $self->exp_command;
+ if (@cmd)
+  {
+   my $file;
+  my $com = ($^O eq 'MSWin32') ? 'rem ': '# ';
+   print  $com,$self->Name,"\n";
+   foreach $file ($self->exp_command)
+    {
+     $file =~ s/^[\ \s-]*//;
+     print "$file\n";
+    }
+  }
+}
+
+#
+# Normal 'make' method
+#
+sub Make
+{
+ my $self = shift;
+ my $file;
+ return unless ($self->out_of_date);
+ my @cmd = $self->exp_command;
+ my $info = $self->Info;
+ if (@cmd)
+  {
+   foreach my $file ($self->exp_command)
+    {
+     $file =~ s/^([\ \s-]*)//;
+     my $prefix = $1;
+     print  "$file\n" unless ($prefix =~ /\@/);
+     my $code = $info->exec($file);
+     if ($code && $prefix !~ /-/)
+      {
+       die "Code $code from $file";
+      }
+    }
+  }
+}
+
+#
+# Print rule out in makefile syntax 
+# - currently has variables expanded as debugging aid.
+# - will eventually become make -p 
+# - may be useful for writing makefiles from MakeMaker too...
+#
+sub Print
+{
+ my $self = shift;
+ my $file;
+ print $self->Name,' ',$self->{KIND},' ';
+ foreach $file ($self->depend)
+  {
+   print " \\\n   $file";
+  }
+ print "\n";
+ my @cmd = $self->exp_command;
+ if (@cmd)
+  {
+   foreach $file ($self->exp_command)
+    {
+     print "\t",$file,"\n";
+    }
+  }
+ else
+  {
+   print STDERR "No commands for ",$self->Name,"\n" unless ($self->target->phony); 
+  }
+ print "\n";
+}
+
+package Make::Target;
+use Carp;
+use strict;
+use Cwd;
+
+#
+# Intermediate 'target' package
+# There is an instance of this for each 'target' that apears on 
+# the left hand side of a rule i.e. for each thing that can be made.
+# 
+sub new
+{
+ my ($class,$info,$target) = @_;
+ return bless { NAME => $target,     # name of thing
+                MAKEFILE => $info,   # Makefile context 
+                Pass => 0            # Used to determine if 'done' this sweep
+              },$class;
+}
+
+sub date
+{
+ my $self = shift;
+ my $info = $self->Info;
+ return $info->date($self->Name);
+}
+
+sub phony
+{
+ my $self = shift;
+ return $self->Info->phony($self->Name);
+}   
+
+
+sub colon
+{
+ my $self = shift;
+ if (@_)
+  {
+   if (exists $self->{COLON})
+    {
+     my $dep = $self->{COLON};
+     if (@_ == 1)
+      {            
+       # merging an existing rule
+       my $other = shift;
+       $dep->depend(scalar $other->depend);
+       $dep->command(scalar $other->command);
+      }
+     else
+      {
+       $dep->depend(shift);
+       $dep->command(shift);
+      }
+    }
+   else
+    {
+     $self->{COLON} = (@_ == 1) ? shift->clone($self) : Make::Rule->new($self,':',@_);
+    }
+  }
+ if (exists $self->{COLON})
+  {
+   return (wantarray) ? ($self->{COLON}) : $self->{COLON};
+  }
+ else
+  {
+   return (wantarray) ? () : undef;
+  }
+}
+
+sub dcolon
+{
+ my $self = shift;
+ if (@_)
+  {
+   my $rule = (@_ == 1) ? shift->clone($self) : Make::Rule->new($self,'::',@_);
+   $self->{DCOLON} = [] unless (exists $self->{DCOLON});
+   push(@{$self->{DCOLON}},$rule);
+  }
+ return (exists $self->{DCOLON}) ? @{$self->{DCOLON}} : ();
+}
+
+sub Name
+{
+ return shift->{NAME};
+}
+
+sub Info
+{
+ return shift->{MAKEFILE};
+}
+
+sub ProcessColon
+{
+ my ($self) = @_;
+ my $c = $self->colon;
+ $c->find_commands if $c;
+}
+
+sub ExpandTarget
+{
+ my ($self) = @_;
+ my $target = $self->Name;
+ my $info   = $self->Info;
+ my $colon  = delete $self->{COLON};
+ my $dcolon = delete $self->{DCOLON};
+ foreach my $expand (split(/\s+/,$info->subsvars($target)))
+  {
+   next unless defined($expand);
+   my $t = $info->Target($expand);
+   if (defined $colon)
+    {
+     $t->colon($colon); 
+    }
+   foreach my $d (@{$dcolon})
+    {
+     $t->dcolon($d);
+    }
+  }
+}
+
+sub done
+{
+ my $self = shift;
+ my $info = $self->Info;
+ my $pass = $info->pass;
+ return 1 if ($self->{Pass} == $pass);
+ $self->{Pass} = $pass;
+ return 0;
+}
+
+sub recurse
+{
+ my ($self,$method,@args) = @_;
+ my $info = $self->Info;
+ my $rule;
+ my $i = 0;
+ foreach $rule ($self->colon,$self->dcolon)
+  {
+   my $dep;
+   my $j = 0;
+   foreach $dep ($rule->exp_depend)
+    {
+     my $t = $info->{Depend}{$dep};
+     if (defined $t)
+      {
+       $t->$method(@args) 
+      }
+     else
+      {
+       unless ($info->exists($dep))
+        {
+         my $dir = cwd();                                      
+         die "Cannot recurse $method - no target $dep in $dir" 
+        }
+      }
+    }
+  }
+}
+
+sub Script
+{
+ my $self = shift;
+ my $info = $self->Info;
+ my $rule = $self->colon;
+ return if ($self->done);
+ $self->recurse('Script');
+ foreach $rule ($self->colon,$self->dcolon)
+  {
+   $rule->Script;
+  }
+}
+
+sub Make
+{
+ my $self = shift;
+ my $info = $self->Info;
+ my $rule = $self->colon;
+ return if ($self->done);
+ $self->recurse('Make');
+ foreach $rule ($self->colon,$self->dcolon)
+  {
+   $rule->Make;
+  }
+}
+
+sub Print
+{
+ my $self = shift;
+ my $info = $self->Info;
+ return if ($self->done);
+ my $rule = $self->colon;
+ foreach $rule ($self->colon,$self->dcolon)
+  {
+   $rule->Print;
+  }
+ $self->recurse('Print');
+}
+
+package Make;
+use 5.005;  # Need look-behind assertions
+use Carp;
+use strict;
+use Config;
+use Cwd;
+use File::Spec;
+use vars qw($VERSION);
+$VERSION = '1.00';
+
+my %date;
+
+sub phony
+{
+ my ($self,$name) = @_;
+ return exists $self->{PHONY}{$name};
+}
+
+sub suffixes
+{
+ my ($self) = @_;
+ return keys %{$self->{'SUFFIXES'}};
+}
+
+#
+# Construct a new 'target' (or find old one)
+# - used by parser to add to data structures
+#
+sub Target
+{
+ my ($self,$target) = @_;
+ unless (exists $self->{Depend}{$target})
+  {
+   my $t = Make::Target->new($self,$target);
+   $self->{Depend}{$target} = $t;
+  if ($target =~ /%/)
+   {
+    $self->{Pattern}{$target} = $t;
+   }
+  elsif ($target =~ /^\./)
+   {
+    $self->{Dot}{$target} = $t;
+   }
+  else
+   {
+    push(@{$self->{Targets}},$t);
+   }
+  }
+ return $self->{Depend}{$target};
+}
+
+#
+# Utility routine for patching %.o type 'patterns'
+#
+sub patmatch
+{
+ my $key = shift;
+ local $_ = shift;
+ my $pat = $key;
+ $pat =~ s/\./\\./;
+ $pat =~ s/%/(\[^\/\]*)/;
+ if (/$pat$/)
+  {
+   return $1;
+  }
+ return undef;
+}
+
+#
+# old vpath lookup routine 
+#
+sub locate
+{
+ my $self = shift;
+ local $_ = shift;
+ return $_ if (-r $_);
+ my $key;
+ foreach $key (keys %{$self->{vpath}})
+  {
+   my $Pat;
+   if (defined($Pat = patmatch($key,$_)))
+    {
+     my $dir;
+     foreach $dir (split(/:/,$self->{vpath}{$key}))
+      {
+       return "$dir/$_"  if (-r "$dir/$_");
+      }
+    }
+  }
+ return undef;
+}
+
+#
+# Convert traditional .c.o rules into GNU-like into %o : %c
+#
+sub dotrules
+{
+ my ($self) = @_;
+ my $t;
+ foreach $t (keys %{$self->{Dot}})
+  {
+   my $e = $self->subsvars($t);
+   $self->{Dot}{$e} = delete $self->{Dot}{$t} unless ($t eq $e);
+  }
+ my (@suffix) = $self->suffixes;
+ foreach $t (@suffix)
+  {
+   my $d;
+   my $r = delete $self->{Dot}{$t};
+   if (defined $r)
+    {
+     my @rule = ($r->colon) ? ($r->colon->depend) : ();
+     if (@rule)
+      {
+       delete $self->{Dot}{$t->Name};
+       print STDERR $t->Name," has dependants\n";
+       push(@{$self->{Targets}},$r);
+      }
+     else
+      {
+       # print STDERR "Build \% : \%$t\n";                   
+       $self->Target('%')->dcolon(['%'.$t],scalar $r->colon->command);
+      }
+    }
+   foreach $d (@suffix)
+    {
+     $r = delete $self->{Dot}{$t.$d};
+     if (defined $r)
+      {
+       # print STDERR "Build \%$d : \%$t\n";
+       $self->Target('%'.$d)->dcolon(['%'.$t],scalar $r->colon->command);
+      }
+    }
+  }
+ foreach $t (keys %{$self->{Dot}})
+  {
+   push(@{$self->{Targets}},delete $self->{Dot}{$t});
+  }
+}
+
+#
+# Return 'full' pathname of name given directory info. 
+# - may be the place to do vpath stuff ?
+#               
+
+my %pathname;
+
+sub pathname
+{
+ my ($self,$name) = @_;
+ my $hash = $self->{'Pathname'}; 
+ unless (exists $hash->{$name})
+  {
+   if (File::Spec->file_name_is_absolute($name))
+    {
+     $hash->{$name} = $name;
+    }
+   else
+    {
+     $name =~ s,^\./,,;                             
+     $hash->{$name} = File::Spec->catfile($self->{Dir},$name);
+    }
+  }
+ return $hash->{$name};
+ 
+}
+
+#
+# Return modified date of name if it exists
+# 
+sub date
+{
+ my ($self,$name) = @_;
+ my $path = $self->pathname($name);
+ unless (exists $date{$path})
+  {
+   $date{$path} = -M $path;
+  }
+ return $date{$path};
+}
+
+#
+# Check to see if name is a target we can make or an existing
+# file - used to see if pattern rules are valid
+# - Needs extending to do vpath lookups
+#
+sub exists
+{
+ my ($self,$name) = @_;
+ return 1 if (exists $self->{Depend}{$name});
+ return 1 if defined $self->date($name);
+ # print STDERR "$name '$path' does not exist\n";
+ return 0;
+}
+
+#
+# See if we can find a %.o : %.c rule for target
+# .c.o rules are already converted to this form 
+#
+sub patrule
+{
+ my ($self,$target) = @_;
+ my $key;
+ # print STDERR "Trying pattern for $target\n";
+ foreach $key (keys %{$self->{Pattern}})
+  {
+   my $Pat;
+   if (defined($Pat = patmatch($key,$target)))
+    {
+     my $t = $self->{Pattern}{$key};
+     my $rule;
+     foreach $rule ($t->dcolon)
+      {
+       my @dep = $rule->exp_depend;
+       if (@dep)
+        {
+         my $dep = $dep[0];
+         $dep =~ s/%/$Pat/g;
+         # print STDERR "Try $target : $dep\n";
+         if ($self->exists($dep)) 
+          {
+           foreach (@dep)
+            {
+             s/%/$Pat/g;
+            }
+           return (\ dep,scalar $rule->command);
+          }
+        }
+      }
+    }
+  }
+ return ();
+}
+
+#
+# Old code to handle vpath stuff - not used yet
+#
+sub needs
+{my ($self,$target) = @_;
+ unless ($self->{Done}{$target})
+  {
+   if (exists $self->{Depend}{$target})
+    {
+     my @depend = split(/\s+/,$self->subsvars($self->{Depend}{$target}));
+     foreach (@depend)
+      {
+       $self->needs($_);
+      }
+    }
+   else
+    {
+     my $vtarget = $self->locate($target);
+     if (defined $vtarget)
+      {
+       $self->{Need}{$vtarget} = $target;
+      }
+     else
+      {
+       $self->{Need}{$target}  = $target;
+      }
+    }
+  }
+}
+
+#
+# Substitute $(xxxx) and $x style variable references
+# - should handle ${xxx} as well
+# - recurses till they all go rather than doing one level,
+#   which may need fixing
+#
+sub subsvars
+{
+ my $self = shift;
+ local $_ = shift;
+ my @var = @_;
+ push(@var,$self->{Override},$self->{Vars},\%ENV);
+ croak("Trying to subsitute undef value") unless (defined $_); 
+ while (/(?<!\$)\$\(([^()]+)\)/ || /(?<!\$)\$([<\ ^?*])/)
+  {
+   my ($key,$head,$tail) = ($1,$`,$');
+   my $value;
+   if ($key =~ /^([\w._]+|\S)(?::(.*))?$/)
+    {
+     my ($var,$op) = ($1,$2);
+     foreach my $hash (@var)
+      {
+       $value = $hash->{$var};
+       if (defined $value)
+        {
+         last; 
+        }
+      }
+     unless (defined $value)
+      {
+       die "$var not defined in '$_'" unless (length($var) > 1); 
+       $value = '';
+      }
+     if (defined $op)
+      {
+       if ($op =~ /^s(.).*\1.*\1/)
+        {
+         local $_ = $self->subsvars($value);
+         $op =~ s/\\/\\\\/g;
+         eval $op.'g';
+         $value = $_;
+        }
+       else
+        {
+         die "$var:$op = '$value'\n"; 
+        }   
+      }
+    }
+   elsif ($key =~ /wildcard\s*(.*)$/)
+    {
+     $value = join(' ',glob($self->pathname($1)));
+    }
+   elsif ($key =~ /shell\s*(.*)$/)
+    {
+     $value = join(' ',split('\n',`$1`));
+    }
+   elsif ($key =~ /addprefix\s*([^,]*),(.*)$/)
+    {
+     $value = join(' ',map($1 . $_,split('\s+',$2)));
+    }
+   elsif ($key =~ /notdir\s*(.*)$/)
+    {
+     my @files = split(/\s+/,$1);
+     foreach (@files)
+      {
+       s#^.*/([^/]*)$#$1#;
+      }
+     $value = join(' ',@files);
+    }
+   elsif ($key =~ /dir\s*(.*)$/)
+    {
+     my @files = split(/\s+/,$1);
+     foreach (@files)
+      {
+       s#^(.*)/[^/]*$#$1#;
+      }
+     $value = join(' ',@files);
+    }
+   elsif ($key =~ /^subst\s+([^,]*),([^,]*),(.*)$/)
+    {
+     my ($a,$b) = ($1,$2);
+     $value = $3;
+     $a =~ s/\./\\./;
+     $value =~ s/$a/$b/; 
+    }
+   elsif ($key =~ /^mktmp,(\S+)\s*(.*)$/)
+    {
+     my ($file,$content) = ($1,$2);
+     open(TMP,">$file") || die "Cannot open $file:$!";
+     $content =~ s/\\n//g;
+     print TMP $content;
+     close(TMP);
+     $value = $file;
+    }
+   else
+    {
+     warn "Cannot evaluate '$key' in '$_'\n";
+    }
+   $_ = "$head$value$tail";
+  }
+ s/\$\$/\$/g;
+ return $_;
+}
+
+#
+# Split a string into tokens - like split(/\s+/,...) but handling
+# $(keyword ...) with embedded \s
+# Perhaps should also understand "..." and '...' ?
+#
+sub tokenize
+{
+ local $_ = $_[0];
+ my @result = ();
+ s/\s+$//;
+ while (length($_))
+  {
+   s/^\s+//;
+   last unless (/^\S/);
+   my $token = "";
+   while (/^\S/)
+    {
+     if (s/^\$([\(\{])//)
+      {
+       $token .= $&; 
+       my $paren = $1 eq '(';
+       my $brace = $1 eq '{';
+       my $count = 1;
+       while (length($_) && ($paren || $brace))
+        {
+         s/^.//;
+         $token .= $&; 
+         $paren += ($& eq '(');
+         $paren -= ($& eq ')');
+         $brace += ($& eq '{');
+         $brace -= ($& eq '}');
+        }
+       die "Mismatched {} in $_[0]" if ($brace);
+       die "Mismatched () in $_[0]" if ($paren);
+      }
+     elsif (s/^(\$\S?|[^\s\$]+)//)
+      {
+       $token .= $&;
+      }
+    }
+   push(@result,$token);
+  }
+ return (wantarray) ? @result : \ result;
+}
+
+
+#
+# read makefile (or fragment of one) either as a result
+# of a command line, or an 'include' in another makefile.
+# 
+sub makefile
+{
+ my ($self,$makefile,$name) = @_;
+ local $_;
+# print STDERR "Reading $name\n";
+Makefile:
+ while (<$makefile>)
+  {
+   last unless (defined $_);
+   chomp($_);
+   if (/\\$/)
+    {
+     chop($_);
+     s/\s*$//;
+     my $more = <$makefile>;
+     $more =~ s/^\s*/ /; 
+     $_ .= $more;
+     redo;
+    }
+   next if (/^\s*#/);
+   next if (/^\s*$/);
+   s/#.*$//;
+   s/^\s+//;
+   if (/^(-?)include\s+(.*)$/)
+    {
+     my $opt = $1;
+     my $file;
+     foreach $file (tokenize($self->subsvars($2)))
+      {
+       local *Makefile;
+       my $path = $self->pathname($file);
+       if (open(Makefile,"<$path"))
+        {
+         $self->makefile(\*Makefile,$path);
+         close(Makefile);
+        }
+       else
+        {
+         warn "Cannot open $path:$!" unless ($opt eq '-') ;
+        }
+      }
+    }
+   elsif (/^\s*([\w._]+)\s*:?=\s*(.*)$/)
+    {
+     $self->{Vars}{$1} = (defined $2) ? $2 : "";
+#    print STDERR "$1 = ",$self->{Vars}{$1},"\n";
+    }
+   elsif (/^vpath\s+(\S+)\s+(.*)$/)
+    {my ($pat,$path) = ($1,$2);
+     $self->{Vpath}{$pat} = $path;
+    }
+   elsif (/^\s*([^:]*)(::?)\s*(.*)$/)
+    {
+     my ($target,$kind,$depend) = ($1,$2,$3);
+     my @cmnds;
+     if ($depend =~ /^([^;]*);(.*)$/)
+      {
+       ($depend,$cmnds[0])  = ($1,$2);
+      }
+     while (<$makefile>)
+      {
+       next if (/^\s*#/);
+       next if (/^\s*$/);
+       last unless (/^\t/);
+       chop($_);         
+       if (/\\$/)        
+        {                
+         chop($_);
+         $_ .= ' ';
+         $_ .= <$makefile>;
+         redo;           
+        }                
+       next if (/^\s*$/);
+       s/^\s+//;
+       push(@cmnds,$_);
+      }
+     $depend =~ s/\s\s+/ /;
+     $target =~ s/\s\s+/ /;
+     my @depend = tokenize($depend);
+     foreach (tokenize($target))
+      {
+       my $t = $self->Target($_);
+       my $index = 0;
+       if ($kind eq '::' || /%/)
+        {
+         $t->dcolon(\ depend,\ cmnds);
+        }
+       else
+        {
+         $t->colon(\ depend,\ cmnds);
+        }
+      }
+     redo Makefile;
+    }
+   else
+    {
+     warn "Ignore '$_'\n";
+    }
+  }
+}
+
+sub pseudos
+{
+ my $self = shift;
+ my $key;
+ foreach $key (qw(SUFFIXES PHONY PRECIOUS PARALLEL))
+  {
+   my $t = delete $self->{Dot}{'.'.$key};
+   if (defined $t)
+    {
+     my $dep;
+     $self->{$key} = {};
+     foreach $dep ($t->colon->exp_depend)
+      {
+       $self->{$key}{$dep} = 1;
+      }
+    }
+  }
+}
+
+
+sub ExpandTarget
+{
+ my $self = shift;
+ foreach my $t (@{$self->{'Targets'}})
+  {
+   $t->ExpandTarget;
+  }
+ foreach my $t (@{$self->{'Targets'}})
+  {
+   $t->ProcessColon;
+  }
+}
+
+sub parse
+{
+ my ($self,$file) = @_;
+ if (defined $file)
+  {
+   $file = $self->pathname($file);
+  }
+ else
+  {
+   my @files = qw(makefile Makefile);
+   unshift(@files,'GNUmakefile') if ($self->{GNU});
+   my $name;
+   foreach $name (@files)
+    {
+     $file = $self->pathname($name);
+     if (-r $file)
+      {
+       $self->{Makefile} = $name;
+       last; 
+      }
+    }
+  }
+ local (*Makefile);
+ open(Makefile,"<$file") || croak("Cannot open $file:$!");
+ $self->makefile(\*Makefile,$file);
+ close(Makefile);
+
+ # Next bits should really be done 'lazy' on need.
+
+ $self->pseudos;         # Pull out .SUFFIXES etc. 
+ $self->dotrules;        # Convert .c.o into %.o : %.c
+}
+
+sub PrintVars
+{
+ my $self = shift;
+ local $_;
+ foreach (keys %{$self->{Vars}})
+  {
+   print "$_ = ",$self->{Vars}{$_},"\n";
+  }
+ print "\n";
+}
+
+sub exec
+{
+ my $self = shift;
+ undef %date;
+ $generation++;
+ if ($^O eq 'MSWin32')
+  {
+   my $cwd = cwd();
+   my $ret;
+   chdir $self->{Dir};
+   $ret = system(@_);
+   chdir $cwd;
+   return $ret;
+  }
+ else
+  {
+   my $pid  = fork;
+   if ($pid)
+    {
+     waitpid $pid,0;
+     return $?;
+    }
+   else
+    {
+     my $dir = $self->{Dir}; 
+     chdir($dir) || die "Cannot cd to $dir";
+     # handle leading VAR=value here ?
+     # To handle trivial cases like ': libpTk.a' force using /bin/sh
+     exec("/bin/sh","-c",@_) || confess "Cannot exec ".join(' ',@_);
+    }
+  }
+}
+
+sub NextPass { shift->{Pass}++ }
+sub pass     { shift->{Pass} }
+
+sub apply
+{
+ my $self = shift;
+ my $method = shift;
+ $self->NextPass;
+ my @targets = ();
+ # print STDERR join(' ',Apply => $method,@_),"\n";
+ foreach (@_)
+  {
+   if (/^(\w+)=(.*)$/)
+    {
+     # print STDERR "OVERRIDE: $1 = $2\n";
+     $self->{Override}{$1} = $2;
+    }
+   else
+    {
+     push(@targets,$_);
+    }
+  }
+ #
+ # This expansion is dubious as it alters the database
+ # as a function of current values of Override.
+ # 
+ $self->ExpandTarget;    # Process $(VAR) : 
+ @targets = ($self->{'Targets'}[0])->Name unless (@targets);
+ # print STDERR join(' ',Targets => $method,map($_->Name,@targets)),"\n";
+ foreach (@targets)
+  {
+   my $t = $self->{Depend}{$_};
+   unless (defined $t)
+    {
+     print STDERR join(' ',$method,@_),"\n";
+     die "Cannot `$method' - no target $_" 
+    }
+   $t->$method();
+  }
+}
+
+sub Script
+{
+ shift->apply(Script => @_);
+}
+
+sub Print
+{
+ shift->apply(Print => @_);
+}
+
+sub Make
+{
+ shift->apply(Make => @_);
+}
+
+sub new
+{
+ my ($class,%args) = @_;
+ unless (defined $args{Dir})
+  {
+   chomp($args{Dir} = getcwd());
+  }
+ my $self = bless { %args, 
+                   Pattern  => {},  # GNU style %.o : %.c 
+                   Dot      => {},  # Trad style .c.o
+                   Vpath    => {},  # vpath %.c info 
+                   Vars     => {},  # Variables defined in makefile
+                   Depend   => {},  # hash of targets
+                   Targets  => [],  # ordered version so we can find 1st one
+                   Pass     => 0,   # incremented each sweep
+                   Pathname => {},  # cache of expanded names
+                   Need     => {},
+                   Done     => {},
+                 },$class;
+ $self->{Vars}{CC}     = $Config{cc};
+ $self->{Vars}{AR}     = $Config{ar};
+ $self->{Vars}{CFLAGS} = $Config{optimize};
+ $self->makefile(\*DATA,__FILE__);
+ $self->parse($self->{Makefile});
+ return $self;
+}
+
+=head1 NAME
+
+Make - module for processing makefiles 
+
+=head1 SYNOPSIS
+
+	require Make;
+	my $make = Make->new(...);
+	$make->parse($file);   
+	$make->Script(@ARGV)
+	$make->Make(@ARGV)
+	$make->Print(@ARGV)
+
+        my $targ = $make->Target($name);
+        $targ->colon([dependancy...],[command...]);
+        $targ->dolon([dependancy...],[command...]);
+        my @depends  = $targ->colon->depend;
+        my @commands = $targ->colon->command;
+
+=head1 DESCRIPTION
+
+Make->new creates an object if C<new(Makefile =E<gt> $file)> is specified
+then it is parsed. If not the usual makefile Makefile sequence is 
+used. (If GNU => 1 is passed to new then GNUmakefile is looked for first.) 
+
+C<$make-E<gt>Make(target...)> 'makes' the target(s) specified
+(or the first 'real' target in the makefile).
+
+C<$make-E<gt>Print> can be used to 'print' to current C<select>'ed stream
+a form of the makefile with all variables expanded. 
+
+C<$make-E<gt>Script(target...)> can be used to 'print' to 
+current C<select>'ed stream the equivalent bourne shell script
+that a make would perform i.e. the output of C<make -n>.
+
+There are other methods (used by parse) which can be used to add and 
+manipulate targets and their dependants. There is a hierarchy of classes
+which is still evolving. These classes and their methods will be documented when
+they are a little more stable.
+
+The syntax of makefile accepted is reasonably generic, but I have not re-read
+any documentation yet, rather I have implemented my own mental model of how
+make works (then fixed it...).
+
+In addition to traditional 
+
+	.c.o : 
+		$(CC) -c ...
+
+GNU make's 'pattern' rules e.g. 
+
+	%.o : %.c 
+		$(CC) -c ...
+
+Likewise a subset of GNU makes $(function arg...) syntax is supported.
+
+Via pmake Make has built perl/Tk from the C<MakeMaker> generated Makefiles...
+
+=head1 BUGS
+
+At present C<new> must always find a makefile, and
+C<$make-E<gt>parse($file)> can only be used to augment that file.
+
+More attention needs to be given to using the package to I<write> makefiles.
+
+The rules for matching 'dot rules' e.g. .c.o   and/or pattern rules e.g. %.o : %.c
+are suspect. For example give a choice of .xs.o vs .xs.c + .c.o behaviour
+seems a little odd.
+
+Variables are probably substituted in different 'phases' of the process
+than in make(1) (or even GNU make), so 'clever' uses will probably not
+work.
+
+UNIXisms abound. 
+
+=head1 SEE ALSO 
+
+L<pmake>
+
+=head1 AUTHOR
+
+Nick Ing-Simmons
+
+=cut 
+
+1;
+#
+# Remainder of file is in makefile syntax and constitutes
+# the built in rules
+#
+__DATA__
+
+.SUFFIXES: .o .c .y .h .sh .cps
+
+.c.o :
+	$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $< 
+
+.c   :
+	$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $< $(LDFLAGS) $(LDLIBS)
+
+.y.o:
+	$(YACC) $<
+	$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ y.tab.c
+	$(RM) y.tab.c
+
+.y.c:
+	$(YACC) $<
+	mv y.tab.c $@
+
+

Added: trunk/plugins/gbf-mkfile/GBF/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/GBF/Makefile.am	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,5 @@
+perlmodulesdir = $(pkgdatadir)/GBF
+perlmodules_DATA = Make.pm General.pm
+
+EXTRA_DIST = $(perlmodules_DATA)
+

Added: trunk/plugins/gbf-mkfile/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/Makefile.am	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,58 @@
+# Plugin UI file
+plugin_uidir = $(anjuta_ui_dir)
+plugin_ui_DATA = 
+
+# Plugin glade file
+plugin_gladedir = $(anjuta_glade_dir)
+plugin_glade_DATA =
+
+# Plugin icon file
+plugin_pixmapsdir = $(anjuta_image_dir)
+plugin_pixmaps_DATA = gbf-mkfile-plugin-48.png
+
+# Plugin scripts
+scriptsdir = $(bindir)
+scripts_SCRIPTS = gbf-mkfile-parse
+
+# Plugin description file
+plugin_in_files = gbf-mkfile.plugin.in
+%.plugin: %.plugin.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugindir = $(anjuta_plugin_dir)
+plugin_DATA = $(plugin_in_files:.plugin.in=.plugin)
+
+AM_CPPFLAGS = 					\
+	$(WARN_CFLAGS) \
+	$(DEPRECATED_FLAGS) \
+	$(LIBANJUTA_CFLAGS) \
+	-DSCRIPTS_DIR=\"$(scriptsdir)\"
+
+plugin_LTLIBRARIES = \
+	libgbf-mkfile.la
+
+libgbf_mkfile_la_SOURCES = \
+	plugin.c \
+	gbf-mkfile-project.c \
+	gbf-mkfile-project.h \
+	gbf-mkfile-config.c \
+	gbf-mkfile-config.h \
+	gbf-mkfile-properties.c \
+	gbf-mkfile-properties.h 
+
+libgbf_mkfile_la_LDFLAGS = $(ANJUTA_PLUGIN_LDFLAGS)
+
+libgbf_mkfile_la_LIBADD = \
+	$(LIBANJUTA_LIBS)
+
+EXTRA_DIST = \
+	$(plugin_in_files) \
+	$(plugin_DATA) \
+	$(plugin_ui_DATA) \
+	$(plugin_pixmaps_DATA)
+
+DISTCLEANFILES = \
+	$(plugin_DATA) \
+	$(plugin_in_files)
+
+SUBDIRS = GBF
+

Added: trunk/plugins/gbf-mkfile/gbf-mkfile-config.c
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/gbf-mkfile-config.c	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,351 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gbf-mkfile-config.c
+ *
+ * This file is part of the Gnome Build framework
+ * Copyright (C) 2005  Eric Greveson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Eric Greveson
+ * Based on the Autotools GBF backend (libgbf-am) by
+ *   JP Rosevear
+ *   Dave Camp
+ *   Naba Kumar
+ *   Gustavo Giráez
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include "gbf-mkfile-config.h"
+
+typedef struct _GbfMkfileConfigEntry  GbfMkfileConfigEntry;
+
+struct _GbfMkfileConfigEntry {
+	gchar            *key;
+	GbfMkfileConfigValue *value;
+};
+
+struct _GbfMkfileConfigMapping {
+	GList *pairs;
+};
+
+
+GbfMkfileConfigValue *
+gbf_mkfile_config_value_new (GbfMkfileValueType type)
+{
+	GbfMkfileConfigValue *new_value;
+
+	g_return_val_if_fail (type != GBF_MKFILE_TYPE_INVALID, NULL);
+	
+	new_value = g_new0 (GbfMkfileConfigValue, 1);
+	new_value->type = type;
+
+	switch (type) {
+	    case GBF_MKFILE_TYPE_STRING:
+		    new_value->string = NULL;
+		    break;
+	    case GBF_MKFILE_TYPE_MAPPING:
+		    new_value->mapping = gbf_mkfile_config_mapping_new ();
+		    break;
+	    case GBF_MKFILE_TYPE_LIST:
+		    new_value->list = NULL;
+		    break;
+	    default:
+		    break;
+	}
+	
+	return new_value;
+}
+
+void 
+gbf_mkfile_config_value_free (GbfMkfileConfigValue *value)
+{
+	if (value == NULL)
+		return;
+	
+	switch (value->type) {
+	    case GBF_MKFILE_TYPE_STRING:
+		    g_free (value->string);
+		    value->string = NULL;
+		    break;
+	    case GBF_MKFILE_TYPE_MAPPING:
+		    gbf_mkfile_config_mapping_destroy (value->mapping);
+		    value->mapping = NULL;
+		    break;
+	    case GBF_MKFILE_TYPE_LIST:
+		    if (value->list) {
+			    g_slist_foreach (value->list,
+					     (GFunc) gbf_mkfile_config_value_free,
+					     NULL);
+			    g_slist_free (value->list);
+			    value->list = NULL;
+		    }
+		    break;
+	    default:
+		    g_warning ("%s", _("Invalid GbfMkfileConfigValue type"));
+		    break;
+	}
+	g_free (value);
+}
+
+GbfMkfileConfigValue *
+gbf_mkfile_config_value_copy (const GbfMkfileConfigValue *source)
+{
+	GbfMkfileConfigValue *value;
+	GSList *l;
+	
+	if (source == NULL)
+		return NULL;
+	
+	value = gbf_mkfile_config_value_new (source->type);
+	
+	switch (source->type) {
+	    case GBF_MKFILE_TYPE_STRING:
+		    value->string = g_strdup (source->string);
+		    break;
+	    case GBF_MKFILE_TYPE_MAPPING:
+		    value->mapping = gbf_mkfile_config_mapping_copy (source->mapping);
+		    break;
+	    case GBF_MKFILE_TYPE_LIST:
+		    value->list = NULL;
+		    for (l = source->list; l; l = l->next) {
+			    GbfMkfileConfigValue *new_value =
+				    gbf_mkfile_config_value_copy ((GbfMkfileConfigValue *)l->data);
+			    value->list = g_slist_prepend (value->list, new_value);
+		    }
+		    value->list = g_slist_reverse (value->list);
+		    break;
+	    default:
+		    g_warning ("%s", _("Invalid GbfMkfileConfigValue type"));
+		    break;
+	}
+
+	return value;
+}
+
+void
+gbf_mkfile_config_value_set_string (GbfMkfileConfigValue *value,
+				const gchar      *string)
+{
+	g_return_if_fail (value != NULL && value->type == GBF_MKFILE_TYPE_STRING);
+
+	if (value->string)
+		g_free (value->string);
+	
+	value->string = g_strdup (string);
+}
+
+void
+gbf_mkfile_config_value_set_list (GbfMkfileConfigValue *value,
+			      GSList           *list)
+{
+	GSList *l;
+	
+	g_return_if_fail (value != NULL && value->type == GBF_MKFILE_TYPE_LIST);
+
+	if (value->list) {
+		g_slist_foreach (value->list, (GFunc) gbf_mkfile_config_value_free, NULL);
+		g_slist_free (value->list);
+	}
+	
+	value->list = NULL;
+	for (l = list; l; l = l->next) {
+		GbfMkfileConfigValue *new_value = gbf_mkfile_config_value_copy
+			((GbfMkfileConfigValue *)l->data);
+		value->list = g_slist_prepend (value->list, new_value);
+	}
+
+	value->list = g_slist_reverse (value->list);
+}
+
+void
+gbf_mkfile_config_value_set_list_nocopy (GbfMkfileConfigValue *value,
+				     GSList           *list)
+{
+	g_return_if_fail (value != NULL && value->type == GBF_MKFILE_TYPE_LIST);
+
+	if (value->list) {
+		g_slist_foreach (value->list, (GFunc) gbf_mkfile_config_value_free, NULL);
+		g_slist_free (value->list);
+	}
+	value->list = list;
+}
+
+void
+gbf_mkfile_config_value_set_mapping (GbfMkfileConfigValue   *value,
+				 GbfMkfileConfigMapping *mapping)
+{
+	g_return_if_fail (value != NULL && value->type == GBF_MKFILE_TYPE_MAPPING);
+
+	gbf_mkfile_config_mapping_destroy (value->mapping);
+
+	value->mapping = mapping;
+}
+
+GbfMkfileConfigMapping *
+gbf_mkfile_config_mapping_new (void)
+{
+	GbfMkfileConfigMapping *new_map;
+
+	new_map = g_new0 (GbfMkfileConfigMapping, 1);
+	new_map->pairs = NULL;
+	
+	return new_map;
+}
+
+void 
+gbf_mkfile_config_mapping_destroy (GbfMkfileConfigMapping *mapping)
+{
+	GbfMkfileConfigEntry *entry;
+	GList            *lp;
+
+	if (mapping == NULL)
+		return;
+
+	for (lp = mapping->pairs; lp; lp = lp->next) {
+		entry = (GbfMkfileConfigEntry *) lp->data;
+		gbf_mkfile_config_value_free (entry->value);
+		g_free (entry->key);
+		g_free (entry);
+	}
+	g_list_free (mapping->pairs);
+	g_free (mapping);
+}
+
+GbfMkfileConfigMapping *
+gbf_mkfile_config_mapping_copy (const GbfMkfileConfigMapping *source)
+{
+	GbfMkfileConfigMapping *new_map;
+	GList              *lp;
+
+	if (source == NULL)
+		return NULL;
+	
+	new_map = g_new0 (GbfMkfileConfigMapping, 1);
+	new_map->pairs = NULL;
+	
+	for (lp = source->pairs; lp; lp = lp->next) {
+		GbfMkfileConfigEntry *new_entry, *entry;
+
+		entry = (GbfMkfileConfigEntry *) lp->data;
+		if (entry == NULL)
+			continue;
+		
+		new_entry = g_new0 (GbfMkfileConfigEntry, 1);
+		new_entry->key = g_strdup (entry->key);
+		new_entry->value = gbf_mkfile_config_value_copy (entry->value);
+		new_map->pairs = g_list_prepend (new_map->pairs, new_entry);
+	}
+
+	return new_map;
+}
+
+GbfMkfileConfigValue * 
+gbf_mkfile_config_mapping_lookup (GbfMkfileConfigMapping *mapping,
+			      const gchar        *key)
+{
+	GbfMkfileConfigEntry *entry = NULL;
+	GList                *lp;
+
+	g_return_val_if_fail (mapping != NULL && key != NULL, NULL);
+
+	for (lp = mapping->pairs; lp; lp = lp->next) {
+		entry = (GbfMkfileConfigEntry *) lp->data;
+		if (!strcmp (entry->key, key))
+			break;
+	}
+	
+	return (lp ? entry->value : NULL);
+}
+
+gboolean 
+gbf_mkfile_config_mapping_insert (GbfMkfileConfigMapping *mapping,
+			      const gchar        *key,
+			      GbfMkfileConfigValue   *value)
+{
+	GbfMkfileConfigEntry *entry = NULL;
+	GList            *lp;
+	gboolean          insert = TRUE;
+	
+	g_return_val_if_fail (mapping != NULL && key != NULL, FALSE);
+
+	for (lp = mapping->pairs; lp; lp = lp->next) {
+		entry = (GbfMkfileConfigEntry *) lp->data;
+		if (!strcmp (entry->key, key)) {
+			insert = FALSE;
+			break;
+		}
+	}
+	
+	if (insert) {
+		GbfMkfileConfigEntry *new_entry;
+
+		new_entry = g_new0 (GbfMkfileConfigEntry, 1);
+		new_entry->key = g_strdup (key);
+		new_entry->value = value;
+		mapping->pairs = g_list_prepend (mapping->pairs, new_entry);
+	}
+	
+	return insert;
+}
+
+gboolean 
+gbf_mkfile_config_mapping_remove (GbfMkfileConfigMapping *mapping,
+			      const gchar        *key)
+{
+	GbfMkfileConfigEntry *entry = NULL;
+	GList                *lp;
+	gboolean              remove = FALSE;
+	
+	g_return_val_if_fail (mapping != NULL && key != NULL, FALSE);
+
+	for (lp = mapping->pairs; lp; lp = lp->next) {
+		entry = (GbfMkfileConfigEntry *) lp->data;
+		if (!strcmp (entry->key, key)) {
+			remove = TRUE;
+			break;
+		}
+	}
+	
+	if (remove) {
+		gbf_mkfile_config_value_free (entry->value);
+		g_free (entry->key);
+		g_free (entry);
+		mapping->pairs = g_list_delete_link (mapping->pairs, lp);
+	}
+	
+	return remove;
+}
+
+void
+gbf_mkfile_config_mapping_foreach (GbfMkfileConfigMapping *mapping,
+			       GbfMkfileConfigMappingFunc callback,
+			       gpointer user_data)
+{
+	GbfMkfileConfigEntry *entry = NULL;
+	GList                *lp;
+	
+	g_return_if_fail (mapping != NULL && callback != NULL);
+
+	for (lp = mapping->pairs; lp; lp = lp->next) {
+		entry = (GbfMkfileConfigEntry *) lp->data;
+		callback (entry->key, entry->value, user_data);
+	}
+}

Added: trunk/plugins/gbf-mkfile/gbf-mkfile-config.h
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/gbf-mkfile-config.h	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,89 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gbf-mkfile-config.h
+ *
+ * This file is part of the Gnome Build framework
+ * Copyright (C) 2005  Eric Greveson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Eric Greveson
+ * Based on the Autotools GBF backend (libgbf-am) by
+ *   JP Rosevear
+ *   Dave Camp
+ *   Naba Kumar
+ *   Gustavo Giráez
+ */
+
+#ifndef __GBF_MKFILE_CONFIG_H__
+#define __GBF_MKFILE_CONFIG_H__
+
+#include <glib.h>
+
+/* config data structures */
+typedef enum {
+	GBF_MKFILE_TYPE_INVALID,
+	GBF_MKFILE_TYPE_STRING,
+	GBF_MKFILE_TYPE_MAPPING,
+	GBF_MKFILE_TYPE_LIST
+} GbfMkfileValueType;
+
+typedef struct _GbfMkfileConfigValue    GbfMkfileConfigValue;
+typedef struct _GbfMkfileConfigMapping  GbfMkfileConfigMapping;
+
+struct _GbfMkfileConfigValue {
+	GbfMkfileValueType type;
+	gchar               *string;
+	GbfMkfileConfigMapping  *mapping;
+	GSList              *list;
+};
+
+typedef void (*GbfMkfileConfigMappingFunc) (const gchar *key,
+					GbfMkfileConfigValue *value,
+					gpointer user_data);
+/* ---------- public interface */
+
+GbfMkfileConfigValue *gbf_mkfile_config_value_new         (GbfMkfileValueType      type);
+void                  gbf_mkfile_config_value_free        (GbfMkfileConfigValue   *value);
+GbfMkfileConfigValue *gbf_mkfile_config_value_copy        (const GbfMkfileConfigValue *source);
+
+void                  gbf_mkfile_config_value_set_string      (GbfMkfileConfigValue   *value,
+						 const gchar        *string);
+void                  gbf_mkfile_config_value_set_list        (GbfMkfileConfigValue   *value,
+							 GSList             *list);
+void                  gbf_mkfile_config_value_set_list_nocopy (GbfMkfileConfigValue   *value,
+							 GSList             *list);
+void                  gbf_mkfile_config_value_set_mapping     (GbfMkfileConfigValue   *value,
+							 GbfMkfileConfigMapping *mapping);
+
+#define gbf_mkfile_config_value_get_string(x)  ((const gchar *)(((GbfMkfileConfigValue *)x)->string))
+#define gbf_mkfile_config_value_get_list(x)    ((GSList *)(((GbfMkfileConfigValue *)x)->list))
+#define gbf_mkfile_config_value_get_mapping(x) ((GbfMkfileConfigMapping *)(((GbfMkfileConfigValue *)x)->mapping))
+
+GbfMkfileConfigMapping *gbf_mkfile_config_mapping_new           (void);
+void                    gbf_mkfile_config_mapping_destroy       (GbfMkfileConfigMapping *mapping);
+GbfMkfileConfigMapping *gbf_mkfile_config_mapping_copy          (const GbfMkfileConfigMapping *source);
+GbfMkfileConfigValue   *gbf_mkfile_config_mapping_lookup        (GbfMkfileConfigMapping *mapping,
+							 const gchar        *key);
+gboolean                gbf_mkfile_config_mapping_insert        (GbfMkfileConfigMapping *mapping,
+							 const gchar        *key,
+							 GbfMkfileConfigValue   *value);
+gboolean                gbf_mkfile_config_mapping_remove        (GbfMkfileConfigMapping *mapping,
+							 const gchar        *key);
+void                    gbf_mkfile_config_mapping_foreach       (GbfMkfileConfigMapping *mapping,
+							 GbfMkfileConfigMappingFunc callback,
+							 gpointer user_data);
+
+#endif /* __GBF_MKFILE_CONFIG_H__ */

Added: trunk/plugins/gbf-mkfile/gbf-mkfile-parse.in
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/gbf-mkfile-parse.in	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,1797 @@
+#!/usr/bin/perl
+# -*- perl -*-
+
+eval 'exec @PERL@ -S $0 ${1+"$@"}'
+    if 0;
+
+BEGIN
+{
+    my $prefix = "@prefix@";
+    my $perlmodulesdir = "@datadir@/@PACKAGE@";
+    unshift @INC, "$perlmodulesdir";
+}
+
+use strict;
+
+use GBF::General;
+use GBF::Make;
+
+# I18n
+use Locale::gettext;
+use POSIX;
+
+setlocale(LC_MESSAGES, "");
+textdomain("@PACKAGE@");
+
+my $debug = 0;
+my $verbose = 0;
+my $dry_run = 0;
+
+
+sub debug { my $message = $_[0]; print STDERR "DEBUG: $message\n" if $debug; }
+
+# File taken from Automake GBF backend and modified for Makefile use by Eric
+# Greveson. Uses Make.pm from Nick Ing-Simmons' Make-1.00 CPAN module.
+
+# General FIXMEs:
+
+# - implement help for the command line
+
+# - Don't remove backslashes from read macros
+
+# File index:
+# 1. CONSTANTS AND VARIABLES
+# 3. SOURCE EXTRACTION
+# 4. GROUP AND PROJECT CONSTRUCTION
+# 5. PROJECT MODIFICATION
+# 6. TARGET WRITER FUNCTIONS
+# 7. XML PRINTING FUNCTIONS
+# 8. XML SCANNING
+# 9. XML PROCESSING
+# 10. HELPER FUNCTIONS
+# 11. MAIN PROGRAM
+
+##########################################################################################
+#
+# BEGIN DOCUMENTATION
+#
+# Syntax:
+# --------------------------------------------------------------------------------------- 
+#
+#  gbf-mkfile-parse [options] <operation> <argument>
+#
+#  Operations:
+#
+#     --get : argument is the project root
+#             analyzes the Makefile project and outputs an xml representation
+#
+#     --set : argument is a file (or - for stdin)
+#             reads an xml file which specifies operations to be performed
+#             outputs changed project groups affected by the operations
+#
+#  Options:
+#
+#     -v (--verbose) : output additional information during processing
+#     -n (--dry-run) : when in --set operation mode don't alter any files, just
+#                      parse the input
+#     -d (--debug)   : output debugging information
+#
+#  Output:
+# 
+#  In stdout the script outputs the xml representation of the project
+#  (or the changed groups).  Through stderr the script outputs errors and warnings
+#  using the following format:
+#
+#  DEBUG: message
+#  ERROR(code): message
+#  WARNING(code): message
+#
+#  Error codes:
+#
+#       0   Success
+#    1-99   General errors
+#       1:  Malformed project
+#       2:  Invalid directory
+#       3:  Cannot open output file
+#       4:  Cannot open input file
+#       5:  Syntax error
+# 100-199   Makefile parser error
+#     100:  Can't open file
+#     101:  Invalid trailing backslash
+# 200-299   Extraction errors
+#     200:  Invalid primary prefix
+# 300-399   Target writer errors
+#     300:  Invalid operation
+#     301:  Invalid operand
+#     302:  Unimplemented target writer
+#     303:  Group not found
+#     304:  Can't write file
+#     305:  Target/group already exists
+#
+#  Warning codes:
+#
+#       0   Success
+#    1-99   General warnings
+# 100-199   Makefile parser warning
+#     100:  Adding text to not previously declared macro
+#     102:  Makefile for a subdirectory not found
+# 200-299   Extraction warning
+#     200:  General semantic error
+# 300-399   Target writer warning
+#     300:  Creating an already existing macro
+#     301:  Out of bounds line number
+#     302:  Target types don't match while creating a new simple primary target
+#
+#
+#
+# Output format (see output.dtd):
+# ---------------------------------------------------------------------------------------
+#
+# Sample output:
+# <?xml version='1.0' encoding='ISO-8859-1' standalone='yes'?>
+# <!DOCTYPE project []>
+# <project root="/home/gustavo/tmp/gdl">
+#
+# the 'root' attribute is a full path to the root of the project
+#
+#  <config>
+#    <param name="source_generators">
+#      <item name="glib_genmarshal" value="GLIB_GENMARSHAL"/>
+#      <item name="glib_mkenums" value="GLIB_MKENUMS"/>
+#      <item name="orbit_idl" value="ORBIT_IDL"/>
+#    </param>
+#  </config>
+#
+# the project itself can contain some 'config' parameters
+# the config is composed of 'param' elements
+# special characters in parameter values are escaped
+# \  (the backslash) -> '\\'
+# \n (newline)       -> '\n'
+# \t (tab character) -> '\t'
+#
+# there are three types of parameters: 
+#
+# 1) simple strings:
+# <param name="parameter" value="string"/>
+#
+# 2) lists:
+# <param name="list">
+#   <item value="item 1"/>
+#   <item value="item 2"/>
+# </param>
+#
+# 3) mappings:
+# <param name="mapping">
+#   <item name="item1" value="value 1"/>
+#   <item name="item2" value="value 2"/>
+# </param>
+#
+# Next in the project comes the root group
+# each group can have:
+# - one config section (defined as above)
+# - some targets
+# - other groups nested in
+#
+#  <group name="/" id="/">
+#
+# here the attribute 'name' is a printable name
+# and 'id' is a unique name to identify the group in the whole project
+# also 'id' is the path from the project root for the group
+# here's a nested group (nested groups appear before targets but after group config
+#
+#    <group name="idl" id="/idl/">
+#      <config>
+#        <param name="installdirs">
+#          <item name="idl" value="$(datadir)/idl/libgdl-1.0"/>
+#        </param>
+#      </config>
+#      <target name="idl" type="data" id="idl:data">
+#
+# each target has a printable 'name' a unique 'id' (within the group) and a 'type'
+# conventionally id is name:type but that is subject to change
+# the targets also can have a config section
+#
+#        <config>
+#          <param name="installdir" value="idl"/>
+#        </config>
+#
+# after the config section, comes the target sources and dependencies
+# the difference between a source and a dependency is that plain sources are
+# files available directly, and dependencies are files which are in turn
+# generated from other targets
+#
+#        <source uri="/idl/GDL.idl"/>
+#        <source uri="/idl/editor-buffer.idl"/>
+#        <source uri="/idl/editor-gutter.idl"/>
+#        <source uri="/idl/symbol-browser.idl"/>
+#      </target>
+#
+# an example of a target with dependencies:
+#
+#      <target name="libgdl-1.la" type="shared_lib" id="libgdl-1.la:shared_lib">
+#        <source uri="/gdl/default-icon.c"/>
+#        .
+#        .
+#        <source uri="/gdl/gdl-recent.c"/>
+#        <dependency file="/gdl/libgdlmarshal.c" target="libgdlmarshal.c:rule"/>
+#        .
+#        .
+#        <dependency file="/gdl/libgdltypebuiltins.c" target="libgdltypebuiltins.c:rule"/>
+#      </target>
+#
+# the 'uri' attribute contains a path absolute to the project root for the file
+# the 'file' is the same as 'uri' but for dependencies, and 'target' is the target
+# 'id' as explained before (the targets belong to the same group)
+#
+#
+# Internal architecture:
+# ---------------------------------------------------------------------------------------
+#
+# All data is kept in anonymous hashes to emulate a C struct
+#
+# Meta structure definitions
+#
+# +--------------
+# | project               : information about the project as a whole
+# +----------------
+# | prefix                : string containing the root directory of the project
+# | root_group            : root group hash (defined below)
+# | all_groups            : flat hash containing all the groups for faster access
+# |
+#
+# +----------------
+# | group                 : group (or directory for this backend) information
+# +-----------------
+# | name                  : string, the name of the group
+# | makefile              : makefile hash (see GBF/AmFiles.pm)
+# | targets               : hash of target hashes (defined below) 
+# |                         (target_name => target_hash)
+# | config                : group_config hash for this directory
+# | groups                : hash of subgroups hashes
+# | changed               : the group_process procedure puts this flag to 1
+# | 
+#
+# +----------------
+# | group_config          : group configuration
+# +-----------------
+# | subdirs               : SUBDIRS macro (order is important!!)
+# | includes (*)          : INCLUDES macro
+# | ldadd (*)             : LDADD macro
+# | compile (*)           : COMPILE macro
+# | link (*)              : LINK macro
+# | options (*)           : AUTOMAKE_OPTIONS macro
+# | omit_deps (*)         : OMIT_DEPENDENCIES macro
+# | built_sources         : BUILT_SOURCES macro (expanded)
+# | extra_dist            : EXTRA_DIST macro (expanded)
+# | cleanfiles (*)        : CLEANFILES macro
+# | mostlyclean (*)       : MOSTLYCLEANFILES macro
+# | maintainclean (*)     : MAINTAINCLEANFILES macro
+# | distclean (*)         : DISTCLEANFILES macro
+# | install_exec_local(*) : install-exec-local rule actions
+# | install_data_local(*) : install-data-local rule actions
+# | dist_hook (*)         : dist-hook rule actions
+# | other_vars            : hash of unused remaining Makefile.am macros after target
+# |                         extraction (macro_name => macro_contents)
+# | installdirs           : hash containing user defined installation prefixes
+# |                         (prefix => installation_dir)
+# | 
+#
+# +----------------
+# | target                : target information
+# +-----------------
+# | id                    : id of the target (how it's identified in the group's hash
+# | name                  : string, name of the target
+# | type                  : type of the target
+# | sources               : list of the source files which this target depends on
+# | dependencies          : list of other targets this target depends on
+# | built_files           : list of files presumably this target generates
+# | config                : target_config hash (defined below)
+# | 
+#
+# +----------------
+# | target_config         : extra target configuration (these are all optional and 
+# |                         dependent of the target type)
+# +-----------------
+# | ldflags (*)           : for a primary target, target_LDFLAGS macro contents
+# | ldadd (*)             : for a primary target, target_LDADD macro contents
+# | explicit_deps (*)     : for a primary target, target_DEPENDENCIES macro contents
+# | actions (*)           : rule actions if the target is derived from a makefile rule
+# | installdir            : installation prefix (or noinst, check, EXTRA)
+# | 
+#
+# (*)  These fields are optional, they can be empty or undef
+#
+#
+# END DOCUMENTATION  ------------------
+#
+##########################################################################################
+
+######################################################################
+####  3. SOURCE EXTRACTION  ##########################################
+######################################################################
+
+sub find_rules_for_target
+{
+	my $target = shift;
+	my $rules;
+	# FIXME: only takes first COLON or DCOLON
+	if (exists $target->{DCOLON})
+	{
+		$rules = $target->{DCOLON};
+	}
+	elsif (exists $target->{COLON})
+	{
+		$rules = $target->{COLON};
+	}
+	# If we only have one rule, make a 1-element array reference
+	if ((ref $rules) eq "Make::Rule")
+	{
+		my @rulearray = ($rules);
+		$rules = \ rulearray;
+	}
+	return $rules;
+}
+
+sub is_phony_target
+{
+	my ($target, $root) = @_;
+	return 0 unless exists $root->{Depend}{".PHONY"};
+	
+	if (defined (my $rules = find_rules_for_target ($root->{Depend}{".PHONY"})))
+	{
+		foreach (@{$rules})
+		{
+			foreach (@{$_->depend})
+			{
+				return 1 if ($_ eq $target);
+			}
+		}
+	}
+	return 0;
+}
+
+sub find_source_suffixes
+{
+	my ($sr, $root) = @_;
+	
+	if (defined (my $rules = find_rules_for_target ($root->{Depend}{$sr}) ))
+	{
+		my @suffixes;
+		foreach (@{$rules})
+		{
+			foreach (@{$_->depend})
+			{
+				my $sufdep = $_;
+				# if suffix dependency starts with a %, strip it
+				$sufdep =~ s/^\%//;
+				
+				push @suffixes, $sufdep;
+			}
+		}
+		return @suffixes;
+	}
+	return undef;
+}
+
+sub find_sources_for_depname
+{
+	my ($depname, $path, $root) = @_;
+	my @sources = ();
+	
+	# use suffix rules to find the sources
+	my @suffix;
+	my $depsuffix = $depname;
+	if ($depsuffix =~ /\.\w*$/)
+	{
+		$depsuffix =~ s/.*\./\./g;
+
+		# The dependency name has a suffix: check for %.blah rule
+		# Otherwise try a .blah rule
+		my $sr = '%' . $depsuffix;
+		if ($root->{Depend}{$sr})
+		{
+			@suffix = find_source_suffixes ($sr, $root);
+		}
+		elsif ($root->{Depend}{$depsuffix})
+		{
+			@suffix = find_source_suffixes ($depsuffix, $root);
+		}
+	}
+	else
+	{
+		# The dependency name has no suffix: check for % rule
+		if ($root->{Depend}{'%'})
+		{
+			@suffix = find_source_suffixes ('%', $root);
+		}
+	}
+	
+	$depname =~ s/\.\w*$//;
+	foreach (@suffix)
+	{
+		my $srcname = $path . "/" . $depname . $_;
+		
+		if ((-e $srcname) && (-T $srcname))
+		{
+			# add the source file to the list and finish
+			push @sources, $srcname;
+			last;
+		}
+	}
+	
+	return (wantarray ? @sources : \ sources);
+}
+
+sub find_all_deps
+{
+	my ($target, $rules, $group, %loopdetect) = @_;
+	my %deps = ();
+	unless (defined %loopdetect) {%loopdetect = ();}
+	
+	foreach (@{$rules})
+	{
+		foreach (@{$_->depend})
+		{
+			if (exists $loopdetect{$_})
+			{
+				&report_error (200, gettext("Loop detected in dependency graph"));
+				return undef;
+			}
+			$loopdetect{$_} = 1;
+			$deps{$_} = 1;
+			
+			if (exists $group->{Depend}{$_})
+			{
+				# It's a target - find its deps and add them to the hash.
+				my $newtarget = $group->{Depend}{$_};
+				my $newrules = find_rules_for_target ($newtarget);		
+
+				%deps = (%deps, find_all_deps ($newtarget, $newrules, 
+											$group, %loopdetect));
+			}
+		}
+	}
+	return %deps;
+}
+
+# find_top_targets: find the top-level non-phony non-suffix targets to
+# build the project tree roots from
+
+sub find_top_targets
+{
+	my $root = shift;
+	my %targets = %{$root->{Depend}};
+	my %deps;
+	
+	foreach (keys %targets)
+	{
+		# get rid of suffix and phony targets
+		if (($_ eq ".PHONY") || ($_ =~ /^[\.%]/))
+		{
+			delete $targets{$_};
+			next;
+		}
+
+		my $curr_targ = $root->{Depend}{$_};
+		%deps = find_all_deps ($curr_targ, find_rules_for_target($curr_targ), 
+								$root);
+		delete @targets{keys %deps};
+	}
+	
+	# Sort keys in same order as the Makefile targets
+	my @sorted = ();
+	foreach (@{$root->{Targets}})
+	{
+		if (exists $targets{$_})
+		{
+			push @sorted, $_;
+		}
+	}
+
+	return (%targets, @sorted);
+}
+
+sub create_group_from_depname
+{
+	my ($depname, $parent, $root) = @_;
+	
+	my $path = $root->{Dir};
+	my $sources = find_sources_for_depname ($depname, $path, $root);
+	
+	my $group = bless  {Name => $depname,
+						Dir => $path,
+						Children => {},
+						Rules => {},
+						Sources => $sources,
+						Changed => 0,
+						Vars => {
+									Phony => is_phony_target ($depname, $root)
+									},
+						Id => "$parent->{Id}$depname/",
+						Parent => $parent,
+						Root => $root};
+						
+	return $group;
+}
+
+sub create_group_from_target
+{
+	my ($target, $parent, $root) = @_;
+
+	my $group = create_group_from_depname ($target->Name, $parent, $root);
+	$group->{Rules} = find_rules_for_target ($target);
+	$group->{Target} = $target->Name;
+
+	# Add child groups
+
+	foreach (@{$group->{Rules}})
+	{
+		foreach (@{$_->depend})
+		{
+			if (exists $root->{Depend}{$_})
+			{
+				# we have a dependency which is itself a target
+				my $childtarget = $root->{Depend}{$_};
+				$group->{Children}{$_} = create_group_from_target ($childtarget, 
+																$group, $root);
+			}
+			else
+			{
+				# we have a dependency built from suffix rules
+				$group->{Children}{$_} = create_group_from_depname($_, 
+																$group, $root);
+			}
+		}
+	}
+	
+	return $group;
+}
+
+sub build_makefile_tree
+{
+	my $root = shift;
+	my (%targets, @targ_sorted) = find_top_targets ($root);
+	my $group = bless  {Name => "/",
+						Vars => $root->{Vars},
+						Children => {},
+						Changed => 0,
+						Dir => $root->{Dir},
+						Id => "/",
+						Root => $root};
+
+	foreach (keys %targets)
+	{
+		$group->{Children}{$_} = create_group_from_target($root->{Depend}{$_},
+															$root, $root);
+	}
+
+	return $group;
+}
+
+sub rec_print
+{
+	my ($root, $tabs) = @_;
+
+	print $tabs. "+" . $root->{Name} . "\n";
+	$tabs .= "\t";
+	if (exists ($root->{Sources}))
+	{
+		foreach (@{$root->{Sources}})
+		{
+			print "$tabs $_\n";
+		}
+	}
+	foreach (values %{$root->{Children}})
+	{
+		rec_print($_, $tabs);
+	}
+}
+
+######################################################################
+####  4. GROUP AND PROJECT CONSTRUCTION  #############################
+######################################################################
+
+sub process_project
+{
+    my $project_dir = $_[0];
+	my $mkfile_name = "Makefile";
+
+    &debug ("Using $project_dir as the project root");
+    if ( ! -f "$project_dir/Makefile") {
+		if ( ! -f "$project_dir/makefile") {
+			&report_error (1, gettext("Can't find $project_dir/Makefile or $project_dir/makefile"));
+			return undef;
+		}
+		$mkfile_name = "makefile";
+    };
+
+    my $project = { prefix      => $project_dir,
+		            mkfile      => $mkfile_name,
+					root        => {}};
+    
+    # Recursively parse the Makefiles
+	$project->{root} = Make->new(Dir => $project_dir, Makefile => $mkfile_name);
+    $project->{root_group} = build_makefile_tree ($project->{root});
+	
+    return $project;
+};
+
+
+######################################################################
+####  5. PROJECT MODIFICATION  #######################################
+######################################################################
+
+###
+# project_reset_changed (project)
+#   reset groups' changed flag
+sub project_reset_changed
+{
+    my $project = shift;
+    foreach my $group (values %{$project->{all_groups}}) {
+	$group->{changed} = 0;
+    }
+}
+
+###
+# project_operate (project, operations)
+#   perform indicated operations on project
+#   returns: true if the project changed
+sub project_operate
+{
+    my ($project, $ops) = @_;
+    my $project_dirty = 0;
+
+  OP:
+    foreach my $op (@$ops) {
+	&debug ("($op->{op}, $op->{group_name}, ".
+		"$op->{target_name}, $op->{operand})");
+	my ($group, $target, $operand, $group_name, $result);
+	
+	if ($op->{op} eq "add_group" || $op->{op} eq "remove_group") {
+	    $operand = $op->{group_name};
+	    $group_name = $operand;
+	    $group_name =~ s/[^\/]+\/\z//;
+	    &debug ("Adding a group $operand on group $group_name");
+	} else {
+	    # Get the group from the project
+	    $group_name = $op->{group_name};
+	}
+	$group = $project->{all_groups}{$group_name};
+	
+	if (!$group) { 
+	    &report_error (303, gettext("The group $group_name doesn't exist"));
+	    next OP; 
+	}
+	&debug ("Using group $group->{name}");
+	
+	# If the operation is on a group, do it now
+	## if ($op->{op} =~ /group/) {
+	if ($op->{target_name} eq "") {
+	    if (!defined($operand)) {
+	    	$operand = $op->{operand};
+	    }
+	    $result = &group_op_handler ($project, $group, $op->{op}, $operand);
+	    $project_dirty = 1;
+	    next OP;
+	}
+
+	# If not, continue with the parameters preparation
+	$operand = $op->{operand};
+	$target = $op->{target_name};
+	my $target_type = $operand;
+	if ($op->{op} ne "add_target" && $op->{op} !~ /group/) {
+	    # Get the target hash from the group
+	    $target = $group->{targets}{$target};
+	    if (! defined ($target)) {
+		&report_error (304, gettext("The target $op->{target_name} doesn't exist"));
+		next OP;
+	    }
+	    $target_type = $target->{type};
+	}
+	
+	# Get the target writer
+#	my $writer = $target_writers{$target_type};
+#	$result = &$writer ($project, $group, $op->{op}, $target, $operand);
+#	if ($result == 0) {
+#	    $project_dirty = 1;
+#	}
+    }
+
+    if ($project_dirty) {
+	# update flat hash first
+	&project_update_all_groups ($project);
+
+	# write configure.in
+	if ($project->{configure_in}{dirty}) {
+	    output_configure_in $project->{configure_in};
+	    # FIXME: possibly reprocess configure.in information
+	};
+
+	foreach my $group (values %{$project->{all_groups}}) {
+	    if ($group->{makefile}{dirty}) {
+		my $makefile = $group->{makefile};
+		&debug ("$makefile->{filename} is dirty");
+
+		# output modified makefile.am if not running in test mode
+		&output_am_file ($makefile, $makefile->{filename}) unless ($dry_run);
+
+		# reprocess group
+		&group_process ($group);
+
+		$makefile->{dirty} = 0;
+	    };
+	};
+    };
+
+    return $project_dirty;
+}
+
+
+######################################################################
+####  6. TARGET WRITER FUNCTIONS  ####################################
+######################################################################
+
+sub group_op_handler
+{
+    my ($project, $group, $op, $operand) = @_;
+    my $makefile = $group->{makefile};
+
+    # group is the group hash in which to do the operation
+    # op is: "add_group" or "remove_group"
+    # operand is full group name for add_group, and ignored for remove_group
+
+    if ($op eq "add_group") {
+	my @components = split '/', $operand;
+	my $group_name = "";
+	$group_name = pop @components while ($group_name eq "");
+
+	# check that the group doesn't exist already
+	if (defined $group->{groups}{$group_name}) {
+	    return &report_error (305, gettext("Group $operand already exists"));
+	};
+
+	# create the directory and an empty Makefile.am
+	my $local_new_dir = $group->{prefix} . $group_name;
+	my $new_dir = $project->{prefix} . $local_new_dir;
+	
+	unless (-d $new_dir) {
+	    mkdir $new_dir || return &report_error (304, gettext("Can't mkdir $new_dir"));
+	};
+	unless (-f "$new_dir/Makefile.am") {
+	    if (open (NEWFILE, ">$new_dir/Makefile.am")) {
+		print NEWFILE "## File created by the gnome-build tools\n\n\n";
+		close NEWFILE;
+	    } else {
+		return &report_error (304, gettext("Can't write $new_dir/Makefile.am"));
+	    };
+	};
+
+	# add new directory to the SUBDIRS macro in the parent group
+	&macro_append_text ($makefile, "SUBDIRS", $group_name);
+
+	# create the internal representation of the group
+	$group->{groups}{$group_name} = &create_group ($operand, "$group_name", $project);
+
+	# add Makefile to list of configure generated files
+	$local_new_dir =~ s/^\///;
+	edit_config_files $project->{configure_in}, "add", "$local_new_dir/Makefile";
+    }
+    elsif ($op eq "remove_group") {
+	my @components = split '/', $operand;
+	my $group_name = "";
+
+	$group_name = pop @components while ($group_name eq "");
+	
+	# check that the group doesn't exist already
+	if (!defined $group->{groups}{$group_name}) {
+	    return &report_error (305, gettext("Group $operand does not exists"));
+	};
+	
+	# remove directory from the SUBDIRS macro in the parent group
+	&macro_remove_text ($makefile, "SUBDIRS", $group_name, 1);
+	
+	# add Makefile to list of configure generated files
+	my $local_new_dir = $group->{prefix} . $group_name;
+	$local_new_dir =~ s/^\///;
+	edit_config_files $project->{configure_in}, "remove", "$local_new_dir/Makefile";
+	
+	## Remove group from internal hashes
+	delete $project->{all_groups}->{$operand};
+	delete $group->{groups}->{$group_name};
+    }
+    elsif ($op eq "set_config") {
+      KEY:
+	foreach my $key (keys %$operand) {
+	    my $value = $operand->{$key};
+
+	    if ($key eq "includes") {
+		&macro_rewrite ($makefile, "INCLUDES", $value);
+	    } elsif ($key eq "installdirs") {
+	    	foreach my $item (keys %$value) {
+			&macro_rewrite ($makefile, $item."dir", $value->{$item});
+		}
+	    }    
+	}
+    }
+    else {
+    
+	return &report_error (300, gettext("Invalid operation '$op' to group_op_handler"));
+    }
+    # success!
+    return 0;
+}
+
+sub unimplemented_writer
+{
+    my ($project, $group, $op, $target, $operand) = @_;
+
+    # group is the group hash in which to do the operation
+    # op is: "add_target", "remove_target", "set_config", "add_source", "remove_source"
+    # target is:
+    #  - the new target name when add_target
+    #  - a ref to the target hash to operate on otherwise
+    # operand is:
+    #  - ignored for remove_target
+    #  - target type for add_target
+    #  - the source uri to add/remove on {add,remove}_source
+    #  - a config hash on set_config containing any/all fields in group config
+    # FIXME: handle dependencies
+
+    return &report_error (302, gettext("Unimplemented target type writer"));
+}
+
+sub compiled_primary_target_writer
+{
+    my ($project, $group, $op, $target, $operand) = @_;
+    my $makefile = $group->{makefile};
+
+    my %primaries = ( program    => "PROGRAMS",
+		      static_lib => "LIBRARIES",
+		      shared_lib => "LTLIBRARIES" );
+
+    &debug ("$op on group $group with $target($operand)");
+ 
+    # FIXME: Modify the $group variable as well as the makefile (worth it?...
+    # only if we have to do a sequence of operations on the same target)
+
+    if ($op eq "add_target") {
+	# $target contains the target name, which in this case is the name of
+	# the compiled objects (program or library)
+	# $operand contains the target type
+	my $canonical = &canonicalize_name ($target);
+	my ($primary, $prefix);
+
+	my %default_prefixes = ( program    => "noinst",
+				 static_lib => "noinst",
+				 shared_lib => "lib" );
+
+	$primary = $primaries{$operand};
+	$prefix = $default_prefixes{$operand};
+
+	if ($target eq "") {
+	    return &report_error (301, gettext("Invalid empty target name"));
+	}
+
+	if (!defined $primary) {
+	    return &report_error (301, gettext("Invalid target type '$operand' to compiled_primary_target_writer"));
+	}
+
+	# FIXME: verify that the target doesn't yet exist
+	&macro_append_text ($makefile, "${prefix}_${primary}", $target);
+	&macro_create ($makefile, "${canonical}_SOURCES", "", "${prefix}_${primary}");
+
+    }
+    elsif ($op eq "remove_target") {
+	my $canonical = &canonicalize_name ($target->{name});
+	my $prefix = $target->{config}{installdir};
+	my $primary = $primaries{$target->{type}};
+
+	# FIXME: what if the target is in a macro which is used in prefix_PRIMARY
+	&macro_remove_text ($makefile, "${prefix}_${primary}", $target->{name}, 1);
+	&macro_remove_text ($makefile, "EXTRA_${primary}", $target->{name}, 1);
+	&macro_remove ($makefile, 
+		       "${canonical}_SOURCES", 
+		       "EXTRA_${canonical}_SOURCES",
+		       "${canonical}_LDADD",
+		       "${canonical}_LIBADD",
+		       "${canonical}_LDFLAGS",
+		       "${canonical}_DEPENDENCIES");
+	## FIXME: The line below reports error.
+	## &rule_remove ($makefile, "\$(${canonical}_OBJECTS)");
+	
+	## Remove the target from internal hash
+	my $target_id = $target->{id};
+	delete $group->{targets}->{$target_id};
+    }
+    elsif ($op eq "set_config") {
+	my $canonical = &canonicalize_name ($target->{name});
+	my $primary = $primaries{$target->{type}};
+	
+      KEY:
+	foreach my $key (keys %$operand) {
+	    my $value = $operand->{$key};
+
+	    if ($key eq "installdir") {
+		my $old_prefix = $target->{config}{installdir};
+		my $new_prefix = &check_primary_prefix ($value, $primary, $group);
+		if (!$new_prefix) {
+		    next KEY;
+		}
+
+		# FIXME: handle the EXTRA prefix
+		&debug ("Removing $target->{name} from ${old_prefix}_${primary}");
+		&macro_remove_text ($makefile, "${old_prefix}_${primary}", 
+				    $target->{name}, 1);
+		&macro_append_text ($makefile, "${new_prefix}_${primary}",
+				    $target->{name});
+	    } elsif ($key eq "ldflags") {
+	    	if ($value ne "") {
+		    &macro_rewrite ($makefile, "${canonical}_LDFLAGS",
+				    $value, "${canonical}_SOURCES");
+		} else {
+		    &macro_remove ($makefile, "${canonical}_LDFLAGS");
+		};
+	    } elsif ($key eq "ldadd") {
+		# Get rid of deprecated variable
+		&macro_remove ($makefile, "${canonical}_LIBADD");
+	    	if ($value ne "") {
+		    &macro_rewrite ($makefile, "${canonical}_LDADD",
+				    $value, "${canonical}_SOURCES");
+		} else {
+		    &macro_remove ($makefile, "${canonical}_LDADD");
+		};
+	    } elsif ($key eq "explicit_deps") {
+		# FIXME: this should perhaps be handled via sources add/remove
+	    	if ($value ne "") {
+		    &macro_rewrite ($makefile, "${canonical}_DEPENDENCIES",
+				    $value, "${canonical}_SOURCES");
+		} else {
+		    &macro_remove ($makefile, "${canonical}_DEPENDENCIES");
+		};
+	    }
+	}
+    }
+    elsif ($op eq "add_source") {
+	my $canonical = &canonicalize_name ($target->{name});
+	my $var = "${canonical}_SOURCES";
+	my $rel_source;
+
+	# Note: we expect the operand to be the absolute to the root of the project
+	# i.e. for group /src/, the operand will be /src/file.c
+	$rel_source = path_absolute_to_relative ($group->{prefix}, $operand);
+
+	&debug ("Will modify $var using source $rel_source");
+	my %macros = %{$makefile->{macros}};
+	if (!exists ($macros{$var})) {
+	    # Need to add the default source the the new var
+	    $operand = "$target->{name}.c " . $rel_source;
+	}
+	&macro_append_text ($makefile, $var, $rel_source);
+	
+    }
+    elsif ($op eq "remove_source") {
+	my $canonical = &canonicalize_name ($target->{name});
+	my $var = "${canonical}_SOURCES";
+	my $rel_source;
+
+	# Note: we expect the operand to be the absolute to the root of the project
+	# i.e. for group /src/, the operand will be /src/file.c
+
+	# FIXME: this needs lot of work, since only works for literal filnanmes
+	# we need to identify where is this filename in expanded from a macro or 
+	# if it has some variable like $(srcdir)
+	$rel_source = path_absolute_to_relative ($group->{prefix}, $operand);
+
+	# FIXME: check for duplicate sources
+
+	&debug ("Will modify $var using source $rel_source");
+	if (!exists ($makefile->{macros}{$var}) &&
+	    $rel_source eq "${$target->{name}}.c") {
+	    # Create the empty macro if the removed source is the default one
+	    &macro_create ($makefile, $var, "");
+	} else {
+	    &macro_remove_text ($makefile, $var, $rel_source);
+	}
+
+    }
+    else {
+	return &report_error (300, gettext("Invalid operation '$op' to program_target_writer"));
+    };
+
+    # Success!
+    return 0;
+}
+
+
+sub simple_primary_target_writer
+{
+    my ($project, $group, $op, $target, $operand) = @_;
+    my $makefile = $group->{makefile};
+
+    my %primaries = ( data    => "DATA",
+		      scripts => "SCRIPTS",
+		      headers => "HEADERS" );
+
+    &debug ("$op on group $group with $target($operand)");
+ 
+    # FIXME: Modify the $group variable as well as the makefile (worth it?...
+    # only if we have to do a sequence of operations on the same target)
+
+    if ($op eq "add_target") {
+	# WARNING, Convention: $target for simple primaries is actually $prefix:$type
+	# $operand also contains the target type
+	my ($primary, $prefix, $check_primary);
+	
+	($prefix, $check_primary) = split /:/, $target;
+
+	$primary = $primaries{$operand};
+	if (!defined $primary) {
+	    return &report_error (301, gettext("Invalid target type '$operand' to simple_primary_target_writer"));
+	}
+
+	if ($check_primary ne $operand) {
+	    &report_warning (302, gettext("The target type supplied in the target name $target and the given target type '$operand' don't match.  Will use the one provided in the name"));
+	}
+
+	if (!&check_primary_prefix ($prefix, $primary, $group)) {
+	    return;
+	}
+
+	&macro_create ($makefile, "${prefix}_${primary}", "");
+
+    }
+    elsif ($op eq "remove_target") {
+	my $prefix = $target->{config}{installdir};
+	my $primary = $primaries{$target->{type}};
+
+	&macro_remove ($makefile, "${prefix}_${primary}");
+    }
+    elsif ($op eq "set_config") {
+	# Nothing to do here, since these kind of targets are not relocatable
+
+    }
+    elsif ($op eq "add_source") {
+	my $prefix = $target->{config}{installdir};
+	my $primary = $primaries{$target->{type}};
+	my $var = "${prefix}_${primary}";
+
+	# FIXME: add file to EXTRA_DIST if it's a DATA primary and the file is not
+	# a built file
+	# Note: we expect the uri to be relative to the group
+	&macro_append_text ($makefile, $var, $operand);
+	
+    }
+    elsif ($op eq "remove_source") {
+	my $prefix = $target->{config}{installdir};
+	my $primary = $primaries{$target->{type}};
+	my $var = "${prefix}_${primary}";
+
+	# Note: we expect the uri to be relative to the group
+	&macro_remove_text ($makefile, $var, $operand);
+
+    }
+    else {
+	return &report_error (300, gettext("Invalid operation '$op' to program_target_writer"));
+    };
+
+    # Success!
+    return 0;
+}
+
+
+######################################################################
+####  7. XML PRINTING FUNCTIONS  #####################################
+######################################################################
+
+my ($indent_level, $have_vspace);
+
+$indent_level = 0;
+$have_vspace = 0;
+
+sub xml_enter  { $indent_level += 2; }
+sub xml_leave  { $indent_level -= 2; }
+sub xml_indent { print " " x $indent_level; $have_vspace = 0; }
+sub xml_vspace { if (not $have_vspace) { print "\n"; $have_vspace = 1; } }
+sub xml_print { &xml_indent; print @_; }
+
+sub xml_escape
+{
+    $_ = $_[0];
+    s/\&/\&amp;/g;
+    s/\</\&lt;/g;
+    s/\>/\&gt;/g;
+    s/\"/\&quot;/g;
+    ## s/\\/\\\\/g;
+    ## s/\n/\\n/g;
+    ## s/\t/\\t/g;
+    return $_;
+}
+
+sub xml_unescape
+{
+    $_ = $_[0];
+    s/\&amp;/\&/g;
+    s/\&lt;/\</g;
+    s/\&gt;/\>/g;
+    s/\&quot;/\"/g;
+    ## s/\\\\/\\/g;
+    ## s/\\n/\n/g;
+    ## s/\\t/\t/g;
+    return $_;
+}
+
+sub xml_print_source
+{
+    my $source = shift;
+
+    &xml_enter ();
+    &xml_print ("<source uri=\"" . reduce_path ($source) . "\"/>\n");
+    &xml_leave ();
+}
+
+sub xml_print_dep
+{
+    my ($dep, $prefix) = @_;
+
+    my ($target, $file) = split ';', $dep;
+    &xml_enter ();
+    # FIXME: uris should be absolute to the project root (i.e. ../ must be reduced)
+    &xml_print ("<dependency file=\"" . 
+				reduce_path ($prefix . "/" . $target) . "\"/>\n");
+    &xml_leave ();
+}
+
+sub xml_print_target
+{
+    my $group = shift;
+	
+	&xml_enter ();
+    
+	my $type = "program";
+	&xml_print ("<target name=\"$group->{Target}\" ".
+				"id=\"$group->{Target}\" type=\"$type\">\n");
+	
+	if (exists $group->{Sources})
+	{
+		foreach (@{$group->{Sources}}) {
+			&xml_print_source ($_);
+		}
+	}
+
+	&xml_print ("</target>\n");
+
+	&xml_leave ();
+}
+
+###
+# group_print_xml (group [, which])
+#   recursively prints the xml representation of the group
+#   if which is "changed" only prints the group if it's flaged as changed
+sub group_print_xml
+{
+    my $group = shift;
+    my $which = shift || "all";
+    
+    if ($which ne "changed" || $group->{Changed})
+	{
+		&xml_enter ();
+		&xml_print ("<group name=\"$group->{Name}\" id=\"$group->{Id}\" " .
+					"source=\"$group->{Dir}/Makefile\">\n");
+
+		# Print the group config
+		&xml_print_config ($group->{Vars}, ());
+	
+		# Print child groups
+		foreach (values %{$group->{Children}}) {
+			&group_print_xml ($_, $which);
+		}
+	
+		# Print the target
+		if (exists $group->{Target})
+		{
+			&xml_print_target ($group);
+		}
+
+		&xml_print ("</group>\n");
+
+		&xml_leave ();
+    }
+}
+
+sub xml_print_config
+{
+    my ($config, @except) = @_;
+    my $value;
+
+    &xml_enter ();
+    &xml_print ("<config>\n");
+    foreach my $key (keys %{$config}) {
+	if (grep $_ eq $key, @except) {
+	    next;
+	};
+
+	&xml_enter ();
+	$value = &empty_if_undef ($config->{$key});
+	if (ref ($value) eq "ARRAY" && @$value) {
+	    # It's a list
+	    &xml_print ("<param name=\"$key\">\n");
+	    &xml_enter ();
+	    foreach (@$value) {
+	    	## Remove white spaces from config parameters.
+	    	$_ =~ s/\s/ /gs;
+		$_ =~ s/\s+/ /gs;
+		$_ = &xml_escape ($_);
+		&xml_print ("<item value=\"$_\"/>\n");
+	    };
+	    &xml_leave ();
+	    &xml_print ("</param>\n");
+	} elsif (ref ($value) eq "HASH" && %$value) {
+	    # It's a hash
+	    &xml_print ("<param name=\"$key\">\n");
+	    &xml_enter ();
+	    foreach my $item (keys %$value) {
+	    	## Remove white spaces from config parameters.
+		my $val = $value->{$item};
+	    	$val =~ s/\s/ /gs;
+	    	$val =~ s/\s+/ /gs;
+		$_ = &xml_escape ($val);
+		&xml_print ("<item name=\"$item\" value=\"$_\"/>\n");
+	    };
+	    &xml_leave ();
+	    &xml_print ("</param>\n");
+	} elsif (! ref ($value) && $value) {
+	    ## Remove white spaces from config parameters.
+	    $value =~ s/\s/ /gs;
+	    $value =~ s/\s+/ /gs;
+	    $value = &xml_escape ($value);
+	    &xml_print ("<param name=\"$key\" value=\"$value\"/>\n");
+	};
+	&xml_leave ();
+    };
+    &xml_print ("</config>\n");
+    &xml_leave ();
+}
+
+###
+# project_print_xml (project [, which])
+#   print xml representation of the project
+#   if which is "changed" only prints changed groups in last modification
+sub project_print_xml
+{
+    my $project = shift;
+    my $which = shift || "all";
+
+    print "<?xml version='1.0' encoding='ISO-8859-1' standalone='yes'?>\n";
+    print "<!DOCTYPE project []>\n\n";
+    print "<project root=\"$project->{prefix}\" report=\"" . 
+	($which eq "changed" ? "partial" : "full") . 
+	"\" source=\"$project->{prefix}/$project->{mkfile}\">\n";
+    
+    # Print the groups
+    &group_print_xml ($project->{root_group}, $which);
+    
+    print "</project>\n";
+}
+
+sub recursive_print
+{
+    my ($level, $el) = @_;
+    my $pad = " " x (2 * $level);
+    if (!ref ($el)) {
+	print $pad, $el, "\n";
+    } elsif (ref ($el) eq "ARRAY") {
+	foreach my $sel (@$el) {
+	    &recursive_print ($level + 1, $sel);
+	}
+    } elsif (ref ($el) eq "HASH") {
+	foreach my $sel (keys %$el) {
+	    print $pad, $sel, " => ", $el->{$sel}, "\n";
+	}
+    }
+}
+
+
+######################################################################
+####  8. XML SCANNING  ###############################################
+######################################################################
+
+sub xml_scan_make_kid_array
+{
+    my %hash = ();
+    my (@sublist, @attr);
+
+    @attr = $_[0] =~ /[^\s]+\s*([a-zA-Z_-]+)\s*\=\s*\"([^\"]*)/g;
+    %hash = @attr;
+    
+    push @sublist, \%hash;
+    return \ sublist;
+}
+
+sub xml_scan_recurse
+{
+    my ($tree, $scan_ref) = @_;
+    
+    my @list = @$tree;
+    my ($el, $sublist);
+    
+    while (@$scan_ref) {
+	$el = shift @$scan_ref;
+
+        # Empty strings, PI and DTD must go.
+	if (($el eq "") || $el =~ /^\<[!?].*\>$/s) { next; }
+
+	if ($el =~ /^\<.*\/\>$/s) {
+	    # Empty.
+	    $el =~ /^\<([a-zA-Z_-]+).*\/\>$/s;
+	    push @list, $1;
+	    push @list, &xml_scan_make_kid_array ($el);
+
+	} elsif ($el =~ /^\<\/.*\>$/s) {
+	    # End.
+	    last;
+
+	} elsif ($el =~ /^\<.*\>$/s) {
+	    # Start.
+	    $el =~ /^\<([a-zA-Z_-]+).*\>$/s;
+	    push @list, $1;
+	    $sublist = &xml_scan_make_kid_array ($el);
+	    push @list, &xml_scan_recurse ($sublist, $scan_ref);
+	    next;
+
+	} elsif ($el ne "") {
+	    # PCDATA.
+	    push @list, 0;
+	    push @list, "$el";
+	}
+    }
+    
+    return \ list;
+}
+
+sub xml_scan
+{
+    my $input_file = $_[0];
+    my ($doc, $tree, $i);
+    
+    if (!$input_file || $input_file eq "-") {
+	$doc .= $i while ($i = <STDIN>);
+
+    } else {
+	if (open INPUT_FILE, $input_file) {
+	    $doc .= $i while ($i = <INPUT_FILE>);
+	    close INPUT_FILE;
+	}
+	else {
+	    &report_error (4, gettext("Can't open input file '$input_file'"));
+	    return [];
+	}
+    }
+
+    &debug ("Got input: $doc");
+
+    my @xml_scan_list = ($doc =~ /([^\<]*)(\<[^\>]*\>)[ \t\n\r]*/mg); # pcdata, tag, pcdata, tag, ...
+    
+    $tree = &xml_scan_recurse ([], \ xml_scan_list);
+    
+    return $tree;
+}
+
+
+######################################################################
+####  9. XML PROCESSING  #############################################
+######################################################################
+
+sub xml_parse_params
+{
+    my $tree = $_[0];
+    my %params;
+    
+    shift @$tree;
+    
+    while ($$tree[0] eq "param") {
+    	shift @$tree;
+	my $child = $$tree[0];
+	if (defined($$child[0]->{value})) {
+		my $str = $$child[0]->{value};
+        	$params{$$child[0]->{name}} = xml_unescape ($str);
+	} else {
+		my %items;
+		$params{$$child[0]->{name}} = \%items;
+		shift @$child;
+		while ($$child[0] eq "item") {
+			shift @$child;
+			my $str = $$child[0][0]->{value};
+			$items{$$child[0][0]->{name}} = xml_unescape ($str);
+			shift @$child;
+		}
+	}
+	shift @$tree;
+    }
+    return \%params;
+}
+
+sub xml_parse_add
+{
+    my $tree = $_[0];
+    my $type;
+    my ($parent, $curr);
+
+    my %new_op = ( op          => "",
+		   group_name  => "",
+		   target_name => "",
+		   operand     => "" );
+
+    $type = $$tree[0]->{type};
+    $new_op{op} = "add_$type";
+
+    shift @$tree;
+
+    $parent = $$tree[0];
+    $curr = $$tree[1];
+
+    while (1) {
+	if ($parent eq "group") {
+	    # The group id is unique
+	    $new_op{group_name} = $$curr[0]->{id};
+	} elsif ($parent eq "target") { 
+	    $new_op{target_name} = $$curr[0]->{id};
+	    if ($type eq "target") {
+		$new_op{operand} = $$curr[0]->{type};
+	    }
+	} elsif ($parent eq "source") { 
+	    $new_op{operand} = $$curr[0]->{uri};
+	}
+
+	if ($parent eq $type) {
+	    last;
+	}
+	
+	shift @$curr;
+	$parent = $$curr[0];
+	$curr = $$curr[1];
+    }
+
+    return \%new_op;
+}
+
+sub xml_parse_change
+{
+}
+
+sub xml_parse_set
+{
+    my $tree = $_[0];
+    my $type;
+    my ($parent, $curr);
+
+    my %new_op = ( op          => "",
+		   group_name  => "",
+		   target_name => "",
+		   operand     => "" );
+    $new_op{operand} = {};
+
+    $type = $$tree[0]->{type};
+    $new_op{op} = "set_$type";
+
+    shift @$tree;
+
+    $parent = $$tree[0];
+    $curr = $$tree[1];
+
+    while (1) {
+	if ($parent eq "group") {
+	    $new_op{group_name} = $$curr[0]->{id};
+	} elsif ($parent eq "target") { 
+	    $new_op{target_name} = $$curr[0]->{id};
+	} elsif ($parent eq "config") {
+	    $new_op{operand} = xml_parse_params($curr);
+	}
+
+	if ($parent eq $type) {
+	    last;
+	}
+	
+	shift @$curr;
+	$parent = $$curr[0];
+	$curr = $$curr[1];
+    }
+
+    return \%new_op;
+}
+
+sub xml_parse_remove
+{
+    my $tree = $_[0];
+    my $type;
+    my ($parent, $curr);
+
+    my %new_op = ( op          => "",
+		   group_name  => "",
+		   target_name => "",
+		   operand     => "" );
+
+    $type = $$tree[0]->{type};
+    $new_op{op} = "remove_$type";
+
+    shift @$tree;
+
+    $parent = $$tree[0];
+    $curr = $$tree[1];
+
+    while (1) {
+	if ($parent eq "group") {
+	    # The group name is unique
+	    $new_op{group_name} = $$curr[0]->{id};
+	} elsif ($parent eq "target") { 
+	    $new_op{target_name} = $$curr[0]->{id};
+	} elsif ($parent eq "source") { 
+	    $new_op{operand} = $$curr[0]->{uri};
+	}
+
+	if ($parent eq $type) {
+	    last;
+	}
+	
+	shift @$curr;
+	$parent = $$curr[0];
+	$curr = $$curr[1];
+    }
+    return \%new_op;
+}
+
+sub xml_parse_ops
+{
+    my $tree = $_[0];
+    my @ops;
+
+    shift @$tree;  # Skip attributes.
+
+    while (@$tree) {
+	if ($$tree[0] eq "add") { 
+	    push @ops, &xml_parse_add ($$tree[1]); 
+	}
+	elsif ($$tree[0] eq "change") { 
+	    push @ops, &xml_parse_change ($$tree[1]); 
+	}
+	elsif ($$tree[0] eq "set") { 
+	    push @ops, &xml_parse_set ($$tree[1]); 
+	}
+	elsif ($$tree[0] eq "remove") { 
+	    push @ops, &xml_parse_remove ($$tree[1]); 
+	}
+
+	shift @$tree;
+	shift @$tree;
+    }
+
+    return \ ops;
+}
+
+
+######################################################################
+####  10. HELPER FUNCTIONS  ##########################################
+######################################################################
+
+sub path_relative_to_absolute
+{
+    my ($prefix, $rel) = @_;
+
+    if (substr ($prefix, -1) ne "/") {
+	$prefix .= "/";
+    };
+
+    return reduce_path ($prefix . $rel);
+}
+
+sub path_absolute_to_relative
+{
+    my ($prefix, $absolute) = @_;
+
+    my @prefix_parts = split '/', $prefix;
+    my @absolute_parts = split '/', $absolute;
+    my @result = ();
+    my $append_parent = 0;
+
+    foreach my $part (@prefix_parts) {
+	my $abs_part = shift @absolute_parts;
+
+	if ($part eq $abs_part && !$append_parent) {
+	    next;
+	} else {
+	    $append_parent = 1;
+	    unshift @result, "..";
+	    push @result, $abs_part;
+	};
+	    
+    };
+    push @result, @absolute_parts;
+
+    return join ('/', @result);
+}
+
+sub reduce_path
+{
+    my ($uri) = @_;
+
+    my @result = ();
+
+    foreach my $part (split '/', $uri) {
+	if ($part eq "..") {
+	    pop @result;
+	} elsif ($part eq ".") {
+	    next;
+	} else {
+	    push @result, $part;
+	};
+    };
+
+    return (join '/', @result);
+}
+
+sub remove_files_from_sources
+{
+    my ($var, $group) = @_;
+
+    my %targets = %{$group->{targets}};
+
+    # Strips from $var all those files already present in some target's sources
+    $var = " $var ";
+    foreach my $target (values %targets) {
+	foreach my $source (@{$target->{sources}}) {
+	    $var =~ s/\s\Q$source\s/ /g;
+	};
+    };
+    return &trim ($var);
+}
+
+sub remove_files_from_built_files
+{
+    my ($var, $group) = @_;
+
+    my %targets = %{$group->{targets}};
+
+    # Strips from $var all those files already present in some target's built_file,
+    # returning the target name
+    my @targets_found;
+    $var = " $var ";
+    foreach my $target (keys %targets) {
+	foreach my $built_file (@{$targets{$target}{built_files}}) {
+	    if ($var =~ /\s\Q$built_file\E\s/) {
+		$var =~ s/\s\Q$built_file\E\s/ /g;
+		push @targets_found, "$target;$built_file";
+	    };
+	};
+    };
+    # Now remove those files present in BUILT_SOURCES
+    foreach my $built_file (split /\s+/, $group->{config}{built_sources}) {
+	$var =~ s/\s$built_file\s/ /g;
+    }
+	
+    return (&trim ($var), @targets_found);
+}
+
+sub get_sources_by_extension
+{
+    my ($group, $ext) = @_;
+
+    my @result;
+
+    foreach my $target (values %{$group->{targets}}) {
+	my @sources = @{$target->{sources}};
+	push @result, grep /$ext\z/, @sources;
+    };
+    return @result;
+}
+
+sub make_absolute_path
+{
+    my $path = $_[0];
+
+    if (substr ($path, 0, 1) ne "/") {
+	# the path is not absolute
+	my $cwd = `pwd`;
+	chomp $cwd;
+	$path = "$cwd/$path";
+    };
+
+    return reduce_path ($path);
+}
+
+######################################################################
+####  11. MAIN PROGRAM  ##############################################
+######################################################################
+
+my ($op, $arg, $newop);
+
+$op = "";
+while (@ARGV) {
+    $_ = shift @ARGV;
+    if    ($_ eq "--get" || $_ eq "-g") { $newop = "get"; }
+    elsif ($_ eq "--set" || $_ eq "-s") { $newop = "set"; }
+    elsif ($_ eq "--verbose" || $_ eq "-v") { $verbose = 1; }
+    elsif ($_ eq "--dry-run" || $_ eq "-n") { $dry_run = 1; }
+    elsif ($_ eq "--debug" || $_ eq "-d") { $debug = 1; }
+
+    elsif ($_ eq "--test-scan") { $newop = "test-scan"; }
+    else {
+	if ($arg) {
+	    &report_error (5, gettext("You can't specify more than one project dir/file"));
+	    exit 5;
+	}
+	$arg = $_;
+    }
+    if ($newop) {
+	if ($op) {
+	    &report_error (5, gettext("You can't specify more than one operation"));
+	    exit 5;
+	}
+	$op = $newop;
+	$newop = "";
+    }
+}
+
+if ($op eq 'get') {
+    #########################################
+    # Get the project XML view
+    #########################################
+
+    my $project_dir = make_absolute_path ($arg);
+
+    if (!$project_dir || ! -d $project_dir || ! -e $project_dir) {
+	&report_error (2, gettext("Project root directory doesn't exist"));
+	exit 2;
+    };
+
+    my $project = &process_project ($project_dir);
+
+    if ($project)
+	{
+		&project_print_xml ($project);
+		
+#		rec_print($project->{root_group});
+	};
+}
+
+elsif ($op eq "test-scan") {
+    #########################################
+    # Test XML scanning
+    #########################################
+
+    my $tree = &xml_scan ($arg);
+    &recursive_print (-1, $tree);
+
+}
+elsif ($op eq "set") {
+    ########################################
+    # Project modification
+    ########################################
+
+    my $tree = &xml_scan ($arg);
+
+    # Walk the tree recursively and extract configuration parameters.
+
+    while (@$tree) {
+	if ($$tree[0] eq "project") { 
+	    # Get the project and execute
+	    my $project_dir = make_absolute_path ($$tree [1][0]->{root});
+	    my $project = &process_project ($project_dir);
+	    my $ops = &xml_parse_ops ($$tree[1]);
+	    if ($project && $ops) {
+		# reset groups changed flag so we later know what
+		# groups changed and need to be fed back to the user
+		&project_reset_changed ($project);
+
+		# perform the operations
+		if (&project_operate ($project, $ops)) {
+		    # print changed groups
+		    &project_print_xml ($project, "changed");
+		}
+	    };
+	}
+	shift @$tree;
+	shift @$tree;
+    }
+}
+else {
+    &report_warning (0, gettext("Nothing to do"));
+}

Added: trunk/plugins/gbf-mkfile/gbf-mkfile-plugin-48.png
==============================================================================
Binary file. No diff available.

Added: trunk/plugins/gbf-mkfile/gbf-mkfile-project.c
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/gbf-mkfile-project.c	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,3510 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8; coding: utf-8 -*- */
+/* gbf-mkfile-project.c
+ *
+ * Copyright (C) 2005  Eric Greveson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Eric Greveson
+ * Based on the Autotools GBF backend (libgbf-am) by
+ *   JP Rosevear
+ *   Dave Camp
+ *   Naba Kumar
+ *   Gustavo GirÃldez
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <glib/gi18n.h>
+#include <libgnome/gnome-macros.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libgnomevfs/gnome-vfs-monitor.h>
+#include <libgnomevfs/gnome-vfs-ops.h>
+#include <libgnomevfs/gnome-vfs-utils.h>
+#include <libanjuta/gbf-project.h>
+#include "gbf-mkfile-project.h"
+#include "gbf-mkfile-config.h"
+#include "gbf-mkfile-properties.h"
+
+#define ENABLE_DEBUG
+
+#ifdef ENABLE_DEBUG
+#define DEBUG(x) x
+#else
+#define DEBUG(x)
+#endif
+
+
+#define UNIMPLEMENTED  G_STMT_START { g_warning (G_STRLOC": unimplemented"); } G_STMT_END
+
+/* Constant strings for parsing perl script error output */
+#define ERROR_PREFIX      "ERROR("
+#define WARNING_PREFIX    "WARNING("
+#define MESSAGE_DELIMITER ": "
+
+
+/* ----- Queue data types ----- */
+
+/* FIXME: extend and make most operations asynchronous */
+typedef enum {
+	BUILD
+} GbfMkfileProjectOpType;
+
+typedef struct {
+	GbfMkfileProject       *project;
+	GbfMkfileProjectOpType  type;
+	gchar 		   *build_id;
+} GbfMkfileProjectOp;
+
+
+/* ----- Change sets data types ----- */
+
+typedef struct _GbfMkfileChange GbfMkfileChange;
+
+typedef enum {
+	GBF_MKFILE_CHANGE_ADDED,
+	GBF_MKFILE_CHANGE_REMOVED
+} GbfMkfileChangeType;
+
+struct _GbfMkfileChange {
+	GbfMkfileChangeType  change;
+	GbfMkfileNodeType    type;
+	gchar           *id;
+};
+
+
+/* ----- XML output parser data types ----- */
+
+typedef struct _GbfMkfileProjectParseData GbfMkfileProjectParseData;
+
+struct _GbfMkfileProjectParseData {
+	GbfMkfileProject       *project;
+
+	/* For tracking state */
+	GNode                  *current_node;
+	gint                    depth;          /* group depth only */
+	GbfMkfileConfigMapping *config;
+	gchar                  *param_key;
+	gboolean                full_report;
+
+	/* parser state */
+	enum {
+		PARSE_INITIAL,
+		PARSE_DONE,
+		
+		PARSE_PROJECT,
+		PARSE_GROUP,
+		PARSE_TARGET,
+		PARSE_SOURCE,
+		PARSE_DEPENDENCY,
+		PARSE_CONFIG,
+		PARSE_PARAM,
+		PARSE_ITEM,
+		PARSE_PARAM_DONE,
+		
+		PARSE_ERROR
+	} state, save_state;
+
+	/* list of GbfAmChange with changed project elements */
+	gboolean            compute_change_set;
+	GSList             *change_set;
+
+	/* hash table to keep track of possibly removed nodes */
+	GHashTable         *nodes;
+};
+
+
+/* ----- Script spawning data types and constants ----- */
+
+#define GBF_MKFILE_PARSE       SCRIPTS_DIR "/gbf-mkfile-parse"
+
+/* timeout in milliseconds */
+#define SCRIPT_TIMEOUT     30000
+
+/* buffer size tuning parameters */
+#define READ_BUFFER_SIZE   32768
+#define READ_BUFFER_DELTA  16384
+
+typedef struct _GbfMkfileSpawnData GbfMkfileSpawnData;
+typedef struct _GbfMkfileChannel   GbfMkfileChannel;
+
+struct _GbfMkfileChannel {
+	GIOChannel *channel;
+	gchar      *buffer;
+	gsize       size;      /* allocated buffer size */
+	gsize       length;    /* real buffer length/index into buffer */
+	guint       tag;       /* main loop source tag for this channel's watch */
+};
+	
+struct _GbfMkfileSpawnData {
+	GMainLoop   *main_loop;
+	
+	/* child PID to kill it on timeout */
+	gint         child_pid;
+	
+	/* buffers and related channels */
+	GbfMkfileChannel input;
+	GbfMkfileChannel output;
+	GbfMkfileChannel error;
+	gint         open_channels;
+};
+
+
+/* ----- Standard GObject types and variables ----- */
+
+enum {
+	PROP_0,
+	PROP_PROJECT_DIR
+};
+
+static GbfProject *parent_class;
+
+
+/* ----------------------------------------------------------------------
+   Private prototypes
+   ---------------------------------------------------------------------- */
+
+static gboolean        uri_is_equal                 (const gchar       *uri1,
+						     const gchar       *uri2);
+static gboolean        uri_is_parent                (const gchar       *parent_uri,
+						     const gchar       *child_uri);
+static gboolean        uri_is_local_path            (const gchar       *uri);
+static gchar          *uri_to_path                  (const gchar       *uri);
+static gchar          *uri_normalize                (const gchar       *uri,
+						     const gchar       *base_uri);
+
+static gboolean        xml_write_add_source         (GbfMkfileProject      *project,
+						     xmlDocPtr          doc,
+						     GNode             *g_node,
+						     const gchar       *uri);
+static gboolean        xml_write_remove_source      (GbfMkfileProject      *project,
+						     xmlDocPtr          doc,
+						     GNode             *g_node);
+static gboolean        xml_write_add_target         (GbfMkfileProject      *project,
+						     xmlDocPtr          doc,
+						     GNode             *g_node,
+						     const gchar       *name,
+						     const gchar       *type);
+static gboolean        xml_write_add_group          (GbfMkfileProject      *project,
+						     xmlDocPtr          doc,
+						     GNode             *g_node,
+						     const gchar       *new_group);
+static xmlDocPtr       xml_new_change_doc           (GbfMkfileProject      *project);
+
+static GbfMkfileChange *change_new                   (GbfMkfileChangeType    ch,
+						     GbfMkfileNode         *node);
+static void            change_free                  (GbfMkfileChange       *change);
+static GbfMkfileChange *change_set_find              (GSList            *change_set,
+						     GbfMkfileChangeType    ch,
+						     GbfMkfileNodeType      type);
+static void            change_set_destroy           (GSList            *change_set);
+
+static void            error_set                    (GError            **error,
+						     gint               code,
+						     const gchar       *message);
+static GError         *parse_errors                 (GbfMkfileProject      *project,
+						     const gchar       *error_buffer);
+static gboolean        parse_output_xml             (GbfMkfileProject      *project,
+						     gchar             *xml,
+						     gint               length,
+						     GSList           **change_set);
+
+static void            spawn_shutdown               (GbfMkfileSpawnData    *data);
+static void            spawn_data_destroy           (GbfMkfileSpawnData    *data);
+static gboolean        spawn_write_child            (GIOChannel        *ioc,
+						     GIOCondition       condition,
+						     gpointer           user_data);
+static gboolean        spawn_read_output            (GIOChannel        *ioc,
+						     GIOCondition       condition,
+						     gpointer           user_data);
+static gboolean        spawn_read_error             (GIOChannel        *ioc,
+						     GIOCondition       condition,
+						     gpointer           user_data);
+static gboolean        spawn_kill_child             (GbfMkfileSpawnData    *data);
+static GbfMkfileSpawnData *spawn_script                 (gchar             **argv,
+						     gint               timeout,
+						     gchar             *input,
+						     gint               input_size,
+						     GIOFunc            input_cb,
+						     GIOFunc            output_cb,
+						     GIOFunc            error_cb);
+
+static gboolean        project_reload               (GbfMkfileProject      *project,
+						     GError           **err);
+static gboolean        project_update               (GbfMkfileProject      *project,
+						     xmlDocPtr          doc,
+						     GSList           **change_set,
+						     GError           **err);
+
+static void            gbf_mkfile_node_free             (GbfMkfileNode         *node);
+static GNode          *project_node_new             (GbfMkfileNodeType      type);
+static void            project_node_destroy         (GbfMkfileProject      *project,
+						     GNode             *g_node);
+static void            project_data_destroy         (GbfMkfileProject      *project);
+static void            project_data_init            (GbfMkfileProject      *project);
+
+static void            gbf_mkfile_project_class_init    (GbfMkfileProjectClass *klass);
+static void            gbf_mkfile_project_instance_init (GbfMkfileProject      *project);
+static void            gbf_mkfile_project_dispose       (GObject           *object);
+static void            gbf_mkfile_project_get_property  (GObject           *object,
+						     guint              prop_id,
+						     GValue            *value,
+						     GParamSpec        *pspec);
+
+/*
+ * URI and path manipulation functions -----------------------------
+ */
+
+static gboolean 
+uri_is_equal (const gchar *uri1,
+	      const gchar *uri2)
+{
+	gboolean retval = FALSE;
+	GnomeVFSURI *vfs_uri1, *vfs_uri2;
+
+	vfs_uri1 = gnome_vfs_uri_new (uri1);
+	vfs_uri2 = gnome_vfs_uri_new (uri2);
+	if (vfs_uri1 != NULL && vfs_uri2 != NULL)
+		retval = gnome_vfs_uri_equal (vfs_uri1, vfs_uri2);
+	gnome_vfs_uri_unref (vfs_uri1);
+	gnome_vfs_uri_unref (vfs_uri2);
+
+	return retval;
+}
+
+static gboolean 
+uri_is_parent (const gchar *parent_uri,
+	       const gchar *child_uri)
+{
+	gboolean retval = FALSE;
+	GnomeVFSURI *parent, *child;
+
+	parent = gnome_vfs_uri_new (parent_uri);
+	child = gnome_vfs_uri_new (child_uri);
+	if (parent != NULL && child != NULL)
+		retval = gnome_vfs_uri_is_parent (parent, child, TRUE);
+	gnome_vfs_uri_unref (parent);
+	gnome_vfs_uri_unref (child);
+
+	return retval;
+}
+
+static gboolean
+uri_is_local_path (const gchar *uri)
+{
+	const gchar *p;
+	
+	for (p = uri;
+	     g_ascii_isalnum (*p) || *p == '+' || *p == '-' || *p == '.';
+	     p++)
+		;
+
+	if (*p == ':')
+		return FALSE;
+	else
+		return TRUE;
+}
+
+static gchar *
+uri_to_path (const gchar *uri)
+{
+	return gnome_vfs_get_local_path_from_uri (uri);
+}
+
+static gchar *
+uri_normalize (const gchar *uri, const gchar *base_uri)
+{
+	gchar *normalized_uri = NULL;
+
+	if (uri_is_local_path (uri)) {
+		gchar *absolute_path;
+		
+		absolute_path = gnome_vfs_expand_initial_tilde (uri);
+		if (!g_path_is_absolute (uri)) {
+			gchar *tmp = absolute_path;
+			gchar *base_dir;
+			if (base_uri != NULL)
+				base_dir = uri_to_path (base_uri);
+			else
+				base_dir = g_get_current_dir ();
+
+			absolute_path = g_build_filename (base_dir, tmp, NULL);
+			g_free (base_dir);
+			g_free (tmp);
+		}
+		normalized_uri = gnome_vfs_make_uri_canonical (absolute_path);
+		g_free (absolute_path);
+	} else {
+		GnomeVFSURI *vfs_uri;
+
+		vfs_uri = gnome_vfs_uri_new (uri);
+		/* FIXME: this should probably check that the method is file: */
+		if (gnome_vfs_uri_is_local (vfs_uri))
+			normalized_uri = gnome_vfs_make_uri_canonical (uri);
+		gnome_vfs_uri_unref (vfs_uri);
+	}
+
+	/* strip trailing slash */
+	if (normalized_uri) {
+		gint length = strlen (normalized_uri);
+		gchar *p;
+		if (length > 0) {
+			p = normalized_uri + length - 1;
+			if (*p == '/')
+				*p = '\0';
+		}
+	}
+
+	return normalized_uri;
+}
+
+/**
+ * uri_get_chrooted_path:
+ * @root_uri: the root uri (or NULL)
+ * @uri: the uri which must be inside @root_uri for which the
+ * root-changed path is wanted
+ *
+ * E.g.: uri_get_chrooted_path ("file:///foo/bar", "file:///foo/bar/baz/winkle") -> "/baz/winkle"
+ *
+ * Return value: a newly allocated chrooted path
+ **/
+static gchar *
+uri_get_chrooted_path (const gchar *root_uri, const gchar *uri)
+{
+	gchar *root_path, *path;
+	gint root_length;
+	gchar *retval = NULL;
+	
+	path = uri_to_path (uri);
+	if (root_uri == NULL)
+		return path;
+	
+	root_path = uri_to_path (root_uri);
+	
+	root_length = strlen (root_path);
+	if (strncmp (root_path, path, root_length) == 0) {
+		/* check for trailing path separator in root... we need it to
+		 * make the returned path absolute */
+		if (root_path [root_length - 1] == '/')
+			root_length--;
+		retval = g_strdup (path + root_length);
+	}
+
+	g_free (root_path);
+	g_free (path);
+	
+	return retval;
+}
+
+/*
+ * Project modification functions -----------------------------
+ */
+
+static xmlNodePtr 
+xml_new_source_node (GbfMkfileProject *project,
+		     xmlDocPtr     doc,
+		     const gchar  *uri)
+{
+	xmlNodePtr source;
+	gchar *filename;
+	
+	source = xmlNewDocNode (doc, NULL, BAD_CAST("source"), NULL);
+	filename = uri_get_chrooted_path (project->project_root_uri, uri);
+	xmlSetProp (source, BAD_CAST("uri"), BAD_CAST(filename));
+	g_free (filename);
+	
+	return source;
+}
+
+static xmlNodePtr 
+xml_write_location_recursive (GbfMkfileProject *project,
+			      xmlDocPtr     doc,
+			      xmlNodePtr    cur,
+			      GNode        *g_node)
+{
+	xmlNodePtr result, xml_node, last_node;
+	gboolean finished = FALSE;
+
+	result = NULL;
+	last_node = NULL;
+	xml_node = NULL;
+	while (g_node && !finished) {
+		GbfMkfileNode *node = GBF_MKFILE_NODE (g_node);
+		switch (node->type) {
+			case GBF_MKFILE_NODE_GROUP:
+				xml_node = xmlNewDocNode (doc, NULL, BAD_CAST("group"), NULL);
+				xmlSetProp (xml_node, BAD_CAST("id"), BAD_CAST(node->id));
+				finished = TRUE;
+				break;
+			case GBF_MKFILE_NODE_TARGET:
+				xml_node = xmlNewDocNode (doc, NULL, BAD_CAST("target"), NULL);
+				/* strip the group id from the target
+				   id, since the script identifies
+				   targets only within the group */
+				xmlSetProp (xml_node, BAD_CAST("id"),
+					    BAD_CAST(node->id +
+						     strlen (GBF_MKFILE_NODE (g_node->parent)->id)));
+				break;
+			case GBF_MKFILE_NODE_SOURCE:
+				xml_node = xml_new_source_node (project, doc, node->uri);
+				break;
+			default:
+				g_assert_not_reached ();
+				break;
+		}
+
+		/* set returned node */
+		if (result == NULL)
+			result = xml_node;
+
+		/* link the previously created node to the new node */
+		if (last_node != NULL)
+			xmlAddChild (xml_node, last_node);
+
+		last_node = xml_node;
+		g_node = g_node->parent;
+	}
+	/* link the last created node to the current node */
+	xmlAddChild (cur, last_node);
+	
+	return result;
+}
+
+static gboolean 
+xml_write_add_source (GbfMkfileProject *project,
+		      xmlDocPtr     doc,
+		      GNode        *g_node,
+		      const gchar  *uri)
+{
+	xmlNodePtr cur, source;
+	
+	cur = xmlNewDocNode (doc, NULL, BAD_CAST("add"), NULL);
+	xmlSetProp (cur, BAD_CAST("type"), BAD_CAST("source"));
+	xmlAddChild (doc->children, cur);
+
+	cur = xml_write_location_recursive (project, doc, cur, g_node);
+	source = xml_new_source_node (project, doc, uri);
+	xmlAddChild (cur, source);
+	
+	return (cur != NULL);
+}
+
+static gboolean
+xml_write_remove_source (GbfMkfileProject *project,
+			 xmlDocPtr     doc,
+			 GNode        *g_node)
+{
+	xmlNodePtr cur;
+
+	cur = xmlNewDocNode (doc, NULL, BAD_CAST("remove"), NULL);
+	xmlSetProp (cur, BAD_CAST("type"), BAD_CAST("source"));
+	xmlAddChild (doc->children, cur);
+	
+	cur = xml_write_location_recursive (project, doc, cur, g_node);
+	
+	return (cur != NULL);
+}
+
+static gboolean 
+xml_write_add_target (GbfMkfileProject *project,
+		      xmlDocPtr     doc,
+		      GNode        *g_node,
+		      const gchar  *name,
+		      const gchar  *type)
+{
+	xmlNodePtr cur, target;
+	
+	g_assert (GBF_MKFILE_NODE (g_node)->type == GBF_MKFILE_NODE_GROUP);
+
+	cur = xmlNewDocNode (doc, NULL, BAD_CAST("add"), NULL);
+	xmlSetProp (cur, BAD_CAST("type"), BAD_CAST("target"));
+	xmlAddChild (doc->children, cur);
+
+	cur = xml_write_location_recursive (project, doc, cur, g_node);
+	
+	target = xmlNewDocNode (doc, NULL, BAD_CAST("target"), NULL);
+	xmlSetProp (target, BAD_CAST("id"), BAD_CAST(name));
+	xmlSetProp (target, BAD_CAST("type"), BAD_CAST(type));
+	xmlAddChild (cur, target);
+
+	return TRUE;
+}
+
+typedef struct {
+	GbfMkfileConfigMapping *old_config;
+	xmlDocPtr doc;
+	xmlNodePtr curr_xml_node;
+} GbfXmlWriteData;
+
+static void
+xml_write_set_item_config_cb (const gchar *param, GbfMkfileConfigValue *value,
+			      gpointer user_data)
+{
+	xmlNodePtr param_node;
+	GbfXmlWriteData *data = (GbfXmlWriteData*)user_data;
+	
+	if (value->type == GBF_MKFILE_TYPE_STRING) {
+		GbfMkfileConfigValue *old_value;
+		const gchar *old_str = "", *new_str = "";
+		
+		new_str = gbf_mkfile_config_value_get_string (value);
+		if (new_str == NULL)
+			new_str = "";
+		
+		old_value = gbf_mkfile_config_mapping_lookup (data->old_config, param);
+		if (old_value) {
+			old_str = gbf_mkfile_config_value_get_string (old_value);
+			if (old_str == NULL)
+				old_str = "";
+		}
+		if (strcmp (new_str, old_str) != 0)
+		{
+			param_node = xmlNewDocNode (data->doc, NULL,
+						    BAD_CAST("item"), NULL);
+			xmlSetProp (param_node, BAD_CAST("name"), BAD_CAST(param));
+			xmlSetProp (param_node, BAD_CAST("value"), BAD_CAST(new_str));
+			xmlAddChild (data->curr_xml_node, param_node);
+		}
+	}
+}
+
+static void
+xml_write_set_param_config_cb (const gchar *param, GbfMkfileConfigValue *value,
+			       gpointer user_data)
+{
+	xmlNodePtr param_node;
+	GbfXmlWriteData *data = (GbfXmlWriteData*)user_data;
+	
+	if (value->type == GBF_MKFILE_TYPE_STRING) {
+		GbfMkfileConfigValue *old_value;
+		const gchar *old_str = "", *new_str = "";
+		
+		new_str = gbf_mkfile_config_value_get_string (value);
+		if (new_str == NULL)
+			new_str = "";
+		
+		old_value = gbf_mkfile_config_mapping_lookup (data->old_config, param);
+		if (old_value) {
+			old_str = gbf_mkfile_config_value_get_string (old_value);
+			if (old_str == NULL)
+				old_str = "";
+		}
+		if (strcmp (new_str, old_str) != 0)
+		{
+			param_node = xmlNewDocNode (data->doc, NULL,
+						    BAD_CAST("param"), NULL);
+			xmlSetProp (param_node, BAD_CAST("name"), BAD_CAST(param));
+			xmlSetProp (param_node, BAD_CAST("value"), BAD_CAST(new_str));
+			xmlAddChild (data->curr_xml_node, param_node);
+		}
+	} else if (value->type == GBF_MKFILE_TYPE_LIST) {
+		param_node = xmlNewDocNode (data->doc, NULL,
+					    BAD_CAST("param"), NULL);
+		xmlSetProp (param_node, BAD_CAST("name"), BAD_CAST(param));
+		/* FIXME */
+	} else if (value->type == GBF_MKFILE_TYPE_MAPPING) {
+		
+		GbfXmlWriteData write_data;
+		GbfMkfileConfigValue *old_value;
+		GbfMkfileConfigMapping *new_mapping, *old_mapping;
+		
+		new_mapping = gbf_mkfile_config_value_get_mapping (value);
+		old_value = gbf_mkfile_config_mapping_lookup (data->old_config, param);
+		old_mapping = gbf_mkfile_config_value_get_mapping (old_value);
+		
+		param_node = xmlNewDocNode (data->doc, NULL, BAD_CAST("param"), NULL);
+		
+		xmlSetProp (param_node, BAD_CAST("name"), BAD_CAST(param));
+		
+		write_data.doc = data->doc;
+		write_data.curr_xml_node = param_node;
+		write_data.old_config = old_mapping;
+		
+		gbf_mkfile_config_mapping_foreach (new_mapping,
+					       xml_write_set_item_config_cb,
+					       &write_data);
+		if (param_node->children)
+			xmlAddChild (data->curr_xml_node, param_node);
+		else
+			xmlFreeNode (param_node);
+	} else {
+		g_warning ("Should not be here");
+	}
+}
+
+static gboolean 
+xml_write_set_config (GbfMkfileProject       *project,
+		      xmlDocPtr           doc,
+		      GNode              *g_node,
+		      GbfMkfileConfigMapping *new_config)
+{
+	xmlNodePtr cur, config;
+	GbfXmlWriteData user_data;
+	
+	cur = xmlNewDocNode (doc, NULL, BAD_CAST("set"), NULL);
+	xmlSetProp (cur, BAD_CAST("type"), BAD_CAST("config"));
+	xmlAddChild (doc->children, cur);
+
+	if (g_node)
+		cur = xml_write_location_recursive (project, doc, cur, g_node);
+	
+	config = xmlNewDocNode (doc, NULL, BAD_CAST("config"), NULL);
+	xmlAddChild (cur, config);
+	
+	user_data.doc = doc;
+	user_data.curr_xml_node = config;
+	user_data.old_config = GBF_MKFILE_NODE (g_node)->config;
+	
+	gbf_mkfile_config_mapping_foreach (new_config,
+				       xml_write_set_param_config_cb,
+				       &user_data);
+	if (config->children)
+		return TRUE;
+	else
+		return FALSE;
+}
+
+static gboolean
+xml_write_remove_target (GbfMkfileProject *project,
+			 xmlDocPtr     doc,
+			 GNode        *g_node)
+{
+	xmlNodePtr cur;
+
+	cur = xmlNewDocNode (doc, NULL, BAD_CAST("remove"), NULL);
+	xmlSetProp (cur, BAD_CAST("type"), BAD_CAST("target"));
+	xmlAddChild (doc->children, cur);
+	
+	cur = xml_write_location_recursive (project, doc, cur, g_node);
+	
+	return (cur != NULL);
+}
+
+static gboolean 
+xml_write_add_group (GbfMkfileProject *project,
+		     xmlDocPtr     doc,
+		     GNode        *g_node,
+		     const gchar  *new_group)
+{
+	xmlNodePtr cur, group;
+	gchar *new_id;
+	
+	g_assert (GBF_MKFILE_NODE (g_node)->type == GBF_MKFILE_NODE_GROUP);
+
+	cur = xmlNewDocNode (doc, NULL, BAD_CAST("add"), NULL);
+	xmlSetProp (cur, BAD_CAST("type"), BAD_CAST("group"));
+	xmlAddChild (doc->children, cur);
+
+	/* calculate new id needed by the script */
+	new_id = g_strdup_printf ("%s%s/", GBF_MKFILE_NODE (g_node)->id, new_group);
+	
+	group = xmlNewDocNode (doc, NULL, BAD_CAST("group"), NULL);
+	xmlSetProp (group, BAD_CAST("id"), BAD_CAST(new_id));
+	xmlAddChild (cur, group);
+	g_free (new_id);
+	
+	return TRUE;
+}
+
+static gboolean
+xml_write_remove_group (GbfMkfileProject *project,
+			 xmlDocPtr     doc,
+			 GNode        *g_node)
+{
+	xmlNodePtr cur;
+
+	cur = xmlNewDocNode (doc, NULL, BAD_CAST("remove"), NULL);
+	xmlSetProp (cur, BAD_CAST("type"), BAD_CAST("group"));
+	xmlAddChild (doc->children, cur);
+	
+	cur = xml_write_location_recursive (project, doc, cur, g_node);
+	
+	return (cur != NULL);
+}
+
+static xmlDocPtr
+xml_new_change_doc (GbfMkfileProject *project)
+{
+	xmlDocPtr doc;
+	
+	doc = xmlNewDoc (BAD_CAST("1.0"));
+	if (doc != NULL) {
+		gchar *root_path;
+		root_path = uri_to_path (project->project_root_uri);
+		doc->children = xmlNewDocNode (doc, NULL, BAD_CAST("project"), NULL);
+		xmlSetProp (doc->children, BAD_CAST("root"), BAD_CAST(root_path));
+		g_free (root_path);
+	}
+
+	return doc;
+}
+
+
+/*
+ * File monitoring support --------------------------------
+ * FIXME: review these
+ */
+
+static void
+monitor_cb (GnomeVFSMonitorHandle   *handle,
+	    const gchar             *monitor_uri,
+	    const gchar             *info_uri,
+	    GnomeVFSMonitorEventType event_type,
+	    gpointer                 data)
+{
+	GbfMkfileProject *project = data;
+
+	g_return_if_fail (project != NULL && GBF_IS_MKFILE_PROJECT (project));
+
+	switch (event_type) {
+		case GNOME_VFS_MONITOR_EVENT_CHANGED:
+		case GNOME_VFS_MONITOR_EVENT_DELETED:
+			/* monitor will be removed here... is this safe? */
+			DEBUG (g_message ("File changed"));
+			project_reload (project, NULL);
+			g_signal_emit_by_name (G_OBJECT (project), "project-updated");
+			break;
+		default:
+			break;
+	}
+}
+
+static void
+monitor_add (GbfMkfileProject *project, const gchar *uri)
+{
+	GnomeVFSMonitorHandle *handle = NULL;
+	GnomeVFSResult result;
+
+	g_return_if_fail (project != NULL);
+	g_return_if_fail (project->monitors != NULL);
+
+	if (!uri)
+		return;
+	
+	handle = g_hash_table_lookup (project->monitors, uri);
+	if (!handle) {
+		GnomeVFSURI *vfs_uri;
+		gboolean exists;
+		
+		vfs_uri = gnome_vfs_uri_new (uri);
+		exists = gnome_vfs_uri_exists (vfs_uri);
+		gnome_vfs_uri_unref (vfs_uri);
+
+		if (exists) {
+			result = gnome_vfs_monitor_add (&handle,
+							uri,
+							GNOME_VFS_MONITOR_FILE,
+							monitor_cb,
+							project);
+			if (result == GNOME_VFS_OK) {
+				g_hash_table_insert (project->monitors,
+						     g_strdup (uri),
+						     handle);
+			}
+		}
+	}
+}
+
+static void
+monitors_remove (GbfMkfileProject *project)
+{
+	g_return_if_fail (project != NULL);
+
+	if (project->monitors)
+		g_hash_table_destroy (project->monitors);
+	project->monitors = NULL;
+}
+
+static void
+group_hash_foreach_monitor (gpointer key,
+			    gpointer value,
+			    gpointer user_data)
+{
+	GNode *group_node = value;
+	GbfMkfileProject *project = user_data;
+
+	monitor_add (project, GBF_MKFILE_NODE (group_node)->uri);
+}
+
+static void
+monitors_setup (GbfMkfileProject *project)
+{
+	g_return_if_fail (project != NULL);
+
+	monitors_remove (project);
+	
+	/* setup monitors hash */
+	project->monitors = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+						   (GDestroyNotify) gnome_vfs_monitor_cancel);
+
+	monitor_add (project, project->project_file);
+	g_hash_table_foreach (project->groups, group_hash_foreach_monitor, project);
+}
+
+
+/*
+ * Change sets functions --------------------------------
+ */
+
+static GbfMkfileChange *
+change_new (GbfMkfileChangeType ch, GbfMkfileNode *node)
+{
+	GbfMkfileChange *change;
+
+	change = g_new0 (GbfMkfileChange, 1);
+	change->change = ch;
+	change->type = node->type;
+	change->id = g_strdup (node->id);
+
+	return change;
+}
+
+static void
+change_free (GbfMkfileChange *change)
+{
+	if (change) {
+		g_free (change->id);
+		g_free (change);
+	}
+}
+
+static GbfMkfileChange *
+change_set_find (GSList *change_set, GbfMkfileChangeType ch, GbfMkfileNodeType type)
+{
+	GSList *iter = change_set;
+
+	while (iter) {
+		GbfMkfileChange *change = iter->data;
+		if (change->change == ch && change->type == type)
+			return change;
+		iter = g_slist_next (iter);
+	}
+
+	return NULL;
+}
+
+static void
+change_set_destroy (GSList *change_set)
+{
+	GSList *l;
+	for (l = change_set; l; l = g_slist_next (l))
+		change_free (l->data);
+	g_slist_free (change_set);
+}
+
+#ifdef ENABLE_DEBUG
+static void
+change_set_debug_print (GSList *change_set)
+{
+	GSList *iter = change_set;
+
+	g_print ("Change set:\n");
+	while (iter) {
+		GbfMkfileChange *change = iter->data;
+
+		switch (change->change) {
+			case GBF_MKFILE_CHANGE_ADDED:
+				g_print ("added   ");
+				break;
+			case GBF_MKFILE_CHANGE_REMOVED:
+				g_print ("removed ");
+				break;
+			default:
+				g_assert_not_reached ();
+				break;
+		}
+		switch (change->type) {
+			case GBF_MKFILE_NODE_GROUP:
+				g_print ("group  ");
+				break;
+			case GBF_MKFILE_NODE_TARGET:
+				g_print ("target ");
+				break;
+			case GBF_MKFILE_NODE_SOURCE:
+				g_print ("source ");
+				break;
+			default:
+				g_assert_not_reached ();
+				break;
+		}
+		g_print ("%s\n", change->id);
+
+		iter = g_slist_next (iter);
+	}
+}
+#endif
+
+/*
+ * Perl script output parser ------------------------------
+ */
+
+#define PARSER_ASSERT(x)  G_STMT_START {						\
+	if (!(x)) {									\
+		DEBUG (g_warning ("Parser assertion at " G_STRLOC " failed: " #x));	\
+		data->state = PARSE_ERROR; return;					\
+	}										\
+											\
+	} G_STMT_END 
+ 
+static void
+sax_start_element (void *ctxt, const xmlChar *name, const xmlChar **attrs)
+{
+	GbfMkfileProjectParseData *data;
+	GbfMkfileProject *project;
+	GNode *g_node;
+	GbfMkfileNode *node;
+	
+	data = ctxt;
+	project = data->project;
+	
+	PARSER_ASSERT (data->state != PARSE_ERROR && data->state != PARSE_DONE);
+	
+	if (xmlStrEqual (name, BAD_CAST "project")) {
+		const xmlChar *project_file = NULL;
+
+		/* project node */
+		PARSER_ASSERT (data->state == PARSE_INITIAL);
+		data->state = PARSE_PROJECT;
+
+		/* process attributes: lookup project file and check
+		 * if we're getting the whole project or just changed
+		 * groups */
+		while (attrs && *attrs != NULL) {
+			const xmlChar **val = attrs;
+			
+			val++;
+			if (xmlStrEqual (*attrs, BAD_CAST "root")) {
+				/* FIXME: check that the root is the
+				 * same as the project's root */
+			}
+			else if (xmlStrEqual (*attrs, BAD_CAST "report"))
+				data->full_report = (xmlStrEqual (*val, BAD_CAST "full") != 0);
+			else if (xmlStrEqual (*attrs, BAD_CAST "source"))
+				project_file = *val;
+			
+			attrs = ++val;
+		}
+
+		/* clear project if we are getting a full report */
+		if (data->full_report) {
+			/* FIXME: will this be correct in all cases? */
+			data->compute_change_set = FALSE;
+			project_data_init (project);
+		}
+		
+		/* assign here, since we destroy the data in project_data_init() */
+		if (project_file) {
+			g_free (project->project_file);
+			project->project_file = g_strdup ((gchar*)project_file);
+		}
+		
+	} else if (xmlStrEqual (name, BAD_CAST "group")) {
+		const xmlChar *group_name = NULL;
+		const xmlChar *group_id = NULL;
+		const xmlChar *group_source = NULL;
+		
+		/* group node */
+		PARSER_ASSERT (data->state == PARSE_PROJECT ||
+			       data->state == PARSE_GROUP);
+		data->state = PARSE_GROUP;
+
+		/* process attributes: lookup name, id and source file
+		 * (i.e. Makefile) */
+		while (attrs && *attrs != NULL) {
+			const xmlChar **val = attrs;
+			
+			val++;
+			if (xmlStrEqual (*attrs, BAD_CAST "name"))
+				group_name = *val;
+			else if (xmlStrEqual (*attrs, BAD_CAST "id"))
+				group_id = *val;
+			else if (xmlStrEqual (*attrs, BAD_CAST "source"))
+				group_source = *val;
+			
+			attrs = ++val;
+		}
+		PARSER_ASSERT (group_name != NULL && group_id != NULL);
+
+		/* lookup the node first, as it could already exist */
+		g_node = g_hash_table_lookup (project->groups, group_id);
+		if (g_node == NULL) {
+			g_node = project_node_new (GBF_MKFILE_NODE_GROUP);
+			node = GBF_MKFILE_NODE (g_node);
+			
+			node->id = g_strdup ((gchar*)group_id);
+			
+			/* set parent group */
+			if (data->current_node != NULL) {
+				g_node_prepend (data->current_node, g_node);
+			} else {
+				/* node is project root */
+				g_assert (project->root_node == NULL);
+				project->root_node = g_node;
+			}
+
+			if (data->compute_change_set)
+				data->change_set = g_slist_prepend (
+					data->change_set,
+					change_new (GBF_MKFILE_CHANGE_ADDED, node));
+			
+			/* save the group in the hash for quick access */
+			g_hash_table_insert (project->groups, g_strdup (node->id), g_node);
+
+		} else {
+			GNode *child;
+			
+			node = GBF_MKFILE_NODE (g_node);
+
+			/* node found so remove it from the tracking hash */
+			g_hash_table_remove (data->nodes, g_node);
+			
+			/* re-set data */
+			g_free (node->name);
+			g_free (node->uri);
+
+			/* recreate the configuration */
+			gbf_mkfile_config_mapping_destroy (node->config);
+			
+			/* track all child nodes */
+			child = g_node_first_child (g_node);
+			while (child != NULL) {
+				/* only track groups if getting a full
+				 * report, otherwise non-changed
+				 * groups are later removed from the
+				 * project because the script doesn't
+				 * report them */
+				if (data->full_report ||
+				    GBF_MKFILE_NODE (child)->type != GBF_MKFILE_NODE_GROUP)
+					g_hash_table_insert (data->nodes, child, child);
+				child = g_node_next_sibling (child);
+			}
+		}
+		node->name = g_strdup ((gchar*)group_name);
+		node->uri = g_strdup ((gchar*)group_source);
+		node->config = gbf_mkfile_config_mapping_new ();
+		
+		/* set working node */
+		data->depth++;
+		data->current_node = g_node;
+
+	} else if (xmlStrEqual (name, BAD_CAST "target")) {
+		const xmlChar *id = NULL;
+		const xmlChar *target_name = NULL;
+		const xmlChar *target_type = NULL;
+		gchar *group_id;
+		
+		/* target node */
+		PARSER_ASSERT (data->state == PARSE_GROUP);
+		data->state = PARSE_TARGET;
+
+		/* process attributes: lookup name, type and id */
+		while (attrs && *attrs != NULL) {
+			const xmlChar **val = attrs;
+			
+			val++;
+			if (xmlStrEqual (*attrs, BAD_CAST "name"))
+				target_name = *val;
+			else if (xmlStrEqual (*attrs, BAD_CAST "type"))
+				target_type = *val;
+			else if (xmlStrEqual (*attrs, BAD_CAST "id"))
+				id = *val;
+
+			attrs = ++val;
+		}
+		PARSER_ASSERT (target_name != NULL && target_type != NULL);
+		
+		/* compute the id using the given by the script if possible */
+		group_id = g_strdup_printf ("%s%s",
+					    GBF_MKFILE_NODE (data->current_node)->id,
+					    id != NULL ? id : target_name);
+		
+		/* lookup the node first, as it could already exist */
+		g_node = g_hash_table_lookup (project->targets, group_id);
+		if (g_node == NULL) {
+			g_node = project_node_new (GBF_MKFILE_NODE_TARGET);
+			node = GBF_MKFILE_NODE (g_node);
+			
+			node->id = group_id;
+
+			/* set target's parent */
+			g_node_prepend (data->current_node, g_node);
+			
+			if (data->compute_change_set)
+				data->change_set = g_slist_prepend (
+					data->change_set,
+					change_new (GBF_MKFILE_CHANGE_ADDED, node));
+			
+			/* save the target in the hash for quick access */
+			g_hash_table_insert (project->targets, g_strdup (node->id), g_node);
+
+		} else {
+			GNode *child;
+			
+			node = GBF_MKFILE_NODE (g_node);
+			g_free (group_id);
+
+			/* node found so remove it from the tracking hash */
+			g_hash_table_remove (data->nodes, g_node);
+			
+			/* re-set data */
+			g_free (node->name);
+			g_free (node->detail);
+
+			/* recreate the configuration */
+			gbf_mkfile_config_mapping_destroy (node->config);
+			
+			/* track all child nodes */
+			child = g_node_first_child (g_node);
+			while (child != NULL) {
+				g_hash_table_insert (data->nodes, child, child);
+				child = g_node_next_sibling (child);
+			}
+		}
+		node->name = g_strdup ((gchar*)target_name);
+		node->detail = g_strdup ((gchar*)target_type);
+		node->config = gbf_mkfile_config_mapping_new ();
+
+		/* set working node */
+		data->current_node = g_node;
+
+	} else if (xmlStrEqual (name, BAD_CAST "source")) {
+		const xmlChar *uri = NULL;
+		gchar *source_uri, *source_id;
+		
+		/* source node */
+		PARSER_ASSERT (data->state == PARSE_TARGET);
+		data->state = PARSE_SOURCE;
+
+		/* process attributes: lookup uri */
+		while (attrs && *attrs != NULL) {
+			const xmlChar **val = attrs;
+			
+			val++;
+			if (xmlStrEqual (*attrs, BAD_CAST "uri"))
+				uri = *val;
+
+			attrs = ++val;
+		}
+		PARSER_ASSERT (uri != NULL);
+		
+		source_uri = g_build_filename (project->project_root_uri, uri, NULL);
+		source_id = g_strdup_printf ("%s:%s",
+					     GBF_MKFILE_NODE (data->current_node)->id,
+					     source_uri);
+
+		/* lookup the node first, as it could already exist */
+		g_node = g_hash_table_lookup (project->sources, source_id);
+		if (g_node == NULL) {
+			g_node = project_node_new (GBF_MKFILE_NODE_SOURCE);
+			node = GBF_MKFILE_NODE (g_node);
+			
+			node->id = source_id;
+			
+			/* set source's parent */
+			g_node_prepend (data->current_node, g_node);
+			
+			if (data->compute_change_set)
+				data->change_set = g_slist_prepend (
+					data->change_set,
+					change_new (GBF_MKFILE_CHANGE_ADDED, node));
+			
+			/* save the source in the hash for quick access */
+			g_hash_table_insert (project->sources, g_strdup (node->id), g_node);
+
+		} else {
+			/* node found so remove it from the tracking hash */
+			g_hash_table_remove (data->nodes, g_node);
+			
+			node = GBF_MKFILE_NODE (g_node);
+			g_free (source_id);
+			
+			/* re-set data */
+			g_free (node->uri);
+		}
+		node->uri = source_uri;
+			
+		/* set working node */
+		data->current_node = g_node;
+		
+	} else if (xmlStrEqual (name, BAD_CAST "dependency")) {
+		const xmlChar *uri = NULL;
+		const xmlChar *target_dep = NULL;
+		gchar *source_uri, *source_id;
+		
+		/* dependency node */
+		PARSER_ASSERT (data->state == PARSE_TARGET);
+		data->state = PARSE_DEPENDENCY;
+
+		/* process attributes: lookup file and dependency target */
+		while (attrs && *attrs != NULL) {
+			const xmlChar **val = attrs;
+			
+			val++;
+			if (xmlStrEqual (*attrs, BAD_CAST "file"))
+				uri = *val;
+			else if (xmlStrEqual (*attrs, BAD_CAST "target"))
+				target_dep = *val;
+
+			attrs = ++val;
+		}
+		PARSER_ASSERT (uri != NULL && target_dep != NULL);
+		
+		/* Compute the id */
+		source_uri = g_build_filename (project->project_root_uri, uri, NULL);
+		source_id = g_strdup_printf ("%s:%s",
+					     GBF_MKFILE_NODE (data->current_node)->id,
+					     source_uri);
+
+		/* lookup the node first, as it could already exist */
+		g_node = g_hash_table_lookup (project->sources, source_id);
+		if (g_node == NULL) {
+			g_node = project_node_new (GBF_MKFILE_NODE_SOURCE);
+			node = GBF_MKFILE_NODE (g_node);
+			
+			node->id = source_id;
+			
+			/* set source's parent */
+			g_node_prepend (data->current_node, g_node);
+			
+			if (data->compute_change_set)
+				data->change_set = g_slist_prepend (
+					data->change_set,
+					change_new (GBF_MKFILE_CHANGE_ADDED, node));
+			
+			/* save the source in the hash for quick access */
+			g_hash_table_insert (project->sources, g_strdup (node->id), g_node);
+
+		} else {
+			/* node found so remove it from the tracking hash */
+			g_hash_table_remove (data->nodes, g_node);
+			
+			g_free (source_id);
+			node = GBF_MKFILE_NODE (g_node);
+			
+			/* re-set data */
+			g_free (node->uri);
+			g_free (node->detail);
+		}
+		node->uri = source_uri;
+		node->detail = g_strdup ((gchar*)target_dep);
+
+		/* set working node */
+		data->current_node = g_node;
+		
+	} else if (xmlStrEqual (name, BAD_CAST "config")) {
+		/* config node */
+		PARSER_ASSERT (data->state == PARSE_PROJECT ||
+			       data->state == PARSE_GROUP ||
+			       data->state == PARSE_TARGET);
+
+		switch (data->state) {
+		    case PARSE_PROJECT:
+			    data->config = project->project_config;
+			    break;
+		    case PARSE_GROUP:
+		    case PARSE_TARGET:
+			    g_assert (data->current_node != NULL);
+			    data->config = GBF_MKFILE_NODE (data->current_node)->config;
+			    break;
+		    default:
+			    g_assert_not_reached ();
+			    break;
+		}
+
+		data->save_state = data->state;
+		data->state = PARSE_CONFIG;
+
+	} else if (xmlStrEqual (name, BAD_CAST "param")) {
+		const xmlChar *param_name = NULL;
+		const xmlChar *param_value = NULL;
+		
+		/* config param node */
+		PARSER_ASSERT (data->state == PARSE_CONFIG);
+
+		/* process attributes: lookup parameter name and value (if scalar) */
+		while (attrs && *attrs != NULL) {
+			const xmlChar **val = attrs;
+			
+			val++;
+			if (xmlStrEqual (*attrs, BAD_CAST "name"))
+				param_name = *val;
+			else if (xmlStrEqual (*attrs, BAD_CAST "value"))
+				param_value = *val;
+			
+			attrs = ++val;
+		}
+		PARSER_ASSERT (param_name != NULL);
+
+		if (param_value != NULL) {
+			GbfMkfileConfigValue *param;
+
+			/* save scalar string parameter */
+			param = gbf_mkfile_config_value_new (GBF_MKFILE_TYPE_STRING);
+			gbf_mkfile_config_value_set_string (param, (gchar*)param_value);
+
+			/* try to save the parameter in the config mapping */
+			if (!gbf_mkfile_config_mapping_insert (data->config,
+							       (gchar*)param_name, param)) {
+				gbf_mkfile_config_value_free (param);
+			}
+
+			/* we are finished with the parameter */
+			data->state = PARSE_PARAM_DONE;
+
+		} else {
+			/* we expect a list or a hash of values */
+			data->param_key = g_strdup ((gchar*)param_name);
+			data->state = PARSE_PARAM;
+		}
+		
+	} else if (xmlStrEqual (name, BAD_CAST "item")) {
+		GbfMkfileConfigValue *param, *item;
+		const xmlChar *item_name = NULL;
+		const xmlChar *item_value = NULL;
+
+		/* parameter item node */
+		PARSER_ASSERT (data->state == PARSE_PARAM);
+		g_assert (data->param_key != NULL);
+		data->state = PARSE_ITEM;
+		
+		/* process attributes: lookup parameter name and value (if scalar) */
+		while (attrs && *attrs != NULL) {
+			const xmlChar **val = attrs;
+			
+			val++;
+			if (xmlStrEqual (*attrs, BAD_CAST "name"))
+				item_name = *val;
+			else if (xmlStrEqual (*attrs, BAD_CAST "value"))
+				item_value = *val;
+			
+			attrs = ++val;
+		}
+		PARSER_ASSERT (item_value != NULL);
+		
+		/* create item value */
+		item = gbf_mkfile_config_value_new (GBF_MKFILE_TYPE_STRING);
+		gbf_mkfile_config_value_set_string (item, (gchar*)item_value);
+		
+		/* get current configuration parameter */
+		param = gbf_mkfile_config_mapping_lookup (data->config, data->param_key);
+		
+		/* if this is the first item, we must decide whether
+		   it's a list or a mapping.  if it's not the first
+		   one, we must check that the items are of the same
+		   type */
+		if (param == NULL) {
+			if (item_name != NULL) {
+				param = gbf_mkfile_config_value_new (GBF_MKFILE_TYPE_MAPPING);
+			} else {
+				param = gbf_mkfile_config_value_new (GBF_MKFILE_TYPE_LIST);
+			}
+			gbf_mkfile_config_mapping_insert (data->config,
+						      data->param_key,
+						      param);
+		}
+		
+		switch (param->type) {
+		    case GBF_MKFILE_TYPE_LIST:
+			    param->list = g_slist_prepend (param->list, item);
+			    break;
+			    
+		    case GBF_MKFILE_TYPE_MAPPING:
+			    if (item_name == NULL ||
+				!gbf_mkfile_config_mapping_insert (param->mapping,
+								   (gchar*)item_name,
+								   item)) {
+				    gbf_mkfile_config_value_free (item);
+			    }
+			    break;
+			    
+		    default:
+			    g_assert_not_reached ();
+			    break;
+		}
+	}
+}
+
+static void
+sax_end_element (void *ctx, const xmlChar *name)
+{
+	GbfMkfileProjectParseData *data = ctx;
+
+	PARSER_ASSERT (data->state != PARSE_ERROR && data->state != PARSE_DONE);
+	
+	if (xmlStrEqual (name, BAD_CAST "project")) {
+		PARSER_ASSERT (data->state == PARSE_PROJECT);
+		g_assert (data->current_node == NULL);
+		data->state = PARSE_DONE;
+		
+	} else if (xmlStrEqual (name, BAD_CAST "group")) {
+		PARSER_ASSERT (data->state == PARSE_GROUP);
+		g_assert (data->current_node != NULL);
+		data->depth--;
+		if (data->depth == 0) {
+			data->current_node = NULL;
+			data->state = PARSE_PROJECT;
+		} else {
+			data->current_node = data->current_node->parent;
+		}
+		
+		/* FIXME: resolve all target inter-dependencies here */
+
+	} else if (xmlStrEqual (name, BAD_CAST "target")) {
+		PARSER_ASSERT (data->state == PARSE_TARGET);
+		g_assert (data->current_node != NULL);
+		data->current_node = data->current_node->parent;
+		data->state = PARSE_GROUP;
+
+	} else if (xmlStrEqual (name, BAD_CAST "source")) {
+		PARSER_ASSERT (data->state == PARSE_SOURCE);
+		g_assert (data->current_node != NULL);
+		data->current_node = data->current_node->parent;
+		data->state = PARSE_TARGET;
+
+	} else if (xmlStrEqual (name, BAD_CAST "dependency")) {
+		PARSER_ASSERT (data->state == PARSE_DEPENDENCY);
+		g_assert (data->current_node != NULL);
+		data->current_node = data->current_node->parent;
+		data->state = PARSE_TARGET;
+
+	} else if (xmlStrEqual (name, BAD_CAST "config")) {
+		PARSER_ASSERT (data->state == PARSE_CONFIG);
+		data->state = data->save_state;
+		data->save_state = PARSE_INITIAL;
+		data->config = NULL;
+		
+	} else if (xmlStrEqual (name, BAD_CAST "param")) {
+		PARSER_ASSERT (data->state == PARSE_PARAM ||
+			       data->state == PARSE_PARAM_DONE);
+		data->state = PARSE_CONFIG;
+		g_free (data->param_key);
+		data->param_key = NULL;
+		
+	} else if (xmlStrEqual (name, BAD_CAST "item")) {
+		PARSER_ASSERT (data->state == PARSE_ITEM);
+		data->state = PARSE_PARAM;
+
+	}
+}
+
+#undef PARSER_ASSERT
+
+static void 
+hash_foreach_add_removed (GNode        *g_node,
+			  gpointer      value,
+			  GSList      **change_set)
+{
+	*change_set = g_slist_prepend (*change_set,
+				       change_new (GBF_MKFILE_CHANGE_REMOVED,
+						   GBF_MKFILE_NODE (g_node)));
+}
+
+static void 
+hash_foreach_destroy_node (GNode        *g_node,
+			   gpointer      value,
+			   GbfMkfileProject *project)
+{
+	project_node_destroy (project, g_node);
+}
+
+static gboolean 
+parse_output_xml (GbfMkfileProject *project,
+		  gchar        *xml_text,
+		  gint          length,
+		  GSList      **change_set)
+{
+	xmlSAXHandler handler;
+	GbfMkfileProjectParseData data;
+	int retval;
+	
+	memset (&handler, 0, sizeof (xmlSAXHandler));
+	handler.startElement = sax_start_element;
+	handler.endElement = sax_end_element;
+	handler.initialized = 0;
+	
+	data.state = PARSE_INITIAL;
+	data.save_state = PARSE_INITIAL;
+	data.project = project;
+	data.current_node = NULL;
+	data.depth = 0;
+	data.config = NULL;
+	data.param_key = NULL;
+	data.full_report = TRUE;
+
+	data.change_set = NULL;
+	data.nodes = g_hash_table_new (g_direct_hash, g_direct_equal);
+	data.compute_change_set = (change_set != NULL);
+
+	xmlSubstituteEntitiesDefault (TRUE);
+
+	/* Parse document */
+	retval = xmlSAXUserParseMemory (&handler, &data, xml_text, length);
+
+	if (data.state != PARSE_DONE)
+		retval = -1;
+
+	/* construct the change set */
+	if (retval >= 0 && data.compute_change_set) {
+		g_hash_table_foreach (data.nodes,
+				      (GHFunc) hash_foreach_add_removed,
+				      &data.change_set);
+		*change_set = data.change_set;
+		data.change_set = NULL;
+	}
+	
+	/* free up any remaining parsing data */
+	change_set_destroy (data.change_set);
+	if (data.nodes) {
+		g_hash_table_foreach (data.nodes,
+				      (GHFunc) hash_foreach_destroy_node,
+				      project);
+		g_hash_table_destroy (data.nodes);
+	}
+	g_free (data.param_key);
+	
+	return (retval >= 0);
+}
+
+
+/*
+ * Perl script error parsing ------------------------
+ */
+
+/* FIXME: set correct error codes in all the places we call this function */
+static void
+error_set (GError **error, gint code, const gchar *message)
+{
+	if (error != NULL) {
+		if (*error != NULL) {
+			gchar *tmp;
+			
+			/* error already created, just change the code
+			 * and prepend the string */
+			(*error)->code = code;
+			tmp = (*error)->message;
+			(*error)->message = g_strconcat (message, "\n\n", tmp, NULL);
+			g_free (tmp);
+			
+		} else {
+			*error = g_error_new_literal (GBF_PROJECT_ERROR,
+						      code, 
+						      message);
+		}
+	}
+}
+
+static GError *
+parse_errors (GbfMkfileProject *project,
+	      const gchar  *error_buffer)
+{
+	const gchar *line_ptr, *next_line, *p;
+	GError *err = NULL;
+	GString *message;
+	
+	message = g_string_new (NULL);
+	line_ptr = error_buffer;
+	while (line_ptr) {
+		/* FIXME: maybe use constants or enums for the error type */
+		gint error_type = 0;
+		gint error_code;
+		gint line_length;
+		
+		next_line = g_strstr_len (line_ptr, strlen (line_ptr), "\n");
+		/* skip newline */
+		next_line = next_line ? next_line + 1 : NULL;
+		line_length = next_line ? next_line - line_ptr : strlen (line_ptr);
+		p = line_ptr;
+		
+		if (g_str_has_prefix (line_ptr, ERROR_PREFIX)) {
+			/* get the error */
+			p = line_ptr + strlen (ERROR_PREFIX);
+			error_type = 1;
+		}
+#if 0
+		/* FIXME: skip warnings for now */
+		else if (g_str_has_prefix (line_ptr, WARNING_PREFIX)) {
+			/* get the warning */
+			p = line_ptr + strlen (WARNING_PREFIX);
+			error_type = 2;
+		}
+#endif
+
+		if (error_type != 0) {
+			/* get the error/warning code */
+			error_code = strtol (p, (char **) &p, 10);
+			if (error_code != 0) {
+				p = g_strstr_len (p, line_length, MESSAGE_DELIMITER);
+				if (p != NULL) {
+					gchar *msg;
+
+					/* FIXME: think another
+					 * solution for getting the
+					 * messages, since this has
+					 * problems with i18n as it's
+					 * very difficult to get
+					 * translated strings from the
+					 * perl script */
+					p += strlen (MESSAGE_DELIMITER);
+					if (next_line != NULL)
+						msg = g_strndup (p, next_line - p - 1);
+					else
+						msg = g_strdup (p);
+
+					if (message->len > 0)
+						g_string_append (message, "\n");
+					g_string_append (message, msg);
+					g_free (msg);
+				}
+			}
+		}
+		
+		line_ptr = next_line;
+	}
+
+	if (message->len > 0) {
+		err = g_error_new (GBF_PROJECT_ERROR,
+				   GBF_PROJECT_ERROR_GENERAL_FAILURE,
+				   "%s", message->str);
+	}
+	g_string_free (message, TRUE);
+	
+	return err;
+}
+
+
+/*
+ * Process spawning ------------------------
+ */
+
+static void
+shutdown_channel (GbfMkfileSpawnData *data, GbfMkfileChannel *channel)
+{
+	if (channel->channel) {
+		g_io_channel_shutdown (channel->channel, TRUE, NULL);
+		g_io_channel_unref (channel->channel);
+		channel->channel = NULL;
+	}
+	if (channel->tag != 0) {
+		GMainContext *context = NULL;
+		GSource *source;
+		if (data->main_loop != NULL)
+			context = g_main_loop_get_context (data->main_loop);
+		source = g_main_context_find_source_by_id (context, channel->tag);
+		if (source != NULL)
+			g_source_destroy (source);
+		channel->tag = 0;
+	}
+}
+
+static void
+spawn_shutdown (GbfMkfileSpawnData *data)
+{
+	g_return_if_fail (data != NULL);
+	
+	if (data->child_pid) {
+		DEBUG (g_message ("Killing child"));
+		kill (data->child_pid, SIGKILL);
+		data->child_pid = 0;
+	}
+
+	/* close channels and remove sources */
+	shutdown_channel (data, &data->input);
+	shutdown_channel (data, &data->output);
+	shutdown_channel (data, &data->error);
+	data->open_channels = 0;
+	
+	if (data->output.buffer) {
+		/* shrink buffer and add terminator */
+		data->output.buffer = g_realloc (data->output.buffer,
+						 ++data->output.length);
+		data->output.buffer [data->output.length - 1] = 0;
+	}
+
+	if (data->error.buffer) {
+		/* shrink buffer and add terminator */
+		data->error.buffer = g_realloc (data->error.buffer,
+						++data->error.length);
+		data->error.buffer [data->error.length - 1] = 0;
+	}
+	
+	if (data->main_loop)
+		g_main_loop_quit (data->main_loop);
+}
+
+static void
+spawn_data_destroy (GbfMkfileSpawnData *data)
+{
+	g_return_if_fail (data != NULL);
+
+	spawn_shutdown (data);
+
+	if (data->input.buffer) {
+		/* input buffer is provided by the user, so it's not freed here */
+		data->input.buffer = NULL;
+		data->input.size = 0;
+		data->input.length = 0;
+	}
+	if (data->output.buffer) {
+		g_free (data->output.buffer);
+		data->output.buffer = NULL;
+		data->output.size = 0;
+		data->output.length = 0;
+	}
+	if (data->error.buffer) {
+		g_free (data->error.buffer);
+		data->error.buffer = NULL;
+		data->error.size = 0;
+		data->error.length = 0;
+	}
+	g_free (data);
+}
+
+static gboolean
+spawn_write_child (GIOChannel *ioc, GIOCondition condition, gpointer user_data)
+{
+	GbfMkfileSpawnData *data = user_data;
+	gboolean retval = FALSE;
+
+	g_assert (data != NULL);
+	g_assert (data->input.channel == ioc);
+	
+	if (condition & G_IO_OUT) {
+		gsize bytes_written = 0;
+		GIOStatus status;
+		GError *error = NULL;
+
+		/* try to write all data remaining */
+		status = g_io_channel_write_chars (ioc,
+						   data->input.buffer + data->input.length,
+						   data->input.size - data->input.length,
+						   &bytes_written, &error);
+		data->input.length += bytes_written;
+
+		switch (status) {
+		    case G_IO_STATUS_NORMAL:			    
+			    if (data->input.length < data->input.size) {
+				    /* don't remove the source */
+				    retval = TRUE;
+			    }
+			    break;
+
+		    default:
+			    if (error) {
+				    g_warning ("Error while writing to stdin: %s",
+					       error->message);
+				    g_error_free (error);
+			    }
+			    break;
+		}
+	}
+
+	if (!retval) {
+		/* finished writing or some error ocurred */
+		g_io_channel_shutdown (data->input.channel, TRUE, NULL);
+		g_io_channel_unref (data->input.channel);
+		data->input.channel = NULL;
+		/* returning false will remove the source */
+		data->input.tag = 0;
+
+		data->open_channels--;
+		if (data->open_channels == 0) {
+			/* need to signal the end of the operation */
+			if (data->main_loop)
+				/* executing synchronously */
+				g_main_loop_quit (data->main_loop);
+			/* FIXME: what to do in the async case? */
+		}
+	}
+
+	return retval;
+}
+
+static gboolean
+read_channel (GbfMkfileChannel *channel, GIOCondition condition, GbfMkfileSpawnData *data)
+{
+	gboolean retval = FALSE;
+	
+	if (condition & (G_IO_IN | G_IO_PRI)) {
+		gsize bytes_read = 0;
+		GIOStatus status;
+		GError *error = NULL;
+
+		/* allocate buffer */
+		if (channel->buffer == NULL) {
+			channel->size = READ_BUFFER_SIZE;
+			channel->buffer = g_malloc (channel->size);
+			channel->length = 0;
+		}
+
+		status = g_io_channel_read_chars (channel->channel,
+						  channel->buffer + channel->length,
+						  channel->size - channel->length,
+						  &bytes_read, &error);
+		channel->length += bytes_read;
+
+		switch (status) {
+		    case G_IO_STATUS_NORMAL:
+			    /* grow buffer if necessary */
+			    if (channel->size - channel->length < READ_BUFFER_DELTA) {
+				    channel->size += READ_BUFFER_DELTA;
+				    channel->buffer = g_realloc (channel->buffer,
+								 channel->size);
+			    }
+			    retval = TRUE;
+			    break;
+			    
+		    case G_IO_STATUS_EOF:
+			    /* will close the channel */
+			    break;
+			    
+		    default:
+			    if (error) {
+				    g_warning ("Error while reading stderr: %s",
+					       error->message);
+				    g_error_free (error);
+			    }
+			    break;
+		}
+	}
+	
+	if (!retval) {
+		/* eof was reached or some error ocurred */
+		g_io_channel_shutdown (channel->channel, FALSE, NULL);
+		g_io_channel_unref (channel->channel);
+		channel->channel = NULL;
+		/* returning false will remove the source */
+		channel->tag = 0;
+
+		data->open_channels--;
+		if (data->open_channels == 0) {
+			/* need to signal the end of the operation */
+			if (data->main_loop)
+				/* executing synchronously */
+				g_main_loop_quit (data->main_loop);
+			/* FIXME: what to do in the async case? */
+		}
+	}
+
+	return retval;
+}
+
+static gboolean
+spawn_read_output (GIOChannel *ioc, GIOCondition condition, gpointer user_data)
+{
+	GbfMkfileSpawnData *data = user_data;
+	
+	/* some checks first */
+	g_assert (data != NULL);
+	g_assert (ioc == data->output.channel);
+	
+	return read_channel (&data->output, condition, data);
+}
+
+static gboolean
+spawn_read_error (GIOChannel *ioc, GIOCondition condition, gpointer user_data)
+{
+	GbfMkfileSpawnData *data = user_data;
+	
+	/* some checks first */
+	g_assert (data != NULL);
+	g_assert (ioc == data->error.channel);
+	
+	return read_channel (&data->error, condition, data);
+}
+
+static gboolean
+spawn_kill_child (GbfMkfileSpawnData *data)
+{
+	/* we can't wait longer */
+	DEBUG (g_message ("Timeout: sending SIGTERM to child process"));
+	
+	kill (data->child_pid, SIGTERM);
+	
+	if (data->main_loop)
+		g_main_loop_quit (data->main_loop);
+
+	return FALSE;
+}
+
+static guint 
+context_io_add_watch (GMainContext *context,
+		      GIOChannel   *channel,
+		      GIOCondition  condition,
+		      GSourceFunc   func,
+		      gpointer      user_data)
+{
+	GSource *source;
+	guint id;
+  
+	g_return_val_if_fail (channel != NULL, 0);
+
+	source = g_io_create_watch (channel, condition);
+	g_source_set_callback (source, func, user_data, NULL);
+	id = g_source_attach (source, context);
+	g_source_unref (source);
+	
+	return id;
+}
+
+static GbfMkfileSpawnData * 
+spawn_script (gchar  **argv,
+	      gint     timeout,
+	      gchar   *input,
+	      gint     input_size,
+	      GIOFunc  input_cb,
+	      GIOFunc  output_cb,
+	      GIOFunc  error_cb)
+{
+	GbfMkfileSpawnData *data;
+	gint child_in, child_out, child_err;
+	GError *error = NULL;
+	gboolean async;
+	
+	data = g_new0 (GbfMkfileSpawnData, 1);
+
+	/* we consider timeout < 0 to mean asynchronous request */
+	async = (timeout <= 0);
+
+	/* setup default callbacks */
+	if (input_cb == NULL) input_cb = spawn_write_child;
+	if (output_cb == NULL) output_cb = spawn_read_output;
+	if (error_cb == NULL) error_cb = spawn_read_error;
+	
+	/* set input buffer */
+	if (input) {
+		data->input.buffer = input;
+		data->input.size = input_size;
+		data->input.length = 0;  /* for input buffer length acts as an index */
+	}
+
+	DEBUG (g_message ("Spawning script"));
+	
+	if (!g_spawn_async_with_pipes (NULL,             /* working dir */
+				       argv,
+				       NULL,             /* environment */
+				       0,                /* flags */
+				       NULL, NULL,       /* child setup func */
+				       &data->child_pid,
+				       &child_in, &child_out, &child_err,
+				       &error)) {
+		g_warning ("Unable to fork: %s", error->message);
+		g_error_free (error);
+		g_free (data);
+		return NULL;
+
+	} else {
+		GMainContext *context = NULL;
+		
+		if (!async) {
+			/* we need a new context to do the i/o
+			 * otherwise we could have re-entrancy
+			 * problems since gtk events will be processed
+			 * while we iterate the inner main loop */
+			context = g_main_context_new ();
+			data->main_loop = g_main_loop_new (context, FALSE);
+		}
+
+		fcntl (child_in, F_SETFL, O_NONBLOCK);
+		fcntl (child_out, F_SETFL, O_NONBLOCK);
+		fcntl (child_err, F_SETFL, O_NONBLOCK);
+
+		data->open_channels = 3;
+		if (input != NULL && input_size > 0) {
+			data->input.channel = g_io_channel_unix_new (child_in);
+			data->input.tag = context_io_add_watch (context,
+								data->input.channel,
+								G_IO_OUT | G_IO_ERR |
+								G_IO_HUP | G_IO_NVAL,
+								(GSourceFunc) input_cb, data);
+		} else {
+			/* we are not interested in stdin */
+			close (child_in);
+			data->open_channels--;
+		}
+		
+		/* create watches for stdout and stderr */
+		data->output.channel = g_io_channel_unix_new (child_out);
+		data->output.tag = context_io_add_watch (context,
+							 data->output.channel,
+							 G_IO_ERR | G_IO_HUP |
+							 G_IO_NVAL | G_IO_IN,
+							 (GSourceFunc) output_cb, data);
+		data->error.channel = g_io_channel_unix_new (child_err);
+		data->error.tag = context_io_add_watch (context,
+							data->error.channel,
+							G_IO_ERR | G_IO_HUP |
+							G_IO_NVAL | G_IO_IN,
+							(GSourceFunc) error_cb, data);
+		
+		if (!async) {
+			GSource *source;
+
+			/* add the timeout */
+			source = g_timeout_source_new (timeout);
+			g_source_set_callback (source, (GSourceFunc) spawn_kill_child, data, NULL);
+			g_source_attach (source, context);
+			g_source_unref (source);
+		
+			g_main_loop_run (data->main_loop);
+
+			/* continue iterations until all channels have been closed */
+			while (data->open_channels > 0 && g_main_context_pending (context))
+				g_main_context_iteration (context, FALSE);
+			
+			/* close channels and remove io watches */
+			if (data->open_channels == 0)
+				/* normal shutdown */
+				data->child_pid = 0;
+			spawn_shutdown (data);
+
+			/* destroy main loop & context */
+			g_main_loop_unref (data->main_loop);
+			data->main_loop = NULL;
+			g_main_context_unref (context);
+		}
+
+		return data;
+	}
+}
+
+/*
+ * Script execution control ----------------------------
+ */
+
+static gboolean
+project_reload (GbfMkfileProject *project, GError **err) 
+{
+	GbfMkfileSpawnData *data;
+	gchar *argv [5], *project_path;
+	gboolean retval;
+	gint i;
+	
+	project_path = uri_to_path (project->project_root_uri);
+		
+	i = 0;
+	argv [i++] = GBF_MKFILE_PARSE;
+	DEBUG (argv [i++] = "-d");
+	argv [i++] = "--get";
+	argv [i++] = project_path;
+	argv [i++] = NULL;
+	g_assert (i <= G_N_ELEMENTS (argv));
+
+	data = spawn_script (argv, SCRIPT_TIMEOUT,
+			     NULL, 0,            /* input buffer */
+			     NULL, NULL, NULL);  /* i/o callbacks */
+
+	g_free (project_path);
+
+	retval = FALSE;
+	if (data != NULL) {
+		if (data->error.length > 0 && err != NULL) {
+			/* the buffer is zero terminated */
+			*err = parse_errors (project, data->error.buffer);
+		}
+		
+		if (data->output.length > 0) {
+			retval = parse_output_xml (project,
+						   data->output.buffer,
+						   data->output.length,
+						   NULL);
+		} else {
+			/* FIXME: generate some kind of error here */
+			g_warning ("Child process returned no data");
+		}
+
+		spawn_data_destroy (data);
+	}
+
+	monitors_setup (project);
+	
+	return retval;
+}
+
+static gboolean 
+project_update (GbfMkfileProject *project,
+		xmlDocPtr     doc,
+		GSList      **change_set,
+		GError      **err) 
+{
+	GbfMkfileSpawnData *data;
+	gchar *argv [5];
+	gboolean retval;
+	gint i;
+	xmlChar *xml_doc;
+	int xml_size;
+	
+	/* remove file monitors */
+	monitors_remove (project);
+
+	i = 0;
+	argv [i++] = GBF_MKFILE_PARSE;
+	DEBUG (argv [i++] = "-d");
+	argv [i++] = "--set";
+	argv [i++] = "-";
+	argv [i++] = NULL;
+	g_assert (i <= G_N_ELEMENTS (argv));
+
+	/* dump the document to memory */
+	xmlSubstituteEntitiesDefault (TRUE);
+	xmlDocDumpMemory (doc, &xml_doc, &xml_size);
+
+	/* execute the script */
+	data = spawn_script (argv, SCRIPT_TIMEOUT,
+			     (gchar*)xml_doc, xml_size,  /* input buffer */
+			     NULL, NULL, NULL);  /* i/o callbacks */
+	xmlFree (xml_doc);
+
+	retval = FALSE;
+	if (data != NULL) {
+		if (data->error.length > 0 && err != NULL) {
+			/* the buffer is zero terminated */
+			*err = parse_errors (project, data->error.buffer);
+		}
+		
+		if (data->output.length > 0) {
+			/* process the xml output for changed groups */
+			retval = parse_output_xml (project,
+						   data->output.buffer,
+						   data->output.length,
+						   change_set);
+			/* FIXME: emit this only if the project has indeed changed */
+			g_signal_emit_by_name (G_OBJECT (project), "project-updated");
+		}
+		
+		spawn_data_destroy (data);
+	}
+
+	monitors_setup (project);
+	
+	return retval;
+}
+
+/*
+ * ---------------- Data structures managment
+ */
+
+static void
+gbf_mkfile_node_free (GbfMkfileNode *node)
+{
+	if (node) {
+		g_free (node->id);
+		g_free (node->name);
+		g_free (node->detail);
+		g_free (node->uri);
+		gbf_mkfile_config_mapping_destroy (node->config);
+
+		g_free (node);
+	}
+}
+
+static GNode *
+project_node_new (GbfMkfileNodeType type)
+{
+	GbfMkfileNode *node;
+
+	node = g_new0 (GbfMkfileNode, 1);
+	node->type = type;
+
+	return g_node_new (node);
+}
+
+static gboolean 
+foreach_node_destroy (GNode    *g_node,
+		      gpointer  data)
+{
+	GbfMkfileProject *project = data;
+	
+	switch (GBF_MKFILE_NODE (g_node)->type) {
+		case GBF_MKFILE_NODE_GROUP:
+			g_hash_table_remove (project->groups, GBF_MKFILE_NODE (g_node)->id);
+			break;
+		case GBF_MKFILE_NODE_TARGET:
+			g_hash_table_remove (project->targets, GBF_MKFILE_NODE (g_node)->id);
+			break;
+		case GBF_MKFILE_NODE_SOURCE:
+			g_hash_table_remove (project->sources, GBF_MKFILE_NODE (g_node)->id);
+			break;
+		default:
+			g_assert_not_reached ();
+			break;
+	}
+	gbf_mkfile_node_free (GBF_MKFILE_NODE (g_node));
+
+	return FALSE;
+}
+
+static void
+project_node_destroy (GbfMkfileProject *project, GNode *g_node)
+{
+	g_return_if_fail (project != NULL);
+	g_return_if_fail (GBF_IS_MKFILE_PROJECT (project));
+	
+	if (g_node) {
+		/* free each node's data first */
+		g_node_traverse (g_node,
+				 G_IN_ORDER, G_TRAVERSE_ALL, -1,
+				 foreach_node_destroy, project);
+
+		/* now destroy the tree itself */
+		g_node_destroy (g_node);
+	}
+}
+
+static void
+project_data_destroy (GbfMkfileProject *project)
+{
+	g_return_if_fail (project != NULL);
+	g_return_if_fail (GBF_IS_MKFILE_PROJECT (project));
+	
+	monitors_remove (project);
+	
+	/* project data */
+	project_node_destroy (project, project->root_node);
+	project->root_node = NULL;
+	g_free (project->project_file);
+	project->project_file = NULL;
+	gbf_mkfile_config_mapping_destroy (project->project_config);
+	project->project_config = NULL;
+	
+	/* shortcut hash tables */
+	if (project->groups) g_hash_table_destroy (project->groups);
+	if (project->targets) g_hash_table_destroy (project->targets);
+	if (project->sources) g_hash_table_destroy (project->sources);
+	project->groups = NULL;
+	project->targets = NULL;
+	project->sources = NULL;
+}
+
+static void
+project_data_init (GbfMkfileProject *project)
+{
+	g_return_if_fail (project != NULL);
+	g_return_if_fail (GBF_IS_MKFILE_PROJECT (project));
+	
+	/* free data if necessary */
+	project_data_destroy (project);
+
+	/* FIXME: initialize monitors here, since monitors' lifecycle
+	 * are bound to source files from the project */
+	
+	/* project data */
+	project->project_file = NULL;
+	project->project_config = gbf_mkfile_config_mapping_new ();
+	project->root_node = NULL;
+	
+	/* shortcut hash tables */
+	project->groups = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+	project->targets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+	project->sources = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+}
+
+GbfMkfileConfigMapping *
+gbf_mkfile_project_get_config (GbfMkfileProject *project, GError **error)
+{
+	g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (project), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+	
+	return gbf_mkfile_config_mapping_copy (project->project_config);
+}
+
+GbfMkfileConfigMapping *
+gbf_mkfile_project_get_group_config (GbfMkfileProject *project, const gchar *group_id,
+				 GError **error)
+{
+	GbfMkfileNode *node;
+	GNode *g_node;
+	
+	g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (project), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+	
+	g_node = g_hash_table_lookup (project->groups, group_id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Group doesn't exist"));
+		return NULL;
+	}
+	node = GBF_MKFILE_NODE (g_node);
+	return gbf_mkfile_config_mapping_copy (node->config);
+}
+
+GbfMkfileConfigMapping *
+gbf_mkfile_project_get_target_config (GbfMkfileProject *project, const gchar *target_id,
+				  GError **error)
+{
+	GbfMkfileNode *node;
+	GNode *g_node;
+	
+	g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (project), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+	
+	g_node = g_hash_table_lookup (project->targets, target_id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Target doesn't exist"));
+		return NULL;
+	}
+	node = GBF_MKFILE_NODE (g_node);
+	return gbf_mkfile_config_mapping_copy (node->config);
+}
+
+void
+gbf_mkfile_project_set_config (GbfMkfileProject *project,
+			   GbfMkfileConfigMapping *new_config, GError **error)
+{
+	xmlDocPtr doc;
+	GSList *change_set = NULL;
+	
+	g_return_if_fail (GBF_IS_MKFILE_PROJECT (project));
+	g_return_if_fail (new_config != NULL);
+	g_return_if_fail (error == NULL || *error == NULL);
+	
+	/* Create the update xml */
+	doc = xml_new_change_doc (project);
+	
+	if (!xml_write_set_config (project, doc, NULL, new_config)) {
+		xmlFreeDoc (doc);
+		return;
+	}
+
+	DEBUG ({
+		xmlSetDocCompressMode (doc, 0);
+		xmlSaveFile ("/tmp/set-config.xml", doc);
+	});
+
+	/* Update the project */
+	if (!project_update (project, doc, &change_set, error)) {
+		error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+			   _("Unable to update project"));
+		xmlFreeDoc (doc);
+		return;
+	}
+	xmlFreeDoc (doc);
+	change_set_destroy (change_set);
+}
+
+void
+gbf_mkfile_project_set_group_config (GbfMkfileProject *project, const gchar *group_id,
+				 GbfMkfileConfigMapping *new_config, GError **error)
+{
+	GbfMkfileNode *node;
+	xmlDocPtr doc;
+	GNode *g_node;
+	GSList *change_set = NULL;
+	
+	g_return_if_fail (GBF_IS_MKFILE_PROJECT (project));
+	g_return_if_fail (new_config != NULL);
+	g_return_if_fail (error == NULL || *error == NULL);
+	
+	g_node = g_hash_table_lookup (project->groups, group_id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Group doesn't exist"));
+		return;
+	}
+	node = GBF_MKFILE_NODE (g_node);
+	
+	/* Create the update xml */
+	doc = xml_new_change_doc (project);
+	if (!xml_write_set_config (project, doc, g_node, new_config)) {
+		xmlFreeDoc (doc);
+		return;
+	}
+
+	DEBUG ({
+		xmlSetDocCompressMode (doc, 0);
+		xmlSaveFile ("/tmp/set-config.xml", doc);
+	});
+	
+	/* Update the project */
+	if (!project_update (project, doc, &change_set, error)) {
+		error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+			   _("Unable to update project"));
+		xmlFreeDoc (doc);
+		return;
+	}
+	xmlFreeDoc (doc);
+	change_set_destroy (change_set);
+}
+
+void
+gbf_mkfile_project_set_target_config (GbfMkfileProject *project,
+				  const gchar *target_id,
+				  GbfMkfileConfigMapping *new_config,
+				  GError **error)
+{
+	xmlDocPtr doc;
+	GNode *g_node;
+	GSList *change_set = NULL;
+	
+	g_return_if_fail (GBF_IS_MKFILE_PROJECT (project));
+	g_return_if_fail (new_config != NULL);
+	g_return_if_fail (error == NULL || *error == NULL);
+	
+	g_node = g_hash_table_lookup (project->targets, target_id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Target doesn't exist"));
+	}
+	
+	/* Create the update xml */
+	doc = xml_new_change_doc (project);
+	if (!xml_write_set_config (project, doc, g_node, new_config)) {
+		xmlFreeDoc (doc);
+		return;
+	}
+
+	DEBUG ({
+		xmlSetDocCompressMode (doc, 0);
+		xmlSaveFile ("/tmp/set-config.xml", doc);
+	});
+
+	/* Update the project */
+	if (!project_update (project, doc, &change_set, error)) {
+		error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+			   _("Unable to update project"));
+		xmlFreeDoc (doc);
+		return;
+	}
+	xmlFreeDoc (doc);
+	change_set_destroy (change_set);
+}
+
+/*
+ * GbfProjectIface methods ------------------------------------------
+ */
+
+static void 
+impl_load (GbfProject  *_project,
+	   const gchar *uri,
+	   GError     **error)
+{
+	GbfMkfileProject *project;
+	gchar *root_path;
+	
+	g_return_if_fail (GBF_IS_MKFILE_PROJECT (_project));
+
+	project = GBF_MKFILE_PROJECT (_project);
+	if (project->project_root_uri) {
+		/* FIXME:
+		 * - do we really want to allow object reutilization
+		 * - cancel some pending operations in the queue?
+		 */
+		project_data_destroy (project);
+		g_free (project->project_root_uri);
+		project->project_root_uri = NULL;
+
+		project_data_init (project);
+	}
+
+	/* allow this? */
+	if (uri == NULL)
+		return;
+	
+	/* check that the uri is in the filesystem */
+	project->project_root_uri = uri_normalize (uri, NULL);
+	if (project->project_root_uri == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Invalid or remote path (only local paths supported)"));
+		return;
+	}
+		
+	/* some basic checks */
+	root_path = uri_to_path (project->project_root_uri);
+	if (root_path == NULL || !g_file_test (root_path, G_FILE_TEST_IS_DIR)) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Project doesn't exist or invalid path"));
+		g_free (root_path);
+		g_free (project->project_root_uri);
+		project->project_root_uri = NULL;
+		return;
+	}
+	g_free (root_path);
+
+	/* now try loading the project */
+	if (!project_reload (project, error)) {
+		error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+			   _("Malformed project"));
+		g_free (project->project_root_uri);
+		project->project_root_uri = NULL;
+	}
+}
+
+static gboolean
+file_exists (const gchar *path, const gchar *filename)
+{
+	gchar *full_path;
+	gboolean retval;
+	
+	full_path = g_build_filename (path, filename, NULL);
+	retval = g_file_test (full_path, G_FILE_TEST_EXISTS);
+	g_free (full_path);
+
+	return retval;
+}
+
+static gboolean 
+impl_probe (GbfProject  *_project,
+	    const gchar *path,
+	    GError     **error)
+{
+	gchar *normalized_uri, *root_path;
+	gboolean retval = FALSE;
+	
+	g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (_project), FALSE);
+
+	normalized_uri = uri_normalize (path, NULL);
+	if (normalized_uri != NULL) {
+		root_path = uri_to_path (normalized_uri);
+		if (root_path != NULL && g_file_test (root_path, G_FILE_TEST_IS_DIR)) {
+			retval = ((file_exists (root_path, "Makefile") ||
+					   file_exists (root_path, "makefile")) &&
+					   !(file_exists (root_path, "Makefile.am") || 
+						 file_exists (root_path, "Makefile.in")));
+			g_free (root_path);
+		}
+		g_free (normalized_uri);
+	}
+
+	return retval;
+}
+
+static void
+impl_refresh (GbfProject *_project,
+	      GError    **error)
+{
+	GbfMkfileProject *project;
+
+	g_return_if_fail (GBF_IS_MKFILE_PROJECT (_project));
+
+	project = GBF_MKFILE_PROJECT (_project);
+
+	if (project_reload (project, error))
+		g_signal_emit_by_name (G_OBJECT (project), "project-updated");
+}
+
+static GbfProjectCapabilities
+impl_get_capabilities (GbfProject *_project, GError    **error)
+{
+	return GBF_PROJECT_CAN_ADD_NONE;
+}
+
+static GbfProjectGroup * 
+impl_get_group (GbfProject  *_project,
+		const gchar *id,
+		GError     **error)
+{
+	GbfMkfileProject *project;
+	GbfProjectGroup *group;
+	GNode *g_node;
+	GbfMkfileNode *node;
+	
+	g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (_project), NULL);
+
+	project = GBF_MKFILE_PROJECT (_project);
+	g_node = g_hash_table_lookup (project->groups, id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Group doesn't exist"));
+		return NULL;
+	}
+	node = GBF_MKFILE_NODE (g_node);
+
+	group = g_new0 (GbfProjectGroup, 1);
+	group->id = g_strdup (node->id);
+	group->name = g_strdup (node->name);
+	if (g_node->parent)
+		group->parent_id = g_strdup (GBF_MKFILE_NODE (g_node->parent)->id);
+	else
+		group->parent_id = NULL;
+	group->groups = NULL;
+	group->targets = NULL;
+
+	/* add subgroups and targets of the group */
+	g_node = g_node_first_child (g_node);
+	while (g_node) {
+		node = GBF_MKFILE_NODE (g_node);
+		switch (node->type) {
+			case GBF_MKFILE_NODE_GROUP:
+				group->groups = g_list_prepend (group->groups,
+								g_strdup (node->id));
+				break;
+			case GBF_MKFILE_NODE_TARGET:
+				group->targets = g_list_prepend (group->targets,
+								 g_strdup (node->id));
+				break;
+			default:
+				break;
+		}
+		g_node = g_node_next_sibling (g_node);
+	}
+	group->groups = g_list_reverse (group->groups);
+	group->targets = g_list_reverse (group->targets);
+	
+	return group;
+}
+
+static void
+foreach_group (gpointer key, gpointer value, gpointer data)
+{
+	GList **groups = data;
+
+	*groups = g_list_prepend (*groups, g_strdup (key));
+}
+
+
+static GList *
+impl_get_all_groups (GbfProject *_project,
+		     GError    **error)
+{
+	GbfMkfileProject *project;
+	GList *groups = NULL;
+
+	g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (_project), NULL);
+
+	project = GBF_MKFILE_PROJECT (_project);
+	g_hash_table_foreach (project->groups, foreach_group, &groups);
+
+	return groups;
+}
+
+static GtkWidget *
+impl_configure_new_group (GbfProject *_project,
+			  GError    **error)
+{
+	UNIMPLEMENTED;
+
+	return NULL;
+}
+
+static GtkWidget * 
+impl_configure_group (GbfProject  *_project,
+		      const gchar *id,
+		      GError     **error)
+{
+	GtkWidget *wid = NULL;
+	GError *err = NULL;
+	
+	g_return_val_if_fail (GBF_IS_PROJECT (_project), NULL);
+	g_return_val_if_fail (id != NULL, NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+	
+	wid = gbf_mkfile_properties_get_group_widget (GBF_MKFILE_PROJECT (_project),
+						  id, &err);
+	if (err) {
+		g_propagate_error (error, err);
+	}
+	return wid;
+}
+
+static gchar * 
+impl_add_group (GbfProject  *_project,
+		const gchar *parent_id,
+		const gchar *name,
+		GError     **error)
+{
+	GbfMkfileProject *project;
+	GNode *g_node, *iter_node;
+	xmlDocPtr doc;
+	GSList *change_set = NULL;
+	GbfMkfileChange *change;
+	gchar *retval;
+	
+	g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (_project), NULL);
+
+	project = GBF_MKFILE_PROJECT (_project);
+	
+	/* find the parent group */
+	g_node = g_hash_table_lookup (project->groups, parent_id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Parent group doesn't exist"));
+		return NULL;
+	}
+
+	/* check that the new group doesn't already exist */
+	iter_node = g_node_first_child (g_node);
+	while (iter_node) {
+		GbfMkfileNode *node = GBF_MKFILE_NODE (iter_node);
+		if (node->type == GBF_MKFILE_NODE_GROUP &&
+		    !strcmp (node->name, name)) {
+			error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+				   _("Group already exists"));
+			return NULL;
+		}
+		iter_node = g_node_next_sibling (iter_node);
+	}
+			
+	/* Create the update xml */
+	doc = xml_new_change_doc (project);
+	if (!xml_write_add_group (project, doc, g_node, name)) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Group couldn't be created"));
+		xmlFreeDoc (doc);
+		return NULL;
+	}
+
+	DEBUG ({
+		xmlSetDocCompressMode (doc, 0);
+		xmlSaveFile ("/tmp/add-group.xml", doc);
+	});
+
+	/* Update the project */
+	if (!project_update (project, doc, &change_set, error)) {
+		error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+			   _("Unable to update project"));
+		xmlFreeDoc (doc);
+		return NULL;
+	}
+	xmlFreeDoc (doc);
+
+	/* get newly created group id */
+	retval = NULL;
+	DEBUG (change_set_debug_print (change_set));
+	change = change_set_find (change_set, GBF_MKFILE_CHANGE_ADDED, GBF_MKFILE_NODE_GROUP);
+	if (change) {
+		retval = g_strdup (change->id);
+	} else {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Group couldn't be created"));
+	}
+	change_set_destroy (change_set);
+
+	return retval;
+}
+
+static void 
+impl_remove_group (GbfProject  *_project,
+		   const gchar *id,
+		   GError     **error)
+{
+	GbfMkfileProject *project;
+	GNode *g_node;
+	xmlDocPtr doc;
+	GSList *change_set = NULL;
+	
+	g_return_if_fail (GBF_IS_MKFILE_PROJECT (_project));
+
+	project = GBF_MKFILE_PROJECT (_project);
+	
+	/* Find the target */
+	g_node = g_hash_table_lookup (project->groups, id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Group doesn't exist"));
+		return;
+	}
+
+	/* Create the update xml */
+	doc = xml_new_change_doc (project);
+	if (!xml_write_remove_group (project, doc, g_node)) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Group coudn't be removed"));
+		xmlFreeDoc (doc);
+		return;
+	}
+
+	DEBUG ({
+		xmlSetDocCompressMode (doc, 0);
+		xmlSaveFile ("/tmp/remove-group.xml", doc);
+	});
+
+	/* Update the project */
+	if (!project_update (project, doc, &change_set, error)) {
+		error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+			   _("Unable to update project"));
+	}
+	xmlFreeDoc (doc);
+	change_set_destroy (change_set);
+}
+
+static GbfProjectTarget * 
+impl_get_target (GbfProject  *_project,
+		 const gchar *id,
+		 GError     **error)
+{
+	GbfMkfileProject *project;
+	GbfProjectTarget *target;
+	GNode *g_node;
+	GbfMkfileNode *node;
+
+	g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (_project), NULL);
+
+	project = GBF_MKFILE_PROJECT (_project);
+	g_node = g_hash_table_lookup (project->targets, id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Target doesn't exist"));
+		return NULL;
+	}
+	node = GBF_MKFILE_NODE (g_node);
+
+	target = g_new0 (GbfProjectTarget, 1);
+	target->id = g_strdup (node->id);
+	target->name = g_strdup (node->name);
+	target->type = g_strdup (node->detail);
+	target->group_id = g_strdup (GBF_MKFILE_NODE (g_node->parent)->id);
+	target->sources = NULL;
+
+	/* add sources to the target */
+	g_node = g_node_first_child (g_node);
+	while (g_node) {
+		node = GBF_MKFILE_NODE (g_node);
+		switch (node->type) {
+			case GBF_MKFILE_NODE_SOURCE:
+				target->sources = g_list_prepend (target->sources,
+								  g_strdup (node->id));
+				break;
+			default:
+				break;
+		}
+		g_node = g_node_next_sibling (g_node);
+	}
+	target->sources = g_list_reverse (target->sources);
+
+	return target;
+}
+
+static void
+foreach_target (gpointer key, gpointer value, gpointer data)
+{
+	GList **targets = data;
+
+	*targets = g_list_prepend (*targets, g_strdup (key));
+}
+
+static GList *
+impl_get_all_targets (GbfProject *_project,
+		      GError    **error)
+{
+	GbfMkfileProject *project;
+	GList *targets = NULL;
+
+	g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (_project), NULL);
+
+	project = GBF_MKFILE_PROJECT (_project);
+	g_hash_table_foreach (project->targets, foreach_target, &targets);
+
+	return targets;
+}
+
+static GtkWidget *
+impl_configure_new_target (GbfProject *_project,
+			   GError    **error)
+{
+	UNIMPLEMENTED;
+
+	return NULL;
+}
+
+static GtkWidget * 
+impl_configure_target (GbfProject  *_project,
+		       const gchar *id,
+		       GError     **error)
+{
+	GtkWidget *wid = NULL;
+	GError *err = NULL;
+	
+	g_return_val_if_fail (GBF_IS_PROJECT (_project), NULL);
+	g_return_val_if_fail (id != NULL, NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+	
+	wid = gbf_mkfile_properties_get_target_widget (GBF_MKFILE_PROJECT (_project),
+						   id, &err);
+	if (err) {
+		g_propagate_error (error, err);
+	}
+	return wid;
+}
+
+static char * 
+impl_add_target (GbfProject  *_project,
+		 const gchar *group_id,
+		 const gchar *name,
+		 const gchar *type,
+		 GError     **error)
+{
+	GbfMkfileProject *project;
+	GNode *g_node, *iter_node;
+	xmlDocPtr doc;
+	GSList *change_set = NULL;
+	GbfMkfileChange *change;
+	gchar *retval;
+
+	g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (_project), NULL);
+
+	project = GBF_MKFILE_PROJECT (_project);
+	
+	/* find the group */
+	g_node = g_hash_table_lookup (project->groups, group_id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Group doesn't exist"));
+		return NULL;
+	}
+
+	/* FIXME: maybe pre-check the target type? */
+
+	/* check that the target doesn't already exist */
+	iter_node = g_node_first_child (g_node);
+	while (iter_node) {
+		GbfMkfileNode *node = GBF_MKFILE_NODE (iter_node);
+		if (node->type == GBF_MKFILE_NODE_TARGET &&
+		    !strcmp (node->name, name)) {
+			error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+				   _("Target already exists"));
+			return NULL;
+		}
+		iter_node = g_node_next_sibling (iter_node);
+	}
+			
+	/* Create the update xml */
+	doc = xml_new_change_doc (project);
+	if (!xml_write_add_target (project, doc, g_node, name, type)) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Target couldn't be created"));
+		xmlFreeDoc (doc);
+		return NULL;
+	}
+
+	DEBUG ({
+		xmlSetDocCompressMode (doc, 0);
+		xmlSaveFile ("/tmp/add-target.xml", doc);
+	});
+
+	/* Update the project */
+	if (!project_update (project, doc, &change_set, error)) {
+		error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+			   _("Unable to update project"));
+		xmlFreeDoc (doc);
+		return NULL;
+	}
+	xmlFreeDoc (doc);
+	
+	/* get newly created target id */
+	retval = NULL;
+	DEBUG (change_set_debug_print (change_set));
+	change = change_set_find (change_set, GBF_MKFILE_CHANGE_ADDED, GBF_MKFILE_NODE_TARGET);
+	if (change) {
+		retval = g_strdup (change->id);
+	} else {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Target couldn't be created"));
+	}
+	change_set_destroy (change_set);
+
+	return retval;
+}
+
+static void 
+impl_remove_target (GbfProject  *_project,
+		    const gchar *id,
+		    GError     **error)
+{
+	GbfMkfileProject *project;
+	GNode *g_node;
+	xmlDocPtr doc;
+	GSList *change_set = NULL;
+	
+	g_return_if_fail (GBF_IS_MKFILE_PROJECT (_project));
+
+	project = GBF_MKFILE_PROJECT (_project);
+	
+	/* Find the target */
+	g_node = g_hash_table_lookup (project->targets, id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Target doesn't exist"));
+		return;
+	}
+
+	/* Create the update xml */
+	doc = xml_new_change_doc (project);
+	if (!xml_write_remove_target (project, doc, g_node)) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Target coudn't be removed"));
+		xmlFreeDoc (doc);
+		return;
+	}
+
+	DEBUG ({
+		xmlSetDocCompressMode (doc, 0);
+		xmlSaveFile ("/tmp/remove-target.xml", doc);
+	});
+
+	/* Update the project */
+	if (!project_update (project, doc, &change_set, error)) {
+		error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+			   _("Unable to update project"));
+	}
+	xmlFreeDoc (doc);
+	change_set_destroy (change_set);
+}
+
+static const gchar * 
+impl_name_for_type (GbfProject  *_project,
+		    const gchar *type)
+{
+	if (!strcmp (type, "static_lib")) {
+		return _("Static Library");
+	} else if (!strcmp (type, "shared_lib")) {
+		return _("Shared Library");
+	} else if (!strcmp (type, "man")) {
+		return _("Man Documentation");
+	} else if (!strcmp (type, "data")) {
+		return _("Miscellaneous Data");
+	} else if (!strcmp (type, "program")) {
+		return _("Program");
+	} else if (!strcmp (type, "script")) {
+		return _("Script");
+	} else if (!strcmp (type, "info")) {
+		return _("Info Documentation");
+	} else {
+		return _("Unknown");
+	}
+}
+
+static const gchar * 
+impl_mimetype_for_type (GbfProject  *_project,
+			const gchar *type)
+{
+	if (!strcmp (type, "static_lib")) {
+		return "application/x-archive";
+	} else if (!strcmp (type, "shared_lib")) {
+		return "application/x-sharedlib";
+	} else if (!strcmp (type, "man")) {
+		return "text/x-troff-man";
+	} else if (!strcmp (type, "data")) {
+		return "application/octet-stream";
+	} else if (!strcmp (type, "program")) {
+		return "application/x-executable";
+	} else if (!strcmp (type, "script")) {
+		return "text/x-shellscript";
+	} else if (!strcmp (type, "info")) {
+		return "application/x-tex-info";
+	} else {
+		return "text/plain";
+	}
+}
+
+static gchar **
+impl_get_types (GbfProject *_project)
+{
+	return g_strsplit ("program:shared_lib:static_lib:"
+			   "man:data:script:info", ":", 0);
+}
+
+static GbfProjectTargetSource * 
+impl_get_source (GbfProject  *_project,
+		 const gchar *id,
+		 GError     **error)
+{
+	GbfMkfileProject *project;
+	GbfProjectTargetSource *source;
+	GNode *g_node;
+	GbfMkfileNode *node;
+
+	g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (_project), NULL);
+
+	project = GBF_MKFILE_PROJECT (_project);
+	g_node = g_hash_table_lookup (project->sources, id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Source doesn't exist"));
+		return NULL;
+	}
+	node = GBF_MKFILE_NODE (g_node);
+
+	source = g_new0 (GbfProjectTargetSource, 1);
+	source->id = g_strdup (node->id);
+	source->source_uri = g_strdup (node->uri);
+	source->target_id = g_strdup (GBF_MKFILE_NODE (g_node->parent)->id);
+
+	return source;
+}
+
+static void
+foreach_source (gpointer key, gpointer value, gpointer data)
+{
+	GList **sources = data;
+
+	*sources = g_list_prepend (*sources, g_strdup (key));
+}
+
+static GList *
+impl_get_all_sources (GbfProject *_project,
+		      GError    **error)
+{
+	GbfMkfileProject *project;
+	GList *sources = NULL;
+
+	g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (_project), NULL);
+
+	project = GBF_MKFILE_PROJECT (_project);
+	g_hash_table_foreach (project->sources, foreach_source, &sources);
+
+	return sources;
+}
+
+static GtkWidget *
+impl_configure_new_source (GbfProject *_project,
+			   GError    **error)
+{
+	UNIMPLEMENTED;
+
+	return NULL;
+}
+
+static GtkWidget * 
+impl_configure_source (GbfProject  *_project,
+		       const gchar *id,
+		       GError     **error)
+{
+	UNIMPLEMENTED;
+
+	return NULL;
+}
+
+/**
+ * impl_add_source:
+ * @project: 
+ * @target_id: the target ID to where to add the source
+ * @uri: an uri to the file, which can be absolute or relative to the target's group
+ * @error: 
+ * 
+ * Add source implementation.  The uri must have the project root as its parent.
+ * 
+ * Return value: 
+ **/
+static gchar * 
+impl_add_source (GbfProject  *_project,
+		 const gchar *target_id,
+		 const gchar *uri,
+		 GError     **error)
+{
+	GbfMkfileProject *project;
+	GNode *g_node, *iter_node;
+	xmlDocPtr doc;
+	gboolean abort_action = FALSE;
+	gchar *full_uri = NULL;
+	gchar *group_uri;
+	GSList *change_set = NULL;
+	GbfMkfileChange *change;
+	gchar *retval;
+	
+	g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (_project), NULL);
+
+	project = GBF_MKFILE_PROJECT (_project);
+	
+	/* check target */
+	g_node = g_hash_table_lookup (project->targets, target_id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Target doesn't exist"));
+		return NULL;
+	}
+
+	/* if the uri is relative, resolve it against the target's
+	 * group directory; we need to compute the group's uri
+	 * first */
+	group_uri = uri_normalize (g_path_skip_root (GBF_MKFILE_NODE (g_node->parent)->id),
+				   project->project_root_uri);
+	full_uri = uri_normalize (uri, group_uri);
+	g_free (group_uri);
+	
+	/* Check that the source uri is inside the project root */
+	if (!uri_is_parent (project->project_root_uri, full_uri)) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Source file must be inside the project directory"));
+		abort_action = TRUE;
+	}
+	
+	/* check for source duplicates */
+	iter_node = g_node_first_child (g_node);
+	while (!abort_action && iter_node) {
+		GbfMkfileNode *node = GBF_MKFILE_NODE (iter_node);
+		
+		if (node->type == GBF_MKFILE_NODE_SOURCE &&
+		    uri_is_equal (full_uri, node->uri)) {
+			error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+				   _("Source is already in target"));
+			abort_action = TRUE;
+		}
+		iter_node = g_node_next_sibling (iter_node);
+	}
+
+	/* have there been any errors? */
+	if (abort_action) {
+		g_free (full_uri);
+		return NULL;
+	}
+	
+	/* Create the update xml */
+	doc = xml_new_change_doc (project);
+
+	if (!xml_write_add_source (project, doc, g_node, full_uri)) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Source couldn't be added"));
+		abort_action = TRUE;
+	}
+
+	g_free (full_uri);
+	if (abort_action) {
+		xmlFreeDoc (doc);
+		return NULL;
+	}
+	
+	DEBUG ({
+		xmlSetDocCompressMode (doc, 0);
+		xmlSaveFile ("/tmp/add-source.xml", doc);
+	});
+
+	/* Update the project */
+	if (!project_update (project, doc, &change_set, error)) {
+		error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+			   _("Unable to update project"));
+		xmlFreeDoc (doc);
+		return NULL;
+	}
+	xmlFreeDoc (doc);
+
+	/* get newly created source id */
+	retval = NULL;
+	DEBUG (change_set_debug_print (change_set));
+	change = change_set_find (change_set, GBF_MKFILE_CHANGE_ADDED, GBF_MKFILE_NODE_SOURCE);
+	if (change) {
+		retval = g_strdup (change->id);
+	} else {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Source couldn't be added"));
+	}
+	change_set_destroy (change_set);
+
+	return retval;
+}
+
+static void 
+impl_remove_source (GbfProject  *_project,
+		    const gchar *id,
+		    GError     **error)
+{
+	GbfMkfileProject *project;
+	GNode *g_node;
+	xmlDocPtr doc;
+	
+	g_return_if_fail (GBF_IS_MKFILE_PROJECT (_project));
+
+	project = GBF_MKFILE_PROJECT (_project);
+	
+	/* Find the source */
+	g_node = g_hash_table_lookup (project->sources, id);
+	if (g_node == NULL) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Source doesn't exist"));
+		return;
+	}
+
+	/* Create the update xml */
+	doc = xml_new_change_doc (project);
+	if (!xml_write_remove_source (project, doc, g_node)) {
+		error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+			   _("Source coudn't be removed"));
+		xmlFreeDoc (doc);
+		return;
+	}
+
+	DEBUG ({
+		xmlSetDocCompressMode (doc, 0);
+		xmlSaveFile ("/tmp/remove-source.xml", doc);
+	});
+
+	/* Update the project */
+	/* FIXME: should get and process the change set to verify that
+	 * the source has been removed? */
+	if (!project_update (project, doc, NULL, error)) {
+		error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+			   _("Unable to update project"));
+	}
+	xmlFreeDoc (doc);
+}
+
+static GtkWidget *
+impl_configure (GbfProject *_project, GError **error)
+{
+	GtkWidget *wid = NULL;
+	GError *err = NULL;
+	
+	g_return_val_if_fail (GBF_IS_PROJECT (_project), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+	
+	wid = gbf_mkfile_properties_get_widget (GBF_MKFILE_PROJECT (_project), &err);
+	if (err) {
+		g_propagate_error (error, err);
+	}
+	return wid;
+}
+
+static GList *
+impl_get_config_modules   (GbfProject *project, GError **error)
+{
+	return NULL;
+}
+
+static GList *
+impl_get_config_packages  (GbfProject *project,
+			   const gchar* module,
+			   GError **error)
+{
+	return NULL;
+}
+
+static void
+gbf_mkfile_project_class_init (GbfMkfileProjectClass *klass)
+{
+	GObjectClass *object_class;
+	GbfProjectClass *project_class;
+
+	object_class = G_OBJECT_CLASS (klass);
+	project_class = GBF_PROJECT_CLASS (klass);
+	parent_class = g_type_class_peek_parent (klass);
+
+	object_class->dispose = gbf_mkfile_project_dispose;
+	object_class->get_property = gbf_mkfile_project_get_property;
+
+	project_class->load = impl_load;
+	project_class->probe = impl_probe;
+	project_class->refresh = impl_refresh;
+	project_class->get_capabilities = impl_get_capabilities;
+
+	project_class->add_group = impl_add_group;
+	project_class->remove_group = impl_remove_group;
+	project_class->get_group = impl_get_group;
+	project_class->get_all_groups = impl_get_all_groups;
+	project_class->configure_group = impl_configure_group;
+	project_class->configure_new_group = impl_configure_new_group;
+
+	project_class->add_target = impl_add_target;
+	project_class->remove_target = impl_remove_target;
+	project_class->get_target = impl_get_target;
+	project_class->get_all_targets = impl_get_all_targets;
+	project_class->configure_target = impl_configure_target;
+	project_class->configure_new_target = impl_configure_new_target;
+
+	project_class->add_source = impl_add_source;
+	project_class->remove_source = impl_remove_source;
+	project_class->get_source = impl_get_source;
+	project_class->get_all_sources = impl_get_all_sources;
+	project_class->configure_source = impl_configure_source;
+	project_class->configure_new_source = impl_configure_new_source;
+
+	project_class->configure = impl_configure;
+	project_class->name_for_type = impl_name_for_type;
+	project_class->mimetype_for_type = impl_mimetype_for_type;
+	project_class->get_types = impl_get_types;
+	
+	project_class->get_config_modules = impl_get_config_modules;
+	project_class->get_config_packages = impl_get_config_packages;
+	
+	/* default signal handlers */
+	project_class->project_updated = NULL;
+
+	/* FIXME: shouldn't we use '_' instead of '-' ? */
+	g_object_class_install_property 
+		(object_class, PROP_PROJECT_DIR,
+		 g_param_spec_string ("project-dir", 
+				      _("Project directory"),
+				      _("Project directory"),
+				      NULL,
+				      G_PARAM_READABLE));
+}
+
+static void
+gbf_mkfile_project_instance_init (GbfMkfileProject *project)
+{
+	/* initialize data & monitors */
+	project->project_root_uri = NULL;
+	project_data_init (project);
+
+	/* setup queue */
+	project->queue_ops = g_queue_new ();
+	project->queue_handler_tag = 0;
+	
+	/* initialize build callbacks */
+	project->callbacks = NULL;
+
+	/* FIXME: those path should be configurable */
+	project->make_command = g_strdup ("/usr/bin/make");
+	project->configure_command = g_strdup ("./configure");
+	project->autogen_command = g_strdup ("./autogen.sh");
+	project->install_prefix = g_strdup ("/gnome");
+}
+
+static void
+gbf_mkfile_project_dispose (GObject *object)
+{
+	GbfMkfileProject *project;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (GBF_IS_MKFILE_PROJECT (object));
+
+	project = GBF_MKFILE_PROJECT (object);
+
+	/* project data & monitors */
+	project_data_destroy (project);
+	g_free (project->project_root_uri);
+	project->project_root_uri = NULL;
+
+	g_free (project->make_command);
+	g_free (project->configure_command);
+	g_free (project->autogen_command);
+	g_free (project->install_prefix);
+	
+	GNOME_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
+}
+
+static void
+gbf_mkfile_project_get_property (GObject    *object,
+			     guint       prop_id,
+			     GValue     *value,
+			     GParamSpec *pspec)
+{
+	GbfMkfileProject *project = GBF_MKFILE_PROJECT (object);
+
+	switch (prop_id) {
+		case PROP_PROJECT_DIR:
+			g_value_set_string (value, project->project_root_uri);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+GbfProject *
+gbf_mkfile_project_new (void)
+{
+	return GBF_PROJECT (g_object_new (GBF_TYPE_MKFILE_PROJECT, NULL));
+}
+
+GBF_BACKEND_BOILERPLATE (GbfMkfileProject, gbf_mkfile_project);

Added: trunk/plugins/gbf-mkfile/gbf-mkfile-project.h
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/gbf-mkfile-project.h	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,135 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8; coding: utf-8 -*- */
+/* gbf-mkfile-project.h
+ *
+ * Copyright (C) 2005  Eric Greveson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Eric Greveson
+ * Based on the Autotools GBF backend (libgbf-am) by
+ *   JP Rosevear
+ *   Dave Camp
+ *   Naba Kumar
+ *   Gustavo GirÃldez
+ */
+
+#ifndef _GBF_MKFILE_PROJECT_H_
+#define _GBF_MKFILE_PROJECT_H_
+
+#include <glib-object.h>
+#include <libanjuta/gbf-project.h>
+#include "gbf-mkfile-config.h"
+
+G_BEGIN_DECLS
+
+#define GBF_TYPE_MKFILE_PROJECT		(gbf_mkfile_project_get_type (NULL))
+#define GBF_MKFILE_PROJECT(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), GBF_TYPE_MKFILE_PROJECT, GbfMkfileProject))
+#define GBF_MKFILE_PROJECT_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass), GBF_TYPE_MKFILE_PROJECT, GbfMkfileProjectClass))
+#define GBF_IS_MKFILE_PROJECT(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GBF_TYPE_MKFILE_PROJECT))
+#define GBF_IS_MKFILE_PROJECT_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((obj), GBF_TYPE_MKFILE_PROJECT))
+
+typedef struct _GbfMkfileProject        GbfMkfileProject;
+typedef struct _GbfMkfileProjectClass   GbfMkfileProjectClass;
+typedef struct _GbfMkfileBuildCallback  GbfMkfileBuildCallback;
+typedef struct _GbfMkfileNode           GbfMkfileNode;
+
+typedef enum {
+	GBF_MKFILE_NODE_GROUP,
+	GBF_MKFILE_NODE_TARGET,
+	GBF_MKFILE_NODE_SOURCE
+} GbfMkfileNodeType;
+	
+struct _GbfMkfileNode {
+	GbfMkfileNodeType       type;
+	gchar                  *id;        /* unique id among nodes of the same type */
+	gchar                  *name;      /* user visible string */
+	GbfMkfileConfigMapping *config;
+	gchar                  *uri;       /* groups: path to Makefile.am;
+					       targets: NULL;
+					       sources: file uri */
+	gchar                  *detail;    /* groups: NULL;
+					       targets: target type;
+					       sources: NULL or target dependency for built sources */
+};
+
+struct _GbfMkfileProject {
+	GbfProject          parent;
+
+	/* uri of the project; this can be a full uri, even though we
+	 * can only work with native local files */
+	gchar              *project_root_uri;
+
+	/* project data */
+	gchar                  *project_file;      /* configure.in uri */
+	GbfMkfileConfigMapping *project_config;    /* project configuration
+						    * (i.e. from configure.in) */
+	GNode                  *root_node;         /* tree containing project data;
+	                        * each GNode's data is a
+						    * GbfAmNode, and the root of
+						    * the tree is the root group. */
+
+	/* shortcut hash tables, mapping id -> GNode from the tree above */
+	GHashTable         *groups;
+	GHashTable         *targets;
+	GHashTable         *sources;
+	
+	/* project files monitors */
+	GHashTable         *monitors;
+
+	/* operations queue */
+	GQueue             *queue_ops;
+	guint               queue_handler_tag;
+
+	/* build callbacks */
+	GList              *callbacks;
+
+	/* build config */
+	gchar	 	   *make_command;
+	gchar 		   *configure_command;
+	gchar 		   *autogen_command;
+	gchar 		   *install_prefix;
+};
+
+struct _GbfMkfileProjectClass {
+	GbfProjectClass parent_class;
+};
+
+/* convenient shortcut macro the get the GbfMkfileNode from a GNode */
+#define GBF_MKFILE_NODE(g_node)  ((g_node) != NULL ? (GbfMkfileNode *)((g_node)->data) : NULL)
+
+GType         gbf_mkfile_project_get_type (GTypeModule *module);
+GbfProject   *gbf_mkfile_project_new      (void);
+
+/* FIXME: The config infrastructure should probably be made part of GbfProject
+ * so that other backend implementations could use them directly and we don't
+ * have to create separate configuration widgets. But then different back end
+ * implementations could have significantly different config management
+ * warranting separate implementations.
+ */
+/* These functions returns a copy of the config. It should be free with
+ * gbf_mkfile_config_value_free() when no longer required
+ */
+GbfMkfileConfigMapping *gbf_mkfile_project_get_config (GbfMkfileProject *project, GError **error);
+GbfMkfileConfigMapping *gbf_mkfile_project_get_group_config (GbfMkfileProject *project, const gchar *group_id, GError **error);
+GbfMkfileConfigMapping *gbf_mkfile_project_get_target_config (GbfMkfileProject *project, const gchar *target_id, GError **error);
+
+void gbf_mkfile_project_set_config (GbfMkfileProject *project, GbfMkfileConfigMapping *new_config, GError **error);
+void gbf_mkfile_project_set_group_config (GbfMkfileProject *project, const gchar *group_id, GbfMkfileConfigMapping *new_config, GError **error);
+void gbf_mkfile_project_set_target_config (GbfMkfileProject *project, const gchar *target_id, GbfMkfileConfigMapping *new_config, GError **error);
+
+G_END_DECLS
+
+#endif /* _GBF_MKFILE_PROJECT_H_ */

Added: trunk/plugins/gbf-mkfile/gbf-mkfile-properties.c
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/gbf-mkfile-properties.c	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,428 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8; coding: utf-8 -*- */
+/* gbf-mkfile-properties.c
+ *
+ * Copyright (C) 2005  Eric Greveson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Eric Greveson
+ * Based on the Autotools GBF backend (libgbf-am) by
+ *   JP Rosevear
+ *   Dave Camp
+ *   Naba Kumar
+ *   Gustavo GirÃldez
+ */
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include "gbf-mkfile-config.h"
+#include "gbf-mkfile-properties.h"
+
+typedef enum {
+	GBF_MKFILE_CONFIG_LABEL,
+	GBF_MKFILE_CONFIG_ENTRY,
+	GBF_MKFILE_CONFIG_TEXT,
+	GBF_MKFILE_CONFIG_LIST,
+} GbfConfigPropertyType;
+
+static void
+on_property_entry_changed (GtkEntry *entry, GbfMkfileConfigValue *value)
+{
+	gbf_mkfile_config_value_set_string (value, gtk_entry_get_text (entry));
+}
+
+static void
+add_configure_property (GbfMkfileProject *project, GbfMkfileConfigMapping *config,
+			GbfConfigPropertyType prop_type,
+			const gchar *display_name, const gchar *direct_value,
+			const gchar *config_key, GtkWidget *table,
+			gint position)
+{
+	GtkWidget *label;
+	const gchar *value;
+	GtkWidget *widget;
+	GbfMkfileConfigValue *config_value = NULL;
+	
+	value = "";
+	if (direct_value)
+	{
+		value = direct_value;
+	} else {
+		config_value = gbf_mkfile_config_mapping_lookup (config,
+							     config_key);
+		if (!config_value) {
+			config_value = gbf_mkfile_config_value_new (GBF_MKFILE_TYPE_STRING);
+			gbf_mkfile_config_value_set_string (config_value, "");
+			gbf_mkfile_config_mapping_insert (config, config_key,
+						      config_value);
+		}
+		if (config_value && config_value->type == GBF_MKFILE_TYPE_STRING) {
+			const gchar *val_str;
+			val_str = gbf_mkfile_config_value_get_string (config_value);
+			if (val_str)
+				value = val_str;
+		}
+	}
+	
+	label = gtk_label_new (display_name);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, -1);
+	gtk_widget_show (label);
+	gtk_table_attach (GTK_TABLE (table), label, 0, 1, position, position+1,
+			  GTK_FILL, GTK_FILL, 5, 3);
+	switch (prop_type) {
+		case GBF_MKFILE_CONFIG_LABEL:
+			widget = gtk_label_new (value);
+			gtk_misc_set_alignment (GTK_MISC (widget), 0, -1);
+			break;
+		case GBF_MKFILE_CONFIG_ENTRY:
+			widget = gtk_entry_new ();
+			gtk_entry_set_text (GTK_ENTRY (widget), value);
+			if (config_value) {
+				g_signal_connect (widget, "changed",
+						  G_CALLBACK (on_property_entry_changed),
+						  config_value);
+			}
+			break;
+		default:
+			g_warning ("Should not reach here");
+			widget = gtk_label_new (_("Unknown"));
+			gtk_misc_set_alignment (GTK_MISC (widget), 0, -1);
+	}
+	gtk_widget_show (widget);
+	gtk_table_attach (GTK_TABLE (table), widget, 1, 2, position, position+1,
+			  GTK_FILL | GTK_EXPAND, GTK_FILL, 5, 3);
+}
+
+static void
+recursive_config_foreach_cb (const gchar *key, GbfMkfileConfigValue *value,
+			     gpointer user_data)
+{
+	GtkWidget *table;
+	GtkWidget *label;
+	GtkWidget *widget;
+	gint position;
+	
+	table = GTK_WIDGET (user_data);
+	position = g_list_length (GTK_TABLE (table)->children);
+	
+	label = gtk_label_new (key);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, -1);
+	gtk_widget_show (label);
+	gtk_table_attach (GTK_TABLE (table), label, 0, 1,
+			  position, position+1,
+			  GTK_FILL, GTK_FILL, 5, 3);
+		
+	if (value->type == GBF_MKFILE_TYPE_STRING) {
+		widget = gtk_entry_new ();
+		gtk_entry_set_text (GTK_ENTRY (widget),
+				    gbf_mkfile_config_value_get_string (value));
+		g_signal_connect (widget, "changed",
+				  G_CALLBACK (on_property_entry_changed),
+				  value);
+	} else if (value->type == GBF_MKFILE_TYPE_LIST) {
+		/* FIXME: */
+		widget = gtk_label_new ("FIXME");
+		gtk_misc_set_alignment (GTK_MISC (widget), 0, -1);
+	} else if (value->type == GBF_MKFILE_TYPE_MAPPING) {
+		/* FIXME: */
+		widget = gtk_label_new ("FIXME");
+		gtk_misc_set_alignment (GTK_MISC (widget), 0, -1);
+	} else {
+		g_warning ("Should not be here");
+		widget = gtk_label_new (_("Unknown"));
+		gtk_misc_set_alignment (GTK_MISC (widget), 0, -1);
+	}
+	gtk_widget_show (widget);
+	gtk_table_attach (GTK_TABLE (table), widget, 1, 2,
+			  position, position+1,
+			  GTK_FILL | GTK_EXPAND, GTK_FILL, 5, 3);
+}
+
+GtkWidget*
+gbf_mkfile_properties_get_widget (GbfMkfileProject *project, GError **error)
+{
+	GbfMkfileConfigMapping *config;
+	GtkWidget *table;
+	GError *err = NULL;
+	
+	g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (project), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+	
+	config = gbf_mkfile_project_get_config (project, &err);
+	if (err)
+	{
+		g_propagate_error (error, err);
+		return NULL;
+	}
+	table = gtk_table_new (7, 2, FALSE);
+	
+	/* Group name */
+	add_configure_property (project, config, GBF_MKFILE_CONFIG_LABEL,
+				_("Project:"), project->project_root_uri,
+				NULL, table, 0);
+	
+	gtk_widget_show_all (table);
+	return table;
+}
+
+static void
+on_group_widget_destroy (GtkWidget *wid, GtkWidget *table)
+{
+	GError *err = NULL;
+	
+	GbfMkfileProject *project = g_object_get_data (G_OBJECT (table), "__project");
+	GbfMkfileConfigMapping *new_config = g_object_get_data (G_OBJECT (table), "__config");
+	const gchar *group_id = g_object_get_data (G_OBJECT (table), "__group_id");
+	gbf_mkfile_project_set_group_config (project, group_id, new_config, &err);
+	if (err)
+	{
+		g_warning ("%s", err->message);
+		g_error_free (err);
+	}
+	g_object_unref (table);
+}
+
+GtkWidget*
+gbf_mkfile_properties_get_group_widget (GbfMkfileProject *project,
+				    const gchar *group_id,
+				    GError **error)
+{
+	GbfProjectGroup *group;
+	GbfMkfileConfigMapping *config;
+	GbfMkfileConfigValue *value;
+	GtkWidget *table;
+	GError *err = NULL;
+	
+	g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (project), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	group = gbf_project_get_group (GBF_PROJECT (project), group_id, &err);
+	if (err)
+	{
+		g_propagate_error (error, err);
+		return NULL;
+	}
+	config = gbf_mkfile_project_get_group_config (project, group_id, &err);
+	if (err)
+	{
+		g_propagate_error (error, err);
+		return NULL;
+	}
+	
+	g_return_val_if_fail (group != NULL, NULL);
+	g_return_val_if_fail (config != NULL, NULL);
+	
+	table = gtk_table_new (7, 2, FALSE);
+	g_object_ref (table);
+	g_object_set_data (G_OBJECT (table), "__project", project);
+	g_object_set_data_full (G_OBJECT (table), "__config", config,
+				(GDestroyNotify)gbf_mkfile_config_mapping_destroy);
+	g_object_set_data_full (G_OBJECT (table), "__group_id",
+				g_strdup (group_id),
+				(GDestroyNotify)g_free);
+	g_signal_connect (table, "destroy",
+			  G_CALLBACK (on_group_widget_destroy), table);
+	
+	/* Group name */
+	add_configure_property (project, config, GBF_MKFILE_CONFIG_LABEL,
+				_("Group name:"), group->name, NULL, table, 0);
+	/* Includes */
+	add_configure_property (project, config, GBF_MKFILE_CONFIG_ENTRY,
+				_("Includes:"), NULL, "includes",
+				table, 1);
+	/* Install directories */
+	value = gbf_mkfile_config_mapping_lookup (config, "installdirs");
+	if (value)
+	{
+		GtkWidget *table2, *frame, *lab;
+		char *text;
+		frame = gtk_frame_new ("");
+		
+		lab = gtk_frame_get_label_widget (GTK_FRAME(frame));
+		text = g_strdup_printf ("<b>%s</b>", _("Install directories:"));
+		gtk_label_set_markup (GTK_LABEL(lab), text);
+		g_free (text);
+		gtk_frame_set_shadow_type (GTK_FRAME (frame),
+					   GTK_SHADOW_NONE);
+		gtk_widget_show (frame);
+		gtk_table_attach (GTK_TABLE (table), frame, 0, 2, 2, 3,
+				  GTK_FILL | GTK_EXPAND, GTK_FILL, 5, 3);
+		table2 = gtk_table_new (0, 0, FALSE);
+		gtk_widget_show (table2);
+		gtk_container_set_border_width (GTK_CONTAINER (table2), 5);
+		gtk_container_add (GTK_CONTAINER(frame), table2);
+		gbf_mkfile_config_mapping_foreach (value->mapping,
+					       recursive_config_foreach_cb,
+					       table2);
+	}
+	gtk_widget_show_all (table);
+	gbf_project_group_free (group);
+	return table;
+}
+
+static void
+on_target_widget_destroy (GtkWidget *wid, GtkWidget *table)
+{
+	GError *err = NULL;
+	
+	GbfMkfileProject *project = g_object_get_data (G_OBJECT (table), "__project");
+	GbfMkfileConfigMapping *new_config = g_object_get_data (G_OBJECT (table), "__config");
+	const gchar *target_id = g_object_get_data (G_OBJECT (table), "__target_id");
+	gbf_mkfile_project_set_target_config (project, target_id, new_config, &err);
+	if (err)
+	{
+		g_warning ("%s", err->message);
+		g_error_free (err);
+	}
+	g_object_unref (table);
+}
+
+GtkWidget*
+gbf_mkfile_properties_get_target_widget (GbfMkfileProject *project,
+				     const gchar *target_id, GError **error)
+{
+	GbfProjectGroup *group;
+	GbfMkfileConfigMapping *group_config;
+	GbfProjectTarget *target;
+	GbfMkfileConfigMapping *config;
+	GbfMkfileConfigValue *value;
+	GbfMkfileConfigMapping *installdirs;
+	GbfMkfileConfigValue *installdirs_val, *dir_val;
+	GtkWidget *table;
+	GError *err = NULL;
+	
+	g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (project), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+	
+	target = gbf_project_get_target (GBF_PROJECT (project), target_id, &err);
+	if (err)
+	{
+		g_propagate_error (error, err);
+		return NULL;
+	}
+	config = gbf_mkfile_project_get_target_config (project, target_id, &err);
+	if (err)
+	{
+		g_propagate_error (error, err);
+		return NULL;
+	}
+	g_return_val_if_fail (target != NULL, NULL);
+	g_return_val_if_fail (config != NULL, NULL);
+	
+	group = gbf_project_get_group (GBF_PROJECT (project),
+				       target->group_id, NULL);
+	group_config = gbf_mkfile_project_get_group_config (project,
+							target->group_id,
+							NULL);
+	
+	table = gtk_table_new (7, 2, FALSE);
+	g_object_ref (table);
+	g_object_set_data (G_OBJECT (table), "__project", project);
+	g_object_set_data_full (G_OBJECT (table), "__config", config,
+				(GDestroyNotify)gbf_mkfile_config_mapping_destroy);
+	g_object_set_data_full (G_OBJECT (table), "__target_id",
+				g_strdup (target_id),
+				(GDestroyNotify)g_free);
+	g_signal_connect (table, "destroy",
+			  G_CALLBACK (on_target_widget_destroy), table);
+	
+	/* Target name */
+	add_configure_property (project, config, GBF_MKFILE_CONFIG_LABEL,
+				_("Target name:"), target->name, NULL, table, 0);
+	/* Target type */
+	add_configure_property (project, config, GBF_MKFILE_CONFIG_LABEL,
+				_("Type:"),
+				gbf_project_name_for_type (GBF_PROJECT (project),
+							   target->type),
+				NULL, table, 1);
+	/* Target group */
+	add_configure_property (project, config, GBF_MKFILE_CONFIG_LABEL,
+				_("Group:"), group->name,
+				NULL, table, 2);
+	
+	/* Target primary */
+	/* FIXME: Target config 'installdir' actually stores target primary,
+	 * and not what it really seems to mean, i.e the primary prefix it
+	 * belongs to. The actual install directory of a target is the
+	 * install directory of primary prefix it belongs to and is
+	 * configured in group properties.
+	 */
+	value = gbf_mkfile_config_mapping_lookup (config, "installdir");
+	installdirs_val = gbf_mkfile_config_mapping_lookup (group_config,
+							"installdirs");
+	if (installdirs_val)
+		installdirs = gbf_mkfile_config_value_get_mapping (installdirs_val);
+	
+	if (!value || !installdirs_val) {
+		add_configure_property (project, config, GBF_MKFILE_CONFIG_LABEL,
+					_("Install directory:"), NULL, "installdir",
+					table, 3);
+	} else {
+		const gchar *primary_prefix;
+		gchar *installdir;
+		
+		primary_prefix = gbf_mkfile_config_value_get_string (value);
+		installdirs = gbf_mkfile_config_value_get_mapping (installdirs_val);
+		dir_val = gbf_mkfile_config_mapping_lookup (installdirs,
+							primary_prefix);
+		if (dir_val)
+		{
+			installdir = g_strconcat (primary_prefix, " = ",
+						  gbf_mkfile_config_value_get_string (dir_val),
+						  NULL);
+			add_configure_property (project, config,
+						GBF_MKFILE_CONFIG_LABEL,
+						_("Install directory:"),
+						installdir, NULL,
+						table, 3);
+			g_free (installdir);
+		} else {
+			add_configure_property (project, config,
+						GBF_MKFILE_CONFIG_LABEL,
+						_("Install directory:"),
+						NULL, "installdir",
+						table, 3);
+		}
+	}
+
+	if (target->type && (strcmp (target->type, "program") == 0 ||
+			     strcmp (target->type, "shared_lib") == 0 ||
+			     strcmp (target->type, "static_lib") == 0)) {
+		/* LDFLAGS */
+		add_configure_property (project, config,
+					GBF_MKFILE_CONFIG_ENTRY,
+					_("Linker flags:"), NULL,
+					"ldflags", table, 4);
+		
+		/* LDADD */
+		add_configure_property (project, config,
+					GBF_MKFILE_CONFIG_ENTRY,
+					_("Libraries:"), NULL,
+					"ldadd", table, 5);
+		
+		/* DEPENDENCIES */
+		add_configure_property (project, config,
+					GBF_MKFILE_CONFIG_ENTRY,
+					_("Dependencies:"), NULL,
+					"explicit_deps", table, 6);
+	}
+	gtk_widget_show_all (table);
+	gbf_project_target_free (target);
+	return table;
+}

Added: trunk/plugins/gbf-mkfile/gbf-mkfile-properties.h
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/gbf-mkfile-properties.h	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,42 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8; coding: utf-8 -*- */
+/* gbf-mkfile-properties.h
+ *
+ * Copyright (C) 2005  Eric Greveson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Eric Greveson
+ * Based on the Autotools GBF backend (libgbf-am) by
+ *   JP Rosevear
+ *   Dave Camp
+ *   Naba Kumar
+ *   Gustavo GirÃldez
+ */
+
+#ifndef _GBF_MKFILE_PROPERTIES_H_
+#define _GBF_MKFILE_PROPERTIES_H_
+
+#include "gbf-mkfile-project.h"
+
+GtkWidget *gbf_mkfile_properties_get_widget (GbfMkfileProject *project, GError **error);
+GtkWidget *gbf_mkfile_properties_get_group_widget (GbfMkfileProject *project,
+					    const gchar *group_id,
+					    GError **error);
+GtkWidget *gbf_mkfile_properties_get_target_widget (GbfMkfileProject *project,
+						const gchar *target_id,
+						GError **error);
+
+#endif /* _GBF_MKFILE_PROPERTIES_H_ */

Added: trunk/plugins/gbf-mkfile/gbf-mkfile.plugin.in
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/gbf-mkfile.plugin.in	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,11 @@
+[Anjuta Plugin]
+_Name=Makefile backend
+_Description=Makefile backend for project manager
+Location=gbf-mkfile:GbfMkfilePlugin
+Icon=gbf-mkfile-plugin-48.png
+UserActivatable=no
+Interfaces=IAnjutaProjectBackend
+Dependencies=anjuta-project-manager:ProjectManagerPlugin
+
+[Project]
+Supported-Project-Types=make

Added: trunk/plugins/gbf-mkfile/plugin.c
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/plugin.c	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,123 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+    plugin.c
+    Copyright (C) 2008 SÃbastien Granjoux
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#define DEBUG
+#include <config.h>
+#include <libanjuta/anjuta-debug.h>
+#include <libanjuta/gbf-project.h>
+#include <libanjuta/interfaces/ianjuta-project-backend.h>
+
+#include "plugin.h"
+#include "gbf-mkfile-project.h"
+
+
+#define ICON_FILE "gbf-mkfile-plugin-48.png"
+
+/* AnjutaPlugin functions
+ *---------------------------------------------------------------------------*/
+
+static gboolean
+activate_plugin (AnjutaPlugin *plugin)
+{
+	DEBUG_PRINT ("GbfMkfilePlugin: Activating Gnome build makefile backend Plugin ...");
+	
+	return TRUE;
+}
+
+static gboolean
+deactivate_plugin (AnjutaPlugin *plugin)
+{
+	DEBUG_PRINT ("GbfMkfilePlugin: Deactivating Gnome build makefile backend Plugin ...");
+	return TRUE;
+}
+
+
+/* IAnjutaProjectBackend implementation
+ *---------------------------------------------------------------------------*/
+
+static GbfProject*
+iproject_backend_new_project (IAnjutaProjectBackend* backend, GError** err)
+{
+	GbfProject *project;
+	
+	project = gbf_mkfile_project_new ();
+		
+	return project;
+}
+
+static void
+iproject_backend_iface_init(IAnjutaProjectBackendIface *iface)
+{
+	iface->new_project = iproject_backend_new_project;
+}
+
+/* GObject functions
+ *---------------------------------------------------------------------------*/
+
+/* Used in dispose and finalize */
+static gpointer parent_class;
+
+static void
+gbf_mkfile_plugin_instance_init (GObject *obj)
+{
+}
+
+/* dispose is used to unref object created with instance_init */
+
+static void
+dispose (GObject *obj)
+{
+	G_OBJECT_CLASS (parent_class)->dispose (obj);
+}
+
+/* finalize used to free object created with instance init */
+
+static void
+finalize (GObject *obj)
+{
+	G_OBJECT_CLASS (parent_class)->finalize (obj);
+}
+
+static void
+gbf_mkfile_plugin_class_init (GObjectClass *klass) 
+{
+	AnjutaPluginClass *plugin_class = ANJUTA_PLUGIN_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	plugin_class->activate = activate_plugin;
+	plugin_class->deactivate = deactivate_plugin;
+	klass->dispose = dispose;
+	klass->finalize = finalize;
+}
+
+/* AnjutaPlugin declaration
+ *---------------------------------------------------------------------------*/
+
+ANJUTA_PLUGIN_BEGIN (GbfMkfilePlugin, gbf_mkfile_plugin);
+ANJUTA_PLUGIN_ADD_INTERFACE (iproject_backend, IANJUTA_TYPE_PROJECT_BACKEND);
+ANJUTA_PLUGIN_END;
+
+G_MODULE_EXPORT void
+anjuta_glue_register_components (GTypeModule *module)
+{
+	gbf_mkfile_plugin_get_type (module);
+        gbf_mkfile_project_get_type (module);
+}                     

Added: trunk/plugins/gbf-mkfile/plugin.h
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/plugin.h	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,47 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+    plugin.h
+    Copyright (C) 2008 SÃbastien Granjoux
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#ifndef __PLUGIN_H__
+#define __PLUGIN_H__
+
+#include <libanjuta/anjuta-plugin.h>
+
+extern GType gbf_mkfile_plugin_get_type (GTypeModule *module);
+#define GBF_TYPE_PLUGIN_MKFILE         (gbf_mkfile_plugin_get_type (NULL))
+#define GBF_PLUGIN_MKFILE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GBF_TYPE_PLUGIN_MKFILE, GbfMkfilePlugin))
+#define GBF_PLUGIN_MKFILE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), GBF_TYPE_PLUGIN_MKFILE, GbfMkfilePluginClass))
+#define GBF_IS_PLUGIN_MKFILE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GBF_TYPE_PLUGIN_MKFILE))
+#define GBF_IS_PLUGIN_MKFILE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GBF_TYPE_PLUGIN_MKFILE))
+#define GBF_PLUGIN_MKFILE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GBF_TYPE_PLUGIN_MKFILE, GbfMkfilePluginClass))
+
+typedef struct _GbfMkfilePlugin GbfMkfilePlugin;
+typedef struct _GbfMkfilePluginClass GbfMkfilePluginClass;
+
+struct _GbfMkfilePlugin 
+{
+	AnjutaPlugin parent;
+};
+
+struct _GbfMkfilePluginClass
+{
+	AnjutaPluginClass parent_class;
+};
+
+#endif

Modified: trunk/plugins/project-import/project-import.c
==============================================================================
--- trunk/plugins/project-import/project-import.c	(original)
+++ trunk/plugins/project-import/project-import.c	Wed Dec 31 11:55:52 2008
@@ -19,12 +19,12 @@
 
 #include "project-import.h"
 #include <libanjuta/interfaces/ianjuta-file-loader.h>
+#include <libanjuta/interfaces/ianjuta-project-backend.h>
+#include <libanjuta/gbf-project.h>
 #include <libanjuta/anjuta-debug.h>
 
 #include <config.h>
 
-#include <gbf/gbf-backend.h>
-
 #define AM_PROJECT_FILE PACKAGE_DATA_DIR"/project/terminal/project.anjuta"
 #define MKFILE_PROJECT_FILE PACKAGE_DATA_DIR"/project/mkfile/project.anjuta"
 
@@ -51,9 +51,10 @@
 static void
 on_import_next(GtkAssistant *assistant, GtkWidget *page, ProjectImport *pi)
 {
-	GSList* l;
-	GbfBackend* backend = NULL;
-	GbfProject* proj;
+	AnjutaPluginManager *plugin_manager;
+	GList *descs = NULL;
+	GList *desc;
+	AnjutaPluginDescription *backend;
 	
 	if (page != pi->import_finish)
 		return;
@@ -67,41 +68,48 @@
 		return;
 	}
 	
-	gbf_backend_init();
-	
-	for (l = gbf_backend_get_backends (); l; l = l->next) {
-		backend = l->data;
-		if (!backend)
-		{
-			g_warning("Backend appears empty!");
-			continue;
-		}
+	plugin_manager = anjuta_shell_get_plugin_manager (ANJUTA_PLUGIN(pi)->shell, NULL);
+	descs = anjuta_plugin_manager_query (plugin_manager,
+										 "Anjuta Plugin",
+										 "Interfaces",
+										 "IAnjutaProjectBackend",
+										 NULL);	
+	for (desc = g_list_first (descs); desc != NULL; desc = g_list_next (desc)) {
+		IAnjutaProjectBackend *plugin;
+		gchar *location = NULL;
+		GbfProject* proj;	
 		
+		backend = (AnjutaPluginDescription *)desc->data;
+		anjuta_plugin_description_get_string (backend, "Anjuta Plugin", "Location", &location);
+		plugin = (IAnjutaProjectBackend *)anjuta_plugin_manager_get_plugin_by_id (plugin_manager, location);
+		g_free (location);
+
 		/* Probe the backend to find out if the project directory is OK */
 		/* If probe() returns TRUE then we have a valid backend */
 		
-		proj = gbf_backend_new_project(backend->id);
+		proj= ianjuta_project_backend_new_project (plugin, NULL);
 		if (proj)
 		{
-			if (gbf_project_probe(proj, path, NULL))
+			if (gbf_project_probe (proj, path, NULL))
 			{
 				/* This is a valid backend for this root directory */
 				/* FIXME: Possibility of more than one valid backend? */
-				g_object_unref(proj);
 				break;
 			}
-			g_object_unref(proj);
+			g_object_unref (proj);
 		}
+		plugin = NULL;
 		backend = NULL;
 	}
-
+	g_list_free (descs);
+	
 	if (!backend)
 	{
 		gchar* message_text =
 		g_strdup_printf(_("Could not find a valid project backend for the "
 						  "directory given (%s). Please select a different "
 						  "directory, or try upgrading to a newer version of "
-						  "the Gnome Build Framework."), path);
+						  "Anjuta."), path);
 		
 		GtkDialog* message = 
 		GTK_DIALOG(gtk_message_dialog_new(GTK_WINDOW(pi->assistant),
@@ -125,11 +133,17 @@
 	}
 	
 	gchar* summary;
+	gchar* type;
 	
+	if (!anjuta_plugin_description_get_string (backend, "Project", "Supported-Project-Types", &type))
+	{
+		type = g_strdup ("unknown");
+	}
 	summary = g_strdup_printf(_("Project name: %s\n"
 								"Project type: %s\n"
 								"Project path: %s\n"),
-								name, backend->name, path);
+								name, type, path);
+		
 	gtk_label_set_text (GTK_LABEL (pi->import_finish),
 			    summary);
 
@@ -142,7 +156,7 @@
 	
 	if (pi->backend_id)
 		g_free(pi->backend_id);
-	pi->backend_id = g_strdup(backend->id);
+	pi->backend_id = type;
 	g_free (path);
 }
 
@@ -402,9 +416,9 @@
 	take a default project file. */
 	
 	GFile* source_file;
-	if (!strcmp (pi->backend_id, "gbf-am:GbfAmProject"))
+	if (!strcmp (pi->backend_id, "automake"))
 		source_file = g_file_new_for_path (AM_PROJECT_FILE);
-	else if (!strcmp (pi->backend_id, "gbf-mkfile:GbfMkfileProject"))
+	else if (!strcmp (pi->backend_id, "make"))
 		source_file = g_file_new_for_path (MKFILE_PROJECT_FILE);
 	else
 	{

Modified: trunk/plugins/project-manager/Makefile.am
==============================================================================
--- trunk/plugins/project-manager/Makefile.am	(original)
+++ trunk/plugins/project-manager/Makefile.am	Wed Dec 31 11:55:52 2008
@@ -2,11 +2,17 @@
 project_uidir = $(anjuta_ui_dir)
 project_ui_DATA = anjuta-project-manager.ui
 
+# Plugin glade file
+project_gladedir = $(anjuta_glade_dir)
+project_glade_DATA = create_dialogs.glade
+
 # Plugin Icon file
 project_pixmapsdir = $(anjuta_image_dir)
 project_pixmaps_DATA = \
 	anjuta-project-manager-plugin.svg \
-	anjuta-project-manager-plugin-48.png
+	anjuta-project-manager-plugin-48.png \
+	gbf-build.png \
+	gbf-install.png
 
 # Plugin description file
 plugin_in_files = anjuta-project-manager.plugin.in
@@ -19,8 +25,8 @@
 AM_CPPFLAGS = \
 	$(WARN_CFLAGS) \
 	$(DEPRECATED_FLAGS) \
-	$(PLUGIN_GNOMEBUILD_CFLAGS) \
-	$(LIBANJUTA_CFLAGS)
+	$(LIBANJUTA_CFLAGS) \
+	-DG_LOG_DOMAIN=\"libanjuta-project-manager\"
 
 # Where to install the plugin
 plugindir = $(anjuta_plugin_dir)
@@ -31,13 +37,20 @@
 # Plugin sources
 libanjuta_project_manager_la_SOURCES = \
 	plugin.c \
-	plugin.h
+	plugin.h \
+	gbf-tree-data.h		\
+	gbf-tree-data.c		\
+	gbf-project-model.h	\
+	gbf-project-model.c	\
+	gbf-project-view.h	\
+	gbf-project-view.c	\
+	gbf-project-util.h	\
+	gbf-project-util.c
 
 libanjuta_project_manager_la_LDFLAGS = $(ANJUTA_PLUGIN_LDFLAGS)
 
 # Plugin dependencies
 libanjuta_project_manager_la_LIBADD = \
-	$(PLUGIN_GNOMEBUILD_LIBS) \
 	$(GNOME_UI_LIBS) \
 	$(LIBANJUTA_LIBS)
 
@@ -46,3 +59,7 @@
 	$(project_plugin_DATA) \
 	$(project_ui_DATA) \
 	$(project_pixmaps_DATA)
+
+DISTCLEANFILES = \
+	$(project_plugin_DATA) \
+	$(project_in_files)

Added: trunk/plugins/project-manager/create_dialogs.glade
==============================================================================
--- (empty file)
+++ trunk/plugins/project-manager/create_dialogs.glade	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,468 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--*- mode: xml -*-->
+<glade-interface>
+  <widget class="GtkDialog" id="new_group_dialog">
+    <property name="visible">True</property>
+    <property name="can_default">True</property>
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">New Group</property>
+    <property name="modal">True</property>
+    <property name="default_width">400</property>
+    <property name="default_height">450</property>
+    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+    <property name="has_separator">False</property>
+    <child internal-child="vbox">
+      <widget class="GtkVBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <widget class="GtkVBox" id="vbox1">
+            <property name="visible">True</property>
+            <property name="border_width">5</property>
+            <property name="spacing">12</property>
+            <child>
+              <widget class="GtkHBox" id="hbox1">
+                <property name="visible">True</property>
+                <property name="spacing">6</property>
+                <child>
+                  <widget class="GtkLabel" id="label1">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">_Group name:</property>
+                    <property name="use_underline">True</property>
+                    <property name="mnemonic_widget">group_name_entry</property>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkEntry" id="group_name_entry">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="invisible_char">*</property>
+                    <property name="activates_default">True</property>
+                    <signal name="changed" handler="on_group_name_entry_changed"/>
+                  </widget>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkVBox" id="vbox2">
+                <property name="visible">True</property>
+                <property name="spacing">6</property>
+                <child>
+                  <widget class="GtkLabel" id="label2">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Specify _where to create the group:</property>
+                    <property name="use_underline">True</property>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkScrolledWindow" id="groups_ph">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                    <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                    <property name="shadow_type">GTK_SHADOW_IN</property>
+                    <child>
+                      <widget class="Custom" id="groups_view">
+                        <property name="visible">True</property>
+                        <property name="string1">GbfProjectView</property>
+                      </widget>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <widget class="GtkHButtonBox" id="dialog-action_area1">
+            <property name="visible">True</property>
+            <property name="can_default">True</property>
+            <property name="layout_style">GTK_BUTTONBOX_END</property>
+            <child>
+              <widget class="GtkButton" id="cancel_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="label">gtk-cancel</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">-6</property>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkButton" id="ok_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="has_default">True</property>
+                <property name="label">gtk-add</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">-5</property>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">GTK_PACK_END</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+  <widget class="GtkDialog" id="new_target_dialog">
+    <property name="visible">True</property>
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">New Target</property>
+    <property name="modal">True</property>
+    <property name="default_width">400</property>
+    <property name="default_height">450</property>
+    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+    <property name="has_separator">False</property>
+    <child internal-child="vbox">
+      <widget class="GtkVBox" id="vbox3">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <widget class="GtkVBox" id="vbox4">
+            <property name="visible">True</property>
+            <property name="border_width">5</property>
+            <property name="spacing">12</property>
+            <child>
+              <widget class="GtkTable" id="table1">
+                <property name="visible">True</property>
+                <property name="n_rows">2</property>
+                <property name="n_columns">2</property>
+                <property name="column_spacing">6</property>
+                <property name="row_spacing">6</property>
+                <child>
+                  <widget class="Custom" id="target_type_combo">
+                    <property name="visible">True</property>
+                    <property name="string1">GtkComboBox</property>
+                    <accessibility>
+                      <atkproperty name="AtkObject::accessible_name" translatable="yes">TargetTypes</atkproperty>
+                    </accessibility>
+                  </widget>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                    <property name="x_options">GTK_FILL</property>
+                    <property name="y_options">GTK_FILL</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="label5">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Target _type:</property>
+                    <property name="use_underline">True</property>
+                  </widget>
+                  <packing>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                    <property name="x_options">GTK_FILL</property>
+                    <property name="y_options"></property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkEntry" id="target_name_entry">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="invisible_char">*</property>
+                    <property name="activates_default">True</property>
+                  </widget>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="y_options"></property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="label3">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Target _name:</property>
+                    <property name="use_underline">True</property>
+                    <property name="mnemonic_widget">target_name_entry</property>
+                  </widget>
+                  <packing>
+                    <property name="x_options">GTK_FILL</property>
+                    <property name="y_options"></property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkVBox" id="vbox5">
+                <property name="visible">True</property>
+                <property name="spacing">6</property>
+                <child>
+                  <widget class="GtkLabel" id="label4">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Specify _where to create the target:</property>
+                    <property name="use_underline">True</property>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkScrolledWindow" id="groups_ph">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                    <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                    <property name="shadow_type">GTK_SHADOW_IN</property>
+                    <child>
+                      <widget class="Custom" id="groups_view">
+                        <property name="visible">True</property>
+                        <property name="string1">GbfProjectView</property>
+                      </widget>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <widget class="GtkHButtonBox" id="hbuttonbox1">
+            <property name="visible">True</property>
+            <property name="layout_style">GTK_BUTTONBOX_END</property>
+            <child>
+              <widget class="GtkButton" id="button2">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="label">gtk-cancel</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">-6</property>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkButton" id="ok_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="has_default">True</property>
+                <property name="label">gtk-add</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">-5</property>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">GTK_PACK_END</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+  <widget class="GtkDialog" id="add_source_dialog">
+    <property name="visible">True</property>
+    <property name="can_default">True</property>
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">Add Source</property>
+    <property name="modal">True</property>
+    <property name="default_width">400</property>
+    <property name="default_height">550</property>
+    <property name="destroy_with_parent">True</property>
+    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+    <property name="has_separator">False</property>
+    <child internal-child="vbox">
+      <widget class="GtkVBox" id="vbox6">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <widget class="GtkVBox" id="vbox7">
+            <property name="visible">True</property>
+            <property name="border_width">5</property>
+            <property name="spacing">12</property>
+            <child>
+              <widget class="GtkVBox" id="vbox8">
+                <property name="visible">True</property>
+                <property name="spacing">6</property>
+                <child>
+                  <widget class="GtkLabel" id="label8">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Select the _target for the new source files:</property>
+                    <property name="use_underline">True</property>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkScrolledWindow" id="targets_ph">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                    <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                    <property name="shadow_type">GTK_SHADOW_IN</property>
+                    <child>
+                      <widget class="Custom" id="targets_view">
+                        <property name="visible">True</property>
+                        <property name="string1">GbfProjectView</property>
+                      </widget>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkTable" id="table2">
+                <property name="visible">True</property>
+                <property name="n_rows">2</property>
+                <property name="n_columns">2</property>
+                <child>
+                  <widget class="GtkScrolledWindow" id="scrolledwindow1">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                    <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                    <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
+                    <child>
+                      <widget class="GtkTreeView" id="source_file_tree">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="headers_visible">False</property>
+                        <property name="fixed_height_mode">True</property>
+                      </widget>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="right_attach">2</property>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                    <property name="x_padding">5</property>
+                    <property name="y_padding">5</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkButton" id="browse_button">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="label" translatable="yes">_Select file to add...</property>
+                    <property name="use_underline">True</property>
+                  </widget>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="x_options"></property>
+                    <property name="y_options"></property>
+                    <property name="x_padding">5</property>
+                    <property name="y_padding">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="label9">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Source files:</property>
+                  </widget>
+                  <packing>
+                    <property name="y_options"></property>
+                    <property name="x_padding">5</property>
+                    <property name="y_padding">2</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <widget class="GtkHButtonBox" id="hbuttonbox2">
+            <property name="visible">True</property>
+            <property name="layout_style">GTK_BUTTONBOX_END</property>
+            <child>
+              <widget class="GtkButton" id="button4">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="label">gtk-cancel</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">-6</property>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkButton" id="ok_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="has_default">True</property>
+                <property name="label">gtk-add</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">-5</property>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">GTK_PACK_END</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+</glade-interface>

Added: trunk/plugins/project-manager/gbf-project-model.c
==============================================================================
--- (empty file)
+++ trunk/plugins/project-manager/gbf-project-model.c	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,957 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* 
+ * Copyright (C) 2002 Dave Camp
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA. 
+ * 
+ * Authors: Dave Camp <dave ximian com>
+ *          Gustavo Giráez <gustavo giraldez gmx net>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <libgnomevfs/gnome-vfs-uri.h>
+
+#include "gbf-project-model.h"
+
+
+struct _GbfProjectModelPrivate {
+	GbfProject          *proj;
+	gulong               project_updated_handler;
+	
+	GtkTreeRowReference *root_row;
+	GList               *shortcuts;
+
+	GbfTreeData         *empty_node;
+};
+
+enum {
+	PROP_NONE,
+	PROP_PROJECT
+};
+
+
+/* Function prototypes ------------- */
+
+static void     gbf_project_model_class_init         (GbfProjectModelClass   *klass);
+static void     gbf_project_model_instance_init      (GbfProjectModel        *tree);
+static void     gbf_project_model_drag_source_init   (GtkTreeDragSourceIface *iface);
+static void     gbf_project_model_drag_dest_init     (GtkTreeDragDestIface   *iface);
+
+static void     load_project                         (GbfProjectModel        *model,
+						      GbfProject             *proj);
+static void     insert_empty_node                    (GbfProjectModel        *model);
+static void     unload_project                       (GbfProjectModel        *model);
+
+static gint     default_sort_func                    (GtkTreeModel           *model,
+						      GtkTreeIter            *iter_a,
+						      GtkTreeIter            *iter_b,
+						      gpointer                user_data);
+
+/* DND functions */
+
+static gboolean row_draggable                        (GtkTreeDragSource      *drag_source,
+						      GtkTreePath            *path);
+
+static gboolean drag_data_delete                     (GtkTreeDragSource      *drag_source,
+						      GtkTreePath            *path);
+
+static gboolean drag_data_received                   (GtkTreeDragDest        *drag_dest,
+						      GtkTreePath            *dest,
+						      GtkSelectionData       *selection_data);
+
+static gboolean row_drop_possible                    (GtkTreeDragDest        *drag_dest,
+						      GtkTreePath            *dest_path,
+						      GtkSelectionData       *selection_data);
+
+
+static GtkTreeStoreClass *parent_class = NULL;
+
+
+/* Implementation ---------------- */
+
+/* Type & interfaces initialization */
+
+static void
+gbf_project_model_drag_source_init (GtkTreeDragSourceIface *iface)
+{
+	iface->row_draggable = row_draggable;
+	iface->drag_data_delete = drag_data_delete;
+}
+
+static void
+gbf_project_model_drag_dest_init (GtkTreeDragDestIface *iface)
+{
+	iface->drag_data_received = drag_data_received;
+	iface->row_drop_possible = row_drop_possible;
+}
+
+static void
+gbf_project_model_class_init_trampoline (gpointer klass,
+					 gpointer data)
+{
+	parent_class = g_type_class_ref (GTK_TYPE_TREE_STORE);
+	gbf_project_model_class_init (klass);
+}
+
+GType
+gbf_project_model_get_type (void)
+{
+	static GType object_type = 0;
+	if (object_type == 0) {
+		static const GTypeInfo object_info = {
+		    sizeof (GbfProjectModelClass),
+		    NULL,		/* base_init */
+		    NULL,		/* base_finalize */
+		    gbf_project_model_class_init_trampoline,
+		    NULL,		/* class_finalize */
+		    NULL,               /* class_data */
+		    sizeof (GbfProjectModel),
+		    0,                  /* n_preallocs */
+		    (GInstanceInitFunc) gbf_project_model_instance_init
+		};
+		static const GInterfaceInfo drag_source_info = {
+			(GInterfaceInitFunc) gbf_project_model_drag_source_init,
+			NULL,
+			NULL
+		};
+		static const GInterfaceInfo drag_dest_info = {
+			(GInterfaceInitFunc) gbf_project_model_drag_dest_init,
+			NULL,
+			NULL
+		};
+
+		object_type = g_type_register_static (
+			GTK_TYPE_TREE_STORE, "GbfProjectModel",
+			&object_info, 0);
+
+		g_type_add_interface_static (object_type,
+					     GTK_TYPE_TREE_DRAG_SOURCE,
+					     &drag_source_info);
+
+		g_type_add_interface_static (object_type,
+					     GTK_TYPE_TREE_DRAG_DEST,
+					     &drag_dest_info);
+	}
+	return object_type;
+}
+
+static void 
+get_property (GObject    *object,
+	      guint       prop_id,
+	      GValue     *value,
+	      GParamSpec *pspec)
+{
+        GbfProjectModel *model = GBF_PROJECT_MODEL (object);
+        
+        switch (prop_id) {
+        case PROP_PROJECT:
+                g_value_set_pointer (value, model->priv->proj);
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void 
+set_property (GObject      *object,
+	      guint         prop_id,
+	      const GValue *value,
+	      GParamSpec   *pspec)
+{
+        GbfProjectModel *model = GBF_PROJECT_MODEL (object);
+
+        switch (prop_id) {
+        case PROP_PROJECT:
+		gbf_project_model_set_project (model, g_value_get_pointer (value));
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }   
+}
+
+static void
+dispose (GObject *obj)
+{
+	GbfProjectModel *model = GBF_PROJECT_MODEL (obj);
+
+	if (model->priv->empty_node) {
+		gbf_tree_data_free (model->priv->empty_node);
+		model->priv->empty_node = NULL;
+	}
+	
+	if (model->priv->proj) {
+		unload_project (model);
+	}
+
+	G_OBJECT_CLASS (parent_class)->finalize (obj);
+}
+
+static void
+finalize (GObject *obj)
+{
+	GbfProjectModel *model = GBF_PROJECT_MODEL (obj);
+
+	g_free (model->priv);
+
+	G_OBJECT_CLASS (parent_class)->dispose (obj);
+}
+
+static void 
+gbf_project_model_class_init (GbfProjectModelClass *klass)
+{
+	parent_class = g_type_class_peek_parent (klass);
+
+	G_OBJECT_CLASS (klass)->dispose = dispose;
+	G_OBJECT_CLASS (klass)->finalize = finalize;
+	G_OBJECT_CLASS (klass)->get_property = get_property;
+	G_OBJECT_CLASS (klass)->set_property = set_property;
+
+	g_object_class_install_property 
+                (G_OBJECT_CLASS (klass), PROP_PROJECT,
+                 g_param_spec_pointer ("project", 
+                                       _("Project"),
+                                       _("GbfProject Object"),
+                                       G_PARAM_READWRITE));
+}
+
+static void
+gbf_project_model_instance_init (GbfProjectModel *model)
+{
+	static GType types [GBF_PROJECT_MODEL_NUM_COLUMNS];
+
+	types [GBF_PROJECT_MODEL_COLUMN_DATA] = GBF_TYPE_TREE_DATA;
+
+	gtk_tree_store_set_column_types (GTK_TREE_STORE (model),
+					 GBF_PROJECT_MODEL_NUM_COLUMNS,
+					 types);
+
+	model->priv = g_new0 (GbfProjectModelPrivate, 1);
+
+	model->priv->empty_node = gbf_tree_data_new_string (_("No project loaded"));
+	/* sorting function */
+	gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (model),
+						 default_sort_func,
+						 NULL, NULL);
+	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
+					      GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
+					      GTK_SORT_ASCENDING);
+
+	insert_empty_node (model);
+}
+
+/* Model data functions ------------ */
+
+static gint 
+default_sort_func (GtkTreeModel *model,
+		   GtkTreeIter  *iter_a,
+		   GtkTreeIter  *iter_b,
+		   gpointer      user_data)
+{
+	GbfTreeData *data_a, *data_b;
+	gint retval = 0;
+	
+	gtk_tree_model_get (model, iter_a,
+			    GBF_PROJECT_MODEL_COLUMN_DATA, &data_a,
+			    -1);
+	gtk_tree_model_get (model, iter_b,
+			    GBF_PROJECT_MODEL_COLUMN_DATA, &data_b,
+			    -1);
+
+	if (data_a->is_shortcut && data_b->is_shortcut) {
+		GList *l;
+		
+		/* special case: the order of shortcuts is
+		 * user customizable */
+		for (l = GBF_PROJECT_MODEL (model)->priv->shortcuts; l; l = l->next) {
+			if (!strcmp (l->data, data_a->id)) {
+				/* a comes first */
+				retval = -1;
+				break;
+			}
+			else if (!strcmp (l->data, data_b->id)) {
+				/* b comes first */
+				retval = 1;
+				break;
+			}
+		}
+		
+	} else if (data_a->is_shortcut && !data_b->is_shortcut) {
+		retval = -1;
+	 
+	} else if (!data_a->is_shortcut && data_b->is_shortcut) {
+		retval = 1;
+	 
+	} else if (data_a->type == data_b->type) {
+		retval = strcmp (data_a->name, data_b->name);
+
+	} else {
+		/* assume a->b and check for the opposite cases */
+		retval = -1;
+		if (data_a->type == GBF_TREE_NODE_TARGET &&
+		    data_b->type == GBF_TREE_NODE_GROUP) {
+			retval = 1;
+		}
+	}
+	
+	gbf_tree_data_free (data_a);
+	gbf_tree_data_free (data_b);
+	
+	return retval;
+}
+
+
+static void 
+add_source (GbfProjectModel *model,
+	    const gchar     *source_id,
+	    GtkTreeIter     *parent)
+{
+	GbfProjectTargetSource *source;
+	GtkTreeIter iter;
+	GbfTreeData *data;
+	
+	source = gbf_project_get_source (model->priv->proj, source_id, NULL);
+	if (!source)
+		return;
+	
+	data = gbf_tree_data_new_source (model->priv->proj, source);
+	gtk_tree_store_append (GTK_TREE_STORE (model), &iter, parent);
+	gtk_tree_store_set (GTK_TREE_STORE (model), &iter, 
+			    GBF_PROJECT_MODEL_COLUMN_DATA, data,
+			    -1);
+	gbf_tree_data_free (data);
+
+	gbf_project_target_source_free (source);
+}
+
+static GtkTreePath *
+find_shortcut (GbfProjectModel *model, const gchar *target_id)
+{
+	GList *l;
+	gint i;
+	
+	for (l = model->priv->shortcuts, i = 0; l; l = l->next, i++) {
+		if (!strcmp (target_id, l->data))
+			return gtk_tree_path_new_from_indices (i, -1);
+	}
+	return NULL;
+}
+
+static void
+remove_shortcut (GbfProjectModel *model, const gchar *target_id)
+{
+	GList *l;
+	
+	for (l = model->priv->shortcuts; l; l = l->next) {
+		if (!strcmp (target_id, l->data)) {
+			g_free (l->data);
+			model->priv->shortcuts = g_list_delete_link (
+				model->priv->shortcuts, l);
+			break;
+		}
+	}
+}
+
+static void 
+add_target_shortcut (GbfProjectModel *model,
+		     const gchar     *target_id,
+		     GtkTreePath     *before_path)
+{
+	GList *l;
+	GtkTreeIter iter, sibling;
+	GbfProjectTarget *target;
+	GtkTreePath *root_path, *old_path;
+	gint *path_indices, i;
+	GbfTreeData *data;
+	
+	target = gbf_project_get_target (model->priv->proj, target_id, NULL);
+	if (!target)
+		return;
+
+	root_path = gtk_tree_row_reference_get_path (model->priv->root_row);
+	/* check before_path */
+	if (!before_path ||
+	    gtk_tree_path_get_depth (before_path) > 1 ||
+	    gtk_tree_path_compare (before_path, root_path) > 0) {
+		before_path = root_path;
+	}
+		
+	/* get the tree iter for the row before which to insert the shortcut */
+	if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &sibling, before_path)) {
+		gtk_tree_path_free (root_path);
+		return;
+	}
+	
+	path_indices = gtk_tree_path_get_indices (before_path);
+	i = path_indices [0];
+
+	/* remove the old shortcut to make sorting actually work */
+	old_path = find_shortcut (model, target_id);
+	if (old_path) {
+		remove_shortcut (model, target_id);
+		if (gtk_tree_path_compare (old_path, before_path) < 0) {
+			/* adjust shortcut insert position if the old
+			 * index was before the new site */
+			i--;
+		}
+		gtk_tree_path_free (old_path);
+	}
+			
+	/* add entry to the shortcut list */
+	model->priv->shortcuts = g_list_insert (model->priv->shortcuts,
+						g_strdup (target->id), i);
+	
+	data = gbf_tree_data_new_target (model->priv->proj, target);
+	data->is_shortcut = TRUE;
+	gtk_tree_store_insert_before (GTK_TREE_STORE (model), &iter, NULL, &sibling);
+	gtk_tree_store_set (GTK_TREE_STORE (model), &iter, 
+			    GBF_PROJECT_MODEL_COLUMN_DATA, data,
+			    -1);
+	gbf_tree_data_free (data);
+	
+	/* add sources */
+	for (l = target->sources; l; l = l->next)
+		add_source (model, l->data, &iter);
+
+	gtk_tree_path_free (root_path);
+	gbf_project_target_free (target);
+}
+
+static void 
+add_target (GbfProjectModel *model,
+	    const gchar     *target_id,
+	    GtkTreeIter     *parent)
+{
+	GbfProjectTarget *target;
+	GList *l;
+	GtkTreeIter iter;	
+	GbfTreeData *data;
+	
+	target = gbf_project_get_target (model->priv->proj, target_id, NULL);
+	if (!target)
+		return;
+	
+	data = gbf_tree_data_new_target (model->priv->proj, target);
+	gtk_tree_store_append (GTK_TREE_STORE (model), &iter, parent);
+	gtk_tree_store_set (GTK_TREE_STORE (model), &iter, 
+			    GBF_PROJECT_MODEL_COLUMN_DATA, data,
+			    -1);
+	gbf_tree_data_free (data);
+	
+	/* add sources */
+	for (l = target->sources; l; l = l->next)
+		add_source (model, l->data, &iter);
+
+	/* add a shortcut to the target if the target's type is a primary */
+	/* FIXME: this shouldn't be here.  We would rather provide a
+	 * set of public functions to add/remove shortcuts to save
+	 * this information in the project metadata (when that's
+	 * implemented) */
+	if (!strcmp (target->type, "program") ||
+	    !strcmp (target->type, "shared_lib") ||
+	    !strcmp (target->type, "static_lib") ||
+	    !strcmp (target->type, "java") ||
+	    !strcmp (target->type, "python")) {
+		add_target_shortcut (model, target->id, NULL);
+	}
+
+	gbf_project_target_free (target);
+}
+
+static void
+update_target (GbfProjectModel *model, const gchar *target_id, GtkTreeIter *iter)
+{
+	GtkTreeModel *tree_model;
+	GbfProjectTarget *target;
+	GtkTreeIter child;
+	GList *node;
+	
+	tree_model = GTK_TREE_MODEL (model);
+	target = gbf_project_get_target (model->priv->proj, target_id, NULL);
+	if (!target)
+		return;
+	
+	/* update target data here */
+	
+	/* walk the tree target */
+	if (gtk_tree_model_iter_children (tree_model, &child, iter)) {
+		GbfTreeData *data;
+		gboolean valid = TRUE;
+		
+		while (valid) {
+			gtk_tree_model_get (tree_model, &child,
+					    GBF_PROJECT_MODEL_COLUMN_DATA, &data,
+					    -1);
+
+			/* find the iterating id in the target's sources */
+			if (data->id) {
+				node = g_list_find_custom (target->sources,
+							   data->id, (GCompareFunc) strcmp);
+				if (node) {
+					target->sources = g_list_delete_link (target->sources, node);
+					valid = gtk_tree_model_iter_next (tree_model, &child);
+				} else {
+					valid = gtk_tree_store_remove (GTK_TREE_STORE (model), &child);
+				}
+				gbf_tree_data_free (data);
+			}
+		}
+	}
+
+	/* add the remaining sources */
+	for (node = target->sources; node; node = node->next)
+		add_source (model, node->data, iter);
+	
+	gbf_project_target_free (target);
+}
+
+static void 
+add_target_group (GbfProjectModel *model,
+		  const gchar     *group_id,
+		  GtkTreeIter     *parent)
+{
+	GbfProjectGroup *group;
+	GtkTreeIter iter;
+	GList *l;
+	GbfTreeData *data;
+
+	group = gbf_project_get_group (model->priv->proj, group_id, NULL);
+	if (!group)
+		return;
+	
+	data = gbf_tree_data_new_group (model->priv->proj, group);
+	gtk_tree_store_append (GTK_TREE_STORE (model), &iter, parent);
+	gtk_tree_store_set (GTK_TREE_STORE (model), &iter, 
+			    GBF_PROJECT_MODEL_COLUMN_DATA, data,
+			    -1);
+	gbf_tree_data_free (data);
+
+	/* create root reference */
+	if (parent == NULL) {
+		GtkTreePath *root_path;
+
+		root_path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+		model->priv->root_row = gtk_tree_row_reference_new (
+			GTK_TREE_MODEL (model), root_path);
+		gtk_tree_path_free (root_path);
+	}
+
+	/* add groups ... */
+	for (l = group->groups; l; l = l->next)
+		add_target_group (model, l->data, &iter);
+
+	/* ... and targets */
+	for (l = group->targets; l; l = l->next)
+		add_target (model, l->data, &iter);
+
+	gbf_project_group_free (group);
+}
+
+static void
+update_group (GbfProjectModel *model, const gchar *group_id, GtkTreeIter *iter)
+{
+	GtkTreeModel *tree_model;
+	GbfProjectGroup *group;
+	GtkTreeIter child;
+	GList *node;
+	
+	tree_model = GTK_TREE_MODEL (model);
+	group = gbf_project_get_group (model->priv->proj, group_id, NULL);
+	
+	/* update group data. nothing to do here */
+
+	/* walk the tree group */
+	/* group can be NULL, but we iterate anyway to remove any
+	 * shortcuts the old group could have had */
+	if (gtk_tree_model_iter_children (tree_model, &child, iter)) {
+		GbfTreeData *data;
+		gboolean valid = TRUE;
+		
+		while (valid) {
+			gboolean remove_child = FALSE;
+			
+			gtk_tree_model_get (tree_model, &child,
+					    GBF_PROJECT_MODEL_COLUMN_DATA, &data,
+					    -1);
+
+			/* find the iterating id in the group's children */
+			if (data->type == GBF_TREE_NODE_GROUP) {
+				/* update recursively */
+				update_group (model, data->id, &child);
+				if (group && (node = g_list_find_custom (group->groups, data->id,
+									 (GCompareFunc) strcmp))) {
+					group->groups = g_list_delete_link (group->groups, node);
+				} else {
+					remove_child = TRUE;
+				}
+				
+			} else if (data->type == GBF_TREE_NODE_TARGET) {
+				GtkTreePath *shortcut;
+
+				if (group && (node = g_list_find_custom (group->targets,
+									 data->id,
+									 (GCompareFunc) strcmp))) {
+					group->targets = g_list_delete_link (group->targets, node);
+					/* update recursively */
+					update_target (model, data->id, &child);
+				} else {
+					remove_child = TRUE;
+				}
+
+				/* remove or update the shortcut if it previously existed */
+				shortcut = find_shortcut (model, data->id);
+				if (shortcut) {
+					GtkTreeIter tmp;
+					
+					if (remove_child)
+						remove_shortcut (model, data->id);
+
+					if (gtk_tree_model_get_iter (tree_model, &tmp, shortcut)) {
+						if (remove_child)
+							gtk_tree_store_remove (GTK_TREE_STORE (model), &tmp);
+						else
+							update_target (model, data->id, &tmp);
+					}
+					gtk_tree_path_free (shortcut);
+				}
+			}
+			
+			gbf_tree_data_free (data);
+			if (remove_child)
+				valid = gtk_tree_store_remove (GTK_TREE_STORE (model), &child);
+			else
+				valid = gtk_tree_model_iter_next (tree_model, &child);
+		};
+	}
+
+	if (group) {
+		/* add the remaining targets and groups */
+		for (node = group->groups; node; node = node->next)
+			add_target_group (model, node->data, iter);
+		
+		for (node = group->targets; node; node = node->next)
+			add_target (model, node->data, iter);
+	
+		gbf_project_group_free (group);
+	}
+}
+
+static void
+project_updated_cb (GbfProject *project, GbfProjectModel *model)
+{
+	GtkTreePath *path;
+	GtkTreeIter iter;
+
+	path = gtk_tree_row_reference_get_path (model->priv->root_row);
+	if (path && gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path))
+		update_group (model, "/", &iter);
+	else
+		add_target_group (model, "/", NULL);
+			
+	if (path)
+		gtk_tree_path_free (path);
+}
+
+static void
+load_project (GbfProjectModel *model, GbfProject *proj)
+{
+	model->priv->proj = proj;
+	g_object_ref (proj);
+
+	/* to get rid of the empty node */
+	gtk_tree_store_clear (GTK_TREE_STORE (model));
+
+	add_target_group (model, "/", NULL);
+
+	model->priv->project_updated_handler =
+		g_signal_connect (model->priv->proj, "project-updated",
+				  (GCallback) project_updated_cb, model);
+}
+
+static void 
+insert_empty_node (GbfProjectModel *model)
+{
+	GtkTreeIter iter;
+
+	gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL);
+	gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
+			    GBF_PROJECT_MODEL_COLUMN_DATA, model->priv->empty_node,
+			    -1);
+}
+
+static void
+unload_project (GbfProjectModel *model)
+{
+	if (model->priv->proj) {
+		gtk_tree_row_reference_free (model->priv->root_row);
+		model->priv->root_row = NULL;
+
+		gtk_tree_store_clear (GTK_TREE_STORE (model));
+
+		g_list_foreach (model->priv->shortcuts, (GFunc) g_free, NULL);
+		g_list_free (model->priv->shortcuts);
+		model->priv->shortcuts = NULL;
+		
+		g_signal_handler_disconnect (model->priv->proj,
+					     model->priv->project_updated_handler);
+		model->priv->project_updated_handler = 0;
+		g_object_unref (model->priv->proj);
+		model->priv->proj = NULL;
+
+		insert_empty_node (model);
+	}
+}
+
+static gboolean 
+recursive_find_id (GtkTreeModel    *model,
+		   GtkTreeIter     *iter,
+		   GbfTreeNodeType  type,
+		   const gchar     *id)
+{
+	GtkTreeIter tmp;
+	GbfTreeData *data;
+	gboolean retval = FALSE;
+
+	tmp = *iter;
+	
+	do {
+		GtkTreeIter child;
+		
+		gtk_tree_model_get (model, &tmp,
+				    GBF_PROJECT_MODEL_COLUMN_DATA, &data, -1);
+		if (data->type == type && !strcmp (id, data->id)) {
+			*iter = tmp;
+			retval = TRUE;
+		}
+		gbf_tree_data_free (data);
+		
+		if (gtk_tree_model_iter_children (model, &child, &tmp)) {
+			if (recursive_find_id (model, &child, type, id)) {
+				*iter = child;
+				retval = TRUE;
+			}
+		}
+				
+	} while (!retval && gtk_tree_model_iter_next (model, &tmp));
+
+	return retval;
+}
+
+gboolean 
+gbf_project_model_find_id (GbfProjectModel *model,
+			   GtkTreeIter     *iter,
+			   GbfTreeNodeType  type,
+			   const gchar     *id)
+{
+	GtkTreePath *root;
+	GtkTreeIter tmp_iter;
+	gboolean retval = FALSE;
+	
+	root = gbf_project_model_get_project_root (model);
+	if (!root)
+		return FALSE;
+
+	if (gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &tmp_iter, root)) {
+		if (recursive_find_id (GTK_TREE_MODEL (model), &tmp_iter, type, id)) {
+			retval = TRUE;
+			*iter = tmp_iter;
+		}
+	}
+	gtk_tree_path_free (root);
+	
+	return retval;
+}
+
+GbfProjectModel *
+gbf_project_model_new (GbfProject *project)
+{
+	return GBF_PROJECT_MODEL (g_object_new (GBF_TYPE_PROJECT_MODEL,
+						"project", project,
+						NULL));
+}
+
+void 
+gbf_project_model_set_project (GbfProjectModel *model, GbfProject *project)
+{
+	g_return_if_fail (model != NULL && GBF_IS_PROJECT_MODEL (model));
+	g_return_if_fail (project == NULL || GBF_IS_PROJECT (project));
+	
+	if (model->priv->proj)
+		unload_project (model);
+	
+	if (project)
+		load_project (model, project);
+}
+
+GbfProject *
+gbf_project_model_get_project (GbfProjectModel *model)
+{
+	g_return_val_if_fail (model != NULL && GBF_IS_PROJECT_MODEL (model), NULL);
+	
+	return model->priv->proj;
+}
+
+GtkTreePath *
+gbf_project_model_get_project_root (GbfProjectModel *model)
+{
+	GtkTreePath *path = NULL;
+	
+	g_return_val_if_fail (GBF_IS_PROJECT_MODEL (model), NULL);
+
+	if (model->priv->root_row)
+		path = gtk_tree_row_reference_get_path (model->priv->root_row);
+	
+	return path;
+}
+
+
+/* DND stuff ------------- */
+
+static gboolean
+row_draggable (GtkTreeDragSource *drag_source, GtkTreePath *path)
+{
+	GtkTreeIter iter;
+	GbfTreeData *data;
+	gboolean retval = FALSE;
+	
+	if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source), &iter, path))
+		return FALSE;
+
+	gtk_tree_model_get (GTK_TREE_MODEL (drag_source), &iter,
+			    GBF_PROJECT_MODEL_COLUMN_DATA, &data,
+			    -1);
+
+	if (data->is_shortcut) {
+		/* shortcuts can be moved */
+		retval = TRUE;
+
+	} else if (data->type == GBF_TREE_NODE_TARGET) {
+		GtkTreePath *found;
+		
+		/* don't allow duplicate shortcuts */
+		found = find_shortcut (GBF_PROJECT_MODEL (drag_source), data->id);
+		if (!found)
+			retval = TRUE;
+		else
+			gtk_tree_path_free (found);
+	}
+	gbf_tree_data_free (data);
+
+	return retval;
+}
+
+static gboolean 
+drag_data_delete (GtkTreeDragSource *drag_source, GtkTreePath *path)
+{
+	GtkTreeIter iter;
+	gboolean retval = FALSE;
+	
+	if (gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source),
+				     &iter, path)) {
+		GbfTreeData *data;
+
+		gtk_tree_model_get (GTK_TREE_MODEL (drag_source), &iter,
+				    GBF_PROJECT_MODEL_COLUMN_DATA, &data,
+				    -1);
+
+		if (data->is_shortcut) {
+			gtk_tree_store_remove (GTK_TREE_STORE (drag_source), &iter);
+			retval = TRUE;
+		}
+		gbf_tree_data_free (data);
+	}
+	
+	return retval;
+}
+
+static gboolean
+drag_data_received (GtkTreeDragDest  *drag_dest,
+		    GtkTreePath      *dest,
+		    GtkSelectionData *selection_data)
+{
+	GtkTreeModel *src_model = NULL;
+	GtkTreePath *src_path = NULL;
+	gboolean retval = FALSE;
+
+	g_return_val_if_fail (GBF_IS_PROJECT_MODEL (drag_dest), FALSE);
+
+	if (gtk_tree_get_row_drag_data (selection_data,
+					&src_model,
+					&src_path) &&
+	    src_model == GTK_TREE_MODEL (drag_dest)) {
+
+		GtkTreeIter iter;
+		GbfTreeData *data = NULL;
+		
+		if (gtk_tree_model_get_iter (src_model, &iter, src_path)) {
+			gtk_tree_model_get (src_model, &iter,
+					    GBF_PROJECT_MODEL_COLUMN_DATA, &data,
+					    -1);
+			if (data && data->id && data->type == GBF_TREE_NODE_TARGET) {
+				add_target_shortcut (GBF_PROJECT_MODEL (drag_dest),
+						     data->id, dest);
+				retval = TRUE;
+			}
+			gbf_tree_data_free (data);
+		}
+	}
+
+	if (src_path)
+		gtk_tree_path_free (src_path);
+
+	return retval;
+}
+
+static gboolean
+row_drop_possible (GtkTreeDragDest  *drag_dest,
+		   GtkTreePath      *dest_path,
+		   GtkSelectionData *selection_data)
+{
+	GtkTreePath *root_path;
+	GbfProjectModel *model;
+	gboolean retval = FALSE;
+	GtkTreeModel *src_model = NULL;
+  
+	g_return_val_if_fail (GBF_IS_PROJECT_MODEL (drag_dest), FALSE);
+
+	model = GBF_PROJECT_MODEL (drag_dest);
+
+	if (!gtk_tree_get_row_drag_data (selection_data,
+					 &src_model,
+					 NULL))
+		return FALSE;
+    
+	/* can only drag to ourselves and only new toplevel nodes will
+	 * be created */
+	if (src_model == GTK_TREE_MODEL (drag_dest) &&
+	    gtk_tree_path_get_depth (dest_path) == 1) {
+		root_path = gtk_tree_row_reference_get_path (model->priv->root_row);
+		if (gtk_tree_path_compare (dest_path, root_path) <= 0) {
+			retval = TRUE;
+		}
+		gtk_tree_path_free (root_path);
+	}
+
+	return retval;
+}

Added: trunk/plugins/project-manager/gbf-project-model.h
==============================================================================
--- (empty file)
+++ trunk/plugins/project-manager/gbf-project-model.h	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,68 @@
+/*  -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 
+ * Copyright (C) 2002 Dave Camp
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA. 
+ * 
+ * Author: Dave Camp <dave ximian com> 
+ */
+
+#ifndef GBF_PROJECT_MODEL_H
+#define GBF_PROJECT_MODEL_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <libanjuta/gbf-project.h>
+#include "gbf-tree-data.h"
+
+#define GBF_TYPE_PROJECT_MODEL            (gbf_project_model_get_type ())
+#define GBF_PROJECT_MODEL(obj)	          (GTK_CHECK_CAST ((obj), GBF_TYPE_PROJECT_MODEL, GbfProjectModel))
+#define GBF_PROJECT_MODEL_CLASS(klass)    (GTK_CHECK_CLASS_CAST ((klass), GBF_TYPE_PROJECT_MODEL, GbfProjectModelClass))
+#define GBF_IS_PROJECT_MODEL(obj)	      (GTK_CHECK_TYPE ((obj), GBF_TYPE_PROJECT_MODEL))
+#define GBF_IS_PROJECT_MODEL_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GBF_TYPE_PROJECT_MODEL))
+
+typedef struct _GbfProjectModel        GbfProjectModel;
+typedef struct _GbfProjectModelClass   GbfProjectModelClass;
+typedef struct _GbfProjectModelPrivate GbfProjectModelPrivate;
+
+enum {
+	GBF_PROJECT_MODEL_COLUMN_DATA,
+	GBF_PROJECT_MODEL_NUM_COLUMNS
+};
+
+struct _GbfProjectModel {
+	GtkTreeStore parent;
+	GbfProjectModelPrivate *priv;
+};
+
+struct _GbfProjectModelClass {
+	GtkTreeStoreClass parent_class;
+};
+
+GType            gbf_project_model_get_type          (void); 
+GbfProjectModel *gbf_project_model_new               (GbfProject      *project);
+
+void             gbf_project_model_set_project       (GbfProjectModel *model,
+                                                      GbfProject      *project);
+GbfProject      *gbf_project_model_get_project       (GbfProjectModel *model);
+
+GtkTreePath     *gbf_project_model_get_project_root  (GbfProjectModel *model);
+gboolean         gbf_project_model_find_id           (GbfProjectModel *model,
+                                                      GtkTreeIter     *iter,
+                                                      GbfTreeNodeType  type,
+                                                      const gchar     *id);
+
+#endif

Added: trunk/plugins/project-manager/gbf-project-util.c
==============================================================================
--- (empty file)
+++ trunk/plugins/project-manager/gbf-project-util.c	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,822 @@
+/*  -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4; coding: utf-8 -*-
+ * 
+ * Copyright (C) 2003 Gustavo GirÃldez
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA. 
+ * 
+ * Author: Gustavo GirÃldez <gustavo giraldez gmx net>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdarg.h>
+#include <glade/glade-xml.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <libgnomevfs/gnome-vfs-uri.h>
+#include <libgnomevfs/gnome-vfs-utils.h>
+#include <bonobo/bonobo-file-selector-util.h>
+
+#include "gbf-tree-data.h"
+#include "gbf-project-view.h"
+#include "gbf-project-model.h"
+#include "gbf-project-util.h"
+
+#define ICON_SIZE 16
+
+#define GLADE_FILE PACKAGE_DATA_DIR  "/glade/create_dialogs.glade"
+
+enum {
+    COLUMN_FILE,
+    COLUMN_URI,
+    N_COLUMNS
+};
+
+static GtkWidget *
+custom_widget_handler (GladeXML *xml,
+                       gchar    *func_name,
+                       gchar    *name,
+                       gchar    *string1,
+                       gchar    *string2,
+                       gint      int1,
+                       gint      int2,
+                       gpointer  user_data)
+{
+    GtkWidget *wid = NULL;
+
+    if (!strcmp (string1, "GtkComboBox")) {
+        wid = gtk_combo_box_new ();
+    } else if (!strcmp (string1, "GbfProjectView")) {
+        wid = gbf_project_view_new ();
+    } else {
+        g_warning ("Unknown custom widget type '%s'", string1);
+    }
+
+    return wid;
+}
+
+static GladeXML *
+load_interface (const gchar *top_widget)
+{
+    GladeXML *xml;
+
+    glade_set_custom_handler (custom_widget_handler, NULL);
+    xml = glade_xml_new (GLADE_FILE, top_widget, GETTEXT_PACKAGE);
+
+    if (!xml) {
+        g_warning ("%s", _("Couldn't load glade file"));
+        return NULL;
+    }
+    
+    return xml;
+}
+
+static gboolean
+groups_filter_fn (GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
+{
+    GbfTreeData *data = NULL;
+    gboolean retval = FALSE;
+
+    gtk_tree_model_get (model, iter,
+                        GBF_PROJECT_MODEL_COLUMN_DATA, &data, -1);
+    retval = (data && data->type == GBF_TREE_NODE_GROUP);
+    gbf_tree_data_free (data);
+    
+    return retval;
+}
+
+static void 
+setup_groups_treeview (GbfProjectModel *model,
+                       GtkWidget       *view,
+                       const gchar     *select_group)
+{
+    GtkTreeModel *filter;
+    GtkTreePath *path;
+    GtkTreeIter iter;
+    
+    g_return_if_fail (model != NULL);
+    g_return_if_fail (view != NULL && GBF_IS_PROJECT_VIEW (view));
+
+    filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (model), NULL);
+    gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter),
+                                            groups_filter_fn, NULL, NULL);
+    gtk_tree_view_set_model (GTK_TREE_VIEW (view), GTK_TREE_MODEL (filter));
+    g_object_unref (filter);
+    
+    /* select default group */
+    if (select_group && gbf_project_model_find_id (model, &iter,
+                                                   GBF_TREE_NODE_GROUP, select_group)) {
+        GtkTreeIter iter_filter;
+        
+        gtk_tree_model_filter_convert_child_iter_to_iter (
+            GTK_TREE_MODEL_FILTER (filter), &iter_filter, &iter);
+        path = gtk_tree_model_get_path (filter, &iter_filter);
+        gtk_tree_view_expand_to_path (GTK_TREE_VIEW (view), path);
+    } else {
+        GtkTreePath *root_path;
+
+        gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
+        root_path = gbf_project_model_get_project_root (model);
+        if (root_path) {
+            path = gtk_tree_model_filter_convert_child_path_to_path (
+                GTK_TREE_MODEL_FILTER (filter), root_path);
+            gtk_tree_path_free (root_path);
+        } else {
+            path = gtk_tree_path_new_first ();
+        }
+    }
+    
+    gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, NULL, FALSE);
+    gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), path, NULL,
+                                  TRUE, 0.5, 0.0);
+    gtk_tree_path_free (path);
+}
+
+static void
+error_dialog (GtkWindow *parent, const gchar *summary, const gchar *msg, ...)
+{
+    va_list ap;
+    gchar *tmp;
+    gchar *message;
+    GtkWidget *dialog;
+    
+    va_start (ap, msg);
+    tmp = g_strdup_vprintf (msg, ap);
+    va_end (ap);
+    
+    message = g_markup_printf_escaped ("<b>%s</b>\n\n%s", summary, tmp);
+    dialog = gtk_message_dialog_new_with_markup (parent,
+						 GTK_DIALOG_DESTROY_WITH_PARENT,
+						 GTK_MESSAGE_ERROR,
+						 GTK_BUTTONS_OK,
+						 "%s", message);
+    g_free (tmp);
+    g_free (message);
+    
+    gtk_dialog_run (GTK_DIALOG (dialog));
+    gtk_widget_destroy (dialog);
+}
+
+static void
+entry_changed_cb (GtkEditable *editable, gpointer user_data)
+{
+    GtkWidget *button = user_data;
+    gchar *text;
+
+    if (!button)
+        return;
+
+    text = gtk_editable_get_chars (editable, 0, -1);
+    if (strlen (text) > 0) {
+        gtk_widget_set_sensitive (button, TRUE);
+        gtk_widget_grab_default (button);
+    } else {
+        gtk_widget_set_sensitive (button, FALSE);
+    }
+    g_free (text);
+}
+
+gchar*
+gbf_project_util_new_group (GbfProjectModel *model,
+                            GtkWindow       *parent,
+                            const gchar     *default_group,
+                            const gchar     *default_group_name_to_add)
+{
+    GladeXML *gui;
+    GtkWidget *dialog, *group_name_entry, *ok_button;
+    GtkWidget *groups_view;
+    gint response;
+    GbfProject *project;
+    gboolean finished = FALSE;
+    gchar *new_group = NULL;
+
+    g_return_val_if_fail (model != NULL, NULL);
+    
+    project = gbf_project_model_get_project (model);
+    if (!project)
+        return NULL;
+
+    gui = load_interface ("new_group_dialog");
+    g_return_val_if_fail (gui != NULL, NULL);
+    
+    /* get all needed widgets */
+    dialog = glade_xml_get_widget (gui, "new_group_dialog");
+    groups_view = glade_xml_get_widget (gui, "groups_view");
+    group_name_entry = glade_xml_get_widget (gui, "group_name_entry");
+    ok_button = glade_xml_get_widget (gui, "ok_button");
+    
+    /* set up dialog */
+    if (default_group_name_to_add)
+        gtk_entry_set_text (GTK_ENTRY (group_name_entry),
+                            default_group_name_to_add);
+    g_signal_connect (group_name_entry, "changed",
+                      (GCallback) entry_changed_cb, ok_button);
+    if (default_group_name_to_add)
+        gtk_widget_set_sensitive (ok_button, TRUE);
+    else
+        gtk_widget_set_sensitive (ok_button, FALSE);
+    
+    setup_groups_treeview (model, groups_view, default_group);
+    gtk_widget_show (groups_view);
+    
+    if (parent) {
+        gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
+    }
+    
+    /* execute dialog */
+    while (!finished) {
+        response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+        switch (response) {
+            case GTK_RESPONSE_OK: 
+            {
+                GError *err = NULL;
+                GbfTreeData *data;
+                gchar *parent_id = NULL, *name;
+            
+                name = gtk_editable_get_chars (
+                    GTK_EDITABLE (group_name_entry), 0, -1);
+                data = gbf_project_view_find_selected (GBF_PROJECT_VIEW (groups_view),
+                                                       GBF_TREE_NODE_GROUP);
+                if (data) {
+                    gchar *new_group = NULL;
+                    
+                    parent_id = g_strdup (data->id);
+                    gbf_tree_data_free (data);
+		    
+                    new_group = gbf_project_add_group (project, parent_id, name, &err);
+                    if (err) {
+                        error_dialog (parent, _("Can not add group"), "%s",
+                                      err->message);
+                        g_error_free (err);
+                    } else {
+			finished = TRUE;
+		    }
+                    g_free (parent_id);
+                } else {
+                    error_dialog (parent, _("Can not add group"),
+				  "%s", _("No parent group selected"));
+                }
+                g_free (name);
+                break;
+            }
+            default:
+                finished = TRUE;
+                break;
+        }
+    }
+    
+    /* destroy stuff */
+    gtk_widget_destroy (dialog);
+    g_object_unref (gui);
+    return new_group;
+}
+
+enum {
+    TARGET_TYPE_TYPE = 0,
+    TARGET_TYPE_NAME,
+    TARGET_TYPE_PIXBUF,
+    TARGET_TYPE_N_COLUMNS
+};
+
+/* create a tree model with the target types */
+static GtkListStore *
+build_types_store (GbfProject *project)
+{
+    GtkListStore *store;
+    GtkTreeIter iter;
+    gchar **types;
+    gint i;
+    
+    types = gbf_project_get_types (project);
+    store = gtk_list_store_new (TARGET_TYPE_N_COLUMNS,
+                                G_TYPE_STRING,
+                                G_TYPE_STRING,
+                                GDK_TYPE_PIXBUF);
+    
+    for (i = 0; types [i]; i++) {
+        GdkPixbuf *pixbuf;
+        const gchar *name;
+
+        name = gbf_project_name_for_type (project, types [i]);
+        pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default(),
+                                           GTK_STOCK_CONVERT,
+                                           ICON_SIZE,
+                                           GTK_ICON_LOOKUP_GENERIC_FALLBACK,
+                                           NULL);
+
+        gtk_list_store_append (store, &iter);
+        gtk_list_store_set (store, &iter,
+                            TARGET_TYPE_TYPE, types [i],
+                            TARGET_TYPE_NAME, name,
+                            TARGET_TYPE_PIXBUF, pixbuf,
+                            -1);
+
+        if (pixbuf)
+            g_object_unref (pixbuf);
+    }
+
+    g_strfreev (types);
+
+    return store;
+}
+
+gchar* 
+gbf_project_util_new_target (GbfProjectModel *model,
+                             GtkWindow       *parent,
+                             const gchar     *default_group,
+                             const gchar     *default_target_name_to_add)
+{
+    GladeXML *gui;
+    GtkWidget *dialog, *target_name_entry, *ok_button;
+    GtkWidget *target_type_combo, *groups_view;
+    GtkListStore *types_store;
+    GtkCellRenderer *renderer;
+    gint response;
+    GbfProject *project;
+    gboolean finished = FALSE;
+    gchar *new_target = NULL;
+    
+    g_return_val_if_fail (model != NULL, NULL);
+    
+    project = gbf_project_model_get_project (model);
+    if (!project)
+        return NULL;
+
+    gui = load_interface ("new_target_dialog");
+    g_return_val_if_fail (gui != NULL, NULL);
+    
+    /* get all needed widgets */
+    dialog = glade_xml_get_widget (gui, "new_target_dialog");
+    groups_view = glade_xml_get_widget (gui, "groups_view");
+    target_name_entry = glade_xml_get_widget (gui, "target_name_entry");
+    target_type_combo = glade_xml_get_widget (gui, "target_type_combo");
+    ok_button = glade_xml_get_widget (gui, "ok_button");
+    
+    /* set up dialog */
+    if (default_target_name_to_add)
+        gtk_entry_set_text (GTK_ENTRY (target_name_entry),
+                            default_target_name_to_add);
+    g_signal_connect (target_name_entry, "changed",
+                      (GCallback) entry_changed_cb, ok_button);
+    if (default_target_name_to_add)
+        gtk_widget_set_sensitive (ok_button, TRUE);
+    else
+        gtk_widget_set_sensitive (ok_button, FALSE);
+    
+    setup_groups_treeview (model, groups_view, default_group);
+    gtk_widget_show (groups_view);
+
+    /* setup target types combo box */
+    types_store = build_types_store (project);
+    gtk_combo_box_set_model (GTK_COMBO_BOX (target_type_combo), 
+                             GTK_TREE_MODEL (types_store));
+
+    /* create cell renderers */
+    renderer = gtk_cell_renderer_pixbuf_new ();
+    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (target_type_combo),
+                                renderer, FALSE);
+    gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (target_type_combo),
+                                    renderer,
+                                    "pixbuf", TARGET_TYPE_PIXBUF,
+                                    NULL);
+
+    renderer = gtk_cell_renderer_text_new ();
+    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (target_type_combo),
+                                renderer, TRUE);
+    gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (target_type_combo),
+                                    renderer,
+                                    "text", TARGET_TYPE_NAME,
+                                    NULL);
+    gtk_widget_show (target_type_combo);
+
+    /* preselect */
+    gtk_combo_box_set_active (GTK_COMBO_BOX (target_type_combo), 0);
+
+    if (parent) {
+        gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
+    }
+    
+    /* execute dialog */
+    while (!finished) {
+        response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+        switch (response) {
+            case GTK_RESPONSE_OK: 
+            {
+                GError *err = NULL;
+                GbfTreeData *data;
+                GtkTreeIter iter;
+                gchar *group_id = NULL, *name, *type = NULL;
+                
+                name = gtk_editable_get_chars (
+                    GTK_EDITABLE (target_name_entry), 0, -1);
+                data = gbf_project_view_find_selected (GBF_PROJECT_VIEW (groups_view),
+                                                       GBF_TREE_NODE_GROUP);
+
+                /* retrieve target type */
+                if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (target_type_combo), &iter)) {
+                    gtk_tree_model_get (GTK_TREE_MODEL (types_store), &iter, 
+                                        TARGET_TYPE_TYPE, &type,
+                                        -1);
+                }
+                
+                if (data && type) {
+                    group_id = g_strdup (data->id);
+                    gbf_tree_data_free (data);
+            
+                    new_target = gbf_project_add_target (project, group_id, name, type, &err);
+                    if (err) {
+                        error_dialog (parent, _("Can not add target"), "%s",
+                                      err->message);
+                        g_error_free (err);
+                    } else {
+			finished = TRUE;
+		    }
+		    g_free (group_id);
+                    g_free (type);
+                } else {
+                    error_dialog (parent, _("Can not add target"), "%s",
+				  _("No group selected"));
+                }
+            
+                g_free (name);
+
+                break;
+            }
+            default:
+                finished = TRUE;
+                break;
+        }
+    }
+    
+    /* destroy stuff */
+    g_object_unref (types_store);
+    gtk_widget_destroy (dialog);
+    g_object_unref (gui);
+    return new_target;
+}
+
+static gboolean
+targets_filter_fn (GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
+{
+    GbfTreeData *data = NULL;
+    gboolean retval = FALSE;
+
+    gtk_tree_model_get (model, iter,
+                        GBF_PROJECT_MODEL_COLUMN_DATA, &data, -1);
+    retval = (data && !data->is_shortcut &&
+              (data->type == GBF_TREE_NODE_GROUP ||
+               data->type == GBF_TREE_NODE_TARGET));
+    gbf_tree_data_free (data);
+    
+    return retval;
+}
+
+static void 
+setup_targets_treeview (GbfProjectModel *model,
+                        GtkWidget       *view,
+                        const gchar     *select_target,
+                        const gchar     *select_group)
+{
+    GtkTreeModel *filter;
+    GtkTreeIter iter, iter_filter;
+    GtkTreePath *path = NULL;
+    
+    g_return_if_fail (model != NULL);
+    g_return_if_fail (view != NULL && GBF_IS_PROJECT_VIEW (view));
+
+    filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (model), NULL);
+    gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter),
+                                            targets_filter_fn, NULL, NULL);
+    gtk_tree_view_set_model (GTK_TREE_VIEW (view), GTK_TREE_MODEL (filter));
+    g_object_unref (filter);
+
+    /* select default target */
+    if (select_target) {
+        if (gbf_project_model_find_id (model, &iter,
+                                       GBF_TREE_NODE_TARGET, select_target)) {
+            gtk_tree_model_filter_convert_child_iter_to_iter (
+                GTK_TREE_MODEL_FILTER (filter), &iter_filter, &iter);
+            path = gtk_tree_model_get_path (filter, &iter_filter);
+        }
+    } else if (select_group) {
+        if (gbf_project_model_find_id (model, &iter,
+                                       GBF_TREE_NODE_GROUP, select_group)) {
+            gtk_tree_model_filter_convert_child_iter_to_iter (
+                  GTK_TREE_MODEL_FILTER (filter), &iter_filter, &iter);
+            path = gtk_tree_model_get_path (filter, &iter_filter);
+        }
+    }
+    if (path)
+    {
+        gtk_tree_view_expand_to_path (GTK_TREE_VIEW (view), path);
+        gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, NULL, FALSE);
+        gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), path, NULL,
+                                      TRUE, 0.5, 0.0);
+        gtk_tree_path_free (path);
+    } else {
+        gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
+    }
+}
+
+static void
+browse_button_clicked_cb (GtkWidget *widget, gpointer user_data)
+{
+    GtkTreeView *tree = user_data;
+    gchar *file, *uri;
+    GnomeVFSURI *tmp_uri;
+    GtkFileChooserDialog* dialog;
+	GtkTreeModel* model;
+    GtkTreeIter iter;
+    gint result;
+    
+    g_return_if_fail (user_data != NULL && GTK_IS_TREE_VIEW (user_data));
+	
+	model = gtk_tree_view_get_model(tree);
+	if (gtk_tree_model_get_iter_first(model, &iter))
+	{
+		gtk_tree_model_get(model, &iter, COLUMN_URI, &file, -1);
+		uri = g_strdup(file);
+	}
+	else
+		uri = g_strdup("");
+	
+    dialog = GTK_FILE_CHOOSER_DIALOG(gtk_file_chooser_dialog_new (_("Select sources..."),
+				      GTK_WINDOW (gtk_widget_get_toplevel (widget)),
+				      GTK_FILE_CHOOSER_ACTION_OPEN,
+				      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+				      GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+				      NULL));
+    tmp_uri = gnome_vfs_uri_new (uri);
+    g_free (uri);
+    uri = NULL;
+    if (tmp_uri) {
+        uri = gnome_vfs_uri_extract_dirname (tmp_uri);
+        gnome_vfs_uri_unref (tmp_uri);
+    }
+    
+    gtk_file_chooser_set_current_folder_uri(GTK_FILE_CHOOSER(dialog),
+    	uri ? uri : g_object_get_data (G_OBJECT (widget), "root"));
+    gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER(dialog), TRUE);
+    g_free (uri);
+
+    result = gtk_dialog_run (GTK_DIALOG (dialog));
+    switch (result)
+    {
+	case GTK_RESPONSE_ACCEPT:
+	{
+	    GSList* uris = gtk_file_chooser_get_uris (GTK_FILE_CHOOSER(dialog));
+	    GSList* node_uri = uris;
+
+	    gtk_list_store_clear (GTK_LIST_STORE (model));
+
+	    while (node_uri != NULL)
+	    {
+		GtkTreeIter iter;
+		gchar* uri = node_uri->data;
+		gchar* file = g_path_get_basename (uri);
+		gtk_list_store_append (GTK_LIST_STORE (model), &iter);
+		gtk_list_store_set (GTK_LIST_STORE (model), &iter, COLUMN_FILE,
+				    file, COLUMN_URI, uri, -1);
+		node_uri = g_slist_next (node_uri);
+	    }
+	    g_slist_free (uris);
+	    break;
+	}
+	default:
+	    break;
+    } 
+    gtk_widget_destroy (GTK_WIDGET(dialog));
+}
+
+static void
+on_row_changed(GtkTreeModel* model, GtkTreePath* path, GtkTreeIter* iter, gpointer data)
+{
+	GtkWidget* button = GTK_WIDGET(data);
+	if (gtk_list_store_iter_is_valid(GTK_LIST_STORE(model), iter))
+		gtk_widget_set_sensitive(button, TRUE);
+	else
+		gtk_widget_set_sensitive(button, FALSE);
+}
+
+gchar*
+gbf_project_util_add_source (GbfProjectModel *model,
+                             GtkWindow       *parent,
+                             const gchar     *default_target,
+                             const gchar     *default_group,
+                             const gchar     *default_uri)
+{
+        GList* new_sources;
+	gchar* uri = NULL;
+	GList* uris = NULL;
+        
+        if (default_uri) {
+            uri = g_strdup (default_uri);
+            uris = g_list_append (NULL, uri);
+        }
+	new_sources = 
+		gbf_project_util_add_source_multi (model, parent,
+                                                   default_target,
+                                                   default_group, uris);
+	g_free (uri);
+	
+	if (new_sources && g_list_length (new_sources))
+	{
+		gchar* new_source = g_strdup (new_sources->data);
+                g_list_foreach (new_sources, (GFunc) g_free, NULL);
+		g_list_free (new_sources);
+		return new_source;
+	}
+	else
+		return NULL;
+}
+
+GList* 
+gbf_project_util_add_source_multi (GbfProjectModel *model,
+				   GtkWindow       *parent,
+				   const gchar     *default_target,
+				   const gchar     *default_group,
+				   GList     *uris_to_add)
+{
+    GladeXML *gui;
+    GtkWidget *dialog, *source_file_tree;
+    GtkWidget *ok_button, *browse_button;
+    GtkWidget *targets_view;
+    gint response;
+    GbfProject *project;
+    gboolean finished = FALSE;
+    gchar *project_root;
+    GtkListStore* list;
+    GtkCellRenderer* renderer;
+    GtkTreeViewColumn* column_filename;
+    GList* new_sources = NULL;
+    GList* uri_node;
+    
+    g_return_val_if_fail (model != NULL, NULL);
+    
+    project = gbf_project_model_get_project (model);
+    if (!project)
+        return NULL;
+
+    gui = load_interface ("add_source_dialog");
+    g_return_val_if_fail (gui != NULL, NULL);
+    
+    /* get all needed widgets */
+    dialog = glade_xml_get_widget (gui, "add_source_dialog");
+    targets_view = glade_xml_get_widget (gui, "targets_view");
+    source_file_tree = glade_xml_get_widget (gui, "source_file_tree");
+    browse_button = glade_xml_get_widget (gui, "browse_button");
+    ok_button = glade_xml_get_widget (gui, "ok_button");
+    
+    /* Prepare file tree */
+    list = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING);
+    gtk_tree_view_set_model (GTK_TREE_VIEW (source_file_tree),
+			     GTK_TREE_MODEL (list));
+    renderer = gtk_cell_renderer_text_new ();
+    column_filename = gtk_tree_view_column_new_with_attributes ("Files",
+							        renderer,
+							        "text",
+							        COLUMN_FILE,
+							        NULL);
+    gtk_tree_view_column_set_sizing (column_filename,
+				     GTK_TREE_VIEW_COLUMN_FIXED);
+    gtk_tree_view_append_column (GTK_TREE_VIEW (source_file_tree),
+				 column_filename);
+    
+    /* set up dialog */
+   uri_node = uris_to_add;
+   while (uri_node) 
+   {
+        GtkTreeIter iter;
+        gchar* filename = g_path_get_basename (uri_node->data);
+        if (!filename)
+        	filename = g_strdup (uri_node->data);
+        gtk_list_store_append (list, &iter);
+        gtk_list_store_set (list, &iter, COLUMN_FILE, filename,
+			    COLUMN_URI, g_strdup(uri_node->data), -1);
+	g_free (filename);
+    	uri_node = g_list_next (uri_node);
+    }
+    if (!g_list_length (uris_to_add))
+	gtk_widget_set_sensitive (ok_button, FALSE);
+    else
+	gtk_widget_set_sensitive (ok_button, TRUE);
+
+    g_signal_connect (G_OBJECT(list), "row_changed",
+		      G_CALLBACK(on_row_changed), ok_button);
+    
+    g_signal_connect (browse_button, "clicked",
+                      G_CALLBACK (browse_button_clicked_cb), source_file_tree);
+    
+    g_object_get (project, "project-dir", &project_root, NULL);
+    g_object_set_data_full (G_OBJECT (browse_button), "root",
+                            project_root, g_free);
+    
+    setup_targets_treeview (model, targets_view, default_target,
+			    default_group);
+    gtk_widget_show (targets_view);
+    
+    if (parent) {
+        gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
+    }
+
+    if (default_target)
+        gtk_widget_grab_focus (source_file_tree);
+    else
+        gtk_widget_grab_focus (targets_view);
+    
+    /* execute dialog */
+    while (!finished) {
+        response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+        switch (response) {
+            case GTK_RESPONSE_OK: 
+            {
+                GbfTreeData *data;
+                gchar *target_id = NULL;
+            
+                data = gbf_project_view_find_selected (GBF_PROJECT_VIEW (targets_view),
+                                                       GBF_TREE_NODE_TARGET);
+		if (data) {
+		    GtkTreeIter iter;
+		    GString *err_mesg = g_string_new (NULL);
+		    
+		    target_id = g_strdup (data->id);
+		    gbf_tree_data_free (data);
+		    
+		    if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (list),
+							&iter))
+			break;
+		    do
+		    {
+			GError *err = NULL;
+			gchar* new_source;
+			gchar* source_file;
+			gtk_tree_model_get (GTK_TREE_MODEL(list), &iter,
+					    COLUMN_URI, &source_file, -1);
+			
+			new_source = gbf_project_add_source (project,
+							     target_id,
+							     source_file,
+							     &err);
+			if (err) {
+			    gchar *str = g_strdup_printf ("%s: %s\n",
+							  source_file,
+							  err->message);
+			    g_string_append (err_mesg, str);
+			    g_error_free (err);
+			    g_free (str);
+			}
+			else
+			    new_sources = g_list_append (new_sources,
+							 new_source);
+			
+			g_free (source_file);
+		    } while (gtk_tree_model_iter_next (GTK_TREE_MODEL(list),
+						       &iter));
+		    
+                    g_free (target_id);
+		    
+		    if (err_mesg->str && strlen (err_mesg->str) > 0) {
+			error_dialog (parent, _("Can not add source files"),
+				      "%s", err_mesg->str);
+		    } else {
+			finished = TRUE;
+		    }
+		    g_string_free (err_mesg, TRUE);
+                } else {
+                    error_dialog (parent, _("Can not add source files"),
+				  "%s", _("No target has been selected"));
+                }
+                
+                break;
+            }
+            default:
+		gtk_list_store_clear (GTK_LIST_STORE (list));
+                finished = TRUE;
+                break;
+        }
+    }
+    
+    /* destroy stuff */
+    gtk_widget_destroy (dialog);
+    g_object_unref (gui);
+    return new_sources;
+}

Added: trunk/plugins/project-manager/gbf-project-util.h
==============================================================================
--- (empty file)
+++ trunk/plugins/project-manager/gbf-project-util.h	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,58 @@
+/*  -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4; coding: utf-8 -*-
+ * 
+ * Copyright (C) 2003 Gustavo GirÃldez
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA. 
+ * 
+ * Author: Gustavo GirÃldez <gustavo giraldez gmx net>
+ */
+
+#ifndef __GBF_PROJECT_UTIL_H__
+#define __GBF_PROJECT_UTIL_H__
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include "gbf-project-model.h"
+
+G_BEGIN_DECLS
+
+gchar* gbf_project_util_new_group  (GbfProjectModel *model,
+				    GtkWindow       *parent,
+				    const gchar     *default_group,
+				    const gchar     *default_group_name_to_add);
+
+gchar* gbf_project_util_new_target (GbfProjectModel *model,
+				    GtkWindow       *parent,
+				    const gchar     *default_group,
+				    const gchar     *default_target_name_to_add);
+
+gchar* gbf_project_util_add_source (GbfProjectModel *model,
+				    GtkWindow       *parent,
+				    const gchar     *default_target,
+				    const gchar     *default_group,
+				    const gchar     *default_uri_to_add);
+
+GList* gbf_project_util_add_source_multi (GbfProjectModel *model,
+				    GtkWindow       *parent,
+				    const gchar     *default_target,
+				    const gchar     *default_group,
+				    GList     *uris_to_add);
+				    
+				    
+				    
+G_END_DECLS
+
+#endif /* __GBF_PROJECT_UTIL_H__ */

Added: trunk/plugins/project-manager/gbf-project-view.c
==============================================================================
--- (empty file)
+++ trunk/plugins/project-manager/gbf-project-view.c	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,416 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8; coding: utf-8 -*- */
+/* gbf_project-tree.c
+ *
+ * Copyright (C) 2000  JP Rosevear
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: JP Rosevear
+ *          Gustavo GirÃldez <gustavo giraldez gmx net>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#include <libgnome/gnome-macros.h>
+#include <gio/gio.h>
+#include <string.h>
+
+#include "gbf-tree-data.h"
+#include "gbf-project-model.h"
+#include "gbf-project-view.h"
+
+#define ICON_SIZE 16
+
+struct _GbfProjectViewPrivate {	
+
+};
+
+enum {
+	URI_ACTIVATED,
+	TARGET_SELECTED,
+	GROUP_SELECTED,
+	LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0 };
+
+static void gbf_project_view_class_init    (GbfProjectViewClass *klass);
+static void gbf_project_view_instance_init (GbfProjectView      *tree);
+static void destroy                        (GtkObject           *object);
+
+static void set_pixbuf                     (GtkTreeViewColumn   *tree_column,
+					    GtkCellRenderer     *cell,
+					    GtkTreeModel        *model,
+					    GtkTreeIter         *iter,
+					    gpointer             data);
+static void set_text                       (GtkTreeViewColumn   *tree_column,
+					    GtkCellRenderer     *cell,
+					    GtkTreeModel        *model,
+					    GtkTreeIter         *iter,
+					    gpointer             data);
+
+
+GNOME_CLASS_BOILERPLATE(GbfProjectView, gbf_project_view,
+			GtkTreeView, GTK_TYPE_TREE_VIEW);
+
+
+static void
+row_activated (GtkTreeView       *tree_view,
+	       GtkTreePath       *path,
+	       GtkTreeViewColumn *column)
+{
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	GbfTreeData *data;
+	
+	model = gtk_tree_view_get_model (tree_view);
+
+	gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path);
+
+	gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
+			    GBF_PROJECT_MODEL_COLUMN_DATA, &data,
+			    -1);
+
+	if (data->uri) {
+		g_signal_emit (G_OBJECT (tree_view), 
+			       signals [URI_ACTIVATED], 0,
+			       data->uri);
+	}
+
+	if (data->type == GBF_TREE_NODE_TARGET) {
+		g_signal_emit (G_OBJECT (tree_view),
+			       signals [TARGET_SELECTED], 0,
+			       data->id);
+	}
+	
+	if (data->type == GBF_TREE_NODE_GROUP) {
+		g_signal_emit (G_OBJECT (tree_view),
+			       signals [GROUP_SELECTED], 0,
+			       data->id);
+	}
+	
+	gbf_tree_data_free (data);
+}
+
+static void
+destroy (GtkObject *object)
+{
+	GbfProjectView *tree;
+	GbfProjectViewPrivate *priv;
+	
+	tree = GBF_PROJECT_VIEW (object);
+	priv = tree->priv;
+	
+	if (priv) {
+		g_free (priv);
+		tree->priv = NULL;
+	}
+	
+	if (GTK_OBJECT_CLASS (parent_class)->destroy)
+		(* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+static GdkPixbuf*
+get_icon (const gchar* uri)
+{
+	const gchar** icon_names;
+	GtkIconInfo* icon_info;
+	GIcon* icon;
+	GdkPixbuf* pixbuf = NULL;
+	GFile* file;
+	GFileInfo* file_info;
+	GError *error = NULL;
+	
+	file = g_file_new_for_uri (uri);
+	file_info = g_file_query_info (file,
+				       G_FILE_ATTRIBUTE_STANDARD_ICON,
+				       G_FILE_QUERY_INFO_NONE,
+				       NULL,
+				       &error);
+
+	if (!file_info)
+	{
+		g_warning (G_STRLOC ": Unable to query information for URI: %s: %s", uri, error->message);
+		g_clear_error (&error);
+		return NULL;
+	}
+	
+	icon = g_file_info_get_icon(file_info);
+	g_object_get (icon, "names", &icon_names, NULL);
+	icon_info = gtk_icon_theme_choose_icon (gtk_icon_theme_get_default(),
+						icon_names,
+						ICON_SIZE,
+						GTK_ICON_LOOKUP_GENERIC_FALLBACK);
+	pixbuf = gtk_icon_info_load_icon (icon_info, NULL);
+	gtk_icon_info_free(icon_info);
+	
+	return pixbuf;
+}
+
+static void
+set_pixbuf (GtkTreeViewColumn *tree_column,
+	    GtkCellRenderer   *cell,
+	    GtkTreeModel      *model,
+	    GtkTreeIter       *iter,
+	    gpointer           user_data)
+{
+	//GbfProjectView *view = GBF_PROJECT_VIEW (user_data);
+	GbfTreeData *data = NULL;
+	GdkPixbuf *pixbuf = NULL;
+	
+	gtk_tree_model_get (model, iter,
+			    GBF_PROJECT_MODEL_COLUMN_DATA, &data, -1);
+	g_return_if_fail (data != NULL);
+	
+	switch (data->type) {
+		case GBF_TREE_NODE_TARGET_SOURCE:
+		{
+			pixbuf = get_icon (data->uri);
+			break;
+		}
+		case GBF_TREE_NODE_GROUP:
+			pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default(),
+							   GTK_STOCK_DIRECTORY,
+							   ICON_SIZE,
+							   GTK_ICON_LOOKUP_GENERIC_FALLBACK,
+							   NULL);
+			break;
+		case GBF_TREE_NODE_TARGET: 
+		{
+			pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default(),
+							   GTK_STOCK_CONVERT,
+							   ICON_SIZE,
+							   GTK_ICON_LOOKUP_GENERIC_FALLBACK,
+							   NULL);
+			break;
+		}
+		default:
+			pixbuf = NULL;
+	}
+
+	g_object_set (GTK_CELL_RENDERER (cell), "pixbuf", pixbuf, NULL);
+	if (pixbuf)
+		g_object_unref (pixbuf);
+
+	gbf_tree_data_free (data);
+}
+
+static void
+set_text (GtkTreeViewColumn *tree_column,
+	  GtkCellRenderer   *cell,
+	  GtkTreeModel      *model,
+	  GtkTreeIter       *iter,
+	  gpointer           user_data)
+{
+	GbfTreeData *data;
+  
+	gtk_tree_model_get (model, iter, 0, &data, -1);
+	g_object_set (GTK_CELL_RENDERER (cell), "text", 
+		      data->name, NULL);
+	gbf_tree_data_free (data);
+}
+
+static gboolean
+search_equal_func (GtkTreeModel *model, gint column,
+		   const gchar *key, GtkTreeIter *iter,
+		   gpointer user_data)
+{
+	GbfTreeData *data;
+	gboolean ret = TRUE;
+		     
+	gtk_tree_model_get (model, iter, 0, &data, -1);
+	if (strncmp (data->name, key, strlen (key)) == 0)
+	    ret = FALSE;
+	gbf_tree_data_free (data);
+	return ret;
+}
+
+static gint
+expose_event (GtkWidget *widget, GdkEventExpose *ev)
+{
+	GtkTreeModel *model;
+	GtkTreeView *tree_view;
+	gint event_handled = FALSE;
+	
+	event_handled = GNOME_CALL_PARENT_WITH_DEFAULT (
+		GTK_WIDGET_CLASS, expose_event, (widget, ev), event_handled);
+
+	tree_view = GTK_TREE_VIEW (widget);
+	model = gtk_tree_view_get_model (tree_view);
+	if (ev->window == gtk_tree_view_get_bin_window (tree_view) &&
+	    model && GBF_IS_PROJECT_MODEL (model)) {
+		GtkTreePath *root;
+		GdkRectangle rect;
+		
+		/* paint an horizontal ruler to separate the project
+		 * tree from the target shortcuts */
+		
+		root = gbf_project_model_get_project_root (GBF_PROJECT_MODEL (model));
+		if (root) {
+			gtk_tree_view_get_background_area (
+				tree_view, root, gtk_tree_view_get_column (tree_view, 0), &rect);
+			gtk_paint_hline (widget->style,
+					 ev->window,
+					 GTK_WIDGET_STATE (widget),
+					 &ev->area,
+					 widget,
+					 "",
+					 rect.x, rect.x + rect.width,
+					 rect.y);
+			gtk_tree_path_free (root);
+		}
+	}
+	
+	return event_handled;
+}
+
+static void 
+gbf_project_view_class_init (GbfProjectViewClass *klass)
+{
+	GObjectClass     *g_object_class;
+	GtkObjectClass   *object_class;
+	GtkWidgetClass   *widget_class;
+	GtkTreeViewClass *tree_view_class;
+
+	g_object_class = G_OBJECT_CLASS (klass);
+	object_class = (GtkObjectClass *) klass;
+	widget_class = GTK_WIDGET_CLASS (klass);
+	tree_view_class = GTK_TREE_VIEW_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	object_class->destroy = destroy;
+	widget_class->expose_event = expose_event;
+	tree_view_class->row_activated = row_activated;
+
+	signals [URI_ACTIVATED] = 
+		g_signal_new ("uri_activated",
+			      GBF_TYPE_PROJECT_VIEW,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (GbfProjectViewClass,
+					       uri_activated),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__STRING,
+			      G_TYPE_NONE, 1, G_TYPE_STRING);
+
+	signals [TARGET_SELECTED] = 
+		g_signal_new ("target_selected",
+			      GBF_TYPE_PROJECT_VIEW,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (GbfProjectViewClass,
+					       target_selected),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__STRING,
+			      G_TYPE_NONE, 1, G_TYPE_STRING);
+	signals [GROUP_SELECTED] = 
+		g_signal_new ("group_selected",
+			      GBF_TYPE_PROJECT_VIEW,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (GbfProjectViewClass,
+					       group_selected),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__STRING,
+			      G_TYPE_NONE, 1, G_TYPE_STRING);
+}
+
+static void 
+gbf_project_view_instance_init (GbfProjectView *tree)
+{
+	GbfProjectViewPrivate *priv;
+	GtkCellRenderer *renderer;
+	GtkTreeViewColumn *column;
+	static GtkTargetEntry row_targets[] = {
+		{ "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, 0 }
+	};
+
+	priv = g_new0 (GbfProjectViewPrivate, 1);
+	tree->priv = priv;
+    
+	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree), FALSE);
+	gtk_tree_view_set_enable_search (GTK_TREE_VIEW (tree), TRUE);
+	gtk_tree_view_set_search_column (GTK_TREE_VIEW (tree), 0);
+	gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (tree),
+					     search_equal_func,
+					     NULL, NULL);
+	
+	gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (tree),
+						GDK_BUTTON1_MASK,
+						row_targets,
+						G_N_ELEMENTS (row_targets),
+						GDK_ACTION_MOVE);
+	gtk_tree_view_enable_model_drag_dest (GTK_TREE_VIEW (tree),
+					      row_targets,
+					      G_N_ELEMENTS (row_targets),
+					      GDK_ACTION_MOVE);
+      
+	/* set renderer for files column. */        
+	column = gtk_tree_view_column_new ();
+	renderer = gtk_cell_renderer_pixbuf_new ();
+	gtk_tree_view_column_pack_start (column, renderer, FALSE);
+	gtk_tree_view_column_set_cell_data_func (column, renderer, set_pixbuf, tree, NULL);
+	
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_tree_view_column_pack_start (column, renderer, FALSE);
+	gtk_tree_view_column_set_cell_data_func (column, renderer, set_text, tree, NULL);
+
+	gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
+}
+
+GtkWidget *
+gbf_project_view_new (void)
+{
+	return GTK_WIDGET (g_object_new (GBF_TYPE_PROJECT_VIEW, NULL));
+}
+
+GbfTreeData *
+gbf_project_view_find_selected (GbfProjectView *view, GbfTreeNodeType type)
+{
+	GbfTreeData *data = NULL;
+	GtkTreeSelection *selection;
+	GtkTreeModel *model;
+	GtkTreeIter iter, iter2;
+
+	g_return_val_if_fail (view != NULL, NULL);
+	g_return_val_if_fail (GBF_IS_PROJECT_VIEW (view), NULL);
+	
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
+	if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+		gtk_tree_model_get (model, &iter,
+				    GBF_PROJECT_MODEL_COLUMN_DATA, &data,
+				    -1);
+		/* walk up the hierarchy searching for a node of the given type */
+		while (NULL != data && data->type != type) {
+			gbf_tree_data_free (data);
+			data = NULL;
+
+			if (!gtk_tree_model_iter_parent (model, &iter2, &iter))
+				break;
+			
+			gtk_tree_model_get (model, &iter2,
+					    GBF_PROJECT_MODEL_COLUMN_DATA, &data,
+					    -1);
+			iter = iter2;
+		}
+	}
+
+	return data;
+}
+

Added: trunk/plugins/project-manager/gbf-project-view.h
==============================================================================
--- (empty file)
+++ trunk/plugins/project-manager/gbf-project-view.h	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,68 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gbf-project-view.h
+ *
+ * Copyright (C) 2000-2002  JP Rosevear
+ * Copyright (C) 2002  Dave Camp
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _GBF_PROJECT_TREE_H_
+#define _GBF_PROJECT_TREE_H_
+
+#include <gtk/gtk.h>
+#include "gbf-tree-data.h"
+
+G_BEGIN_DECLS
+
+#define GBF_TYPE_PROJECT_VIEW		  (gbf_project_view_get_type ())
+#define GBF_PROJECT_VIEW(obj)		  (GTK_CHECK_CAST ((obj), GBF_TYPE_PROJECT_VIEW, GbfProjectView))
+#define GBF_PROJECT_VIEW_CLASS(klass)	  (GTK_CHECK_CLASS_CAST ((klass), GBF_TYPE_PROJECT_VIEW, GbfProjectViewClass))
+#define GBF_IS_PROJECT_VIEW(obj)	  (GTK_CHECK_TYPE ((obj), GBF_TYPE_PROJECT_VIEW))
+#define GBF_IS_PROJECT_VIEW_CLASS(klass)  (GTK_CHECK_CLASS_TYPE ((obj), GBF_TYPE_PROJECT_VIEW))
+
+typedef struct _GbfProjectView        GbfProjectView;
+typedef struct _GbfProjectViewPrivate GbfProjectViewPrivate;
+typedef struct _GbfProjectViewClass   GbfProjectViewClass;
+
+
+struct _GbfProjectView {
+	GtkTreeView parent;
+
+	GbfProjectViewPrivate *priv;
+};
+
+struct _GbfProjectViewClass {
+	GtkTreeViewClass parent_class;
+
+	void (* uri_activated)    (GbfProjectView *project_view,
+				   const char     *uri);
+
+	void (* target_selected)  (GbfProjectView *project_view,
+				   const gchar    *target_id);
+	void (* group_selected)  (GbfProjectView *project_view,
+				   const gchar    *group_id);
+};
+
+GType                       gbf_project_view_get_type         (void);
+GtkWidget                  *gbf_project_view_new              (void);
+
+GbfTreeData                *gbf_project_view_find_selected    (GbfProjectView *view,
+							       GbfTreeNodeType type);
+
+G_END_DECLS
+
+#endif /* _GBF_PROJECT_VIEW_H_ */

Added: trunk/plugins/project-manager/gbf-tree-data.c
==============================================================================
--- (empty file)
+++ trunk/plugins/project-manager/gbf-tree-data.c	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,123 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gbf-tree-data.c
+ *
+ * Copyright (C) 2000  JP Rosevear
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: JP Rosevear
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <libgnomevfs/gnome-vfs-uri.h>
+#include "gbf-tree-data.h"
+
+GType
+gbf_tree_data_get_type (void)
+{
+    static GType our_type = 0;
+  
+    if (our_type == 0)
+        our_type = g_boxed_type_register_static ("GbfProjectTreeNodeData",
+                                                 (GBoxedCopyFunc) gbf_tree_data_copy,
+                                                 (GBoxedFreeFunc) gbf_tree_data_free);
+
+    return our_type;
+}
+
+GbfTreeData *
+gbf_tree_data_new_string (const gchar *string)
+{
+	GbfTreeData *node = g_new0 (GbfTreeData, 1);
+	
+	node->type = GBF_TREE_NODE_STRING;
+	node->name = g_strdup (string);
+	
+	return node;
+}
+
+GbfTreeData *
+gbf_tree_data_new_group (GbfProject *project, const GbfProjectGroup *group)
+{
+	GbfTreeData *node = g_new0 (GbfTreeData, 1);
+	
+	node->type = GBF_TREE_NODE_GROUP;
+	node->name = g_strdup (group->name);
+	node->id = g_strdup (group->id);
+
+	return node;
+}
+
+GbfTreeData *
+gbf_tree_data_new_target (GbfProject *project, const GbfProjectTarget *target)
+{
+	GbfTreeData *node = g_new0 (GbfTreeData, 1);
+	
+	node->type = GBF_TREE_NODE_TARGET;
+	node->name = g_strdup (target->name);
+	node->id = g_strdup (target->id);
+	node->mime_type = g_strdup (gbf_project_mimetype_for_type (project, target->type));
+	
+	return node;
+}
+
+GbfTreeData *
+gbf_tree_data_new_source (GbfProject *project, const GbfProjectTargetSource *source)
+{
+	GbfTreeData *node = g_new0 (GbfTreeData, 1);
+	GnomeVFSURI *uri;
+	
+	node->type = GBF_TREE_NODE_TARGET_SOURCE;
+	node->id = g_strdup (source->id);
+	node->uri = g_strdup (source->source_uri);
+	
+	uri = gnome_vfs_uri_new (source->source_uri);
+	node->name = gnome_vfs_uri_extract_short_name (uri);
+	gnome_vfs_uri_unref (uri);
+	
+	return node;
+}
+
+GbfTreeData *
+gbf_tree_data_copy (GbfTreeData *src)
+{
+	GbfTreeData *node;
+
+	node = g_new (GbfTreeData, 1);
+	node->type = src->type;
+	node->name = g_strdup (src->name);
+	node->id = g_strdup (src->id);
+	node->uri = g_strdup (src->uri);
+	node->is_shortcut = src->is_shortcut;
+	node->mime_type = g_strdup (src->mime_type);
+	
+	return node;
+}
+
+void
+gbf_tree_data_free (GbfTreeData *node)
+{
+	if (node) {
+		g_free (node->name);
+		g_free (node->id);
+		g_free (node->uri);
+		g_free (node->mime_type);
+		g_free (node);
+	}
+}

Added: trunk/plugins/project-manager/gbf-tree-data.h
==============================================================================
--- (empty file)
+++ trunk/plugins/project-manager/gbf-tree-data.h	Wed Dec 31 11:55:52 2008
@@ -0,0 +1,67 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gbf-tree-data.h
+ *
+ * Copyright (C) 2000  JP Rosevear
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: JP Rosevear
+ */
+
+#ifndef _GBF_TREE_DATA_H_
+#define _GBF_TREE_DATA_H_
+
+#include <glib-object.h>
+#include <libanjuta/gbf-project.h>
+
+G_BEGIN_DECLS
+
+#define GBF_TYPE_TREE_DATA			(gbf_tree_data_get_type ())
+
+typedef struct _GbfTreeData GbfTreeData;
+
+typedef enum {
+	GBF_TREE_NODE_STRING,
+	GBF_TREE_NODE_GROUP,
+	GBF_TREE_NODE_TARGET,
+	GBF_TREE_NODE_TARGET_SOURCE,
+} GbfTreeNodeType;
+
+struct _GbfTreeData
+{
+	GbfTreeNodeType  type;
+	gchar           *name;
+	gchar           *id;
+	gchar           *uri;
+	gboolean         is_shortcut;
+	gchar           *mime_type;
+};
+
+GType          gbf_tree_data_get_type            (void);
+GbfTreeData   *gbf_tree_data_new_string          (const gchar                  *string);
+GbfTreeData   *gbf_tree_data_new_group           (GbfProject                   *project,
+						  const GbfProjectGroup        *group);
+GbfTreeData   *gbf_tree_data_new_target          (GbfProject                   *project,
+						  const GbfProjectTarget       *target);
+GbfTreeData   *gbf_tree_data_new_source          (GbfProject                   *project,
+						  const GbfProjectTargetSource *source);
+GbfTreeData   *gbf_tree_data_copy                (GbfTreeData                  *data);
+void           gbf_tree_data_free                (GbfTreeData                  *data);
+
+
+G_END_DECLS
+
+#endif /* _GBF_TREE_DATA_H_ */

Modified: trunk/plugins/project-manager/plugin.c
==============================================================================
--- trunk/plugins/project-manager/plugin.c	(original)
+++ trunk/plugins/project-manager/plugin.c	Wed Dec 31 11:55:52 2008
@@ -28,12 +28,12 @@
 #include <libanjuta/interfaces/ianjuta-document-manager.h>
 #include <libanjuta/interfaces/ianjuta-file-manager.h>
 #include <libanjuta/interfaces/ianjuta-builder.h>
+#include <libanjuta/interfaces/ianjuta-project-backend.h>
 #include <libanjuta/anjuta-profile-manager.h>
 #include <libanjuta/anjuta-debug.h>
 #include <libanjuta/anjuta-status.h>
 
-#include <gbf/gbf-project-util.h>
-#include <gbf/gbf-backend.h>
+#include "gbf-project-util.h"
 
 #include "plugin.h"
 
@@ -1177,12 +1177,13 @@
 static void
 project_manager_load_gbf (ProjectManagerPlugin *pm_plugin)
 {
+	AnjutaPluginManager *plugin_manager;
 	AnjutaStatus *status;
 	gchar *dirname;
 	const gchar *root_uri;
-	GSList *l;
 	GError *error = NULL;
-	GbfBackend *backend = NULL;
+	GList *descs = NULL;
+	GList *desc;
 	
 	root_uri = pm_plugin->project_root_uri;
 	
@@ -1193,12 +1194,24 @@
 	if (pm_plugin->project != NULL)
 			g_object_unref (pm_plugin->project);
 	
-	DEBUG_PRINT ("%s", "initializing gbf backend...\n");
-	gbf_backend_init ();
-	for (l = gbf_backend_get_backends (); l; l = l->next) {
-		backend = l->data;
+	DEBUG_PRINT ("loading gbf backend...\n");
+	plugin_manager = anjuta_shell_get_plugin_manager (ANJUTA_PLUGIN(pm_plugin)->shell, NULL);
+	descs = anjuta_plugin_manager_query (plugin_manager,
+										 "Anjuta Plugin",
+										 "Interfaces",
+										 "IAnjutaProjectBackend",
+										 NULL);
+	for (desc = g_list_first (descs); desc != NULL; desc = g_list_next (desc)) {
+		AnjutaPluginDescription *backend;
+		IAnjutaProjectBackend *plugin;
+		gchar *location = NULL;
 		
-		pm_plugin->project = gbf_backend_new_project (backend->id);
+		backend = (AnjutaPluginDescription *)desc->data;
+		anjuta_plugin_description_get_string (backend, "Anjuta Plugin", "Location", &location);
+		plugin = (IAnjutaProjectBackend *)anjuta_plugin_manager_get_plugin_by_id (plugin_manager, location);
+		g_free (location);
+			
+		pm_plugin->project = ianjuta_project_backend_new_project (plugin, NULL);
 		if (pm_plugin->project)
 		{
 			if (gbf_project_probe (pm_plugin->project, dirname, NULL))
@@ -1213,10 +1226,11 @@
 		if (!strcmp (backend->id, "gbf-am:GbfAmProject"))
 			break;
 		*/
-		backend = NULL;
+		plugin = NULL;
 	}
+	g_list_free (descs);
 	
-	if (!backend)
+	if (!pm_plugin->project)
 	{
 		/* FIXME: Set err */
 		g_warning ("no backend available for this project\n");
@@ -1465,7 +1479,7 @@
 	/* GladeXML *gxml; */
 	ProjectManagerPlugin *pm_plugin;
 	
-	DEBUG_PRINT ("%s", "ProjectManagerPlugin: Activating Project Manager plugin...");
+	DEBUG_PRINT ("ProjectManagerPlugin: Activating Project Manager plugin %p...", plugin);
 	
 	if (!initialized)
 		register_stock_icons (plugin);
@@ -1577,6 +1591,7 @@
 	ProjectManagerPlugin *pm_plugin;
 	pm_plugin = ANJUTA_PLUGIN_PROJECT_MANAGER (plugin);
 
+	DEBUG_PRINT ("ProjectManagerPlugin: Deactivate Project Manager plugin...");
 	if (pm_plugin->close_project_idle > -1)
 	{
 		g_source_remove (pm_plugin->close_project_idle);
@@ -1699,7 +1714,7 @@
 {
 	gchar *path, *ptr;
 	gchar *uri;
-	const gchar *project_root;
+	const gchar *project_root = NULL;
 	
 	if (!id)
 		return NULL;
@@ -1721,6 +1736,15 @@
 	anjuta_shell_get (ANJUTA_PLUGIN (plugin)->shell,
 					  root, G_TYPE_STRING,
 					  &project_root, NULL);
+	if (project_root == NULL)
+	{
+		/* Perhaps missing build URI, use project URI instead */
+		anjuta_shell_get (ANJUTA_PLUGIN (plugin)->shell,
+					  IANJUTA_PROJECT_MANAGER_PROJECT_ROOT_URI,
+					  G_TYPE_STRING,
+					  &project_root,
+					  NULL);
+	}
 	uri = g_build_filename (project_root, path, NULL);
 	/* DEBUG_PRINT ("Converting id: %s to %s", id, uri); */
 	g_free (path);
@@ -1735,6 +1759,15 @@
 	anjuta_shell_get (ANJUTA_PLUGIN (plugin)->shell,
 					  root, G_TYPE_STRING,
 					  &project_root, NULL);
+	if (project_root == NULL)
+	{
+		/* Perhaps missing build URI, use project URI instead */
+		anjuta_shell_get (ANJUTA_PLUGIN (plugin)->shell,
+					  IANJUTA_PROJECT_MANAGER_PROJECT_ROOT_URI,
+					  G_TYPE_STRING,
+					  &project_root,
+					  NULL);
+	}
 	if (project_root)
 	{
 		if (uri[0] != '/')
@@ -2152,7 +2185,7 @@
 {
 	ProjectManagerPlugin *plugin;
 	IAnjutaProjectManagerElementType default_location_type;
-	gchar *location_id;
+	gchar *location_id = NULL;
 	gchar* source_id;
 	gchar* source_uri;
 	
@@ -2210,7 +2243,7 @@
 {
 	ProjectManagerPlugin *plugin;
 	IAnjutaProjectManagerElementType default_location_type;
-	gchar *location_id;
+	gchar *location_id = NULL;
 	GList* source_ids;
 	GList* source_uris = NULL;
 	
@@ -2392,7 +2425,7 @@
 	GError *error = NULL;
 	gchar* uri = g_file_get_uri (file);
 	GnomeVFSURI* vfs_uri;
-	
+
 	plugin = ANJUTA_PLUGIN_PROJECT_MANAGER (ifile);
 	
 	/* If there is already a project loaded, load in separate anjuta window */
@@ -2403,6 +2436,8 @@
 		g_free (quoted_uri);
 		anjuta_util_execute_shell (NULL, cmd);
 		g_free (cmd);
+		g_free (uri);
+		
 		return;
 	}
 	
@@ -2420,26 +2455,30 @@
 	session_profile = g_file_new_for_uri (DEFAULT_PROFILE);
 	anjuta_profile_add_plugins_from_xml (profile, session_profile,
 										 TRUE, &error);
+	g_object_unref (session_profile);
 	if (error)
 	{
-		anjuta_util_dialog_error (GTK_WINDOW (ANJUTA_PLUGIN (ifile)->shell),
-								  "%s", error->message);
-		g_error_free (error);
-		error = NULL;
+		g_propagate_error (e, error);
+
+		g_free(uri);
+		g_object_unref (profile);
+		
+		return;
 	}
-	g_object_unref (session_profile);
 	
 	/* Project default profile */
 	session_profile = g_file_new_for_uri (uri);
 	anjuta_profile_add_plugins_from_xml (profile, session_profile, TRUE, &error);
+	g_object_unref (session_profile);
 	if (error)
 	{
-		anjuta_util_dialog_error (GTK_WINDOW (ANJUTA_PLUGIN (ifile)->shell),
-								  "%s", error->message);
-		g_error_free (error);
-		error = NULL;
+		g_propagate_error (e, error);
+		
+		g_free(uri);
+		g_object_unref (profile);
+
+		return;
 	}
-	g_object_unref (session_profile);
 	
 	/* Project session profile */
 	vfs_uri = gnome_vfs_uri_new (uri);
@@ -2461,14 +2500,16 @@
 											 FALSE, &error);
 		if (error)
 		{
-			anjuta_util_dialog_error (GTK_WINDOW (ANJUTA_PLUGIN (ifile)->shell),
-									  "%s", error->message);
-			g_error_free (error);
-			error = NULL;
+			g_propagate_error (e, error);
+			
+			g_free(uri);
+			g_object_unref (profile);
+			g_object_unref (session_profile);
+			
+			return;
 		}
 	}
 	anjuta_profile_set_sync_file (profile, session_profile); 
-	g_object_unref (session_profile);
 	g_free (session_profile_path);
 	g_free (profile_name);
 	

Modified: trunk/plugins/project-manager/plugin.h
==============================================================================
--- trunk/plugins/project-manager/plugin.h	(original)
+++ trunk/plugins/project-manager/plugin.h	Wed Dec 31 11:55:52 2008
@@ -22,9 +22,9 @@
 #define _PROJECT_MANAGER_PLUGIN_H_
 
 #include <libanjuta/anjuta-plugin.h>
-#include <gbf/gbf-project.h>
-#include <gbf/gbf-project-model.h>
-#include <gbf/gbf-project-view.h>
+#include <libanjuta/gbf-project.h>
+#include "gbf-project-model.h"
+#include "gbf-project-view.h"
 
 extern GType project_manager_plugin_get_type (GTypeModule *module);
 #define ANJUTA_TYPE_PLUGIN_PROJECT_MANAGER         (project_manager_plugin_get_type (NULL))



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]