/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4; coding: utf-8 -*- * * moofold.c * * Copyright (C) 2004-2006 by Yevgen Muntyan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * See COPYING file that comes with this distribution. */ #define MOOEDIT_COMPILATION #include "mooedit/mootext-private.h" #include "mooutils/moomarshals.h" #ifdef MOO_DEBUG #define WANT_CHECKS 1 #else #define WANT_CHECKS 0 #endif static void moo_fold_finalize (GObject *object); static void moo_fold_free_recursively (MooFold *fold); enum { LAST_SIGNAL }; // static guint signals[LAST_SIGNAL]; enum { PROP_0 }; /* MOO_TYPE_FOLD */ G_DEFINE_TYPE (MooFold, moo_fold, G_TYPE_OBJECT) static void moo_fold_class_init (MooFoldClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = moo_fold_finalize; } static void moo_fold_init (MooFold *fold) { fold->deleted = TRUE; } static void moo_fold_finalize (GObject *object) { MooFold *fold = MOO_FOLD (object); if (!fold->deleted) g_critical ("%s: oops, crash pending...", G_STRLOC); G_OBJECT_CLASS (moo_fold_parent_class)->finalize (object); } static void moo_fold_free_recursively (MooFold *fold) { MooFold *child; g_return_if_fail (MOO_IS_FOLD (fold)); fold->deleted = TRUE; child = fold->children; fold->children = NULL; for ( ; child != NULL; child = child->next) moo_fold_free_recursively (child); if (fold->start) g_object_unref (fold->start); if (fold->end) g_object_unref (fold->end); g_object_unref (fold); } int moo_fold_get_start (MooFold *fold) { g_return_val_if_fail (MOO_IS_FOLD (fold), -1); g_return_val_if_fail (!moo_fold_is_deleted (fold), -1); return moo_line_mark_get_line (fold->start); } int moo_fold_get_end (MooFold *fold) { g_return_val_if_fail (MOO_IS_FOLD (fold), -1); g_return_val_if_fail (!moo_fold_is_deleted (fold), -1); return moo_line_mark_get_line (fold->end); } gboolean moo_fold_is_deleted (MooFold *fold) { g_return_val_if_fail (MOO_IS_FOLD (fold), TRUE); return fold->deleted != 0; } #if WANT_CHECKS static void CHECK_LIST_MEMBER__ (MooFold *list, MooFold *fold) { while (list) { if (list == fold) return; list = list->next; } g_assert_not_reached (); } static void CHECK_FOLD (MooFoldTree *tree, MooFold *fold) { g_assert (tree != NULL); g_assert (MOO_IS_FOLD (fold)); g_assert (!fold->deleted); while (fold->parent) { CHECK_LIST_MEMBER__ (fold->parent->children, fold); fold = fold->parent; } CHECK_LIST_MEMBER__ (tree->folds, fold); } #else /* WANT_CHECKS */ #define CHECK_FOLD(tree,fold) #endif MooFoldTree * moo_fold_tree_new (MooTextBuffer *buffer) { MooFoldTree *tree = g_new0 (MooFoldTree, 1); tree->buffer = buffer; tree->consistent = TRUE; return tree; } void moo_fold_tree_free (MooFoldTree *tree) { MooFold *child; g_return_if_fail (tree != NULL); for (child = tree->folds; child != NULL; child = child->next) moo_fold_free_recursively (child); g_free (tree); } inline static int get_line_count (MooFoldTree *tree) { return gtk_text_buffer_get_line_count (GTK_TEXT_BUFFER (tree->buffer)); } static MooLineMark * fold_mark_new (MooFold *fold) { MooLineMark *mark = g_object_new (MOO_TYPE_LINE_MARK, NULL); _moo_line_mark_set_fold (mark, fold); return mark; } static MooFold * insert_fold (MooFoldTree *tree, MooFold *parent, int first_line, int last_line) { MooFold *fold, *last, *new_fold; g_assert (!parent || MOO_IS_FOLD (parent)); g_assert (!parent || moo_fold_get_start (parent) < first_line); g_assert (!parent || moo_fold_get_end (parent) >= last_line); last = NULL; for (fold = (parent ? parent->children : tree->folds); fold != NULL; fold = fold->next) { int start, end; start = moo_fold_get_start (fold); end = moo_fold_get_end (fold); if (!fold->next) last = fold; if (end < first_line) continue; if (start > last_line) break; if (start == first_line || end == first_line || start == last_line) return NULL; if (start < first_line) { if (end < last_line) return NULL; /* new fold is inserted into the old one */ return insert_fold (tree, fold, first_line, last_line); } else /* start > first_line */ { if (end > last_line) return NULL; /* old fold is inserted into the new one */ new_fold = g_object_new (MOO_TYPE_FOLD, NULL); new_fold->deleted = FALSE; if (parent) { if (!parent->children || fold == parent->children) parent->children = new_fold; } else { if (!tree->folds || fold == tree->folds) tree->folds = new_fold; } new_fold->parent = parent; fold->parent = new_fold; new_fold->children = fold; new_fold->n_children = 1; if (fold->next) { fold->next->prev = new_fold; new_fold->next = fold->next; fold->next = NULL; } if (fold->prev) { fold->prev->next = new_fold; new_fold->prev = fold->prev; fold->prev = NULL; } new_fold->start = fold_mark_new (new_fold); new_fold->end = fold_mark_new (new_fold); moo_text_buffer_add_line_mark (tree->buffer, new_fold->start, first_line); moo_text_buffer_add_line_mark (tree->buffer, new_fold->end, last_line); CHECK_FOLD (tree, new_fold); return new_fold; } } /* insert new fold before or in the end if is NULL */ new_fold = g_object_new (MOO_TYPE_FOLD, NULL); new_fold->deleted = FALSE; new_fold->parent = parent; new_fold->next = fold; if (parent) { if (fold == parent->children) parent->children = new_fold; parent->n_children++; } else { if (fold == tree->folds) tree->folds = new_fold; tree->n_folds++; } if (fold) { if (fold->prev) new_fold->prev = fold->prev; fold->prev = new_fold; } else if (last) { g_assert (last->next == NULL); last->next = new_fold; new_fold->prev = last; } new_fold->start = fold_mark_new (new_fold); new_fold->end = fold_mark_new (new_fold); moo_text_buffer_add_line_mark (tree->buffer, new_fold->start, first_line); moo_text_buffer_add_line_mark (tree->buffer, new_fold->end, last_line); CHECK_FOLD (tree, new_fold); return new_fold; } MooFold * moo_fold_tree_add (MooFoldTree *tree, int first_line, int last_line) { g_assert (tree != NULL); g_assert (first_line >= 0 && first_line < get_line_count (tree)); g_assert (last_line >= 0 && last_line < get_line_count (tree)); g_assert (last_line > first_line); return insert_fold (tree, NULL, first_line, last_line); } static void fold_free (MooFold *fold) { fold->deleted = TRUE; if (fold->start) { _moo_line_mark_set_fold (fold->start, NULL); if (!moo_line_mark_get_deleted (fold->start)) moo_text_buffer_delete_line_mark (moo_line_mark_get_buffer (fold->start), fold->start); g_object_unref (fold->start); fold->start = NULL; } if (fold->end) { _moo_line_mark_set_fold (fold->end, NULL); if (!moo_line_mark_get_deleted (fold->end)) moo_text_buffer_delete_line_mark (moo_line_mark_get_buffer (fold->end), fold->end); g_object_unref (fold->end); fold->end = NULL; } g_object_unref (fold); } void moo_fold_tree_remove (MooFoldTree *tree, MooFold *fold) { guint n_children; MooFold *children, *parent, *last; CHECK_FOLD (tree, fold); moo_fold_tree_expand (tree, fold); n_children = fold->n_children; last = children = fold->children; parent = fold->parent; if (children) { while (TRUE) { g_assert (last->parent == fold); last->parent = fold->parent; if (!last->next) break; } } g_assert ((last && children) || (!last && !children)); if (parent) { if (fold == parent->children) parent->children = children ? children : parent->children->next; parent->n_children = parent->n_children + n_children - 1; } else { if (fold == tree->folds) tree->folds = children ? children : tree->folds->next; tree->n_folds = tree->n_folds + n_children - 1; } if (last) last->next = fold->next; if (fold->next) fold->next->prev = last ? last : fold->prev; if (children) children->prev = fold->prev; if (fold->prev) fold->prev->next = children ? children : fold->next; fold_free (fold); } static void expand_check_visible (MooFoldTree *tree, MooFold *fold) { GtkTextIter start, end; GtkTextBuffer *buffer; MooFold *child; CHECK_FOLD (tree, fold); buffer = GTK_TEXT_BUFFER (tree->buffer); gtk_text_buffer_get_iter_at_line (buffer, &start, moo_fold_get_start (fold)); end = start; gtk_text_iter_forward_line (&end); gtk_text_buffer_remove_tag_by_name (buffer, MOO_FOLD_TAG, &start, &end); if (fold->collapsed) return; for (child = fold->children; child != NULL; child = child->next) { int start_line, end_line; if (child->prev) start_line = moo_fold_get_end (child->prev) + 1; else start_line = moo_fold_get_start (fold) + 1; end_line = moo_fold_get_start (child); gtk_text_buffer_get_iter_at_line (buffer, &start, start_line); gtk_text_buffer_get_iter_at_line (buffer, &end, end_line); gtk_text_iter_forward_line (&end); gtk_text_buffer_remove_tag_by_name (buffer, MOO_FOLD_TAG, &start, &end); expand_check_visible (tree, child); if (!child->next) break; } if (child) { if (moo_fold_get_end (child) < moo_fold_get_end (fold)) { gtk_text_buffer_get_iter_at_line (buffer, &start, moo_fold_get_end (child) + 1); gtk_text_buffer_get_iter_at_line (buffer, &end, moo_fold_get_end (fold)); gtk_text_iter_forward_line (&end); gtk_text_buffer_remove_tag_by_name (buffer, MOO_FOLD_TAG, &start, &end); } } else { start = end; gtk_text_buffer_get_iter_at_line (buffer, &end, moo_fold_get_end (fold)); gtk_text_iter_forward_line (&end); gtk_text_buffer_remove_tag_by_name (buffer, MOO_FOLD_TAG, &start, &end); } } void moo_fold_tree_expand (MooFoldTree *tree, MooFold *fold) { CHECK_FOLD (tree, fold); if (!fold->collapsed) return; fold->collapsed = FALSE; expand_check_visible (tree, fold); } void moo_fold_tree_collapse (MooFoldTree *tree, MooFold *fold) { GtkTextIter start, end; GtkTextBuffer *buffer; CHECK_FOLD (tree, fold); if (fold->collapsed) return; fold->collapsed = TRUE; buffer = GTK_TEXT_BUFFER (tree->buffer); gtk_text_buffer_get_iter_at_line (buffer, &start, moo_fold_get_start (fold) + 1); gtk_text_buffer_get_iter_at_line (buffer, &end, moo_fold_get_end (fold)); gtk_text_iter_forward_line (&end); gtk_text_buffer_apply_tag_by_name (buffer, MOO_FOLD_TAG, &start, &end); } static GSList * get_folds_in_range (MooFoldTree *tree, MooFold *parent, int first_line, int last_line, GSList *list) { MooFold *child; if (parent && first_line <= moo_fold_get_start (parent) && last_line >= moo_fold_get_start (parent)) list = g_slist_prepend (list, parent); for (child = parent ? parent->children : tree->folds; child != NULL; child = child->next) { if (last_line < moo_fold_get_start (child)) break; if (first_line >= moo_fold_get_end (child)) continue; list = get_folds_in_range (tree, child, first_line, last_line, list); } return list; } GSList * moo_fold_tree_get (MooFoldTree *tree, int first_line, int last_line) { GSList *list; g_assert (tree != NULL); g_assert (first_line >= 0 && first_line < get_line_count (tree)); g_assert (last_line >= 0 && last_line < get_line_count (tree)); g_assert (last_line >= first_line); list = get_folds_in_range (tree, NULL, first_line, last_line, NULL); return g_slist_reverse (list); }