/* -*- 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 <swilmet@gnome.org>
*
* 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);
}