/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */ /* gtksourcemarkssequence.c * This file is part of GtkSourceView * * Copyright (C) 2013 - Sébastien Wilmet * * GtkSourceView 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. * * GtkSourceView 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "gtksourcemarkssequence.h" /* An object for storing GtkTextMarks. The text marks are sorted internally with * a GSequence. Going to the previous or next text mark has a O(1) complexity. * Finding a text mark in the neighborhood of a text iter has a O(log n) * complexity, with 'n' the number of marks in the sequence. * * The GSequenceIter associated to a text mark is inserted into the text mark, * with g_object_set_qdata(). So the text mark knows its position in the * GSequence. This allows to use normal GtkTextMarks in the API, instead of * using a subclass or a custom iter. * * The MarksSequence has a weak reference to the text buffer. */ enum { PROP_0, PROP_BUFFER, }; struct _GtkSourceMarksSequencePrivate { GtkTextBuffer *buffer; GSequence *seq; /* The quark used for storing the GSequenceIter into the text mark, with * g_object_set_qdata(). */ GQuark quark; }; G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceMarksSequence, _gtk_source_marks_sequence, G_TYPE_OBJECT) static void remove_qdata (GtkTextMark *mark, GtkSourceMarksSequence *seq) { g_object_set_qdata (G_OBJECT (mark), seq->priv->quark, NULL); } static void free_sequence (GtkSourceMarksSequence *seq) { if (seq->priv->seq != NULL) { g_sequence_foreach (seq->priv->seq, (GFunc)remove_qdata, seq); g_sequence_free (seq->priv->seq); seq->priv->seq = NULL; } } static gint compare_marks (GtkTextMark *mark1, GtkTextMark *mark2) { GtkTextBuffer *buffer; GtkTextIter iter1; GtkTextIter iter2; g_assert (GTK_IS_TEXT_MARK (mark1)); g_assert (GTK_IS_TEXT_MARK (mark2)); buffer = gtk_text_mark_get_buffer (mark1); g_assert (buffer == gtk_text_mark_get_buffer (mark2)); gtk_text_buffer_get_iter_at_mark (buffer, &iter1, mark1); gtk_text_buffer_get_iter_at_mark (buffer, &iter2, mark2); return gtk_text_iter_compare (&iter1, &iter2); } static void mark_set_cb (GtkTextBuffer *buffer, GtkTextIter *location, GtkTextMark *mark, GtkSourceMarksSequence *seq) { GSequenceIter *seq_iter; seq_iter = g_object_get_qdata (G_OBJECT (mark), seq->priv->quark); if (seq_iter != NULL) { g_sequence_sort_changed (seq_iter, (GCompareDataFunc)compare_marks, NULL); } } static void mark_deleted_cb (GtkTextBuffer *buffer, GtkTextMark *mark, GtkSourceMarksSequence *seq) { _gtk_source_marks_sequence_remove (seq, mark); } static void set_buffer (GtkSourceMarksSequence *seq, GtkTextBuffer *buffer) { g_assert (seq->priv->buffer == NULL); seq->priv->buffer = buffer; g_object_add_weak_pointer (G_OBJECT (buffer), (gpointer *)&seq->priv->buffer); g_signal_connect_object (buffer, "mark-set", G_CALLBACK (mark_set_cb), seq, 0); g_signal_connect_object (buffer, "mark-deleted", G_CALLBACK (mark_deleted_cb), seq, 0); } static void _gtk_source_marks_sequence_dispose (GObject *object) { GtkSourceMarksSequence *seq = GTK_SOURCE_MARKS_SEQUENCE (object); if (seq->priv->buffer != NULL) { g_object_remove_weak_pointer (G_OBJECT (seq->priv->buffer), (gpointer *)&seq->priv->buffer); seq->priv->buffer = NULL; } free_sequence (seq); G_OBJECT_CLASS (_gtk_source_marks_sequence_parent_class)->dispose (object); } static void _gtk_source_marks_sequence_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkSourceMarksSequence *seq; g_return_if_fail (GTK_SOURCE_IS_MARKS_SEQUENCE (object)); seq = GTK_SOURCE_MARKS_SEQUENCE (object); switch (prop_id) { case PROP_BUFFER: g_value_set_object (value, seq->priv->buffer); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void _gtk_source_marks_sequence_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkSourceMarksSequence *seq; g_return_if_fail (GTK_SOURCE_IS_MARKS_SEQUENCE (object)); seq = GTK_SOURCE_MARKS_SEQUENCE (object); switch (prop_id) { case PROP_BUFFER: set_buffer (seq, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void _gtk_source_marks_sequence_class_init (GtkSourceMarksSequenceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = _gtk_source_marks_sequence_dispose; object_class->get_property = _gtk_source_marks_sequence_get_property; object_class->set_property = _gtk_source_marks_sequence_set_property; g_object_class_install_property (object_class, PROP_BUFFER, g_param_spec_object ("buffer", "Buffer", "The text buffer", GTK_TYPE_TEXT_BUFFER, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); } static void _gtk_source_marks_sequence_init (GtkSourceMarksSequence *seq) { gchar *unique_str; seq->priv = _gtk_source_marks_sequence_get_instance_private (seq); seq->priv->seq = g_sequence_new ((GDestroyNotify)g_object_unref); unique_str = g_strdup_printf ("gtk-source-marks-sequence-%p", seq); seq->priv->quark = g_quark_from_string (unique_str); g_free (unique_str); } GtkSourceMarksSequence * _gtk_source_marks_sequence_new (GtkTextBuffer *buffer) { g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL); return g_object_new (GTK_SOURCE_TYPE_MARKS_SEQUENCE, "buffer", buffer, NULL); } gboolean _gtk_source_marks_sequence_is_empty (GtkSourceMarksSequence *seq) { g_return_val_if_fail (GTK_SOURCE_IS_MARKS_SEQUENCE (seq), TRUE); return g_sequence_is_empty (seq->priv->seq); } void _gtk_source_marks_sequence_add (GtkSourceMarksSequence *seq, GtkTextMark *mark) { GSequenceIter *seq_iter; g_return_if_fail (GTK_SOURCE_IS_MARKS_SEQUENCE (seq)); g_return_if_fail (GTK_IS_TEXT_MARK (mark)); g_return_if_fail (gtk_text_mark_get_buffer (mark) == seq->priv->buffer); seq_iter = g_object_get_qdata (G_OBJECT (mark), seq->priv->quark); if (seq_iter != NULL) { /* The mark is already added. */ return; } seq_iter = g_sequence_insert_sorted (seq->priv->seq, mark, (GCompareDataFunc)compare_marks, NULL); g_object_ref (mark); g_object_set_qdata (G_OBJECT (mark), seq->priv->quark, seq_iter); } void _gtk_source_marks_sequence_remove (GtkSourceMarksSequence *seq, GtkTextMark *mark) { GSequenceIter *seq_iter; g_return_if_fail (GTK_SOURCE_IS_MARKS_SEQUENCE (seq)); g_return_if_fail (GTK_IS_TEXT_MARK (mark)); seq_iter = g_object_get_qdata (G_OBJECT (mark), seq->priv->quark); if (seq_iter != NULL) { g_object_set_qdata (G_OBJECT (mark), seq->priv->quark, NULL); g_sequence_remove (seq_iter); } } GtkTextMark * _gtk_source_marks_sequence_next (GtkSourceMarksSequence *seq, GtkTextMark *mark) { GSequenceIter *seq_iter; g_return_val_if_fail (GTK_SOURCE_IS_MARKS_SEQUENCE (seq), NULL); g_return_val_if_fail (GTK_IS_TEXT_MARK (mark), NULL); g_return_val_if_fail (gtk_text_mark_get_buffer (mark) == seq->priv->buffer, NULL); seq_iter = g_object_get_qdata (G_OBJECT (mark), seq->priv->quark); g_return_val_if_fail (seq_iter != NULL, NULL); seq_iter = g_sequence_iter_next (seq_iter); return g_sequence_iter_is_end (seq_iter) ? NULL : g_sequence_get (seq_iter); } GtkTextMark * _gtk_source_marks_sequence_prev (GtkSourceMarksSequence *seq, GtkTextMark *mark) { GSequenceIter *seq_iter; g_return_val_if_fail (GTK_SOURCE_IS_MARKS_SEQUENCE (seq), NULL); g_return_val_if_fail (GTK_IS_TEXT_MARK (mark), NULL); g_return_val_if_fail (gtk_text_mark_get_buffer (mark) == seq->priv->buffer, NULL); seq_iter = g_object_get_qdata (G_OBJECT (mark), seq->priv->quark); g_return_val_if_fail (seq_iter != NULL, NULL); if (g_sequence_iter_is_begin (seq_iter)) { return NULL; } seq_iter = g_sequence_iter_prev (seq_iter); return g_sequence_get (seq_iter); } /* Moves @iter forward to the next position where there is at least one mark. * Returns %TRUE if @iter was moved. */ gboolean _gtk_source_marks_sequence_forward_iter (GtkSourceMarksSequence *seq, GtkTextIter *iter) { GtkTextMark *mark; GSequenceIter *seq_iter; g_return_val_if_fail (GTK_SOURCE_IS_MARKS_SEQUENCE (seq), FALSE); g_return_val_if_fail (iter != NULL, FALSE); g_return_val_if_fail (gtk_text_iter_get_buffer (iter) == seq->priv->buffer, FALSE); mark = gtk_text_buffer_create_mark (seq->priv->buffer, NULL, iter, TRUE); seq_iter = g_sequence_search (seq->priv->seq, mark, (GCompareDataFunc)compare_marks, NULL); gtk_text_buffer_delete_mark (seq->priv->buffer, mark); while (!g_sequence_iter_is_end (seq_iter)) { GtkTextMark *cur_mark = g_sequence_get (seq_iter); GtkTextIter cur_iter; gtk_text_buffer_get_iter_at_mark (seq->priv->buffer, &cur_iter, cur_mark); if (gtk_text_iter_compare (iter, &cur_iter) < 0) { *iter = cur_iter; return TRUE; } seq_iter = g_sequence_iter_next (seq_iter); } return FALSE; } /* Moves @iter backward to the previous position where there is at least one * mark. Returns %TRUE if @iter was moved. */ gboolean _gtk_source_marks_sequence_backward_iter (GtkSourceMarksSequence *seq, GtkTextIter *iter) { GtkTextMark *mark; GSequenceIter *seq_iter; g_return_val_if_fail (GTK_SOURCE_IS_MARKS_SEQUENCE (seq), FALSE); g_return_val_if_fail (iter != NULL, FALSE); g_return_val_if_fail (gtk_text_iter_get_buffer (iter) == seq->priv->buffer, FALSE); mark = gtk_text_buffer_create_mark (seq->priv->buffer, NULL, iter, TRUE); seq_iter = g_sequence_search (seq->priv->seq, mark, (GCompareDataFunc)compare_marks, NULL); gtk_text_buffer_delete_mark (seq->priv->buffer, mark); if (g_sequence_iter_is_end (seq_iter)) { seq_iter = g_sequence_iter_prev (seq_iter); } if (g_sequence_iter_is_end (seq_iter)) { /* The sequence is empty. */ return FALSE; } while (TRUE) { GtkTextMark *cur_mark; GtkTextIter cur_iter; cur_mark = g_sequence_get (seq_iter); gtk_text_buffer_get_iter_at_mark (seq->priv->buffer, &cur_iter, cur_mark); if (gtk_text_iter_compare (&cur_iter, iter) < 0) { *iter = cur_iter; return TRUE; } if (g_sequence_iter_is_begin (seq_iter)) { break; } seq_iter = g_sequence_iter_prev (seq_iter); } return FALSE; } GSList * _gtk_source_marks_sequence_get_marks_in_range (GtkSourceMarksSequence *seq, const GtkTextIter *iter1, const GtkTextIter *iter2) { GtkTextIter start; GtkTextIter end; GtkTextMark *mark_start; GSequenceIter *seq_iter; GSequenceIter *first_seq_iter = NULL; GSList *ret = NULL; g_return_val_if_fail (GTK_SOURCE_IS_MARKS_SEQUENCE (seq), NULL); g_return_val_if_fail (iter1 != NULL, NULL); g_return_val_if_fail (iter2 != NULL, NULL); g_return_val_if_fail (gtk_text_iter_get_buffer (iter1) == seq->priv->buffer, NULL); g_return_val_if_fail (gtk_text_iter_get_buffer (iter2) == seq->priv->buffer, NULL); start = *iter1; end = *iter2; gtk_text_iter_order (&start, &end); mark_start = gtk_text_buffer_create_mark (seq->priv->buffer, NULL, &start, TRUE); seq_iter = g_sequence_search (seq->priv->seq, mark_start, (GCompareDataFunc)compare_marks, NULL); gtk_text_buffer_delete_mark (seq->priv->buffer, mark_start); if (g_sequence_iter_is_end (seq_iter)) { seq_iter = g_sequence_iter_prev (seq_iter); } if (g_sequence_iter_is_end (seq_iter)) { /* The sequence is empty. */ return NULL; } /* Find the first mark */ while (TRUE) { GtkTextMark *cur_mark; GtkTextIter cur_iter; cur_mark = g_sequence_get (seq_iter); gtk_text_buffer_get_iter_at_mark (seq->priv->buffer, &cur_iter, cur_mark); if (gtk_text_iter_compare (&cur_iter, &start) < 0) { break; } first_seq_iter = seq_iter; if (g_sequence_iter_is_begin (seq_iter)) { break; } seq_iter = g_sequence_iter_prev (seq_iter); } if (first_seq_iter == NULL) { /* The last mark in the sequence is before @start. */ return NULL; } /* Go forward until @end to fill the list of marks */ for (seq_iter = first_seq_iter; !g_sequence_iter_is_end (seq_iter); seq_iter = g_sequence_iter_next (seq_iter)) { GtkTextMark *cur_mark; GtkTextIter cur_iter; cur_mark = g_sequence_get (seq_iter); gtk_text_buffer_get_iter_at_mark (seq->priv->buffer, &cur_iter, cur_mark); if (gtk_text_iter_compare (&end, &cur_iter) < 0) { break; } ret = g_slist_prepend (ret, cur_mark); } return ret; } GSList * _gtk_source_marks_sequence_get_marks_at_iter (GtkSourceMarksSequence *seq, const GtkTextIter *iter) { return _gtk_source_marks_sequence_get_marks_in_range (seq, iter, iter); }