[gnome-builder/wip/path-bar: 2/3] egg: start on hierarchical listbox widget
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/path-bar: 2/3] egg: start on hierarchical listbox widget
- Date: Fri, 14 Aug 2015 21:53:20 +0000 (UTC)
commit 8736d158836b7f726965feb0c8ce5027b37cbe46
Author: Christian Hergert <christian hergert me>
Date: Fri Aug 14 14:34:59 2015 -0600
egg: start on hierarchical listbox widget
The test code for this is just that. Test code. It leaks, it's crappy, but
it does enough for me to figure how I'd like animations and other stuff
to work.
contrib/egg/Makefile.am | 6 +
contrib/egg/egg-directory-model.c | 475 +++++++++++++++++++++++++++++++
contrib/egg/egg-directory-model.h | 46 +++
contrib/egg/egg-rect.c | 171 ++++++++++++
contrib/egg/egg-rect.h | 41 +++
contrib/egg/egg-stack-list.c | 553 +++++++++++++++++++++++++++++++++++++
contrib/egg/egg-stack-list.h | 56 ++++
tests/Makefile.am | 6 +
tests/test-stack-list.c | 259 +++++++++++++++++
9 files changed, 1613 insertions(+), 0 deletions(-)
---
diff --git a/contrib/egg/Makefile.am b/contrib/egg/Makefile.am
index 2370e96..d4da3c8 100644
--- a/contrib/egg/Makefile.am
+++ b/contrib/egg/Makefile.am
@@ -7,16 +7,22 @@ libegg_la_SOURCES = \
egg-binding-group.h \
egg-counter.c \
egg-counter.h \
+ egg-directory-model.c \
+ egg-directory-model.h \
egg-frame-source.c \
egg-frame-source.h \
egg-heap.c \
egg-heap.h \
+ egg-rect.c \
+ egg-rect.h \
egg-search-bar.c \
egg-search-bar.h \
egg-settings-sandwich.c \
egg-settings-sandwich.h \
egg-signal-group.c \
egg-signal-group.h \
+ egg-stack-list.c \
+ egg-stack-list.h \
egg-state-machine.c \
egg-state-machine.h \
egg-state-machine-action.c \
diff --git a/contrib/egg/egg-directory-model.c b/contrib/egg/egg-directory-model.c
new file mode 100644
index 0000000..5f6486c
--- /dev/null
+++ b/contrib/egg/egg-directory-model.c
@@ -0,0 +1,475 @@
+/* egg-directory-model.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file 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 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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.
+ *
+ * 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 "egg-directory-model"
+
+#include <glib/gi18n.h>
+
+#include "egg-directory-model.h"
+
+#define NEXT_FILES_CHUNK_SIZE 25
+
+struct _EggDirectoryModel
+{
+ GObject parent_instance;
+
+ GCancellable *cancellable;
+ GFile *directory;
+ GSequence *items;
+ GFileMonitor *monitor;
+
+ EggDirectoryModelVisibleFunc visible_func;
+ gpointer visible_func_data;
+ GDestroyNotify visible_func_destroy;
+};
+
+static void list_model_iface_init (GListModelInterface *iface);
+static void egg_directory_model_reload (EggDirectoryModel *self);
+
+G_DEFINE_TYPE_EXTENDED (EggDirectoryModel, egg_directory_model, G_TYPE_OBJECT, 0,
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
+
+enum {
+ PROP_0,
+ PROP_DIRECTORY,
+ LAST_PROP
+};
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+
+static gint
+compare_display_name (gconstpointer a,
+ gconstpointer b,
+ gpointer data)
+{
+ GFileInfo *file_info_a = (GFileInfo *)a;
+ GFileInfo *file_info_b = (GFileInfo *)b;
+ const gchar *display_name_a = g_file_info_get_display_name (file_info_a);
+ const gchar *display_name_b = g_file_info_get_display_name (file_info_b);
+ g_autofree gchar *name_a = g_utf8_collate_key_for_filename (display_name_a, -1);
+ g_autofree gchar *name_b = g_utf8_collate_key_for_filename (display_name_b, -1);
+
+ return g_utf8_collate (name_a, name_b);
+}
+
+static gint
+compare_directories_first (gconstpointer a,
+ gconstpointer b,
+ gpointer data)
+{
+ GFileInfo *file_info_a = (GFileInfo *)a;
+ GFileInfo *file_info_b = (GFileInfo *)b;
+ GFileType file_type_a = g_file_info_get_file_type (file_info_a);
+ GFileType file_type_b = g_file_info_get_file_type (file_info_b);
+
+ if (file_type_a == file_type_b)
+ return compare_display_name (a, b, data);
+
+ return (file_type_a == G_FILE_TYPE_DIRECTORY) ? -1 : 1;
+}
+
+static void
+egg_directory_model_remove_all (EggDirectoryModel *self)
+{
+ GSequence *seq;
+ guint length;
+
+ g_assert (EGG_IS_DIRECTORY_MODEL (self));
+
+ length = g_sequence_get_length (self->items);
+
+ if (length > 0)
+ {
+ seq = self->items;
+ self->items = g_sequence_new (g_object_unref);
+ g_list_model_items_changed (G_LIST_MODEL (self), 0, length, 0);
+ g_sequence_free (seq);
+ }
+}
+
+static void
+egg_directory_model_take_item (EggDirectoryModel *self,
+ GFileInfo *file_info)
+{
+ GSequenceIter *iter;
+ guint position;
+
+ g_assert (EGG_IS_DIRECTORY_MODEL (self));
+ g_assert (G_IS_FILE_INFO (file_info));
+
+ if ((self->visible_func != NULL) &&
+ !self->visible_func (self, self->directory, file_info, self->visible_func_data))
+ {
+ g_object_unref (file_info);
+ return;
+ }
+
+ iter = g_sequence_insert_sorted (self->items,
+ file_info,
+ compare_directories_first,
+ NULL);
+ position = g_sequence_iter_get_position (iter);
+ g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
+}
+
+static void
+egg_directory_model_next_files_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GFileEnumerator *enumerator = (GFileEnumerator *)object;
+ g_autoptr(GTask) task = user_data;
+ EggDirectoryModel *self;
+ GList *files;
+ GList *iter;
+
+ g_assert (G_IS_FILE_ENUMERATOR (enumerator));
+ g_assert (G_IS_TASK (task));
+
+ if (!(files = g_file_enumerator_next_files_finish (enumerator, result, NULL)))
+ return;
+
+ self = g_task_get_source_object (task);
+
+ g_assert (EGG_IS_DIRECTORY_MODEL (self));
+
+ for (iter = files; iter; iter = iter->next)
+ {
+ GFileInfo *file_info = iter->data;
+
+ egg_directory_model_take_item (self, file_info);
+ }
+
+ g_list_free (files);
+
+ g_file_enumerator_next_files_async (enumerator,
+ NEXT_FILES_CHUNK_SIZE,
+ G_PRIORITY_DEFAULT,
+ g_task_get_cancellable (task),
+ egg_directory_model_next_files_cb,
+ g_object_ref (task));
+}
+
+static void
+egg_directory_model_enumerate_children_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GFile *directory = (GFile *)object;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(GFileEnumerator) enumerator = NULL;
+
+ g_assert (G_IS_FILE (directory));
+ g_assert (G_IS_TASK (task));
+
+ if (!(enumerator = g_file_enumerate_children_finish (directory, result, NULL)))
+ return;
+
+ g_file_enumerator_next_files_async (enumerator,
+ NEXT_FILES_CHUNK_SIZE,
+ G_PRIORITY_DEFAULT,
+ g_task_get_cancellable (task),
+ egg_directory_model_next_files_cb,
+ g_object_ref (task));
+}
+
+static void
+egg_directory_model_remove_file (EggDirectoryModel *self,
+ GFile *file)
+{
+ g_autofree gchar *name = NULL;
+ GSequenceIter *iter;
+
+ g_assert (G_IS_FILE (file));
+
+ name = g_file_get_basename (file);
+
+ /*
+ * We have to lookup linearly since the items will likely be
+ * sorted by name, directory, file-system ordering, or some
+ * combination thereof.
+ */
+
+ for (iter = g_sequence_get_begin_iter (self->items);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ GFileInfo *file_info = g_sequence_get (iter);
+ const gchar *file_info_name = g_file_info_get_name (file_info);
+
+ if (0 == g_strcmp0 (file_info_name, name))
+ {
+ guint position;
+
+ position = g_sequence_iter_get_position (iter);
+ g_sequence_remove (iter);
+ g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 0);
+ break;
+ }
+ }
+}
+
+static void
+egg_directory_model_directory_changed (EggDirectoryModel *self,
+ GFile *file,
+ GFile *other_file,
+ GFileMonitorEvent event_type,
+ GFileMonitor *monitor)
+{
+ g_assert (EGG_IS_DIRECTORY_MODEL (self));
+
+ switch ((int)event_type)
+ {
+ case G_FILE_MONITOR_EVENT_CREATED:
+ /*
+ * TODO: incremental changes
+ *
+ * When adding, we need to first add the GFileInfo for the file with all
+ * of the attributes we load in the primary case.
+ */
+ egg_directory_model_reload (self);
+ break;
+
+ case G_FILE_MONITOR_EVENT_DELETED:
+ egg_directory_model_remove_file (self, file);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+egg_directory_model_reload (EggDirectoryModel *self)
+{
+ g_assert (EGG_IS_DIRECTORY_MODEL (self));
+
+ if (self->monitor != NULL)
+ {
+ g_file_monitor_cancel (self->monitor);
+ g_signal_handlers_disconnect_by_func (self->monitor,
+ G_CALLBACK (egg_directory_model_directory_changed),
+ self);
+ g_clear_object (&self->monitor);
+ }
+
+ if (self->cancellable != NULL)
+ {
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+ }
+
+ egg_directory_model_remove_all (self);
+
+ if (self->directory != NULL)
+ {
+ g_autoptr(GTask) task = NULL;
+
+ self->cancellable = g_cancellable_new ();
+ task = g_task_new (self, self->cancellable, NULL, NULL);
+
+ g_file_enumerate_children_async (self->directory,
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME","
+ G_FILE_ATTRIBUTE_STANDARD_NAME","
+ G_FILE_ATTRIBUTE_STANDARD_TYPE","
+ G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ self->cancellable,
+ egg_directory_model_enumerate_children_cb,
+ g_object_ref (task));
+
+ self->monitor = g_file_monitor_directory (self->directory,
+ G_FILE_MONITOR_NONE,
+ self->cancellable,
+ NULL);
+
+ g_signal_connect_object (self->monitor,
+ "changed",
+ G_CALLBACK (egg_directory_model_directory_changed),
+ self,
+ G_CONNECT_SWAPPED);
+ }
+}
+
+static void
+egg_directory_model_finalize (GObject *object)
+{
+ EggDirectoryModel *self = (EggDirectoryModel *)object;
+
+ g_clear_object (&self->cancellable);
+ g_clear_object (&self->directory);
+ g_clear_pointer (&self->items, g_sequence_free);
+
+ if (self->visible_func_destroy)
+ self->visible_func_destroy (self->visible_func_data);
+
+ G_OBJECT_CLASS (egg_directory_model_parent_class)->finalize (object);
+}
+
+static void
+egg_directory_model_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EggDirectoryModel *self = EGG_DIRECTORY_MODEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_DIRECTORY:
+ g_value_set_object (value, egg_directory_model_get_directory (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+egg_directory_model_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EggDirectoryModel *self = EGG_DIRECTORY_MODEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_DIRECTORY:
+ egg_directory_model_set_directory (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+egg_directory_model_class_init (EggDirectoryModelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = egg_directory_model_finalize;
+ object_class->get_property = egg_directory_model_get_property;
+ object_class->set_property = egg_directory_model_set_property;
+
+ gParamSpecs [PROP_DIRECTORY] =
+ g_param_spec_object ("directory",
+ _("Directory"),
+ _("The directory to list files from."),
+ G_TYPE_FILE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, gParamSpecs);
+}
+
+static void
+egg_directory_model_init (EggDirectoryModel *self)
+{
+ self->items = g_sequence_new (g_object_unref);
+}
+
+GListModel *
+egg_directory_model_new (GFile *directory)
+{
+ return g_object_new (EGG_TYPE_DIRECTORY_MODEL,
+ "directory", directory,
+ NULL);
+}
+
+GFile *
+egg_directory_model_get_directory (EggDirectoryModel *self)
+{
+ g_return_val_if_fail (EGG_IS_DIRECTORY_MODEL (self), NULL);
+
+ return self->directory;
+}
+
+void
+egg_directory_model_set_directory (EggDirectoryModel *self,
+ GFile *directory)
+{
+ g_return_if_fail (EGG_IS_DIRECTORY_MODEL (self));
+ g_return_if_fail (!directory || G_IS_FILE (directory));
+
+ if (g_set_object (&self->directory, directory))
+ {
+ egg_directory_model_reload (self);
+ g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_DIRECTORY]);
+ }
+}
+
+static guint
+egg_directory_model_get_n_items (GListModel *model)
+{
+ EggDirectoryModel *self = (EggDirectoryModel *)model;
+
+ g_return_val_if_fail (EGG_IS_DIRECTORY_MODEL (self), 0);
+
+ return g_sequence_get_length (self->items);
+}
+
+static GType
+egg_directory_model_get_item_type (GListModel *model)
+{
+ return G_TYPE_FILE_INFO;
+}
+
+static gpointer
+egg_directory_model_get_item (GListModel *model,
+ guint position)
+{
+ EggDirectoryModel *self = (EggDirectoryModel *)model;
+ GSequenceIter *iter;
+ gpointer ret;
+
+ g_return_val_if_fail (EGG_IS_DIRECTORY_MODEL (self), NULL);
+
+ if ((iter = g_sequence_get_iter_at_pos (self->items, position)) &&
+ (ret = g_sequence_get (iter)))
+ return g_object_ref (ret);
+
+ return NULL;
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+ iface->get_n_items = egg_directory_model_get_n_items;
+ iface->get_item = egg_directory_model_get_item;
+ iface->get_item_type = egg_directory_model_get_item_type;
+}
+
+void
+egg_directory_model_set_visible_func (EggDirectoryModel *self,
+ EggDirectoryModelVisibleFunc visible_func,
+ gpointer user_data,
+ GDestroyNotify user_data_free_func)
+{
+ g_return_if_fail (EGG_IS_DIRECTORY_MODEL (self));
+
+ if (self->visible_func_destroy != NULL)
+ self->visible_func_destroy (self->visible_func_data);
+
+ self->visible_func = visible_func;
+ self->visible_func_data = user_data;
+ self->visible_func_destroy = user_data_free_func;
+
+ egg_directory_model_reload (self);
+}
diff --git a/contrib/egg/egg-directory-model.h b/contrib/egg/egg-directory-model.h
new file mode 100644
index 0000000..1377491
--- /dev/null
+++ b/contrib/egg/egg-directory-model.h
@@ -0,0 +1,46 @@
+/* egg-directory-model.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file 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 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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.
+ *
+ * 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 EGG_DIRECTORY_MODEL_H
+#define EGG_DIRECTORY_MODEL_H
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_DIRECTORY_MODEL (egg_directory_model_get_type())
+
+G_DECLARE_FINAL_TYPE (EggDirectoryModel, egg_directory_model, EGG, DIRECTORY_MODEL, GObject)
+
+typedef gboolean (*EggDirectoryModelVisibleFunc) (EggDirectoryModel *self,
+ GFile *directory,
+ GFileInfo *file_info,
+ gpointer user_data);
+
+GListModel *egg_directory_model_new (GFile *directory);
+GFile *egg_directory_model_get_directory (EggDirectoryModel *self);
+void egg_directory_model_set_directory (EggDirectoryModel *self,
+ GFile *directory);
+void egg_directory_model_set_visible_func (EggDirectoryModel *self,
+ EggDirectoryModelVisibleFunc visible_func,
+ gpointer user_data,
+ GDestroyNotify user_data_free_func);
+
+G_END_DECLS
+
+#endif /* EGG_DIRECTORY_MODEL_H */
diff --git a/contrib/egg/egg-rect.c b/contrib/egg/egg-rect.c
new file mode 100644
index 0000000..aea1d85
--- /dev/null
+++ b/contrib/egg/egg-rect.c
@@ -0,0 +1,171 @@
+/* egg-rect.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file 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.1 of the License, or (at your option) any later version.
+ *
+ * This file 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.
+ *
+ * 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 "egg-rect.h"
+
+struct _EggRect
+{
+ GObject parent_instance;
+
+ gint x;
+ gint y;
+ gint width;
+ gint height;
+};
+
+enum {
+ PROP_0,
+ PROP_X,
+ PROP_Y,
+ PROP_WIDTH,
+ PROP_HEIGHT,
+ LAST_PROP
+};
+
+G_DEFINE_TYPE (EggRect, egg_rect, G_TYPE_OBJECT)
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+
+static void
+egg_rect_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EggRect *self = EGG_RECT (object);
+
+ switch (prop_id)
+ {
+ case PROP_X:
+ g_value_set_int (value, self->x);
+ break;
+
+ case PROP_Y:
+ g_value_set_int (value, self->y);
+ break;
+
+ case PROP_WIDTH:
+ g_value_set_int (value, self->width);
+ break;
+
+ case PROP_HEIGHT:
+ g_value_set_int (value, self->height);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+egg_rect_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EggRect *self = EGG_RECT (object);
+
+ switch (prop_id)
+ {
+ case PROP_X:
+ self->x = g_value_get_int (value);
+ break;
+
+ case PROP_Y:
+ self->y = g_value_get_int (value);
+ break;
+
+ case PROP_WIDTH:
+ self->width = g_value_get_int (value);
+ break;
+
+ case PROP_HEIGHT:
+ self->height = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+egg_rect_class_init (EggRectClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = egg_rect_get_property;
+ object_class->set_property = egg_rect_set_property;
+
+ gParamSpecs [PROP_X] =
+ g_param_spec_int ("x",
+ "X",
+ "X",
+ G_MININT,
+ G_MAXINT,
+ 0,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gParamSpecs [PROP_Y] =
+ g_param_spec_int ("y",
+ "Y",
+ "Y",
+ G_MININT,
+ G_MAXINT,
+ 0,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gParamSpecs [PROP_WIDTH] =
+ g_param_spec_int ("width",
+ "Width",
+ "Width",
+ G_MININT,
+ G_MAXINT,
+ 0,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gParamSpecs [PROP_HEIGHT] =
+ g_param_spec_int ("height",
+ "Height",
+ "Height",
+ G_MININT,
+ G_MAXINT,
+ 0,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, gParamSpecs);
+}
+
+static void
+egg_rect_init (EggRect *rect)
+{
+}
+
+void
+egg_rect_get_rect (EggRect *self,
+ GdkRectangle *rect)
+{
+ g_return_if_fail (EGG_IS_RECT (self));
+ g_return_if_fail (rect != NULL);
+
+ rect->x = self->x;
+ rect->y = self->y;
+ rect->width = self->width;
+ rect->height = self->height;
+}
diff --git a/contrib/egg/egg-rect.h b/contrib/egg/egg-rect.h
new file mode 100644
index 0000000..61c052f
--- /dev/null
+++ b/contrib/egg/egg-rect.h
@@ -0,0 +1,41 @@
+/* egg-rect.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file 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.1 of the License, or (at your option) any later version.
+ *
+ * This file 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.
+ *
+ * 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 EGG_RECT_H
+#define EGG_RECT_H
+
+#include <gdk/gdk.h>
+
+/*
+ * This is just a helper object for animating rectangles.
+ * It allows us to use egg_object_animate() to animate
+ * coordinates.
+ */
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_RECT (egg_rect_get_type())
+
+G_DECLARE_FINAL_TYPE (EggRect, egg_rect, EGG, RECT, GObject)
+
+void egg_rect_get_rect (EggRect *self,
+ GdkRectangle *rect);
+
+G_END_DECLS
+
+#endif /* EGG_RECT_H */
diff --git a/contrib/egg/egg-stack-list.c b/contrib/egg/egg-stack-list.c
new file mode 100644
index 0000000..661efc9
--- /dev/null
+++ b/contrib/egg/egg-stack-list.c
@@ -0,0 +1,553 @@
+/* egg-stack-list.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 "egg-stack-list"
+
+#include <glib/gi18n.h>
+
+#include "egg-animation.h"
+#include "egg-rect.h"
+#include "egg-stack-list.h"
+
+#define FADE_DURATION 250
+#define SLIDE_DURATION 350
+
+typedef struct
+{
+ GtkOverlay *overlay;
+ GtkScrolledWindow *scroller;
+ GtkBox *box;
+ GtkListBox *headers;
+ GtkListBox *content;
+ GtkListBox *fake_list;
+ GtkStack *flip_stack;
+
+ GPtrArray *models;
+
+ GtkListBoxRow *activated;
+
+ GtkListBoxRow *animating;
+ EggAnimation *animation;
+ EggRect *animating_rect;
+} EggStackListPrivate;
+
+typedef struct
+{
+ GListModel *model;
+ GtkWidget *header;
+ EggStackListCreateWidgetFunc create_widget_func;
+ gpointer user_data;
+ GDestroyNotify user_data_free_func;
+} ModelInfo;
+
+G_DEFINE_TYPE_WITH_PRIVATE (EggStackList, egg_stack_list, GTK_TYPE_BIN)
+
+enum {
+ PROP_0,
+ PROP_MODEL,
+ LAST_PROP
+};
+
+enum {
+ HEADER_ACTIVATED,
+ ROW_ACTIVATED,
+ LAST_SIGNAL
+};
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+static guint gSignals [LAST_SIGNAL];
+
+static void
+model_info_free (gpointer data)
+{
+ ModelInfo *info = data;
+
+ g_object_unref (info->model);
+ if (info->user_data_free_func)
+ info->user_data_free_func (info->user_data);
+ g_slice_free (ModelInfo, info);
+}
+
+static GtkWidget *
+egg_stack_list_create_widget_func (gpointer item,
+ gpointer user_data)
+{
+ ModelInfo *info = user_data;
+
+ return info->create_widget_func (item, info->user_data);
+}
+
+static void
+egg_stack_list_content_row_activated (EggStackList *self,
+ GtkListBoxRow *row,
+ GtkListBox *box)
+{
+ EggStackListPrivate *priv = egg_stack_list_get_instance_private (self);
+
+ g_return_if_fail (EGG_IS_STACK_LIST (self));
+ g_return_if_fail (GTK_IS_LIST_BOX_ROW (row));
+ g_return_if_fail (GTK_IS_LIST_BOX (box));
+
+ priv->activated = row;
+
+ g_signal_emit (self, gSignals [ROW_ACTIVATED], 0, row);
+
+ priv->activated = NULL;
+}
+
+static void
+egg_stack_list_header_row_activated (EggStackList *self,
+ GtkListBoxRow *row,
+ GtkListBox *box)
+{
+ EggStackListPrivate *priv = egg_stack_list_get_instance_private (self);
+
+ g_return_if_fail (EGG_IS_STACK_LIST (self));
+ g_return_if_fail (GTK_IS_LIST_BOX_ROW (row));
+ g_return_if_fail (GTK_IS_LIST_BOX (box));
+
+ priv->activated = row;
+
+ g_signal_emit (self, gSignals [HEADER_ACTIVATED], 0, row);
+
+ priv->activated = NULL;
+}
+
+static gboolean
+egg_stack_list__overlay__get_child_position (EggStackList *self,
+ GtkWidget *widget,
+ GdkRectangle *rect,
+ GtkOverlay *overlay)
+{
+ EggStackListPrivate *priv = egg_stack_list_get_instance_private (self);
+
+ g_assert (EGG_IS_STACK_LIST (self));
+ g_assert (GTK_IS_WIDGET (widget));
+ g_assert (rect != NULL);
+ g_assert (GTK_IS_OVERLAY (overlay));
+
+ egg_rect_get_rect (priv->animating_rect, rect);
+
+ return TRUE;
+}
+
+static void
+egg_stack_list_end_anim (EggStackList *self)
+{
+ EggStackListPrivate *priv = egg_stack_list_get_instance_private (self);
+ GtkListBoxRow *header;
+ ModelInfo *info;
+
+ g_assert (EGG_IS_STACK_LIST (self));
+ g_assert (priv->animating != NULL);
+ g_assert (priv->models->len > 0);
+
+ info = g_ptr_array_index (priv->models, priv->models->len - 1);
+ header = g_object_ref (priv->animating);
+
+ priv->animating = NULL;
+
+ if (priv->animation != NULL)
+ {
+ egg_animation_stop (priv->animation);
+ g_clear_object (&priv->animation);
+ }
+
+ g_assert (header != NULL);
+ g_assert (GTK_IS_LIST_BOX_ROW (header));
+ g_assert (gtk_widget_get_parent (GTK_WIDGET (header)) == GTK_WIDGET (priv->overlay));
+
+ gtk_container_remove (GTK_CONTAINER (priv->overlay),
+ GTK_WIDGET (header));
+
+ gtk_container_add (GTK_CONTAINER (priv->headers), GTK_WIDGET (header));
+
+ gtk_list_box_bind_model (priv->content,
+ info->model,
+ egg_stack_list_create_widget_func,
+ info,
+ NULL);
+
+ gtk_stack_set_visible_child (GTK_STACK (priv->flip_stack), GTK_WIDGET (priv->scroller));
+
+ g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_MODEL]);
+
+ g_object_unref (header);
+}
+
+static void
+animation_finished (gpointer data)
+{
+ EggStackListPrivate *priv;
+ EggStackList *self;
+ GtkListBoxRow *row;
+ gpointer *closure = data;
+
+ g_assert (closure != NULL);
+ g_assert (EGG_IS_STACK_LIST (closure [0]));
+ g_assert (GTK_IS_LIST_BOX_ROW (closure [1]));
+
+ self = closure [0];
+ row = closure [1];
+
+ priv = egg_stack_list_get_instance_private (self);
+
+ if (row == priv->animating)
+ egg_stack_list_end_anim (self);
+
+ g_object_unref (closure[0]);
+ g_object_unref (closure[1]);
+ g_free (closure);
+}
+
+static void
+egg_stack_list_begin_anim (EggStackList *self,
+ GtkListBoxRow *row,
+ const GdkRectangle *begin_area,
+ const GdkRectangle *end_area)
+{
+ EggStackListPrivate *priv = egg_stack_list_get_instance_private (self);
+ GdkFrameClock *frame_clock;
+ gpointer *closure;
+
+ g_assert (EGG_IS_STACK_LIST (self));
+ g_assert (row != NULL);
+ g_assert (begin_area != NULL);
+ g_assert (end_area != NULL);
+
+ priv->animating = row;
+
+ g_object_set (priv->animating_rect,
+ "x", begin_area->x,
+ "y", begin_area->y,
+ "width", begin_area->width,
+ "height", begin_area->height,
+ NULL);
+
+ frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (self));
+
+ closure = g_new0 (gpointer, 2);
+ closure [0] = g_object_ref (self);
+ closure [1] = g_object_ref_sink (row);
+
+ gtk_overlay_add_overlay (GTK_OVERLAY (priv->overlay), GTK_WIDGET (row));
+
+ priv->animation = egg_object_animate_full (priv->animating_rect,
+ EGG_ANIMATION_EASE_OUT_CUBIC,
+ SLIDE_DURATION,
+ frame_clock,
+ animation_finished,
+ closure,
+ "x", end_area->x,
+ "y", end_area->y,
+ "width", end_area->width,
+ "height", end_area->height,
+ NULL);
+
+ g_object_ref (priv->animation);
+
+ g_signal_connect_object (priv->animating_rect,
+ "notify",
+ G_CALLBACK (gtk_widget_queue_resize),
+ priv->animating,
+ G_CONNECT_SWAPPED);
+
+ gtk_stack_set_visible_child (GTK_STACK (priv->flip_stack),
+ GTK_WIDGET (priv->fake_list));
+}
+
+static void
+egg_stack_list_finalize (GObject *object)
+{
+ EggStackList *self = (EggStackList *)object;
+ EggStackListPrivate *priv = egg_stack_list_get_instance_private (self);
+
+ g_clear_pointer (&priv->models, g_ptr_array_unref);
+ g_clear_object (&priv->animating_rect);
+ g_clear_object (&priv->animation);
+
+ G_OBJECT_CLASS (egg_stack_list_parent_class)->finalize (object);
+}
+
+static void
+egg_stack_list_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EggStackList *self = EGG_STACK_LIST (object);
+
+ switch (prop_id)
+ {
+ case PROP_MODEL:
+ g_value_set_object (value, egg_stack_list_get_model (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+egg_stack_list_class_init (EggStackListClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = egg_stack_list_finalize;
+ object_class->get_property = egg_stack_list_get_property;
+
+ gParamSpecs [PROP_MODEL] =
+ g_param_spec_object ("model",
+ _("Model"),
+ _("Model"),
+ G_TYPE_LIST_MODEL,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, gParamSpecs);
+
+ gSignals [HEADER_ACTIVATED] =
+ g_signal_new ("header-activated",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggStackListClass, header_activated),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ GTK_TYPE_LIST_BOX_ROW);
+
+ gSignals [ROW_ACTIVATED] =
+ g_signal_new ("row-activated",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggStackListClass, row_activated),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ GTK_TYPE_LIST_BOX_ROW);
+}
+
+static void
+egg_stack_list_init (EggStackList *self)
+{
+ EggStackListPrivate *priv = egg_stack_list_get_instance_private (self);
+
+ priv->animating_rect = g_object_new (EGG_TYPE_RECT, NULL);
+
+ priv->models = g_ptr_array_new_with_free_func (model_info_free);
+
+ priv->overlay = g_object_new (GTK_TYPE_OVERLAY,
+ "visible", TRUE,
+ NULL);
+ g_signal_connect_object (priv->overlay,
+ "get-child-position",
+ G_CALLBACK (egg_stack_list__overlay__get_child_position),
+ self,
+ G_CONNECT_SWAPPED);
+ gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (priv->overlay));
+
+ priv->box = g_object_new (GTK_TYPE_BOX,
+ "orientation", GTK_ORIENTATION_VERTICAL,
+ "vexpand", TRUE,
+ "visible", TRUE,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (priv->overlay), GTK_WIDGET (priv->box));
+
+ priv->headers = g_object_new (GTK_TYPE_LIST_BOX,
+ "selection-mode", GTK_SELECTION_NONE,
+ "visible", TRUE,
+ NULL);
+ g_signal_connect_object (priv->headers,
+ "row-activated",
+ G_CALLBACK (egg_stack_list_header_row_activated),
+ self,
+ G_CONNECT_SWAPPED);
+ gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (priv->headers)),
+ "stack-header");
+ gtk_container_add (GTK_CONTAINER (priv->box), GTK_WIDGET (priv->headers));
+
+ priv->flip_stack = g_object_new (GTK_TYPE_STACK,
+ "transition-duration", FADE_DURATION,
+ "transition-type", GTK_STACK_TRANSITION_TYPE_CROSSFADE,
+ "visible", TRUE,
+ "vexpand", TRUE,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (priv->box), GTK_WIDGET (priv->flip_stack));
+
+ priv->scroller = g_object_new (GTK_TYPE_SCROLLED_WINDOW,
+ "shadow-type", GTK_SHADOW_NONE,
+ "vexpand", TRUE,
+ "visible", TRUE,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (priv->flip_stack), GTK_WIDGET (priv->scroller));
+
+ priv->content = g_object_new (GTK_TYPE_LIST_BOX,
+ "visible", TRUE,
+ NULL);
+ g_signal_connect_object (priv->content,
+ "row-activated",
+ G_CALLBACK (egg_stack_list_content_row_activated),
+ self,
+ G_CONNECT_SWAPPED);
+ gtk_container_add (GTK_CONTAINER (priv->scroller), GTK_WIDGET (priv->content));
+
+ priv->fake_list = g_object_new (GTK_TYPE_LIST_BOX,
+ "visible", TRUE,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (priv->flip_stack), GTK_WIDGET (priv->fake_list));
+}
+
+GtkWidget *
+egg_stack_list_new (void)
+{
+ return g_object_new (EGG_TYPE_STACK_LIST, NULL);
+}
+
+void
+egg_stack_list_push (EggStackList *self,
+ GtkWidget *header,
+ GListModel *model,
+ EggStackListCreateWidgetFunc create_widget_func,
+ gpointer user_data,
+ GDestroyNotify user_data_free_func)
+{
+ EggStackListPrivate *priv = egg_stack_list_get_instance_private (self);
+ ModelInfo *info;
+ GdkRectangle current_area;
+ GdkRectangle target_area;
+ gint nat_height;
+
+ g_return_if_fail (EGG_IS_STACK_LIST (self));
+ g_return_if_fail (GTK_IS_WIDGET (header));
+ g_return_if_fail (G_IS_LIST_MODEL (model));
+ g_return_if_fail (create_widget_func != NULL);
+
+ if (priv->animating != NULL)
+ egg_stack_list_end_anim (self);
+
+ if (!GTK_IS_LIST_BOX_ROW (header))
+ header = g_object_new (GTK_TYPE_LIST_BOX_ROW,
+ "child", header,
+ "visible", TRUE,
+ NULL);
+
+ info = g_slice_new0 (ModelInfo);
+ info->header = header;
+ info->model = g_object_ref (model);
+ info->create_widget_func = create_widget_func;
+ info->user_data = user_data;
+ info->user_data_free_func = user_data_free_func;
+
+ g_ptr_array_add (priv->models, info);
+
+ /*
+ * Nothing to animate, make everything happen immediately.
+ */
+ if (priv->activated == NULL)
+ {
+ gtk_container_add (GTK_CONTAINER (priv->headers), GTK_WIDGET (header));
+ gtk_list_box_bind_model (priv->content,
+ model,
+ egg_stack_list_create_widget_func,
+ info,
+ NULL);
+ g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_MODEL]);
+ return;
+ }
+
+ /*
+ * Get the location to begin the animation.
+ */
+ gtk_widget_get_allocation (GTK_WIDGET (priv->activated), ¤t_area);
+ gtk_widget_translate_coordinates (GTK_WIDGET (priv->activated),
+ GTK_WIDGET (priv->overlay),
+ current_area.x, current_area.y,
+ ¤t_area.x, ¤t_area.y);
+
+ /*
+ * Get the location to end the animation.
+ */
+ gtk_widget_get_allocation (GTK_WIDGET (priv->headers), &target_area);
+ gtk_widget_get_preferred_height (GTK_WIDGET (header), NULL, &nat_height);
+ target_area.y += target_area.height;
+ target_area.height = nat_height;
+ gtk_widget_translate_coordinates (GTK_WIDGET (header),
+ GTK_WIDGET (priv->overlay),
+ target_area.x, target_area.y,
+ &target_area.x, &target_area.y);
+
+ egg_stack_list_begin_anim (self, GTK_LIST_BOX_ROW (header), ¤t_area, &target_area);
+}
+
+void
+egg_stack_list_pop (EggStackList *self)
+{
+ EggStackListPrivate *priv = egg_stack_list_get_instance_private (self);
+ ModelInfo *info;
+
+ g_return_if_fail (EGG_IS_STACK_LIST (self));
+
+ if (priv->models->len == 0)
+ return;
+
+ if (priv->animating != NULL)
+ egg_stack_list_end_anim (self);
+
+ info = g_ptr_array_index (priv->models, priv->models->len - 1);
+
+ gtk_container_remove (GTK_CONTAINER (priv->headers), GTK_WIDGET (info->header));
+ gtk_list_box_bind_model (priv->content, NULL, NULL, NULL, NULL);
+ g_ptr_array_remove_index (priv->models, priv->models->len - 1);
+
+ if (priv->models->len > 0)
+ {
+ info = g_ptr_array_index (priv->models, priv->models->len - 1);
+ gtk_list_box_bind_model (priv->content,
+ info->model,
+ egg_stack_list_create_widget_func,
+ info,
+ NULL);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_MODEL]);
+}
+
+GListModel *
+egg_stack_list_get_model (EggStackList *self)
+{
+ EggStackListPrivate *priv = egg_stack_list_get_instance_private (self);
+ ModelInfo *info;
+
+ g_return_val_if_fail (EGG_IS_STACK_LIST (self), NULL);
+
+ if (priv->models->len == 0)
+ return NULL;
+
+ info = g_ptr_array_index (priv->models, priv->models->len - 1);
+
+ return info->model;
+}
+
+guint
+egg_stack_list_get_depth (EggStackList *self)
+{
+ EggStackListPrivate *priv = egg_stack_list_get_instance_private (self);
+
+ g_return_val_if_fail (EGG_IS_STACK_LIST (self), 0);
+
+ return priv->models->len;
+}
diff --git a/contrib/egg/egg-stack-list.h b/contrib/egg/egg-stack-list.h
new file mode 100644
index 0000000..da08362
--- /dev/null
+++ b/contrib/egg/egg-stack-list.h
@@ -0,0 +1,56 @@
+/* egg-stack-list.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 EGG_STACK_LIST_H
+#define EGG_STACK_LIST_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_STACK_LIST (egg_stack_list_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (EggStackList, egg_stack_list, EGG, STACK_LIST, GtkBin)
+
+struct _EggStackListClass
+{
+ GtkBinClass parent_instance;
+
+ void (*row_activated) (EggStackList *self,
+ GtkListBoxRow *row);
+ void (*header_activated) (EggStackList *self,
+ GtkListBoxRow *row);
+};
+
+typedef GtkWidget *(*EggStackListCreateWidgetFunc) (gpointer item,
+ gpointer user_data);
+
+GtkWidget *egg_stack_list_new (void);
+void egg_stack_list_push (EggStackList *self,
+ GtkWidget *header,
+ GListModel *model,
+ EggStackListCreateWidgetFunc create_widget_func,
+ gpointer user_data,
+ GDestroyNotify user_data_free_func);
+void egg_stack_list_pop (EggStackList *self);
+GListModel *egg_stack_list_get_model (EggStackList *self);
+guint egg_stack_list_get_depth (EggStackList *self);
+
+G_END_DECLS
+
+#endif /* EGG_STACK_LIST_H */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index bb008f1..cdf6a48 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -133,6 +133,12 @@ test_vim_LDADD = \
$(NULL)
+misc_programs += test-stack-list
+test_stack_list_SOURCES = test-stack-list.c
+test_stack_list_CFLAGS = $(tests_cflags)
+test_stack_list_LDADD = $(tests_libs)
+
+
misc_programs += test-ide-source-view
test_ide_source_view_SOURCES = test-ide-source-view.c
test_ide_source_view_CFLAGS = $(tests_cflags)
diff --git a/tests/test-stack-list.c b/tests/test-stack-list.c
new file mode 100644
index 0000000..822a89b
--- /dev/null
+++ b/tests/test-stack-list.c
@@ -0,0 +1,259 @@
+/* test-stack-list.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/>.
+ */
+
+/* this code leaks, it's just a quick prototype */
+
+#include <gtk/gtk.h>
+
+#include "egg-directory-model.h"
+#include "egg-stack-list.h"
+
+#define TEST_CSS \
+ "EggStackList GtkListBox.view {" \
+ " background-color: #fafafa;" \
+ "}\n" \
+ "EggStackList GtkListBox GtkListBoxRow {" \
+ " background-color: #f2f2f2;" \
+ " color: #2e3436;" \
+ " padding-bottom: 3px;" \
+ "}\n" \
+ "EggStackList GtkListBox.stack-header GtkListBoxRow {" \
+ " background-color: #fafafa;" \
+ " color: #919191;" \
+ "}\n" \
+ "EggStackList GtkListBox.stack-header GtkListBoxRow:last-child {" \
+ " border-bottom: 1px solid #dbdbdb;" \
+ " color: #000000;" \
+ "}\n"
+
+static GtkWidget *
+create_row_func (gpointer item,
+ gboolean is_header,
+ gpointer user_data)
+{
+ GFileInfo *file_info = item;
+ GFile *parent = user_data;
+ GtkWidget *row;
+ GtkWidget *box;
+ GtkWidget *image;
+ GtkWidget *label;
+ g_autoptr(GIcon) local_gicon = NULL;
+ GObject *gicon;
+ const gchar *display_name;
+ g_autoptr(GFile) copy = NULL;
+
+ g_assert (!file_info || G_IS_FILE_INFO (file_info));
+ g_assert (G_IS_FILE (parent));
+
+ if (parent == NULL)
+ parent = copy = g_file_new_for_path (".");
+
+ row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
+ "visible", TRUE,
+ NULL);
+
+ g_object_set_data_full (G_OBJECT (row),
+ "G_FILE_INFO",
+ file_info ? g_object_ref (file_info) : NULL,
+ g_object_unref);
+
+ g_object_set_data_full (G_OBJECT (row),
+ "G_FILE",
+ g_file_get_child (parent, file_info ? g_file_info_get_name (file_info) : "."),
+ g_object_unref);
+
+ box = g_object_new (GTK_TYPE_BOX,
+ "border-width", 3,
+ "spacing", 6,
+ "orientation", GTK_ORIENTATION_HORIZONTAL,
+ "visible", TRUE,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (row), GTK_WIDGET (box));
+
+ if (is_header)
+ {
+ local_gicon = g_themed_icon_new ("folder-open-symbolic");
+ gicon = G_OBJECT (local_gicon);
+ }
+ else
+ gicon = g_file_info_get_attribute_object (file_info, G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON);
+
+ display_name = g_file_info_get_display_name (file_info);
+
+ image = g_object_new (GTK_TYPE_IMAGE,
+ "gicon", gicon,
+ "visible", TRUE,
+ "margin-start", 6,
+ "margin-end", 3,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (image));
+
+ label = g_object_new (GTK_TYPE_LABEL,
+ "label", display_name,
+ "hexpand", TRUE,
+ "visible", TRUE,
+ "xalign", 0.0f,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (label));
+
+ return row;
+}
+
+static GtkWidget *
+create_regular_func (gpointer item,
+ gpointer user_data)
+{
+ return create_row_func (item, FALSE, user_data);
+}
+
+static GtkWidget *
+create_header_func (gpointer item,
+ gpointer user_data)
+{
+ return create_row_func (item, TRUE, user_data);
+}
+
+static gboolean
+hide_dot_files (EggDirectoryModel *model,
+ GFile *directory,
+ GFileInfo *file_info,
+ gpointer user_data)
+{
+ const gchar *name = g_file_info_get_display_name (file_info);
+ return (name && *name != '.');
+}
+
+static void
+header_activated (EggStackList *stack_list,
+ GtkListBoxRow *row,
+ gpointer user_data)
+{
+ GListModel *model;
+ GFile *directory;
+
+ directory = g_object_get_data (G_OBJECT (row), "G_FILE");
+ model = egg_stack_list_get_model (stack_list);
+
+ while (model != NULL)
+ {
+ GFile *current = egg_directory_model_get_directory (EGG_DIRECTORY_MODEL (model));
+
+ if (g_file_equal (current, directory))
+ break;
+
+ if (egg_stack_list_get_depth (stack_list) == 1)
+ break;
+
+ egg_stack_list_pop (stack_list);
+
+ model = egg_stack_list_get_model (stack_list);
+ }
+}
+
+static void
+row_activated (EggStackList *stack_list,
+ GtkListBoxRow *row,
+ gpointer user_data)
+{
+ GFileInfo *file_info;
+ EggDirectoryModel *model;
+ g_autoptr(GFile) child = NULL;
+ GFile *directory;
+
+ model = EGG_DIRECTORY_MODEL (egg_stack_list_get_model (stack_list));
+ directory = egg_directory_model_get_directory (model);
+ file_info = g_object_get_data (G_OBJECT (row), "G_FILE_INFO");
+
+ if ((file_info == NULL) || (G_FILE_TYPE_DIRECTORY != g_file_info_get_file_type (file_info)))
+ return;
+
+ child = g_file_get_child (directory, g_file_info_get_name (file_info));
+ model = EGG_DIRECTORY_MODEL (egg_directory_model_new (child));
+
+ egg_stack_list_push (stack_list,
+ create_header_func (file_info, directory),
+ G_LIST_MODEL (model),
+ create_regular_func,
+ directory, NULL);
+
+ g_object_unref (model);
+}
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ GtkWindow *window;
+ EggStackList *stack_list;
+ GListModel *model;
+ GFile *root;
+ GFile *directory;
+ GFileInfo *info;
+ GtkCssProvider *provider;
+
+ gtk_init (&argc, &argv);
+
+ provider = gtk_css_provider_new ();
+ gtk_css_provider_load_from_data (provider, TEST_CSS, -1, NULL);
+ gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
+ GTK_STYLE_PROVIDER (provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+ window = g_object_new (GTK_TYPE_WINDOW,
+ "default-width", 300,
+ "default-height", 700,
+ NULL);
+
+ root = g_file_new_for_path (g_get_current_dir ());
+ directory = g_file_get_parent (root);
+
+ info = g_file_info_new ();
+ g_file_info_set_name (info, g_file_get_basename (directory));
+ g_file_info_set_display_name (info, g_file_get_basename (directory));
+ g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
+
+ model = egg_directory_model_new (directory);
+ egg_directory_model_set_visible_func (EGG_DIRECTORY_MODEL (model), hide_dot_files, NULL, NULL);
+
+ stack_list = g_object_new (EGG_TYPE_STACK_LIST,
+ "visible", TRUE,
+ NULL);
+ g_signal_connect (stack_list,
+ "header-activated",
+ G_CALLBACK (header_activated),
+ NULL);
+ g_signal_connect (stack_list,
+ "row-activated",
+ G_CALLBACK (row_activated),
+ NULL);
+ gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (stack_list));
+
+ egg_stack_list_push (stack_list,
+ create_header_func (info, directory),
+ model,
+ create_regular_func,
+ directory,
+ NULL);
+
+ g_signal_connect (window, "delete-event", gtk_main_quit, NULL);
+ gtk_window_present (window);
+
+ gtk_main ();
+
+ return 0;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]