diff --git a/include/openmg/chapter.h b/include/openmg/chapter.h new file mode 100644 index 0000000..791043d --- /dev/null +++ b/include/openmg/chapter.h @@ -0,0 +1,16 @@ +#pragma once +#include + +G_BEGIN_DECLS; + +#define MG_TYPE_MANGA_CHAPTER mg_manga_chapter_get_type() +G_DECLARE_FINAL_TYPE (MgMangaChapter, mg_manga_chapter, MG, MANGA_CHAPTER, GObject) + +MgMangaChapter * +mg_manga_chapter_new (const char *const title, + const char *const published_text, + const char *const url); +char * +mg_manga_chapter_get_title (MgMangaChapter *self); + +G_END_DECLS diff --git a/include/openmg/manga.h b/include/openmg/manga.h index 60d9cb1..22c3fd3 100644 --- a/include/openmg/manga.h +++ b/include/openmg/manga.h @@ -22,6 +22,10 @@ int mg_manga_has_details (MgManga *self); void mg_manga_details_recovered (MgManga *self); +void +mg_manga_set_chapter_list (MgManga *self, GListStore *chapter_list); +GListStore * +mg_manga_get_chapter_list (MgManga *self); MgManga *mg_manga_new (const char *const image_url, const char *const title, const char *const id); diff --git a/include/openmg/view/list_view_chapter.h b/include/openmg/view/list_view_chapter.h new file mode 100644 index 0000000..4047980 --- /dev/null +++ b/include/openmg/view/list_view_chapter.h @@ -0,0 +1,5 @@ +#include +#include + +GtkListView * +create_list_view_chapters (MgManga *manga); diff --git a/meson.build b/meson.build index e02c641..d3c0866 100644 --- a/meson.build +++ b/meson.build @@ -12,6 +12,7 @@ openmgdeps = [ ] sources = [ + 'src/view/list_view_chapter.c', 'src/view/picture.c', 'src/util/gobject_utility_extensions.c', 'src/view/detail_manga.c', @@ -22,6 +23,7 @@ sources = [ 'src/view/list_view_manga.c', 'src/view/main_view.c', 'src/manga.c', + 'src/chapter.c', 'src/backend/readmng.c', 'src/main.c', ] diff --git a/src/backend/readmng.c b/src/backend/readmng.c index ca6072a..2fd2cf4 100644 --- a/src/backend/readmng.c +++ b/src/backend/readmng.c @@ -8,6 +8,7 @@ #include #include +#include typedef enum { MG_BACKEND_READMNG_BASE_URL = 1, @@ -44,6 +45,17 @@ mg_backend_readmng_class_init (MgBackendReadmngClass *class) { mg_backend_readmng_properties); } +static xmlNodePtr +mg_backend_readmng_get_a_for_chapter ( + MgBackendReadmng *self, + xmlNodePtr li); +static MgMangaChapter * +mg_backend_readmng_loop_li_chapter ( + MgBackendReadmng *self, + xmlNodePtr li); +static GListStore * +mg_backend_readmng_recover_chapter_list (MgBackendReadmng *self, + xmlDocPtr html_document_details); static xmlDocPtr mg_backend_readmng_fetch_xml_details (MgBackendReadmng *self, MgManga *manga); @@ -140,13 +152,15 @@ mg_backend_readmng_retrieve_manga_details (MgBackendReadmng *self, xmlNodePtr *movie_detail = NULL; xmlXPathObjectPtr xpath_result = NULL; xmlNodeSetPtr node_set = NULL; + GListStore *manga_chapters = NULL; size_t movie_detail_len = 0; if (mg_manga_has_details (manga)) { return; } - xml_utils = mg_util_xml_new (); + + xml_utils = self->xml_utils; html_document = mg_backend_readmng_fetch_xml_details (self, manga); xpath_result = mg_util_xml_get_nodes_xpath_expression (xml_utils, @@ -165,9 +179,87 @@ mg_backend_readmng_retrieve_manga_details (MgBackendReadmng *self, mg_manga_set_description (manga, (char *) xmlNodeGetContent (movie_detail[0])); } + manga_chapters = mg_backend_readmng_recover_chapter_list (self, html_document); + mg_manga_set_chapter_list (manga, manga_chapters); mg_manga_details_recovered (manga); } +static GListStore * +mg_backend_readmng_recover_chapter_list (MgBackendReadmng *self, + xmlDocPtr html_document_details) { + MgUtilXML *xml_utils = self->xml_utils; + xmlXPathObjectPtr xpath_result = NULL; + xmlNodeSetPtr node_set = NULL; + xmlNodePtr *uls = NULL; + xmlNodePtr ul; + GListStore *return_value = g_list_store_new ( + MG_TYPE_MANGA_CHAPTER); + size_t ul_len = 0; + + xpath_result = mg_util_xml_get_nodes_xpath_expression (xml_utils, + html_document_details, "//ul[@class]"); + node_set = xpath_result->nodesetval; + if (!node_set) { + fprintf(stderr, "No matching ul\n"); + return return_value; + } + for (int i = 0; i < node_set->nodeNr; i++) { + xmlNodePtr node = node_set->nodeTab[i]; + uls = mg_util_xml_loop_search_class (xml_utils, + node, uls, "chp_lst", &ul_len); + } + if (!ul_len) { + fprintf(stderr, "No matching chp_lst\n"); + return return_value; + } + ul = uls[0]; + for (xmlNodePtr li = ul->children; li; li = li->next) { + if (!strcmp ((char *) li->name, "li")) { + MgMangaChapter *chapter = mg_backend_readmng_loop_li_chapter (self, li); + if (chapter) { + g_list_store_append (return_value, chapter); + } + } + } + return return_value; +} + +static MgMangaChapter * +mg_backend_readmng_loop_li_chapter ( + MgBackendReadmng *self, + xmlNodePtr li) { + MgUtilXML *xml_utils = self->xml_utils; + xmlNodePtr a = mg_backend_readmng_get_a_for_chapter ( + self, li); + if (!a) return NULL; + + char *url = mg_util_xml_get_attr (xml_utils, a, "href"); + size_t val_len = 0; + size_t dte_len = 0; + + xmlNodePtr *val = mg_util_xml_find_class (xml_utils, a, "val", &val_len, NULL, 1); + xmlNodePtr *dte = mg_util_xml_find_class (xml_utils, a, "dte", &dte_len, NULL, 1); + if (val_len && dte_len) { + return mg_manga_chapter_new ( + (char *) xmlNodeGetContent (val[0]), + (char *) xmlNodeGetContent(dte[0]), + url); + } + return NULL; +} + +static xmlNodePtr +mg_backend_readmng_get_a_for_chapter ( + MgBackendReadmng *self, + xmlNodePtr li) { + for (xmlNodePtr child = li->children; child; child = child->next) { + if (!strcmp((char *) child->name, "a")) { + return child; + } + } + return NULL; +} + static xmlDocPtr mg_backend_readmng_fetch_xml_details (MgBackendReadmng *self, MgManga *manga) { diff --git a/src/chapter.c b/src/chapter.c new file mode 100644 index 0000000..70c2c4f --- /dev/null +++ b/src/chapter.c @@ -0,0 +1,162 @@ +#include + +#include + +#include + +struct _MgMangaChapter { + GObject parent_instance; + char *title; + char *url; + char *published_text; +}; + +static void +mg_manga_chapter_class_init (MgMangaChapterClass *class); +static void +mg_manga_chapter_class_init (MgMangaChapterClass *class); +static void +mg_manga_chapter_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void +mg_manga_chapter_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +G_DEFINE_TYPE (MgMangaChapter, mg_manga_chapter, + G_TYPE_OBJECT) + +typedef enum { + MG_MANGA_CHAPTER_TITLE = 1, + MG_MANGA_CHAPTER_URL, + MG_MANGA_CHAPTER_PUBLISHED_TEXT, + MG_MANGA_CHAPTER_N_PROPERTIES +} MgMangaChapterProperties; + +static GParamSpec *manga_chapter_properties[MG_MANGA_CHAPTER_N_PROPERTIES] = { NULL, }; + + +MgMangaChapter * +mg_manga_chapter_new (const char *const title, + const char *const published_text, + const char *const url) { + MgMangaChapter *self = NULL; + MgUtilString *string_util = mg_util_string_new (); + + self = MG_MANGA_CHAPTER ( + g_object_new (MG_TYPE_MANGA_CHAPTER, NULL)); + + self->title = mg_util_string_alloc_string ( + string_util, strlen (title)); + self->url = mg_util_string_alloc_string ( + string_util, strlen (url)); + self->published_text = mg_util_string_alloc_string ( + string_util, strlen (published_text)); + + mg_util_string_copy_substring (string_util, + title, self->title, + strlen(title) + 1, 0, strlen (title)); + mg_util_string_copy_substring (string_util, + url, self->url, + strlen(url) + 1, 0, strlen (url)); + mg_util_string_copy_substring (string_util, + published_text, self->published_text, + strlen(published_text) + 1, 0, + strlen (published_text)); + + return self; +} + +static void +mg_manga_chapter_class_init (MgMangaChapterClass *class) { + GObjectClass *object_class = G_OBJECT_CLASS (class); + object_class->set_property = mg_manga_chapter_set_property; + object_class->get_property = mg_manga_chapter_get_property; + + manga_chapter_properties[MG_MANGA_CHAPTER_TITLE] = + g_param_spec_string ("title", + "Title", + "Title of the chapter.", + NULL, + G_PARAM_CONSTRUCT_ONLY + | G_PARAM_READWRITE); + manga_chapter_properties[MG_MANGA_CHAPTER_URL] = + g_param_spec_string ("url", + "URL", + "URL of the chapter.", + NULL, + G_PARAM_CONSTRUCT_ONLY + | G_PARAM_READWRITE); + manga_chapter_properties[MG_MANGA_CHAPTER_PUBLISHED_TEXT] = + g_param_spec_string ("published_text", + "PublishedText", + "Text of publication.", + NULL, + G_PARAM_CONSTRUCT_ONLY + | G_PARAM_READWRITE); + g_object_class_install_properties (object_class, + MG_MANGA_CHAPTER_N_PROPERTIES, + manga_chapter_properties); +} + +static void +mg_manga_chapter_init (MgMangaChapter *self) { +} + +static void +mg_manga_chapter_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) { + MgMangaChapter *self = MG_MANGA_CHAPTER (object); + switch ((MgMangaChapterProperties) property_id) { + case MG_MANGA_CHAPTER_TITLE: + g_value_set_string (value, self->title); + break; + case MG_MANGA_CHAPTER_URL: + g_value_set_string (value, self->url); + break; + case MG_MANGA_CHAPTER_PUBLISHED_TEXT: + g_value_set_string (value, self->published_text); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +mg_manga_chapter_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) { + MgMangaChapter *self = MG_MANGA_CHAPTER (object); + switch ((MgMangaChapterProperties) property_id) { + case MG_MANGA_CHAPTER_TITLE: + g_free (self->title); + self->title = g_value_dup_string (value); + break; + case MG_MANGA_CHAPTER_URL: + g_free (self->url); + self->url = g_value_dup_string (value); + break; + case MG_MANGA_CHAPTER_PUBLISHED_TEXT: + g_free (self->published_text); + self->published_text = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +char * +mg_manga_chapter_get_title (MgMangaChapter *self) { + GValue value = G_VALUE_INIT; + g_value_init (&value, G_TYPE_STRING); + g_object_get_property (G_OBJECT (self), "title", &value); + return g_value_dup_string (&value); +} diff --git a/src/manga.c b/src/manga.c index 01d56d0..cbf4392 100644 --- a/src/manga.c +++ b/src/manga.c @@ -1,7 +1,11 @@ +#include + #include +#include #include #include +#include struct _MgManga { GObject parent_instance; @@ -9,6 +13,7 @@ struct _MgManga { char *title; char *id; char *description; + GListStore *chapter_list; int has_details; }; @@ -19,6 +24,7 @@ typedef enum { MG_MANGA_TITLE, MG_MANGA_ID, MG_MANGA_DESCRIPTION, + MG_MANGA_CHAPTER_LIST, MG_MANGA_N_PROPERTIES } MgMangaProperties; @@ -62,6 +68,13 @@ mg_manga_class_init (MgMangaClass *class) { "Description of the manga.", NULL, G_PARAM_READWRITE); + manga_properties[MG_MANGA_CHAPTER_LIST] = g_param_spec_object ( + "chapter_list", + "ChapterList", + "List of chapters.", + G_TYPE_LIST_STORE, + G_PARAM_READWRITE); + g_object_class_install_properties (object_class, MG_MANGA_N_PROPERTIES, @@ -123,6 +136,24 @@ mg_manga_get_description (MgManga *self) { return g_value_dup_string (&value); } +GListStore * +mg_manga_get_chapter_list (MgManga *self) { + if (!mg_manga_has_details (self)) { + fprintf(stderr, "Manga has still not details\n"); + return NULL; + } + GValue value = G_VALUE_INIT; + GListStore *return_value; + + g_value_init (&value, G_TYPE_LIST_STORE); + g_object_get_property (G_OBJECT (self), + "chapter_list", + &value); + return_value = G_LIST_STORE (g_value_peek_pointer (&value)); + + return return_value; +} + void mg_manga_set_description (MgManga *self, const char *description) { GValue value = G_VALUE_INIT; @@ -131,12 +162,21 @@ mg_manga_set_description (MgManga *self, const char *description) { g_object_set_property (G_OBJECT (self), "description", &value); } +void +mg_manga_set_chapter_list (MgManga *self, GListStore *chapter_list) { + GValue value = G_VALUE_INIT; + g_value_init (&value, G_TYPE_LIST_STORE); + g_value_set_instance (&value, chapter_list); + g_object_set_property (G_OBJECT (self), "chapter_list", &value); +} + static void mg_manga_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { MgManga *self = MG_MANGA (object); + GListStore *chapter_list; switch ((MgMangaProperties) property_id) { case MG_MANGA_IMAGE_URL: g_free (self->image_url); @@ -154,6 +194,11 @@ mg_manga_set_property (GObject *object, g_free (self->description); self->description = g_value_dup_string (value); break; + case MG_MANGA_CHAPTER_LIST: + g_free (self->chapter_list); + chapter_list = g_value_peek_pointer (value); + self->chapter_list = chapter_list; + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -179,6 +224,9 @@ mg_manga_get_property (GObject *object, case MG_MANGA_DESCRIPTION: g_value_set_string (value, self->description); break; + case MG_MANGA_CHAPTER_LIST: + g_value_set_instance (value, self->chapter_list); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; diff --git a/src/view/detail_manga.c b/src/view/detail_manga.c index 3039e3e..cbee37a 100644 --- a/src/view/detail_manga.c +++ b/src/view/detail_manga.c @@ -6,26 +6,32 @@ #include #include +#include #include #include +#include GtkBox * create_detail_view (MgManga *manga) { MgBackendReadmng *readmng = mg_backend_readmng_new (); + GtkWidget *scroll; GtkBox *detail_view = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0)); GtkBox *avatar_title_box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0)); MgUtilXML *xml_util = mg_util_xml_new (); GtkLabel *manga_title = NULL; GtkLabel *manga_description = NULL; + GtkListView *chapter_list = NULL; GtkPicture *manga_image = create_picture_from_url ( mg_manga_get_image_url(manga), 200); char *title_text = mg_util_xml_get_title_text ( xml_util, mg_manga_get_title (manga)); char *description_text; - mg_backend_readmng_retrieve_manga_details (readmng, manga); + scroll = gtk_scrolled_window_new (); + mg_backend_readmng_retrieve_manga_details (readmng, manga); + chapter_list = create_list_view_chapters (manga); description_text = mg_manga_get_description (manga); manga_title = GTK_LABEL (gtk_label_new (title_text)); @@ -39,6 +45,9 @@ create_detail_view (MgManga *manga) { gtk_box_append (avatar_title_box, GTK_WIDGET (manga_title)); gtk_box_append (detail_view, GTK_WIDGET (avatar_title_box)); gtk_box_append (detail_view, GTK_WIDGET (manga_description)); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scroll), GTK_WIDGET (chapter_list)); + g_object_set_property_int (G_OBJECT (scroll), "vexpand", 1); + gtk_box_append (detail_view, GTK_WIDGET (scroll)); return detail_view; } diff --git a/src/view/list_view_chapter.c b/src/view/list_view_chapter.c new file mode 100644 index 0000000..2c7333c --- /dev/null +++ b/src/view/list_view_chapter.c @@ -0,0 +1,49 @@ +#include + +#include +#include +#include +#include + +static void +setup_list_view_chapter_list (GtkSignalListItemFactory *factory, + GtkListItem *list_item, + gpointer user_data); + +GtkListView * +create_list_view_chapters (MgManga *manga) { + GListStore *manga_chapter_list = NULL; + GtkListView *list_view_chapters = NULL; + GtkSingleSelection *selection = NULL; + GtkListItemFactory *factory = + gtk_signal_list_item_factory_new (); + + manga_chapter_list = mg_manga_get_chapter_list (manga); + selection = gtk_single_selection_new + (G_LIST_MODEL (manga_chapter_list)); + + g_signal_connect (G_OBJECT (factory), "bind", + G_CALLBACK (setup_list_view_chapter_list), + NULL); + + list_view_chapters = GTK_LIST_VIEW (gtk_list_view_new (GTK_SELECTION_MODEL (selection), + factory)); + + return list_view_chapters; +} + +static void +setup_list_view_chapter_list (GtkSignalListItemFactory *factory, + GtkListItem *list_item, + gpointer user_data) { + MgMangaChapter *manga_chapter = gtk_list_item_get_item (list_item); + GtkBox *box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0)); + GtkLabel *title = GTK_LABEL (gtk_label_new (mg_manga_chapter_get_title (manga_chapter))); + GtkImage *icon = GTK_IMAGE (gtk_image_new_from_icon_name ( + "weather-clear-night-symbolic")); + gtk_image_set_icon_size (icon, GTK_ICON_SIZE_LARGE); + gtk_label_set_wrap (title, 1); + gtk_box_append (box, GTK_WIDGET (icon)); + gtk_box_append (box, GTK_WIDGET (title)); + gtk_list_item_set_child (list_item, GTK_WIDGET (box)); +}