[gitg/wip/new-dash] wip
- From: Ignacio Casal Quinteiro <icq src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gitg/wip/new-dash] wip
- Date: Sun, 24 Jan 2016 15:01:02 +0000 (UTC)
commit 1d933f76991dff6ad611157b9d1338308a5fb9a4
Author: Ignacio Casal Quinteiro <icq gnome org>
Date: Sat Jan 23 12:46:27 2016 +0100
wip
Makefile.am | 3 +
configure.ac | 6 +
contrib/ide/Makefile.am | 26 +
contrib/ide/ide-doap-person.c | 178 ++++++
contrib/ide/ide-doap-person.h | 40 ++
contrib/ide/ide-doap.c | 630 ++++++++++++++++++++
contrib/ide/ide-doap.h | 60 ++
contrib/ide/ide.h | 31 +
contrib/ide/ide.vapi | 48 ++
contrib/xml/Makefile.am | 11 +
contrib/xml/xml-reader.c | 597 +++++++++++++++++++
contrib/xml/xml-reader.h | 100 +++
gitg/resources/ui/gitg-dash-view.ui | 27 +-
libgitg/Makefile.am | 6 +-
libgitg/gitg-repository-list-box.vala | 187 +++----
.../resources/ui/gitg-repository-list-box-row.ui | 47 +-
libgitg/resources/ui/libgitg-style.css | 6 +
17 files changed, 1844 insertions(+), 159 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 61f85be..9267a3c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -83,6 +83,7 @@ GITIGNOREFILES =
CLEANFILES =
bin_PROGRAMS =
noinst_PROGRAMS =
+noinst_LTLIBRARIES =
SCALABLE_ICONS =
gsettings_SCHEMAS =
TESTS =
@@ -91,6 +92,8 @@ GITG_PLUGIN_VAPISOURCES = \
libgitg-ext/libgitg-ext-1.0.vapi \
libgitg/libgitg-1.0.vapi
+include contrib/xml/Makefile.am
+include contrib/ide/Makefile.am
include libgitg/Makefile.am
include libgitg-ext/Makefile.am
include plugins/Makefile.am
diff --git a/configure.ac b/configure.ac
index c950490..b6ed060 100644
--- a/configure.ac
+++ b/configure.ac
@@ -92,6 +92,7 @@ GTKSOURCEVIEW_REQUIRED_VERSION=3.10
INTROSPECTION_REQUIRED=0.10.1
LIBGIT2_GLIB_REQUIRED_VERSION=0.23.5
LIBGIT2_GLIB_REQUIRED_MAX_VERSION=0.24.0
+LIBXML_REQUIRED_VERSION=2.9.0
gdk_targets=`$PKG_CONFIG --variable=targets gdk-3.0`
@@ -154,6 +155,11 @@ PKG_CHECK_MODULES(LIBGITG, [
libsecret-1
])
+PKG_CHECK_MODULES(XML, [
+ gio-2.0 >= $GLIB_REQUIRED_VERSION
+ libxml-2.0 >= $LIBXML_REQUIRED_VERSION
+])
+
AC_SUBST(GIO_SYSTEM_PKG)
AM_CONDITIONAL(GDK_WINDOWING_X11, test "$gdk_windowing_x11" = "yes")
AM_CONDITIONAL(GDK_WINDOWING_QUARTZ, test "$gdk_windowing_quartz" = "yes")
diff --git a/contrib/ide/Makefile.am b/contrib/ide/Makefile.am
new file mode 100644
index 0000000..c1f83ab
--- /dev/null
+++ b/contrib/ide/Makefile.am
@@ -0,0 +1,26 @@
+noinst_LTLIBRARIES += contrib/ide/libide.la
+
+contrib_ide_libide_la_CPPFLAGS = \
+ -I$(top_srcdir)/contrib/xml
+
+contrib_ide_libide_la_SOURCES = \
+ contrib/ide/ide-doap.c \
+ contrib/ide/ide-doap.h \
+ contrib/ide/ide-doap-person.c \
+ contrib/ide/ide-doap-person.h \
+ contrib/ide/ide.h \
+ $(NULL)
+
+contrib_ide_libide_la_CFLAGS = \
+ $(XML_CFLAGS) \
+ $(NULL)
+
+contrib_ide_libide_la_LIBADD = \
+ $(XML_LIBS) \
+ contrib/xml/libxml.la \
+ $(NULL)
+
+EXTRA_DIST += \
+ contrib/ide/ide.vapi
+
+-include $(top_srcdir)/git.mk
diff --git a/contrib/ide/ide-doap-person.c b/contrib/ide/ide-doap-person.c
new file mode 100644
index 0000000..ca1d84d
--- /dev/null
+++ b/contrib/ide/ide-doap-person.c
@@ -0,0 +1,178 @@
+/* ide-doap-person.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "ide-doap-person.h"
+
+struct _IdeDoapPerson
+{
+ GObject parent_instance;
+
+ gchar *email;
+ gchar *name;
+};
+
+G_DEFINE_TYPE (IdeDoapPerson, ide_doap_person, G_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_EMAIL,
+ PROP_NAME,
+ LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+IdeDoapPerson *
+ide_doap_person_new (void)
+{
+ return g_object_new (IDE_TYPE_DOAP_PERSON, NULL);
+}
+
+const gchar *
+ide_doap_person_get_name (IdeDoapPerson *self)
+{
+ g_return_val_if_fail (IDE_IS_DOAP_PERSON (self), NULL);
+
+ return self->name;
+}
+
+void
+ide_doap_person_set_name (IdeDoapPerson *self,
+ const gchar *name)
+{
+ g_return_if_fail (IDE_IS_DOAP_PERSON (self));
+
+ if (g_strcmp0 (self->name, name) != 0)
+ {
+ g_free (self->name);
+ self->name = g_strdup (name);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_NAME]);
+ }
+}
+
+const gchar *
+ide_doap_person_get_email (IdeDoapPerson *self)
+{
+ g_return_val_if_fail (IDE_IS_DOAP_PERSON (self), NULL);
+
+ return self->email;
+}
+
+void
+ide_doap_person_set_email (IdeDoapPerson *self,
+ const gchar *email)
+{
+ g_return_if_fail (IDE_IS_DOAP_PERSON (self));
+
+ if (g_strcmp0 (self->email, email) != 0)
+ {
+ g_free (self->email);
+ self->email = g_strdup (email);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_EMAIL]);
+ }
+}
+
+static void
+ide_doap_person_finalize (GObject *object)
+{
+ IdeDoapPerson *self = (IdeDoapPerson *)object;
+
+ g_clear_pointer (&self->email, g_free);
+ g_clear_pointer (&self->name, g_free);
+
+ G_OBJECT_CLASS (ide_doap_person_parent_class)->finalize (object);
+}
+
+static void
+ide_doap_person_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeDoapPerson *self = IDE_DOAP_PERSON (object);
+
+ switch (prop_id)
+ {
+ case PROP_EMAIL:
+ g_value_set_string (value, ide_doap_person_get_email (self));
+ break;
+
+ case PROP_NAME:
+ g_value_set_string (value, ide_doap_person_get_name (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_doap_person_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeDoapPerson *self = IDE_DOAP_PERSON (object);
+
+ switch (prop_id)
+ {
+ case PROP_EMAIL:
+ ide_doap_person_set_email (self, g_value_get_string (value));
+ break;
+
+ case PROP_NAME:
+ ide_doap_person_set_name (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_doap_person_class_init (IdeDoapPersonClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = ide_doap_person_finalize;
+ object_class->get_property = ide_doap_person_get_property;
+ object_class->set_property = ide_doap_person_set_property;
+
+ properties [PROP_EMAIL] =
+ g_param_spec_string ("email",
+ "Email",
+ "The email of the person.",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_NAME] =
+ g_param_spec_string ("name",
+ "Name",
+ "The name of the person.",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+ide_doap_person_init (IdeDoapPerson *self)
+{
+}
diff --git a/contrib/ide/ide-doap-person.h b/contrib/ide/ide-doap-person.h
new file mode 100644
index 0000000..c5f6439
--- /dev/null
+++ b/contrib/ide/ide-doap-person.h
@@ -0,0 +1,40 @@
+/* ide-doap-person.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_DOAP_PERSON_H
+#define IDE_DOAP_PERSON_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DOAP_PERSON (ide_doap_person_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeDoapPerson, ide_doap_person, IDE, DOAP_PERSON, GObject)
+
+IdeDoapPerson *ide_doap_person_new (void);
+const gchar *ide_doap_person_get_name (IdeDoapPerson *self);
+void ide_doap_person_set_name (IdeDoapPerson *self,
+ const gchar *name);
+const gchar *ide_doap_person_get_email (IdeDoapPerson *self);
+void ide_doap_person_set_email (IdeDoapPerson *self,
+ const gchar *email);
+
+G_END_DECLS
+
+#endif /* IDE_DOAP_PERSON_H */
diff --git a/contrib/ide/ide-doap.c b/contrib/ide/ide-doap.c
new file mode 100644
index 0000000..1bf0542
--- /dev/null
+++ b/contrib/ide/ide-doap.c
@@ -0,0 +1,630 @@
+/* ide-doap.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-doap"
+
+#include <glib/gi18n.h>
+
+#include "ide-doap.h"
+
+#include "xml-reader.h"
+
+/*
+ * TODO: We don't do any XMLNS checking or anything here.
+ */
+
+struct _IdeDoap
+{
+ GObject parent_instance;
+
+ gchar *bug_database;
+ gchar *category;
+ gchar *description;
+ gchar *download_page;
+ gchar *homepage;;
+ gchar *name;
+ gchar *shortdesc;
+
+ GPtrArray *languages;
+ GList *maintainers;
+};
+
+G_DEFINE_QUARK (ide_doap_error, ide_doap_error)
+G_DEFINE_TYPE (IdeDoap, ide_doap, G_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_BUG_DATABASE,
+ PROP_CATEGORY,
+ PROP_DESCRIPTION,
+ PROP_DOWNLOAD_PAGE,
+ PROP_HOMEPAGE,
+ PROP_LANGUAGES,
+ PROP_NAME,
+ PROP_SHORTDESC,
+ LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+IdeDoap *
+ide_doap_new (void)
+{
+ return g_object_new (IDE_TYPE_DOAP, NULL);
+}
+
+const gchar *
+ide_doap_get_name (IdeDoap *self)
+{
+ g_return_val_if_fail (IDE_IS_DOAP (self), NULL);
+
+ return self->name;
+}
+
+const gchar *
+ide_doap_get_shortdesc (IdeDoap *self)
+{
+ g_return_val_if_fail (IDE_IS_DOAP (self), NULL);
+
+ return self->shortdesc;
+}
+
+const gchar *
+ide_doap_get_description (IdeDoap *self)
+{
+ g_return_val_if_fail (IDE_IS_DOAP (self), NULL);
+
+ return self->description;
+}
+
+const gchar *
+ide_doap_get_bug_database (IdeDoap *self)
+{
+ g_return_val_if_fail (IDE_IS_DOAP (self), NULL);
+
+ return self->bug_database;
+}
+
+const gchar *
+ide_doap_get_download_page (IdeDoap *self)
+{
+ g_return_val_if_fail (IDE_IS_DOAP (self), NULL);
+
+ return self->download_page;
+}
+
+const gchar *
+ide_doap_get_homepage (IdeDoap *self)
+{
+ g_return_val_if_fail (IDE_IS_DOAP (self), NULL);
+
+ return self->homepage;
+}
+
+const gchar *
+ide_doap_get_category (IdeDoap *self)
+{
+ g_return_val_if_fail (IDE_IS_DOAP (self), NULL);
+
+ return self->category;
+}
+
+/**
+ * ide_doap_get_languages:
+ *
+ * Returns: (transfer none): A #GStrv.
+ */
+gchar **
+ide_doap_get_languages (IdeDoap *self)
+{
+ g_return_val_if_fail (IDE_IS_DOAP (self), NULL);
+
+ if (self->languages != NULL)
+ return (gchar **)self->languages->pdata;
+
+ return NULL;
+}
+
+static void
+ide_doap_set_bug_database (IdeDoap *self,
+ const gchar *bug_database)
+{
+ g_return_if_fail (IDE_IS_DOAP (self));
+
+ if (g_strcmp0 (self->bug_database, bug_database) != 0)
+ {
+ g_free (self->bug_database);
+ self->bug_database = g_strdup (bug_database);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUG_DATABASE]);
+ }
+}
+
+static void
+ide_doap_set_category (IdeDoap *self,
+ const gchar *category)
+{
+ g_return_if_fail (IDE_IS_DOAP (self));
+
+ if (g_strcmp0 (self->category, category) != 0)
+ {
+ g_free (self->category);
+ self->category = g_strdup (category);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CATEGORY]);
+ }
+}
+
+static void
+ide_doap_set_description (IdeDoap *self,
+ const gchar *description)
+{
+ g_return_if_fail (IDE_IS_DOAP (self));
+
+ if (g_strcmp0 (self->description, description) != 0)
+ {
+ g_free (self->description);
+ self->description = g_strdup (description);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DESCRIPTION]);
+ }
+}
+
+static void
+ide_doap_set_download_page (IdeDoap *self,
+ const gchar *download_page)
+{
+ g_return_if_fail (IDE_IS_DOAP (self));
+
+ if (g_strcmp0 (self->download_page, download_page) != 0)
+ {
+ g_free (self->download_page);
+ self->download_page = g_strdup (download_page);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DOWNLOAD_PAGE]);
+ }
+}
+
+static void
+ide_doap_set_homepage (IdeDoap *self,
+ const gchar *homepage)
+{
+ g_return_if_fail (IDE_IS_DOAP (self));
+
+ if (g_strcmp0 (self->homepage, homepage) != 0)
+ {
+ g_free (self->homepage);
+ self->homepage = g_strdup (homepage);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HOMEPAGE]);
+ }
+}
+
+static void
+ide_doap_set_name (IdeDoap *self,
+ const gchar *name)
+{
+ g_return_if_fail (IDE_IS_DOAP (self));
+
+ if (g_strcmp0 (self->name, name) != 0)
+ {
+ g_free (self->name);
+ self->name = g_strdup (name);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_NAME]);
+ }
+}
+
+static void
+ide_doap_set_shortdesc (IdeDoap *self,
+ const gchar *shortdesc)
+{
+ g_return_if_fail (IDE_IS_DOAP (self));
+
+ if (g_strcmp0 (self->shortdesc, shortdesc) != 0)
+ {
+ g_free (self->shortdesc);
+ self->shortdesc = g_strdelimit (g_strdup (shortdesc), "\n", ' ');
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHORTDESC]);
+ }
+}
+
+/**
+ * ide_doap_get_maintainers:
+ *
+ *
+ *
+ * Returns: (transfer none) (element-type IdeDoapPerson*): A #GList of #IdeDoapPerson.
+ */
+GList *
+ide_doap_get_maintainers (IdeDoap *self)
+{
+ g_return_val_if_fail (IDE_IS_DOAP (self), NULL);
+
+ return self->maintainers;
+}
+
+static void
+ide_doap_add_language (IdeDoap *self,
+ const gchar *language)
+{
+ g_return_if_fail (IDE_IS_DOAP (self));
+ g_return_if_fail (language != NULL);
+
+ if (self->languages == NULL)
+ {
+ self->languages = g_ptr_array_new_with_free_func (g_free);
+ g_ptr_array_add (self->languages, NULL);
+ }
+
+ g_assert (self->languages->len > 0);
+
+ g_ptr_array_index (self->languages, self->languages->len - 1) = g_strdup (language);
+ g_ptr_array_add (self->languages, NULL);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LANGUAGES]);
+}
+
+static void
+ide_doap_set_languages (IdeDoap *self,
+ gchar **languages)
+{
+ gsize i;
+
+ g_return_if_fail (IDE_IS_DOAP (self));
+
+ if ((self->languages != NULL) && (self->languages->len > 0))
+ g_ptr_array_remove_range (self->languages, 0, self->languages->len);
+
+ g_object_freeze_notify (G_OBJECT (self));
+ for (i = 0; languages [i]; i++)
+ ide_doap_add_language (self, languages [i]);
+ g_object_thaw_notify (G_OBJECT (self));
+}
+
+static void
+ide_doap_finalize (GObject *object)
+{
+ IdeDoap *self = (IdeDoap *)object;
+
+ g_clear_pointer (&self->bug_database, g_free);
+ g_clear_pointer (&self->category, g_free);
+ g_clear_pointer (&self->description, g_free);
+ g_clear_pointer (&self->download_page, g_free);
+ g_clear_pointer (&self->homepage, g_free);
+ g_clear_pointer (&self->languages, g_ptr_array_unref);
+ g_clear_pointer (&self->name, g_free);
+ g_clear_pointer (&self->shortdesc, g_free);
+
+ g_list_free_full (self->maintainers, g_object_unref);
+ self->maintainers = NULL;
+
+ G_OBJECT_CLASS (ide_doap_parent_class)->finalize (object);
+}
+
+static void
+ide_doap_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeDoap *self = IDE_DOAP (object);
+
+ switch (prop_id)
+ {
+ case PROP_BUG_DATABASE:
+ g_value_set_string (value, ide_doap_get_bug_database (self));
+ break;
+
+ case PROP_CATEGORY:
+ g_value_set_string (value, ide_doap_get_category (self));
+ break;
+
+ case PROP_DESCRIPTION:
+ g_value_set_string (value, ide_doap_get_description (self));
+ break;
+
+ case PROP_DOWNLOAD_PAGE:
+ g_value_set_string (value, ide_doap_get_download_page (self));
+ break;
+
+ case PROP_HOMEPAGE:
+ g_value_set_string (value, ide_doap_get_homepage (self));
+ break;
+
+ case PROP_LANGUAGES:
+ g_value_set_boxed (value, ide_doap_get_languages (self));
+ break;
+
+ case PROP_NAME:
+ g_value_set_string (value, ide_doap_get_name (self));
+ break;
+
+ case PROP_SHORTDESC:
+ g_value_set_string (value, ide_doap_get_shortdesc (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_doap_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeDoap *self = IDE_DOAP (object);
+
+ switch (prop_id)
+ {
+ case PROP_BUG_DATABASE:
+ ide_doap_set_bug_database (self, g_value_get_string (value));
+ break;
+
+ case PROP_CATEGORY:
+ ide_doap_set_category (self, g_value_get_string (value));
+ break;
+
+ case PROP_DESCRIPTION:
+ ide_doap_set_description (self, g_value_get_string (value));
+ break;
+
+ case PROP_DOWNLOAD_PAGE:
+ ide_doap_set_download_page (self, g_value_get_string (value));
+ break;
+
+ case PROP_HOMEPAGE:
+ ide_doap_set_homepage (self, g_value_get_string (value));
+ break;
+
+ case PROP_LANGUAGES:
+ ide_doap_set_languages (self, g_value_get_boxed (value));
+ break;
+
+ case PROP_NAME:
+ ide_doap_set_name (self, g_value_get_string (value));
+ break;
+
+ case PROP_SHORTDESC:
+ ide_doap_set_shortdesc (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_doap_class_init (IdeDoapClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = ide_doap_finalize;
+ object_class->get_property = ide_doap_get_property;
+ object_class->set_property = ide_doap_set_property;
+
+ properties [PROP_BUG_DATABASE] =
+ g_param_spec_string ("bug-database",
+ "Bug Database",
+ "Bug Database",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_CATEGORY] =
+ g_param_spec_string ("category",
+ "Category",
+ "Category",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_DESCRIPTION] =
+ g_param_spec_string ("description",
+ "Description",
+ "Description",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_DOWNLOAD_PAGE] =
+ g_param_spec_string ("download-page",
+ "Download Page",
+ "Download Page",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_HOMEPAGE] =
+ g_param_spec_string ("homepage",
+ "Homepage",
+ "Homepage",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_LANGUAGES] =
+ g_param_spec_string ("languages",
+ "Languages",
+ "Languages",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_NAME] =
+ g_param_spec_string ("name",
+ "Name",
+ "Name",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_SHORTDESC] =
+ g_param_spec_string ("shortdesc",
+ "Shortdesc",
+ "Shortdesc",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+ide_doap_init (IdeDoap *self)
+{
+}
+
+static gboolean
+ide_doap_parse_maintainer (IdeDoap *self,
+ XmlReader *reader)
+{
+ g_assert (IDE_IS_DOAP (self));
+ g_assert (XML_IS_READER (reader));
+
+ if (!xml_reader_read (reader))
+ return FALSE;
+
+ do
+ {
+ if (xml_reader_is_a_local (reader, "Person") && xml_reader_read (reader))
+ {
+ g_autoptr(IdeDoapPerson) person = ide_doap_person_new ();
+
+ do
+ {
+ if (xml_reader_is_a_local (reader, "name"))
+ {
+ ide_doap_person_set_name (person, xml_reader_read_string (reader));
+ }
+ else if (xml_reader_is_a_local (reader, "mbox"))
+ {
+ gchar *str;
+
+ str = xml_reader_get_attribute (reader, "rdf:resource");
+ if (str != NULL && str[0] != '\0' && g_str_has_prefix (str, "mailto:"))
+ ide_doap_person_set_email (person, str + strlen ("mailto:"));
+ g_free (str);
+ }
+ }
+ while (xml_reader_read_to_next (reader));
+
+ if (ide_doap_person_get_name (person) || ide_doap_person_get_email (person))
+ self->maintainers = g_list_append (self->maintainers, g_object_ref (person));
+ }
+ }
+ while (xml_reader_read_to_next (reader));
+
+ return TRUE;
+}
+
+static gboolean
+load_doap (IdeDoap *self,
+ XmlReader *reader,
+ GError **error)
+{
+ if (!xml_reader_read_start_element (reader, "Project"))
+ {
+ g_set_error (error,
+ IDE_DOAP_ERROR,
+ IDE_DOAP_ERROR_INVALID_FORMAT,
+ "Project element is missing from doap.");
+ return FALSE;
+ }
+
+ g_object_freeze_notify (G_OBJECT (self));
+
+ xml_reader_read (reader);
+
+ do
+ {
+ const gchar *element_name;
+
+ element_name = xml_reader_get_local_name (reader);
+
+ if (g_strcmp0 (element_name, "name") == 0 ||
+ g_strcmp0 (element_name, "shortdesc") == 0 ||
+ g_strcmp0 (element_name, "description") == 0)
+ {
+ gchar *str;
+
+ str = xml_reader_read_string (reader);
+ if (str != NULL)
+ g_object_set (self, element_name, g_strstrip (str), NULL);
+ g_free (str);
+ }
+ else if (g_strcmp0 (element_name, "category") == 0 ||
+ g_strcmp0 (element_name, "homepage") == 0 ||
+ g_strcmp0 (element_name, "download-page") == 0 ||
+ g_strcmp0 (element_name, "bug-database") == 0)
+ {
+ gchar *str;
+
+ str = xml_reader_get_attribute (reader, "rdf:resource");
+ if (str != NULL)
+ g_object_set (self, element_name, g_strstrip (str), NULL);
+ g_free (str);
+ }
+ else if (g_strcmp0 (element_name, "programming-language") == 0)
+ {
+ gchar *str;
+
+ str = xml_reader_read_string (reader);
+ if (str != NULL && str[0] != '\0')
+ ide_doap_add_language (self, g_strstrip (str));
+ g_free (str);
+ }
+ else if (g_strcmp0 (element_name, "maintainer") == 0)
+ {
+ if (!ide_doap_parse_maintainer (self, reader))
+ break;
+ }
+ }
+ while (xml_reader_read_to_next (reader));
+
+ g_object_thaw_notify (G_OBJECT (self));
+
+ return TRUE;
+}
+
+gboolean
+ide_doap_load_from_file (IdeDoap *self,
+ GFile *file,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(XmlReader) reader = NULL;
+
+ g_return_val_if_fail (IDE_IS_DOAP (self), FALSE);
+ g_return_val_if_fail (G_IS_FILE (file), FALSE);
+ g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+ reader = xml_reader_new ();
+
+ if (!xml_reader_load_from_file (reader, file, cancellable, error))
+ return FALSE;
+
+ return load_doap (self, reader, error);
+}
+
+gboolean
+ide_doap_load_from_data (IdeDoap *self,
+ const gchar *data,
+ gsize length,
+ GError **error)
+{
+ g_autoptr(XmlReader) reader = NULL;
+
+ g_return_val_if_fail (IDE_IS_DOAP (self), FALSE);
+ g_return_val_if_fail (data != NULL, FALSE);
+
+ reader = xml_reader_new ();
+
+ if (!xml_reader_load_from_data (reader, (const gchar *)data, length, NULL, NULL))
+ return FALSE;
+
+ return load_doap (self, reader, error);
+}
diff --git a/contrib/ide/ide-doap.h b/contrib/ide/ide-doap.h
new file mode 100644
index 0000000..0751313
--- /dev/null
+++ b/contrib/ide/ide-doap.h
@@ -0,0 +1,60 @@
+/* ide-doap.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_DOAP_H
+#define IDE_DOAP_H
+
+#include <gio/gio.h>
+
+#include "ide-doap-person.h"
+
+G_BEGIN_DECLS
+
+#define IDE_DOAP_ERROR (ide_doap_error_quark())
+#define IDE_TYPE_DOAP (ide_doap_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeDoap, ide_doap, IDE, DOAP, GObject)
+
+typedef enum
+{
+ IDE_DOAP_ERROR_INVALID_FORMAT = 1,
+} IdeDoapError;
+
+IdeDoap *ide_doap_new (void);
+GQuark ide_doap_error_quark (void);
+gboolean ide_doap_load_from_file (IdeDoap *self,
+ GFile *file,
+ GCancellable *cancellable,
+ GError **error);
+gboolean ide_doap_load_from_data (IdeDoap *self,
+ const gchar *data,
+ gsize length,
+ GError **error);
+const gchar *ide_doap_get_name (IdeDoap *self);
+const gchar *ide_doap_get_shortdesc (IdeDoap *self);
+const gchar *ide_doap_get_description (IdeDoap *self);
+const gchar *ide_doap_get_bug_database (IdeDoap *self);
+const gchar *ide_doap_get_download_page (IdeDoap *self);
+const gchar *ide_doap_get_homepage (IdeDoap *self);
+const gchar *ide_doap_get_category (IdeDoap *self);
+gchar **ide_doap_get_languages (IdeDoap *self);
+GList *ide_doap_get_maintainers (IdeDoap *self);
+
+G_END_DECLS
+
+#endif /* IDE_DOAP_H */
diff --git a/contrib/ide/ide.h b/contrib/ide/ide.h
new file mode 100644
index 0000000..be0ee0c
--- /dev/null
+++ b/contrib/ide/ide.h
@@ -0,0 +1,31 @@
+/* ide.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_H
+#define IDE_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#include "ide-doap.h"
+#include "ide-doap-person.h"
+
+G_END_DECLS
+
+#endif /* IDE_H */
diff --git a/contrib/ide/ide.vapi b/contrib/ide/ide.vapi
new file mode 100644
index 0000000..c14fc63
--- /dev/null
+++ b/contrib/ide/ide.vapi
@@ -0,0 +1,48 @@
+// FIXME: in the future we might want to automatically generate this
+[CCode (cprefix = "Ide", gir_namespace = "Ide", gir_version = "1.0", lower_case_cprefix = "ide_")]
+namespace Ide {
+ [CCode (cheader_filename = "ide.h", type_id = "ide_doap_get_type ()")]
+ public class Doap : GLib.Object {
+ [CCode (has_construct_function = false)]
+ public Doap ();
+ public unowned string get_bug_database ();
+ public unowned string get_category ();
+ public unowned string get_description ();
+ public unowned string get_download_page ();
+ public unowned string get_homepage ();
+ [CCode (array_length = false, array_null_terminated = true)]
+ public unowned string[] get_languages ();
+ public unowned GLib.List<Ide.DoapPerson> get_maintainers ();
+ public unowned string get_name ();
+ public unowned string get_shortdesc ();
+ public bool load_from_file (GLib.File file, GLib.Cancellable? cancellable = null) throws
GLib.Error;
+ public bool load_from_data (string data, size_t length) throws GLib.Error;
+ [NoAccessorMethod]
+ public string bug_database { owned get; set; }
+ [NoAccessorMethod]
+ public string category { owned get; set; }
+ [NoAccessorMethod]
+ public string description { owned get; set; }
+ [NoAccessorMethod]
+ public string download_page { owned get; set; }
+ [NoAccessorMethod]
+ public string homepage { owned get; set; }
+ [NoAccessorMethod]
+ public string languages { owned get; set; }
+ [NoAccessorMethod]
+ public string name { owned get; set; }
+ [NoAccessorMethod]
+ public string shortdesc { owned get; set; }
+ }
+ [CCode (cheader_filename = "ide.h", type_id = "ide_doap_person_get_type ()")]
+ public class DoapPerson : GLib.Object {
+ [CCode (has_construct_function = false)]
+ public DoapPerson ();
+ public unowned string get_email ();
+ public unowned string get_name ();
+ public void set_email (string email);
+ public void set_name (string name);
+ public string email { get; set; }
+ public string name { get; set; }
+ }
+}
diff --git a/contrib/xml/Makefile.am b/contrib/xml/Makefile.am
new file mode 100644
index 0000000..487fa52
--- /dev/null
+++ b/contrib/xml/Makefile.am
@@ -0,0 +1,11 @@
+noinst_LTLIBRARIES += contrib/xml/libxml.la
+
+contrib_xml_libxml_la_SOURCES = \
+ contrib/xml/xml-reader.c \
+ contrib/xml/xml-reader.h \
+ $(NULL)
+
+contrib_xml_libxml_la_CFLAGS = $(XML_CFLAGS)
+contrib_xml_libxml_la_LIBADD = $(XML_LIBS)
+
+-include $(top_srcdir)/git.mk
diff --git a/contrib/xml/xml-reader.c b/contrib/xml/xml-reader.c
new file mode 100644
index 0000000..3e783a7
--- /dev/null
+++ b/contrib/xml/xml-reader.c
@@ -0,0 +1,597 @@
+/* xml-reader.c
+ *
+ * Copyright (C) 2009 Christian Hergert <chris dronelabs com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * Author:
+ * Christian Hergert <chris dronelabs com>
+ */
+
+#include <glib/gi18n.h>
+#include <string.h>
+#include <libxml/xmlreader.h>
+
+#include "xml-reader.h"
+
+#define XML_TO_CHAR(s) ((char *) (s))
+#define CHAR_TO_XML(s) ((unsigned char *) (s))
+#define RETURN_STRDUP_AND_XMLFREE(stmt) \
+ G_STMT_START { \
+ guchar *x; \
+ gchar *y; \
+ x = stmt; \
+ y = g_strdup((char *)x); \
+ xmlFree(x); \
+ return y; \
+ } G_STMT_END
+
+struct _XmlReader
+{
+ GObject parent_instance;
+ xmlTextReaderPtr xml;
+ GInputStream *stream;
+ gchar *cur_name;
+ gchar *encoding;
+ gchar *uri;
+};
+
+enum {
+ PROP_0,
+ PROP_ENCODING,
+ PROP_URI,
+ LAST_PROP
+};
+
+enum {
+ ERROR,
+ LAST_SIGNAL
+};
+
+G_DEFINE_QUARK (xml_reader_error, xml_reader_error)
+G_DEFINE_TYPE (XmlReader, xml_reader, G_TYPE_OBJECT)
+
+static GParamSpec *properties [LAST_PROP];
+static guint signals [LAST_SIGNAL];
+
+#define XML_NODE_TYPE_ELEMENT 1
+#define XML_NODE_TYPE_END_ELEMENT 15
+#define XML_NODE_TYPE_ATTRIBUTE 2
+
+static void
+xml_reader_set_encoding (XmlReader *reader,
+ const gchar *encoding)
+{
+ g_return_if_fail (XML_IS_READER (reader));
+ g_free (reader->encoding);
+ reader->encoding = g_strdup (encoding);
+}
+
+static void
+xml_reader_set_uri (XmlReader *reader,
+ const gchar *uri)
+{
+ g_return_if_fail (XML_IS_READER (reader));
+ g_free (reader->uri);
+ reader->uri = g_strdup (uri);
+}
+
+static void
+xml_reader_clear (XmlReader *reader)
+{
+ g_return_if_fail(XML_IS_READER(reader));
+
+ g_free (reader->cur_name);
+ reader->cur_name = NULL;
+
+ if (reader->xml) {
+ xmlTextReaderClose(reader->xml);
+ xmlFreeTextReader(reader->xml);
+ reader->xml = NULL;
+ }
+
+ if (reader->stream) {
+ g_object_unref(reader->stream);
+ reader->stream = NULL;
+ }
+}
+
+static void
+xml_reader_finalize (GObject *object)
+{
+ XmlReader *reader = (XmlReader *)object;
+
+ xml_reader_clear (reader);
+
+ g_free (reader->encoding);
+ reader->encoding = NULL;
+
+ g_free (reader->uri);
+ reader->uri = NULL;
+
+ G_OBJECT_CLASS (xml_reader_parent_class)->finalize (object);
+}
+
+static void
+xml_reader_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ XmlReader *reader = (XmlReader *)object;
+
+ switch (prop_id)
+ {
+ case PROP_ENCODING:
+ g_value_set_string (value, reader->encoding);
+ break;
+
+ case PROP_URI:
+ g_value_set_string (value, reader->uri);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void
+xml_reader_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ XmlReader *reader = (XmlReader *)object;
+
+ switch (prop_id)
+ {
+ case PROP_ENCODING:
+ xml_reader_set_encoding (reader, g_value_get_string (value));
+ break;
+
+ case PROP_URI:
+ xml_reader_set_uri (reader, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void
+xml_reader_class_init (XmlReaderClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = xml_reader_finalize;
+ object_class->get_property = xml_reader_get_property;
+ object_class->set_property = xml_reader_set_property;
+
+ properties [PROP_ENCODING] =
+ g_param_spec_string ("encoding",
+ "Encoding",
+ "Encoding",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_URI] =
+ g_param_spec_string ("uri",
+ "URI",
+ "URI",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ signals [ERROR] =
+ g_signal_new ("error",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_STRING);
+}
+
+static void
+xml_reader_init (XmlReader *reader)
+{
+}
+
+XmlReader*
+xml_reader_new (void)
+{
+ return g_object_new (XML_TYPE_READER, NULL);
+}
+
+static void
+xml_reader_error_cb (void *arg,
+ const char *msg,
+ xmlParserSeverities severity,
+ xmlTextReaderLocatorPtr locator)
+{
+ XmlReader *reader = arg;
+
+ g_assert (XML_IS_READER (reader));
+
+ g_signal_emit (reader, signals [ERROR], 0, msg);
+}
+
+gboolean
+xml_reader_load_from_path (XmlReader *reader,
+ const gchar *path)
+{
+ g_return_val_if_fail (XML_IS_READER (reader), FALSE);
+
+ xml_reader_clear (reader);
+
+ if ((reader->xml = xmlNewTextReaderFilename (path)))
+ xmlTextReaderSetErrorHandler (reader->xml, xml_reader_error_cb, reader);
+
+ return (reader->xml != NULL);
+}
+
+gboolean
+xml_reader_load_from_file (XmlReader *reader,
+ GFile *file,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GFileInputStream *stream;
+ gboolean ret;
+
+ g_return_val_if_fail (XML_IS_READER (reader), FALSE);
+ g_return_val_if_fail (G_IS_FILE (file), FALSE);
+ g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+ if (!(stream = g_file_read (file, cancellable, error)))
+ return FALSE;
+
+ ret = xml_reader_load_from_stream (reader, G_INPUT_STREAM (stream), error);
+
+ g_clear_object (&stream);
+
+ return ret;
+}
+
+gboolean
+xml_reader_load_from_data (XmlReader *reader,
+ const gchar *data,
+ gsize length,
+ const gchar *uri,
+ const gchar *encoding)
+{
+ g_return_val_if_fail (XML_IS_READER (reader), FALSE);
+
+ xml_reader_clear (reader);
+
+ if (length == -1)
+ length = strlen (data);
+
+ reader->xml = xmlReaderForMemory (data, length, uri, encoding, 0);
+ xmlTextReaderSetErrorHandler (reader->xml, xml_reader_error_cb, reader);
+
+ return (reader->xml != NULL);
+}
+
+static int
+xml_reader_io_read_cb (void *context,
+ char *buffer,
+ int len)
+{
+ GInputStream *stream = (GInputStream *)context;
+ g_return_val_if_fail (G_IS_INPUT_STREAM(stream), -1);
+ return g_input_stream_read (stream, buffer, len, NULL, NULL);
+}
+
+static int
+xml_reader_io_close_cb (void *context)
+{
+ GInputStream *stream = (GInputStream *)context;
+
+ g_return_val_if_fail (G_IS_INPUT_STREAM(stream), -1);
+
+ return g_input_stream_close (stream, NULL, NULL) ? 0 : -1;
+}
+
+gboolean
+xml_reader_load_from_stream (XmlReader *reader,
+ GInputStream *stream,
+ GError **error)
+{
+ g_return_val_if_fail (XML_IS_READER(reader), FALSE);
+
+ xml_reader_clear (reader);
+
+ reader->xml = xmlReaderForIO (xml_reader_io_read_cb,
+ xml_reader_io_close_cb,
+ stream,
+ reader->uri,
+ reader->encoding,
+ XML_PARSE_RECOVER | XML_PARSE_NOBLANKS | XML_PARSE_COMPACT);
+
+ if (!reader->xml)
+ {
+ g_set_error (error,
+ XML_READER_ERROR,
+ XML_READER_ERROR_INVALID,
+ _("Could not parse XML from stream"));
+ return FALSE;
+ }
+
+ reader->stream = g_object_ref (stream);
+
+ xmlTextReaderSetErrorHandler (reader->xml, xml_reader_error_cb, reader);
+
+ return TRUE;
+}
+
+G_CONST_RETURN gchar *
+xml_reader_get_value (XmlReader *reader)
+{
+ g_return_val_if_fail (XML_IS_READER (reader), NULL);
+
+ g_return_val_if_fail (reader->xml != NULL, NULL);
+
+ return XML_TO_CHAR (xmlTextReaderConstValue (reader->xml));
+}
+
+G_CONST_RETURN gchar *
+xml_reader_get_name (XmlReader *reader)
+{
+ g_return_val_if_fail (XML_IS_READER (reader), NULL);
+ g_return_val_if_fail (reader->xml != NULL, NULL);
+
+ return XML_TO_CHAR (xmlTextReaderConstName (reader->xml));
+}
+
+gchar *
+xml_reader_read_string (XmlReader *reader)
+{
+ g_return_val_if_fail (XML_IS_READER (reader), NULL);
+ g_return_val_if_fail (reader->xml != NULL, NULL);
+
+ RETURN_STRDUP_AND_XMLFREE (xmlTextReaderReadString (reader->xml));
+}
+
+gchar *
+xml_reader_get_attribute (XmlReader *reader,
+ const gchar *name)
+{
+ g_return_val_if_fail (XML_IS_READER (reader), NULL);
+ g_return_val_if_fail (reader->xml != NULL, NULL);
+
+ RETURN_STRDUP_AND_XMLFREE (xmlTextReaderGetAttribute (reader->xml, CHAR_TO_XML (name)));
+}
+
+static gboolean
+read_to_type_and_name (XmlReader *reader,
+ gint type,
+ const gchar *name)
+{
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (XML_IS_READER (reader), FALSE);
+
+ g_return_val_if_fail (reader->xml != NULL, FALSE);
+
+ while (xmlTextReaderRead (reader->xml) == 1)
+ {
+ if (xmlTextReaderNodeType (reader->xml) == type)
+ {
+ if (g_strcmp0 (XML_TO_CHAR (xmlTextReaderConstName (reader->xml)), name) == 0)
+ {
+ success = TRUE;
+ break;
+ }
+ }
+ }
+
+ return success;
+}
+
+gboolean
+xml_reader_read_start_element (XmlReader *reader,
+ const gchar *name)
+{
+ g_return_val_if_fail (XML_IS_READER (reader), FALSE);
+
+ if (read_to_type_and_name (reader, XML_NODE_TYPE_ELEMENT, name))
+ {
+ g_free (reader->cur_name);
+ reader->cur_name = g_strdup (name);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+xml_reader_read_end_element (XmlReader *reader)
+{
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (XML_IS_READER (reader), FALSE);
+
+ if (reader->cur_name)
+ success = read_to_type_and_name (reader, XML_NODE_TYPE_END_ELEMENT, reader->cur_name);
+
+ return success;
+}
+
+gchar *
+xml_reader_read_inner_xml (XmlReader *reader)
+{
+ g_return_val_if_fail (XML_IS_READER (reader), FALSE);
+
+ RETURN_STRDUP_AND_XMLFREE (xmlTextReaderReadInnerXml (reader->xml));
+}
+
+gchar*
+xml_reader_read_outer_xml (XmlReader *reader)
+{
+ g_return_val_if_fail (XML_IS_READER (reader), FALSE);
+
+ RETURN_STRDUP_AND_XMLFREE (xmlTextReaderReadOuterXml (reader->xml));
+}
+
+gboolean
+xml_reader_read_to_next (XmlReader *reader)
+{
+ g_return_val_if_fail (XML_IS_READER (reader), FALSE);
+
+ return (xmlTextReaderNext (reader->xml) == 1);
+}
+
+gboolean
+xml_reader_read (XmlReader *reader)
+{
+ g_return_val_if_fail (XML_IS_READER (reader), FALSE);
+
+ return (xmlTextReaderRead (reader->xml) == 1);
+}
+
+gboolean
+xml_reader_read_to_next_sibling (XmlReader *reader)
+{
+ g_return_val_if_fail (XML_IS_READER (reader), FALSE);
+
+ xmlTextReaderMoveToElement (reader->xml);
+
+ return (xmlTextReaderNextSibling (reader->xml) == 1);
+}
+
+gint
+xml_reader_get_depth (XmlReader *reader)
+{
+ g_return_val_if_fail (XML_IS_READER(reader), -1);
+
+ return xmlTextReaderDepth (reader->xml);
+}
+
+void
+xml_reader_move_up_to_depth (XmlReader *reader,
+ gint depth)
+{
+ g_return_if_fail(XML_IS_READER(reader));
+
+ while (xml_reader_get_depth(reader) > depth)
+ xml_reader_read_end_element(reader);
+}
+
+xmlReaderTypes
+xml_reader_get_node_type (XmlReader *reader)
+{
+ g_return_val_if_fail (XML_IS_READER (reader), 0);
+
+ return xmlTextReaderNodeType (reader->xml);
+}
+
+gboolean
+xml_reader_is_empty_element (XmlReader *reader)
+{
+ g_return_val_if_fail (XML_IS_READER (reader), FALSE);
+
+ return xmlTextReaderIsEmptyElement (reader->xml);
+}
+
+gboolean
+xml_reader_is_a (XmlReader *reader,
+ const gchar *name)
+{
+ return (g_strcmp0 (xml_reader_get_name (reader), name) == 0);
+}
+
+gboolean
+xml_reader_is_a_local (XmlReader *reader,
+ const gchar *local_name)
+{
+ return (g_strcmp0 (xml_reader_get_local_name (reader), local_name) == 0);
+}
+
+gboolean
+xml_reader_is_namespace (XmlReader *reader,
+ const gchar *ns)
+{
+ g_return_val_if_fail (XML_IS_READER (reader), FALSE);
+
+ return (g_strcmp0 (XML_TO_CHAR(xmlTextReaderConstNamespaceUri (reader->xml)), ns) == 0);
+}
+
+G_CONST_RETURN gchar *
+xml_reader_get_local_name (XmlReader *reader)
+{
+ g_return_val_if_fail(XML_IS_READER (reader), NULL);
+
+ return XML_TO_CHAR (xmlTextReaderConstLocalName (reader->xml));
+}
+
+gboolean
+xml_reader_move_to_element (XmlReader *reader)
+{
+ g_return_val_if_fail (XML_IS_READER (reader), FALSE);
+
+ return (xmlTextReaderMoveToElement (reader->xml) == 1);
+}
+
+gboolean
+xml_reader_move_to_attribute (XmlReader *reader,
+ const gchar *name)
+{
+ g_return_val_if_fail (XML_IS_READER (reader), FALSE);
+
+ return (xmlTextReaderMoveToAttribute (reader->xml, CHAR_TO_XML (name)) == 1);
+}
+
+gboolean
+xml_reader_move_to_first_attribute (XmlReader *reader)
+{
+ g_return_val_if_fail (XML_IS_READER (reader), FALSE);
+
+ return (xmlTextReaderMoveToFirstAttribute (reader->xml) == 1);
+}
+
+gboolean
+xml_reader_move_to_next_attribute (XmlReader *reader)
+{
+ g_return_val_if_fail (XML_IS_READER (reader), FALSE);
+
+ return (xmlTextReaderMoveToNextAttribute (reader->xml) == 1);
+}
+
+gboolean
+xml_reader_move_to_nth_attribute (XmlReader *reader,
+ gint nth)
+{
+ g_return_val_if_fail (XML_IS_READER (reader), FALSE);
+
+ return (xmlTextReaderMoveToAttributeNo (reader->xml, nth) == 1);
+}
+
+gint
+xml_reader_count_attributes (XmlReader *reader)
+{
+ g_return_val_if_fail (XML_IS_READER (reader), FALSE);
+
+ return xmlTextReaderAttributeCount (reader->xml);
+}
+
+gint
+xml_reader_get_line_number (XmlReader *reader)
+{
+ g_return_val_if_fail(XML_IS_READER(reader), -1);
+
+ if (reader->xml)
+ return xmlTextReaderGetParserLineNumber(reader->xml);
+
+ return -1;
+}
diff --git a/contrib/xml/xml-reader.h b/contrib/xml/xml-reader.h
new file mode 100644
index 0000000..cfdfa53
--- /dev/null
+++ b/contrib/xml/xml-reader.h
@@ -0,0 +1,100 @@
+/* xml-reader.h
+ *
+ * Copyright (C) 2009 Christian Hergert <chris dronelabs com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * Author:
+ * Christian Hergert <chris dronelabs com>
+ *
+ * Based upon work by:
+ * Emmanuele Bassi
+ */
+
+#ifndef XML_READER_H
+#define XML_READER_H
+
+#include <gio/gio.h>
+#include <libxml/xmlreader.h>
+
+G_BEGIN_DECLS
+
+#define XML_TYPE_READER (xml_reader_get_type ())
+
+#define XML_READER_ERROR (xml_reader_error_quark())
+
+G_DECLARE_FINAL_TYPE (XmlReader, xml_reader, XML, READER, GObject)
+
+typedef enum
+{
+ XML_READER_ERROR_INVALID,
+} XmlReaderError;
+
+GQuark xml_reader_error_quark (void);
+XmlReader *xml_reader_new (void);
+gboolean xml_reader_load_from_path (XmlReader *reader,
+ const gchar *path);
+gboolean xml_reader_load_from_file (XmlReader *reader,
+ GFile *file,
+ GCancellable *cancellable,
+ GError **error);
+gboolean xml_reader_load_from_data (XmlReader *reader,
+ const gchar *data,
+ gsize length,
+ const gchar *uri,
+ const gchar *encoding);
+gboolean xml_reader_load_from_stream (XmlReader *reader,
+ GInputStream *stream,
+ GError **error);
+
+gint xml_reader_get_depth (XmlReader *reader);
+xmlReaderTypes xml_reader_get_node_type (XmlReader *reader);
+const gchar *xml_reader_get_value (XmlReader *reader);
+const gchar *xml_reader_get_name (XmlReader *reader);
+const gchar *xml_reader_get_local_name (XmlReader *reader);
+gchar *xml_reader_read_string (XmlReader *reader);
+gchar *xml_reader_get_attribute (XmlReader *reader,
+ const gchar *name);
+gboolean xml_reader_is_a (XmlReader *reader,
+ const gchar *name);
+gboolean xml_reader_is_a_local (XmlReader *reader,
+ const gchar *local_name);
+gboolean xml_reader_is_namespace (XmlReader *reader,
+ const gchar *ns);
+gboolean xml_reader_is_empty_element (XmlReader *reader);
+
+gboolean xml_reader_read_start_element (XmlReader *reader,
+ const gchar *name);
+gboolean xml_reader_read_end_element (XmlReader *reader);
+
+gchar *xml_reader_read_inner_xml (XmlReader *reader);
+gchar *xml_reader_read_outer_xml (XmlReader *reader);
+
+gboolean xml_reader_read (XmlReader *reader);
+gboolean xml_reader_read_to_next (XmlReader *reader);
+gboolean xml_reader_read_to_next_sibling (XmlReader *reader);
+
+gboolean xml_reader_move_to_element (XmlReader *reader);
+gboolean xml_reader_move_to_attribute (XmlReader *reader,
+ const gchar *name);
+void xml_reader_move_up_to_depth (XmlReader *reader,
+ gint depth);
+
+gboolean xml_reader_move_to_first_attribute (XmlReader *reader);
+gboolean xml_reader_move_to_next_attribute (XmlReader *reader);
+gint xml_reader_count_attributes (XmlReader *reader);
+gboolean xml_reader_move_to_nth_attribute (XmlReader *reader,
+ gint nth);
+gint xml_reader_get_line_number (XmlReader *reader);
+
+G_END_DECLS
+
+#endif /* XML_READER_H */
diff --git a/gitg/resources/ui/gitg-dash-view.ui b/gitg/resources/ui/gitg-dash-view.ui
index f74b43e..a1959f4 100644
--- a/gitg/resources/ui/gitg-dash-view.ui
+++ b/gitg/resources/ui/gitg-dash-view.ui
@@ -11,9 +11,6 @@
<property name="halign">center</property>
<property name="hexpand">True</property>
<property name="can_focus">False</property>
- <style>
- <class name="dim-label"/>
- </style>
<child>
<object class="GtkImage" id="gitg_icon">
<property name="visible">True</property>
@@ -104,16 +101,22 @@
<property name="visible">False</property>
<property name="vexpand">True</property>
<property name="hexpand">True</property>
- <style>
- <class name="view"/>
- </style>
<child>
- <object class="GitgRepositoryListBox" id="repository_list_box">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <style>
- <class name="view"/>
- </style>
+ <object class="GtkFrame" id="repository_list_frame">
+ <property name="halign">center</property>
+ <property name="visible">true</property>
+ <property name="margin-bottom">32</property>
+ <property name="margin-top">32</property>
+ <property name="width-request">550</property>
+ <child>
+ <object class="GitgRepositoryListBox" id="repository_list_box">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <style>
+ <class name="view"/>
+ </style>
+ </object>
+ </child>
</object>
</child>
</object>
diff --git a/libgitg/Makefile.am b/libgitg/Makefile.am
index 7456787..8262f0e 100644
--- a/libgitg/Makefile.am
+++ b/libgitg/Makefile.am
@@ -4,6 +4,7 @@ libgitgexec_LTLIBRARIES = libgitg/libgitg-1.0.la
libgitg_libgitg_1_0_la_CPPFLAGS = \
-I$(top_srcdir) \
-I$(srcdir) \
+ -I$(top_srcdir)/contrib/ide \
-DDATADIR=\""$(datadir)"\" \
-DLIBDIR=\""$(libdir)"\" \
-DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\"
@@ -19,7 +20,8 @@ libgitg_libgitg_1_0_la_LDFLAGS = \
-export-symbols-regex "^[^_].*"
libgitg_libgitg_1_0_la_LIBADD = \
- $(LIBGITG_LIBS)
+ $(LIBGITG_LIBS) \
+ contrib/ide/libide.la
if GDK_WINDOWING_QUARTZ
libgitg_libgitg_1_0_la_LIBADD += -lobjc
@@ -38,8 +40,10 @@ libgitg_libgitg_1_0_la_VALAFLAGS = \
--pkg libsoup-2.4 \
--pkg gtksourceview-3.0 \
--pkg gitg-platform-support \
+ --pkg ide \
$(GITG_VALAFLAGS) \
--vapidir $(top_srcdir)/vapi \
+ --vapidir $(top_srcdir)/contrib/ide \
--includedir libgitg \
--basedir $(top_srcdir) \
--gir Gitg-1.0.gir \
diff --git a/libgitg/gitg-repository-list-box.vala b/libgitg/gitg-repository-list-box.vala
index 30b0fdc..d37dc01 100644
--- a/libgitg/gitg-repository-list-box.vala
+++ b/libgitg/gitg-repository-list-box.vala
@@ -1,7 +1,7 @@
/*
* This file is part of gitg
*
- * Copyright (C) 2012 - Ignacio Casal Quinteiro
+ * Copyright (C) 2012-2016 - Ignacio Casal Quinteiro
*
* gitg is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -38,17 +38,14 @@ namespace Gitg
private Repository? d_repository;
private DateTime d_time;
private bool d_loading;
- private bool d_has_remote;
[GtkChild]
private ProgressBin d_progress_bin;
[GtkChild]
- private Gtk.Image d_image;
- [GtkChild]
private Gtk.Label d_repository_label;
[GtkChild]
- private Gtk.Label d_branch_label;
+ private Gtk.Label d_description_label;
[GtkChild]
- private Gtk.Arrow d_arrow;
+ private Gtk.Label d_branch_label;
[GtkChild]
private Gtk.Spinner d_spinner;
[GtkChild]
@@ -56,7 +53,7 @@ namespace Gitg
[GtkChild]
private Gtk.Revealer d_remove_revealer;
[GtkChild]
- private Gtk.Box d_submodule_box;
+ private Gtk.Box d_languages_box;
public signal void request_remove();
@@ -64,13 +61,6 @@ namespace Gitg
private string? d_dirname;
private string? d_branch_name;
- private static Gtk.IconSize s_icon_size;
-
- static construct
- {
- s_icon_size = Gtk.icon_size_register("gitg", 64, 64);
- }
-
public SelectionMode mode
{
get { return d_mode; }
@@ -108,18 +98,7 @@ namespace Gitg
set
{
d_repository = value;
-
- branch_name = "";
-
- if (d_repository != null)
- {
- try
- {
- var head = d_repository.get_head();
- branch_name = head.parsed_name.shortname;
- }
- catch {}
- }
+ update_repository_data();
}
}
@@ -185,6 +164,64 @@ namespace Gitg
}
}
+ private void update_repository_data()
+ {
+ string head_name = "";
+ string head_description = "";
+
+ if (d_repository != null)
+ {
+ try
+ {
+ var head = d_repository.get_head();
+ head_name = head.parsed_name.shortname;
+
+ var commit = (Ggit.Commit)head.lookup();
+ var tree = commit.get_tree();
+
+ Ggit.OId? entry_id = null;
+ tree.walk(Ggit.TreeWalkMode.PRE, (root, entry) => {
+ if (root == "" && entry.get_name() != null &&
entry.get_name().has_suffix(".doap"))
+ {
+ entry_id = entry.get_id();
+ return 1;
+ }
+ return 0;
+ });
+
+ if (entry_id != null)
+ {
+ var blob = d_repository.lookup<Ggit.Blob>(entry_id);
+
+ unowned uint8[] content = blob.get_raw_content();
+ var doap = new Ide.Doap();
+ doap.load_from_data((string)content, -1);
+
+ head_description = doap.get_shortdesc();
+
+ foreach (var lang in doap.get_languages())
+ {
+ var frame = new Gtk.Frame(null);
+ frame.shadow_type = Gtk.ShadowType.NONE;
+
frame.get_style_context().add_class("language-frame");
+ frame.show();
+
+ var label = new Gtk.Label(lang);
+
label.get_attributes().insert_before(Pango.attr_scale_new(Pango.Scale.SMALL));
+ label.show();
+
+ frame.add(label);
+ d_languages_box.add(frame);
+ }
+ }
+ } catch {}
+ }
+
+ repository_name = d_repository != null ? d_repository.name : "";
+ d_description_label.label = head_description;
+ branch_name = head_name;
+ }
+
public bool loading
{
get { return d_loading; }
@@ -196,77 +233,19 @@ namespace Gitg
{
d_spinner.stop();
d_spinner.hide();
- d_arrow.show();
d_progress_bin.fraction = 0;
}
else
{
- d_arrow.hide();
d_spinner.show();
d_spinner.start();
}
}
}
- public bool has_remote
+ public Row(Repository? repository, string dirname)
{
- get { return d_has_remote; }
- set
- {
- d_has_remote = value;
-
- var folder_icon_name = d_has_remote ? "folder-remote" : "folder";
- d_image.set_from_icon_name(folder_icon_name, s_icon_size);
- }
- }
-
- public Row(string name, string dirname, string branch_name, bool has_remote)
- {
- Object(repository_name: name, dirname: dirname, branch_name: branch_name,
has_remote: has_remote);
- }
-
- public void add_submodule(Ggit.Submodule module)
- {
- var submodule_url = module.get_url();
- if (submodule_url == null)
- {
- return;
- }
-
- var box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 3);
- var tip = @"$(module.get_path())/ ($(submodule_url))";
-
- box.set_tooltip_text(tip);
- box.show();
-
- var icon = new Gtk.Image.from_icon_name("folder-remote-symbolic",
- Gtk.IconSize.MENU);
- icon.show();
-
- var name = Path.get_basename(submodule_url);
-
- if (name.has_suffix(".git"))
- {
- name = name[0:-4];
- }
-
- var labelName = new Gtk.Label(name);
- labelName.show();
-
- var arrow = new Gtk.Arrow(Gtk.ArrowType.RIGHT, Gtk.ShadowType.NONE);
- arrow.show();
-
- var path = module.get_path();
- var labelPath = new Gtk.Label(@"$path/");
- labelPath.set_ellipsize(Pango.EllipsizeMode.MIDDLE);
- labelPath.show();
-
- box.add(icon);
- box.add(labelName);
- box.add(arrow);
- box.add(labelPath);
-
- d_submodule_box.add(box);
+ Object(repository: repository, dirname: dirname);
}
}
@@ -423,7 +402,9 @@ namespace Gitg
public Row? begin_cloning(File location)
{
- var row = new Row(location.get_basename(),
Utils.replace_home_dir_with_tilde(location.get_parent()), _("Cloning…"), true);
+ var row = new Row(null, Utils.replace_home_dir_with_tilde(location.get_parent()));
+ row.repository_name = location.get_basename();
+ row.branch_name = _("Cloning…");
row.loading = true;
row.show();
@@ -440,41 +421,13 @@ namespace Gitg
if (row == null)
{
- string head_name = "";
- bool has_remote = true;
-
- try
- {
- var head = repository.get_head();
- head_name = head.parsed_name.shortname;
-
- var remotes = repository.list_remotes();
-
- if (remotes.length == 0)
- {
- has_remote = false;
- }
- } catch {}
-
var dirname = Utils.replace_home_dir_with_tilde((repository.workdir != null ?
repository.workdir : repository.location).get_parent());
- row = new Row(repository.name, dirname, head_name, has_remote);
- row.repository = repository;
+ row = new Row(repository, dirname);
row.show();
- try
- {
- repository.submodule_foreach((module) => {
- row.add_submodule(module);
- return 0;
- });
- }
- catch {}
-
if (f != null)
{
- bind_property("mode",
- row,
- "mode");
+ bind_property("mode", row, "mode");
}
if (f != null)
diff --git a/libgitg/resources/ui/gitg-repository-list-box-row.ui
b/libgitg/resources/ui/gitg-repository-list-box-row.ui
index 635194f..aeb19c9 100644
--- a/libgitg/resources/ui/gitg-repository-list-box-row.ui
+++ b/libgitg/resources/ui/gitg-repository-list-box-row.ui
@@ -45,22 +45,29 @@
</packing>
</child>
<child>
- <object class="GtkImage" id="d_image">
+ <object class="GtkLabel" id="d_repository_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="has_focus">False</property>
<property name="is_focus">False</property>
- <property name="icon_name">image-missing</property>
+ <property name="halign">start</property>
+ <property name="valign">end</property>
+ <property name="hexpand">True</property>
+ <property name="ellipsize">end</property>
+ <attributes>
+ <attribute name="scale" value="1.2"/>
+ <attribute name="weight" value="bold"/>
+ </attributes>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
<property name="width">1</property>
- <property name="height">3</property>
+ <property name="height">1</property>
</packing>
</child>
<child>
- <object class="GtkLabel" id="d_repository_label">
+ <object class="GtkLabel" id="d_description_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="has_focus">False</property>
@@ -69,13 +76,10 @@
<property name="valign">end</property>
<property name="hexpand">True</property>
<property name="ellipsize">end</property>
- <attributes>
- <attribute name="weight" value="bold"/>
- </attributes>
</object>
<packing>
- <property name="left_attach">2</property>
- <property name="top_attach">0</property>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
@@ -97,14 +101,14 @@
</style>
</object>
<packing>
- <property name="left_attach">2</property>
- <property name="top_attach">1</property>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
- <object class="GtkBox" id="d_submodule_box">
+ <object class="GtkBox" id="d_languages_box">
<property name="orientation">horizontal</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -112,7 +116,7 @@
<property name="is_focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
- <property name="spacing">24</property>
+ <property name="spacing">3</property>
</object>
<packing>
<property name="left_attach">2</property>
@@ -122,28 +126,13 @@
</packing>
</child>
<child>
- <object class="GtkArrow" id="d_arrow">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="has_focus">False</property>
- <property name="is_focus">False</property>
- <property name="shadow_type">none</property>
- </object>
- <packing>
- <property name="left_attach">3</property>
- <property name="top_attach">0</property>
- <property name="width">1</property>
- <property name="height">3</property>
- </packing>
- </child>
- <child>
<object class="GtkSpinner" id="d_spinner">
<property name="can_focus">False</property>
<property name="has_focus">False</property>
<property name="is_focus">False</property>
</object>
<packing>
- <property name="left_attach">4</property>
+ <property name="left_attach">3</property>
<property name="top_attach">0</property>
<property name="width">1</property>
<property name="height">3</property>
diff --git a/libgitg/resources/ui/libgitg-style.css b/libgitg/resources/ui/libgitg-style.css
index 19628e6..3e2c325 100644
--- a/libgitg/resources/ui/libgitg-style.css
+++ b/libgitg/resources/ui/libgitg-style.css
@@ -102,3 +102,9 @@ GitgDiffViewOptions GtkButton {
GitgDiffViewOptions {
border-top: 1px solid @borders;
}
+
+.language-frame {
+ background-color: #e1e1e1;
+ border-radius: 3px;
+ padding: 3px;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]