diff --git a/.gitignore b/.gitignore index d48371f..499497f 100644 --- a/.gitignore +++ b/.gitignore @@ -40,8 +40,8 @@ Makefile.in /data/astrognome.desktop.in # Generated files -/src/ag-resources.c -/src/ag-resources.h +/src/ag-resources.[ch] +/src/ag-enumtypes.[ch] # Geonames related things /data/geonames/*.txt diff --git a/src/Makefile.am b/src/Makefile.am index c39ca17..3f9c4f1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,15 +1,27 @@ RESOURCE_DIR = $(srcdir)/resources resource_files = $(shell glib-compile-resources --sourcedir=$(RESOURCE_DIR) --generate-dependencies $(srcdir)/ag.gresource.xml) +ag_enum_headers = ag-icon-view.h + ag-resources.c: ag.gresource.xml $(resource_files) glib-compile-resources --target=$@ --sourcedir=$(RESOURCE_DIR) --generate-source --c-name ag $(srcdir)/ag.gresource.xml ag-resources.h: ag.gresource.xml $(resource_files) glib-compile-resources --target=$@ --sourcedir=$(RESOURCE_DIR) --generate-header --c-name ag $(srcdir)/ag.gresource.xml +ag-enumtypes.h: $(ag_enum_headers) ag-enumtypes.h.template + $(GLIB_MKENUMS) --template $(filter %.template,$^) $(filter-out %.template,$^) > \ + ag-enumtypes.h.tmp && mv ag-enumtypes.h.tmp ag-enumtypes.h + +ag-enumtypes.c: $(ag_enum_headers) ag-enumtypes.c.template + $(GLIB_MKENUMS) --template $(filter %.template,$^) $(filter-out %.template,$^) > \ + ag-enumtypes.c.tmp && mv ag-enumtypes.c.tmp ag-enumtypes.c + BUILT_SOURCES = \ ag-resources.h \ ag-resources.c \ + ag-enumtypes.h \ + ag-enumtypes.c \ $(NULL) astrognome_source_files = \ @@ -20,6 +32,7 @@ astrognome_source_files = \ ag-preferences.c \ ag-db.c \ ag-display-theme.c \ + ag-icon-view.c \ ag-chart-renderer.c \ astrognome.c \ $(NULL) diff --git a/src/ag-enumtypes.c.template b/src/ag-enumtypes.c.template new file mode 100644 index 0000000..70758d9 --- /dev/null +++ b/src/ag-enumtypes.c.template @@ -0,0 +1,63 @@ +/*** BEGIN file-header ***/ +/* ag-enumtypes.c - Enumeration types for Astrognome + * + * Copyright © 2013 Gergely Polonkai + * + * Astrognome 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. + * + * Astrognome 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 library; if not, see . + */ + +#include "ag-enumtypes.h" +#include "@filename@" + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* enumerations from "@filename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType +@enum_name@_get_type(void) +{ + static volatile gsize g_define_type_id__volatile = 0; + + if (g_once_init_enter(&g_define_type_id__volatile)) { + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { + @VALUENAME@, + "@VALUENAME@", + "@valuenick@" + }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + + GType g_define_type_id = g_@type@_register_static( + g_intern_static_string("@EnumName@"), + values + ); + + g_once_init_leave(&g_define_type_id__volatile, g_define_type_id); + } + + return g_define_type_id__volatile; +} + +/*** END value-tail ***/ + diff --git a/src/ag-enumtypes.h.template b/src/ag-enumtypes.h.template new file mode 100644 index 0000000..a89ed67 --- /dev/null +++ b/src/ag-enumtypes.h.template @@ -0,0 +1,42 @@ +/*** BEGIN file-header ***/ +/* ag-enumtypes.h - Enumeration types for Astrognome + * + * Copyright © 2013 Gergely Polonkai + * + * Astrognome 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. + * + * Astrognome 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 library; if not, see . + */ +#ifndef __AG_ENUM_TYPES_H__ +#define __AG_ENUM_TYPES_H__ + +#include + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ + +/* enumerations from "@filename@" */ + +#include "@filename@" +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType @enum_name@_get_type(void); +#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type()) +/*** END value-header ***/ + +/*** BEGIN file-tail ***/ + +#endif /* __AG_ENUM_TYPES_H__ */ +/*** END file-tail ***/ + diff --git a/src/ag-icon-view.c b/src/ag-icon-view.c new file mode 100644 index 0000000..bde569a --- /dev/null +++ b/src/ag-icon-view.c @@ -0,0 +1,423 @@ +#include "ag-enumtypes.h" +#include "ag-icon-view.h" +#include "ag-db.h" +#include "ag-chart-renderer.h" + +typedef struct _AgIconViewPrivate { + AgIconViewMode mode; + AgChartRenderer *thumb_renderer; + GtkCellRenderer *text_renderer; + GtkListStore *model; +} AgIconViewPrivate; + +enum { + PROP_0, + PROP_MODE, + PROP_LAST +}; + +enum { + AG_ICON_VIEW_COLUMN_SELECTED, + AG_ICON_VIEW_COLUMN_ITEM, + AG_ICON_VIEW_COLUMN_COLUMNS +}; + +G_DEFINE_TYPE_WITH_PRIVATE(AgIconView, ag_icon_view, GTK_TYPE_ICON_VIEW); + +static GParamSpec *properties[PROP_LAST]; + +void +ag_icon_view_set_mode(AgIconView *icon_view, AgIconViewMode mode) +{ + AgIconViewPrivate *priv = ag_icon_view_get_instance_private(icon_view); + + if (priv->mode != mode) { + priv->mode = mode; + + if (mode != AG_ICON_VIEW_MODE_SELECTION) { + ag_icon_view_unselect_all(icon_view); + } + + ag_chart_renderer_set_toggle_visible(priv->thumb_renderer, (mode == AG_ICON_VIEW_MODE_SELECTION)); + + gtk_widget_queue_draw(GTK_WIDGET(icon_view)); + + g_object_notify_by_pspec( + G_OBJECT(icon_view), + properties[PROP_MODE] + ); + } +} + +AgIconViewMode +ag_icon_view_get_mode(AgIconView *icon_view) +{ + AgIconViewPrivate *priv = ag_icon_view_get_instance_private(icon_view); + + return priv->mode; +} + +static void +ag_icon_view_set_property(GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *param_spec) +{ + switch (prop_id) { + case PROP_MODE: + ag_icon_view_set_mode( + AG_ICON_VIEW(gobject), + g_value_get_enum(value) + ); + + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, param_spec); + + break; + } +} + +static void +ag_icon_view_get_property(GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *param_spec) +{ + AgIconViewPrivate *priv = ag_icon_view_get_instance_private( + AG_ICON_VIEW(gobject) + ); + + switch (prop_id) { + case PROP_MODE: + g_value_set_enum(value, priv->mode); + + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, param_spec); + + break; + } +} + +static void +ag_icon_view_selection_changed(AgIconView *icon_view) +{ + g_signal_emit_by_name(icon_view, "selection-changed"); +} + +static void +ag_icon_view_item_activated(AgIconView *icon_view, GtkTreePath *path) +{ + g_signal_emit_by_name(icon_view, "item-activated", path); +} + +static gboolean +ag_icon_view_button_press_event_cb(GtkWidget *widget, + GdkEventButton *event) +{ + GtkTreePath *path; + GtkIconView *gtk_icon_view = GTK_ICON_VIEW(widget); + AgIconView *ag_icon_view = AG_ICON_VIEW(widget); + + path = gtk_icon_view_get_path_at_pos(gtk_icon_view, ((GdkEventButton *)event)->x, ((GdkEventButton *)event)->y); + + if (path != NULL) { + gboolean selected; + AgDbChartSave *chart_save; + GtkListStore *store = GTK_LIST_STORE(gtk_icon_view_get_model(gtk_icon_view)); + + if (event->button == GDK_BUTTON_SECONDARY) { + ag_icon_view_set_mode(ag_icon_view, AG_ICON_VIEW_MODE_SELECTION); + } + + if (ag_icon_view_get_mode(ag_icon_view) == AG_ICON_VIEW_MODE_SELECTION) { + GtkTreeIter iter; + + if (gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path)) { + gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, AG_ICON_VIEW_COLUMN_SELECTED, &selected, AG_ICON_VIEW_COLUMN_ITEM, &chart_save, -1); + + gtk_list_store_set(store, &iter, AG_ICON_VIEW_COLUMN_SELECTED, !selected, -1); + + ag_icon_view_selection_changed(ag_icon_view); + } + } else { + ag_icon_view_item_activated(ag_icon_view, path); + } + } + + return FALSE; +} + +static void +ag_icon_view_class_init(AgIconViewClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + + gobject_class->set_property = ag_icon_view_set_property; + gobject_class->get_property = ag_icon_view_get_property; + widget_class->button_press_event = ag_icon_view_button_press_event_cb; + + properties[PROP_MODE] = g_param_spec_enum( + "mode", + "Mode", + "Mode", + AG_TYPE_ICON_VIEW_MODE, + AG_ICON_VIEW_MODE_NORMAL, + G_PARAM_STATIC_NAME + | G_PARAM_STATIC_NICK + | G_PARAM_STATIC_BLURB + | G_PARAM_READABLE + | G_PARAM_WRITABLE + ); + g_object_class_install_property( + gobject_class, + PROP_MODE, + properties[PROP_MODE] + ); +} + +static void +ag_icon_view_chart_renderer_func(GtkCellLayout *layout, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + AgIconView *icon_view) +{ + AgDbChartSave *chart_save; + + gtk_tree_model_get(model, iter, AG_ICON_VIEW_COLUMN_ITEM, &chart_save, -1); + + if (chart_save) { + g_object_set(renderer, "pixbuf", NULL, NULL); + } +} + +static void +ag_icon_view_text_renderer_func(GtkCellLayout *layout, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + AgIconView *icon_view) +{ + AgDbChartSave *chart_save; + + gtk_tree_model_get(model, iter, AG_ICON_VIEW_COLUMN_ITEM, &chart_save, -1); + + if (chart_save) { + gchar *text; + + text = g_markup_escape_text(chart_save->name, -1); + g_object_set(renderer, "markup", text, NULL); + g_free(text); + } +} + +static void +ag_icon_view_init(AgIconView *icon_view) +{ + AgIconViewPrivate *priv = ag_icon_view_get_instance_private(icon_view); + guint tile_width, + tile_height; + + priv->model = gtk_list_store_new( + AG_ICON_VIEW_COLUMN_COLUMNS, + G_TYPE_BOOLEAN, + AG_TYPE_DB_CHART_SAVE + ); + gtk_icon_view_set_model( + GTK_ICON_VIEW(icon_view), + GTK_TREE_MODEL(priv->model) + ); + + gtk_icon_view_set_selection_mode( + GTK_ICON_VIEW(icon_view), + GTK_SELECTION_NONE + ); + priv->mode = AG_ICON_VIEW_MODE_NORMAL; + + gtk_icon_view_set_item_padding(GTK_ICON_VIEW(icon_view), 0); + gtk_icon_view_set_margin(GTK_ICON_VIEW(icon_view), 12); + + tile_width = AG_CHART_RENDERER_TILE_SIZE + + 2 * AG_CHART_RENDERER_TILE_MARGIN; + tile_height = AG_CHART_RENDERER_TILE_SIZE + + AG_CHART_RENDERER_TILE_MARGIN + + AG_CHART_RENDERER_TILE_MARGIN_BOTTOM; + + priv->thumb_renderer = ag_chart_renderer_new(); + gtk_cell_renderer_set_alignment( + GTK_CELL_RENDERER(priv->thumb_renderer), + 0.5, 0.5 + ); + gtk_cell_renderer_set_fixed_size( + GTK_CELL_RENDERER(priv->thumb_renderer), + tile_width, tile_height + ); + gtk_cell_layout_pack_start( + GTK_CELL_LAYOUT(icon_view), + GTK_CELL_RENDERER(priv->thumb_renderer), + FALSE + ); + gtk_cell_layout_add_attribute( + GTK_CELL_LAYOUT(icon_view), + GTK_CELL_RENDERER(priv->thumb_renderer), + "checked", AG_ICON_VIEW_COLUMN_SELECTED + ); + gtk_cell_layout_set_cell_data_func( + GTK_CELL_LAYOUT(icon_view), + GTK_CELL_RENDERER(priv->thumb_renderer), + (GtkCellLayoutDataFunc)ag_icon_view_chart_renderer_func, + icon_view, + NULL + ); + + priv->text_renderer = gtk_cell_renderer_text_new(); + gtk_cell_renderer_set_alignment( + GTK_CELL_RENDERER(priv->text_renderer), + 0.5, 0.5 + ); + gtk_cell_layout_pack_start( + GTK_CELL_LAYOUT(icon_view), + priv->text_renderer, + TRUE + ); + gtk_cell_layout_set_cell_data_func( + GTK_CELL_LAYOUT(icon_view), + GTK_CELL_RENDERER(priv->text_renderer), + (GtkCellLayoutDataFunc)ag_icon_view_text_renderer_func, + icon_view, + NULL + ); +} + +void +ag_icon_view_add_chart(AgIconView *icon_view, AgDbChartSave *chart_save) +{ + GtkTreeIter iter; + AgIconViewPrivate *priv = ag_icon_view_get_instance_private(icon_view); + + g_debug("Adding chart for %s", chart_save->name); + + gtk_list_store_append(priv->model, &iter); + gtk_list_store_set( + priv->model, &iter, + AG_ICON_VIEW_COLUMN_SELECTED, FALSE, + AG_ICON_VIEW_COLUMN_ITEM, chart_save, + -1 + ); +} + +static gboolean +ag_icon_view_check_selected(GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + GList **list) +{ + gboolean selected; + + gtk_tree_model_get(model, iter, AG_ICON_VIEW_COLUMN_SELECTED, &selected, -1); + + if (selected) { + *list = g_list_prepend(*list, path); + } + + return FALSE; +} + +GList * +ag_icon_view_get_selected_items(AgIconView *icon_view) +{ + AgIconViewPrivate *priv = ag_icon_view_get_instance_private(icon_view); + GList *items = NULL; + + gtk_tree_model_foreach(GTK_TREE_MODEL(priv->model), (GtkTreeModelForeachFunc)ag_icon_view_check_selected, &items); + + return g_list_reverse(items); +} + +static gboolean +ag_icon_view_change_item_selection(GtkListStore *model, + GtkTreePath *path, + GtkTreeIter *iter, + gboolean *selected) +{ + gtk_list_store_set(model, iter, AG_ICON_VIEW_COLUMN_SELECTED, *selected, -1); + + return FALSE; +} + +void +ag_icon_view_select_all(AgIconView *icon_view) +{ + AgIconViewPrivate *priv = ag_icon_view_get_instance_private(icon_view); + gboolean selected = TRUE; + + gtk_tree_model_foreach( + GTK_TREE_MODEL(priv->model), + (GtkTreeModelForeachFunc)ag_icon_view_change_item_selection, + &selected + ); + + ag_icon_view_selection_changed(icon_view); +} + +void +ag_icon_view_unselect_all(AgIconView *icon_view) +{ + AgIconViewPrivate *priv = ag_icon_view_get_instance_private(icon_view); + gboolean selected = FALSE; + + gtk_tree_model_foreach( + GTK_TREE_MODEL(priv->model), + (GtkTreeModelForeachFunc)ag_icon_view_change_item_selection, + &selected + ); + + ag_icon_view_selection_changed(icon_view); +} + +AgDbChartSave * +ag_icon_view_get_chart_save_at_path(AgIconView *icon_view, + const GtkTreePath *path) +{ + AgIconViewPrivate *priv = ag_icon_view_get_instance_private(icon_view); + GtkTreeIter iter; + AgDbChartSave *save_data; + + if (gtk_tree_model_get_iter(GTK_TREE_MODEL(priv->model), &iter, (GtkTreePath *)path)) { + gtk_tree_model_get( + GTK_TREE_MODEL(priv->model), &iter, + AG_ICON_VIEW_COLUMN_ITEM, &save_data, + -1 + ); + } else { + g_warning("Invalid tree path"); + + save_data = NULL; + } + + return save_data; +} + +void +ag_icon_view_remove_selected(AgIconView *icon_view) +{ + AgIconViewPrivate *priv = ag_icon_view_get_instance_private(icon_view); + GList *paths = g_list_reverse(ag_icon_view_get_selected_items(icon_view)), + *l; + + for (l = paths; l; l = g_list_next(l)) { + GtkTreeIter iter; + GtkTreePath *path = l->data; + + if (gtk_tree_model_get_iter(GTK_TREE_MODEL(priv->model), &iter, path)) { + gtk_list_store_remove(priv->model, &iter); + } + } + + ag_icon_view_selection_changed(icon_view); +} diff --git a/src/ag-icon-view.h b/src/ag-icon-view.h new file mode 100644 index 0000000..9808a0b --- /dev/null +++ b/src/ag-icon-view.h @@ -0,0 +1,69 @@ +#ifndef __AG_ICON_VIEW_H__ +#define __AG_ICON_VIEW_H__ + +#include + +#include "ag-db.h" + +G_BEGIN_DECLS + +#define AG_TYPE_ICON_VIEW \ + (ag_icon_view_get_type()) +#define AG_ICON_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + AG_TYPE_ICON_VIEW, \ + AgIconView)) +#define AG_ICON_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + AG_TYPE_ICON_VIEW, \ + AgIconViewClass)) +#define IS_AG_ICON_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + AG_TYPE_ICON_VIEW)) +#define IS_AG_ICON_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + AG_TYPE_ICON_VIEW)) +#define AG_ICON_VIEW_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + AG_TYPE_ICON_VIEW, \ + AgIconViewClass)) + +typedef struct _AgIconView AgIconView; +typedef struct _AgIconViewClass AgIconViewClass; + +struct _AgIconViewClass +{ + GtkIconViewClass parent_class; +}; + +struct _AgIconView +{ + GtkIconView parent; +}; + +typedef enum { + AG_ICON_VIEW_MODE_NORMAL, + AG_ICON_VIEW_MODE_SELECTION +} AgIconViewMode; + +GType ag_icon_view_get_type (void) G_GNUC_CONST; + +void ag_icon_view_set_mode(AgIconView *icon_view, AgIconViewMode mode); + +AgIconViewMode ag_icon_view_get_mode(AgIconView *icon_view); + +void ag_icon_view_add_chart(AgIconView *icon_view, AgDbChartSave *chart_save); + +GList *ag_icon_view_get_selected_items(AgIconView *icon_view); + +AgDbChartSave *ag_icon_view_get_chart_save_at_path(AgIconView *icon_view, const GtkTreePath *path); + +void ag_icon_view_select_all(AgIconView *icon_view); + +void ag_icon_view_unselect_all(AgIconView *icon_view); + +void ag_icon_view_remove_selected(AgIconView *icon_view); + +G_END_DECLS + +#endif /* __AG_ICON_VIEW_H__ */