/* Java-Gnome Bindings Library
 *
 * Copyright 1998-2004 the Java-Gnome Team, all rights reserved.
 *
 * The Java-Gnome bindings library is free software distributed under
 * the terms of the GNU Library General Public License version 2.
 */

package org.gnu.gtk;

import java.util.Vector;

import org.gnu.glib.EventMap;
import org.gnu.glib.EventType;
import org.gnu.glib.GObject;
import org.gnu.glib.Type;
import org.gnu.gtk.event.TextBufferEvent;
import org.gnu.gtk.event.TextBufferListener;
import org.gnu.glib.Handle;

/**
 * The TextBuffer is the object used to store data to be displayed in a {@link
 * TextView} widget. See the {@link TextView} widget description for details.
 */
public class TextBuffer extends GObject {

    /**
     * The list of objects interested in this widget's events
     */
    private Vector listeners;

    /**
     * Create butter from native handle
     */
    protected TextBuffer(Handle handle) {
        super(handle);
    }

    /**
     * Internal static factory method to be used by Java-Gnome only.
     */
    protected static TextBuffer getTextBuffer(Handle handle) {
        if (handle == null)
            return null;

        TextBuffer obj = (TextBuffer) getGObjectFromHandle(handle);
        if (obj == null)
            obj = new TextBuffer(handle);

        return obj;
    }

    /**
     * Creates a new text buffer.
     * 
     * @param table
     *            A tag table
     */
    public TextBuffer(TextTagTable table) {
        super(gtk_text_buffer_new(table.getHandle()));
    }

    /**
     * Creates a new buffer and a new default TextTagTable.
     */
    public TextBuffer() {
        super(gtk_text_buffer_new_noTable());
    }

    /**
     * Obtains the number of lines in the buffer. This value is cached, so the
     * function is very fast.
     * 
     * @return The number of lines in the buffer.
     */
    public int getLineCount() {
        return gtk_text_buffer_get_line_count(getHandle());
    }

    /**
     * Gets the number of characters in the buffer. Note that characters and
     * bytes are not the same. You can't expect the contents of the buffer in
     * string form to be this many bytes long. The character count is cached, so
     * this function is very fast.
     * 
     * @return Number of characters in the buffer.
     */
    public int getCharCount() {
        return gtk_text_buffer_get_char_count(getHandle());
    }

    /**
     * Get the GtkTextTagTable associated with this buffer.
     * 
     * @return Associated TextTagTable.
     */
    public TextTagTable getTextTagTable() {
        Handle hndl = gtk_text_buffer_get_tag_table(getHandle());
        return TextTagTable.getTextTagTable(hndl);
    }

    /**
     * Inserts text at position iter. Emits the "insert_text" signal; insertion
     * actually occurs in the default handler for the signal. iter is
     * invalidated when insertion occurs (because the buffer contents change),
     * but the default signal handler revalidates it to point to the end of the
     * inserted text.
     * 
     * @param iter
     *            A position in the buffer
     * @param text
     *            The text to insert
     * @throws IllegalArgumentException
     *             If <code>text</code> is null
     */
    public void insertText(TextIter iter, String text) {
        if (text == null)
            throw new IllegalArgumentException("Text cannot be null");
        gtk_text_buffer_insert(getHandle(), iter.getHandle(), text);
    }

    /**
     * Inserts text at the current cursor position
     * 
     * @param text
     *            The text to insert
     * @throws IllegalArgumentException
     *             If <code>text</code> is null
     */
    public void insertText(String text) {
        if (text == null)
            throw new IllegalArgumentException("Text cannot be null");
        gtk_text_buffer_insert_at_cursor(getHandle(), text);
    }

    /**
     * Like {@link #insertText(TextIter, String)}, but the insertion will not
     * occur if iter is at a non-editable location in the buffer. Usually you
     * want to prevent insertions at ineditable locations if the insertion
     * results from a user action (is interactive).
     * 
     * <p>
     * defaultEditable indicates the editability of text that doesn't have a tag
     * affecting editability applied to it. Typically the result of
     * {@link TextView#getEditable()} is appropriate here.
     * 
     * @param iter
     *            A position in buffer
     * @param text
     *            The text to insert
     * @param defaultEditable
     *            Default editability of buffer
     * @return Whether text was actually inserted
     * @throws IllegalArgumentException
     *             If <code>text</code> is null
     */
    public boolean insertTextInteractive(TextIter iter, String text,
            boolean defaultEditable) {
        if (text == null)
            throw new IllegalArgumentException("Text cannot be null");
        return gtk_text_buffer_insert_interactive(getHandle(),
                iter.getHandle(), text, defaultEditable);
    }

    /**
     * Calls {@link #insertTextInteractive(TextIter, String, boolean)} at the
     * cursor position.
     * 
     * <p>
     * defaultEditable indicates the editability of text that doesn't have a tag
     * affecting editability applied to it. Typically the result of
     * {@link TextView#getEditable()} is appropriate here.
     * 
     * @param text
     *            The text to insert.
     * @param defaultEditable
     *            Default editability of buffer
     * @return Whether text was actually inserted.
     * @throws IllegalArgumentException
     *             If <code>text</code> is null
     */
    public boolean insertTextInteractive(String text, boolean defaultEditable) {
        if (text == null)
            throw new IllegalArgumentException("Text cannot be null");
        return gtk_text_buffer_insert_interactive_at_cursor(getHandle(), text,
                defaultEditable);
    }

    /**
     * Copies text, tags, and pixbufs between start and end (the order of start
     * and end doesn't matter) and inserts the copy at iter. Used instead of
     * simply getting/inserting text because it preserves images and tags. If
     * start and end are in a different buffer from buffer, the two buffers must
     * share the same tag table.
     * <p>
     * Implemented via emissions of the insert_text and apply_tag signals, so
     * expect those.
     * 
     * @param iter
     *            A position in the buffer
     * @param start
     *            A position in a TextBuffer
     * @param end
     *            Another position in the same buffer as start
     */
    public void insertRange(TextIter iter, TextIter start, TextIter end) {
        gtk_text_buffer_insert_range(getHandle(), iter.getHandle(), start
                .getHandle(), end.getHandle());
    }

    /**
     * Inserts text and applies a tag to that text.
     * 
     * @param iter
     *            Place to insert text
     * @param text
     *            Text to insert
     * @param tag
     *            name of TextTag to apply to that text.
     * @throws IllegalArgumentException
     *             If <code>text</code> is null
     */
    public void insertText(TextIter iter, String text, String tag) {
        if (text == null)
            throw new IllegalArgumentException("Text cannot be null");
        insert_with_tag(getHandle(), iter.getHandle(), text, tag);
    }

    /**
     * Inserts text and applies tags to that text.
     * 
     * @param iter
     *            Place to insert text
     * @param text
     *            Text to insert
     * @param tags
     *            Array of TextTag names to apply to that text.
     */
    public void insertText(TextIter iter, String text, String[] tags) {
        TextMark mark = createMark("jginternal", getEndIter(), true);
        insertText(iter, text);
        TextIter start = getIter(mark);
        deleteMark("jginternal");
        for (int i = 0; i < tags.length; i++) {
            applyTag(tags[i], start, iter);
        }
    }

    /**
     * Same as {@link #insertRange(TextIter, TextIter, TextIter)}, but does
     * nothing if the insertion point isn't editable. The defaultEditable
     * parameter indicates whether the text is editable at iter if no tags
     * enclosing iter affect editability. Typically the result of {@link
     * TextView#getEditable()} is appropriate here.
     * 
     * @param iter
     *            A position in buffer
     * @param start
     *            A position in a TextBuffer
     * @param end
     *            Another position in the same buffer as start
     * @param defaultEditable
     *            default editability of the buffer
     * @return Whether an insertion was possible at iter.
     */
    public boolean insertRangeInteractive(TextIter iter, TextIter start,
            TextIter end, boolean defaultEditable) {
        return gtk_text_buffer_insert_range_interactive(getHandle(), iter
                .getHandle(), start.getHandle(), end.getHandle(),
                defaultEditable);
    }

    /**
     * Deletes text between <code>start</code> and <code>end</code>. The
     * order of start and end is not actually relevant. This function actually
     * emits the "delete_range" signal, and the default handler of that signal
     * deletes the text. Because the buffer is modified, all outstanding
     * iterators become invalid after calling this function; however, the start
     * and end will be re-initialized to point to the location where text was
     * deleted.
     * 
     * @param start
     *            A position in buffer
     * @param end
     *            Another position in buffer.
     */
    public void deleteText(TextIter start, TextIter end) {
        gtk_text_buffer_delete(getHandle(), start.getHandle(), end.getHandle());
    }

    /**
     * Deletes all <em>editable</em> text in the given range. Calls {@link
     * #deleteText(TextIter, TextIter)} for each editable sub-range of
     * start,end. <code>start</code> and <code>end</code> are revalidated to
     * point to the location of the last deleted range, or left untouched if no
     * text was deleted.
     * 
     * @param start
     *            Start of range to delete
     * @param end
     *            End of range
     * @param defaultEditable
     *            Ehether the buffer is editable by default
     * @return Whether some text was actually deleted
     */
    public boolean deleteTextInteractive(TextIter start, TextIter end,
            boolean defaultEditable) {
        return gtk_text_buffer_delete_interactive(getHandle(), start
                .getHandle(), end.getHandle(), defaultEditable);
    }

    /**
     * Deletes current contents of buffer, and inserts <code>text</code>
     * instead.
     * 
     * @param text
     *            The text to use as the content of the buffer.
     * @throws IllegalArgumentException
     *             If <code>text</code> is null
     */
    public void setText(String text) {
        if (text == null)
            throw new IllegalArgumentException("Text cannot be null");
        gtk_text_buffer_set_text(getHandle(), text);
    }

    /**
     * Returns the text in the range start,end. Excludes undisplayed text (text
     * marked with tags that set the invisibility attribute) if
     * includeHiddenChars is FALSE. Does not include characters representing
     * embedded images, so byte and character indexes into the returned string
     * do not correspond to byte and character indexes into the buffer. Contrast
     * with {@link #getSlice(TextIter, TextIter, boolean)}.
     * 
     * @param start
     *            Start of a range
     * @param end
     *            End of a range
     * @param includeHiddenChars
     *            Whether to include invisible text
     * @return The text of the buffer
     */
    public String getText(TextIter start, TextIter end,
            boolean includeHiddenChars) {
        return gtk_text_buffer_get_text(getHandle(), start.getHandle(), end
                .getHandle(), includeHiddenChars);
    }

    /**
     * Returns the text in the range start,end. Excludes undisplayed text (text
     * marked with tags that set the invisibility attribute) if
     * includeHiddenChars is FALSE. The returned string includes a 0xFFFC
     * character whenever the buffer contains embedded images, so byte and
     * character indexes into the returned string do correspond to byte and
     * character indexes into the buffer. Contrast with
     * {@link #getText(TextIter, TextIter, boolean)}. Note that 0xFFFC can
     * occur in normal text as well, so it is not a reliable indicator that a
     * pixbuf or widget is in the buffer.
     * 
     * @param start
     *            Start of a range
     * @param end
     *            End of a range.
     * @param includeHiddenChars
     *            Whether to include invisible text
     * @return The string
     */
    public String getSlice(TextIter start, TextIter end,
            boolean includeHiddenChars) {
        return gtk_text_buffer_get_slice(getHandle(), start.getHandle(), end
                .getHandle(), includeHiddenChars);
    }

    /**
     * Inserts an image into the text buffer at iter. The image will be counted
     * as one character in character counts, and when obtaining the buffer
     * contents as a string, will be represented by the Unicode "object
     * replacement character" 0xFFFC. Note that the "slice" variants for
     * obtaining portions of the buffer as a string include this character for
     * pixbufs, but the "text" variants do not.
     * 
     * @param iter
     *            Location to insert the pixbuf
     * @param pixbuf
     *            A Pixbuf
     */
    public void insertPixbuf(TextIter iter, org.gnu.gdk.Pixbuf pixbuf) {
        gtk_text_buffer_insert_pixbuf(getHandle(), iter.getHandle(), pixbuf
                .getHandle());
    }

    /**
     * Inserts a child widget anchor into the text buffer at iter. The anchor
     * will be counted as one character in character counts, and when obtaining
     * the buffer contents as a string, will be represented by the Unicode
     * "object replacement character" 0xFFFC. Note that the "slice" variants for
     * obtaining portions of the buffer as a string include this character for
     * child anchors, but the "text" variants do not.
     * 
     * @param iter
     *            Location to insert the anchor
     * @param anchor
     *            A TextChildAnchor
     */
    public void inserChildAnchor(TextIter iter, TextChildAnchor anchor) {
        gtk_text_buffer_insert_child_anchor(getHandle(), iter.getHandle(),
                anchor.getHandle());
    }

    /**
     * This is a convenience function which simply creates a child anchor with
     * {link TextChildAnchor#TextChildAnchor()} and inserts it into the buffer
     * 
     * @param iter
     *            Location in the buffer
     * @return The created child anchor
     */
    public TextChildAnchor createChildAnchor(TextIter iter) {
        return new TextChildAnchor(gtk_text_buffer_create_child_anchor(
                getHandle(), iter.getHandle()));
    }

    /**
     * Creates a mark at position <code>where</code>. Otherwise, the mark can
     * be retrieved by name using {@link #getMark(String)}. If a mark has left
     * gravity, and text is inserted at the mark's current location, the mark
     * will be moved to the left of the newly-inserted text. If the mark has
     * right gravity (leftGravity = FALSE), the mark will end up on the right of
     * newly-inserted text. The standard left-to-right cursor is a mark with
     * right gravity (when you type, the cursor stays on the right side of the
     * text you're typing).
     * 
     * <p>
     * Emits the "mark_set" event as notification of the mark's initial
     * placement.
     * 
     * @param markName
     *            Name for mark
     * @param where
     *            Location to place mark
     * @param leftGravity
     *            Whether the mark has left gravity
     * @return The new TextMark object
     */
    public TextMark createMark(String markName, TextIter where,
            boolean leftGravity) {
        return TextMark.getTextMark(gtk_text_buffer_create_mark(getHandle(),
                markName, where.getHandle(), leftGravity));
    }

    /**
     * Moves <code>mark</code> to the new location <code>where</code>.
     * Emits the "mark_set" event as notification of the move.
     * 
     * @param mark
     *            A TextMark
     * @param where
     *            New location for mark in buffer
     */
    public void moveMark(TextMark mark, TextIter where) {
        gtk_text_buffer_move_mark(getHandle(), mark.getHandle(), where
                .getHandle());
    }

    /**
     * Moves the mark named <code>name</code> (which must exist) to location
     * <code>where</code>
     * 
     * @param name
     *            Name of a mark
     * @param where
     *            New location for mark
     */
    public void moveMark(String name, TextIter where) {
        gtk_text_buffer_move_mark_by_name(getHandle(), name, where.getHandle());
    }

    /**
     * Deletes <code>mark</code>, so that it's no longer located anywhere in
     * the buffer. There is no way to undelete a mark.
     * {@link TextMark#getDeleted()} indicates that a mark no longer belongs to
     * a buffer. The "mark_deleted" signal will be emitted as notification after
     * the mark is deleted.
     * 
     * @param mark
     *            A TextMark in buffer
     */
    public void deleteMark(TextMark mark) {
        gtk_text_buffer_delete_mark(getHandle(), mark.getHandle());
    }

    /**
     * Deletes the mark named <code>name</code>; the mark must exist.
     * 
     * @see #deleteMark(TextMark)
     * @param name
     *            The name of the mark to delete
     */
    public void deleteMark(String name) {
        gtk_text_buffer_delete_mark_by_name(getHandle(), name);
    }

    /**
     * Returns true if a mark exists with the given name.
     */
    public boolean markExists(String name) {
        return (gtk_text_buffer_get_mark(getHandle(), name) != null);
    }

    /**
     * Returns the mark named <code>name</code> in the buffer, or
     * <code>null</code> if no such mark exists in the buffer
     */
    public TextMark getMark(String name) {
        Handle markHandle = gtk_text_buffer_get_mark(getHandle(), name);
        return TextMark.getTextMark(markHandle);
    }

    /**
     * Returns the mark that represents the cursor (insertion point). Equivalent
     * to calling gtk_text_buffer_get_mark() to get the mark named "insert", but
     * very slightly more efficient, and involves less typing.
     * 
     * @return Insertion point mark
     */
    public TextMark getInsert() {
        Handle hndl = gtk_text_buffer_get_insert(getHandle());
        return TextMark.getTextMark(hndl);
    }

    /**
     * Returns the mark that represents the selection bound. Equivalent to
     * calling {@link #getMark(String)} to get the mark named "selection_bound",
     * but very slightly more efficient, and involves less typing.
     * 
     * <p>
     * The currently-selected text in buffer is the region between the
     * "selection_bound" and "insert" marks. If "selection_bound" and "insert"
     * are in the same place, then there is no current selection.
     * 
     * @return Mark for the selection
     */
    public TextMark getSelectionBound() {
        Handle hndl = gtk_text_buffer_get_selection_bound(getHandle());
        return TextMark.getTextMark(hndl);
    }

    /**
     * This function moves the "insert" and "selection_bound" marks
     * simultaneously. If you move them to the same place in two steps with
     * {@link #moveMark(TextMark, TextIter)}, you will temporarily select a
     * region in between their old and new locations, which can be pretty
     * inefficient since the temporarily-selected region will force stuff to be
     * recalculated. This function moves them as a unit, which can be optimized.
     * 
     * @param where
     *            Where to put the cursor
     */
    public void placeCursor(TextIter where) {
        gtk_text_buffer_place_cursor(getHandle(), where.getHandle());
    }

    /**
     * Emits the "apply_tag" signal on buffer. The default handler for the
     * signal applies tag to the given range. start and end do not have to be in
     * order.
     * 
     * @param tag
     *            A TextTag
     * @param start
     *            One bound of range to be tagged
     * @param end
     *            Other bound of range to be tagged
     */
    public void applyTag(TextTag tag, TextIter start, TextIter end) {
        gtk_text_buffer_apply_tag(getHandle(), tag.getHandle(), start
                .getHandle(), end.getHandle());
    }

    /**
     * Emits the "remove_tag" signal. The default handler for the signal removes
     * all occurrences of tag from the given range. start and end don't have to
     * be in order
     * 
     * @param tag
     *            A TextTag
     * @param start
     *            One bound of range to be untagged
     * @param end
     *            Other bound of range to be untagged
     */
    public void removeTag(TextTag tag, TextIter start, TextIter end) {
        gtk_text_buffer_remove_tag(getHandle(), tag.getHandle(), start
                .getHandle(), end.getHandle());
    }

    /**
     * Looks up a tag by name and then applies it.
     * 
     * @param name
     *            Name of a named TextTag
     * @param start
     *            One bound of range to be tagged
     * @param end
     *            Other bound of range to be tagged
     */
    public void applyTag(String name, TextIter start, TextIter end) {
        gtk_text_buffer_apply_tag_by_name(getHandle(), name, start.getHandle(),
                end.getHandle());
    }

    /**
     * Removes a tag based on it's name.
     * 
     * @param name
     *            Name of a TextTag
     * @param start
     *            One bound of range to be untagged
     * @param end
     *            Other bound of range to be untagged
     */
    public void removeTag(String name, TextIter start, TextIter end) {
        gtk_text_buffer_remove_tag_by_name(getHandle(), name,
                start.getHandle(), end.getHandle());
    }

    /**
     * Removes all tags in the range between start and end. Be careful with this
     * function; it could remove tags added in code unrelated to the code you're
     * currently writing. That is, using this function is probably a bad idea if
     * you have two or more unrelated code sections that add tags.
     * 
     * <p>
     * <b>THIS METHOD IS INCORRECT AND SHOULD NOT BE USED</b> Use
     * {@link #removeAllTags(TextIter, TextIter)} instead
     * </p>
     * 
     * @param start
     *            One bound of range to be untagged
     * @param end
     *            Other bound of range to be untagged
     * @deprecated
     */
    public void removeAllTags(TextTag start, TextTag end) {
        gtk_text_buffer_remove_all_tags(getHandle(), start.getHandle(), end
                .getHandle());
    }

    /**
     * Removes all tags in the range between start and end. Be careful with this
     * function; it could remove tags added in code unrelated to the code you're
     * currently writing. That is, using this function is probably a bad idea if
     * you have two or more unrelated code sections that add tags.
     * 
     * @param start
     * @param end
     */
    public void removeAllTags(TextIter start, TextIter end) {
        gtk_text_buffer_remove_all_tags(getHandle(), start.getHandle(), end
                .getHandle());
    }

    /**
     * Obtains an iterator pointing to charOffset within the given line. The
     * charOffset must exist, offsets off the end of the line are not allowed.
     * Note characters, not bytes; UTF-8 may encode one character as multiple
     * bytes.
     * 
     * @param lineNumber
     *            Line number counting from 0
     * @param charOffset
     *            Char offset from start of line
     * @return Iterator
     */
    public TextIter getIter(int lineNumber, int charOffset) {
        return TextIter.getTextIter(gtk_text_buffer_get_iter_at_line_offset(
                getHandle(), lineNumber, charOffset));
    }

    /**
     * Obtains an iterator pointing to charOffset within the entire string,
     * statring from the beginning
     * 
     * @param charOffset
     *            Character offset from the beginning of the text
     * @return Iterator pointing to that point.
     */
    public TextIter getIter(int charOffset) {
        return TextIter.getTextIter(gtk_text_buffer_get_iter_at_offset(
                getHandle(), charOffset));
    }

    /**
     * Obtains an iterator pointing to the start of the given line
     * 
     * @param line
     *            The line
     * @return Iterator
     */
    public TextIter getLineIter(int line) {
        return TextIter.getTextIter(gtk_text_buffer_get_iter_at_line(
                getHandle(), line));
    }

    /**
     * Returns an iter at the given mark
     * 
     * @param mark
     *            The position for the iter
     * @return Iterator
     */
    public TextIter getIter(TextMark mark) {
        return TextIter.getTextIter(gtk_text_buffer_get_iter_at_mark(
                getHandle(), mark.getHandle()));
    }

    /**
     * Obtains the location of anchor within buffer.
     * 
     * @param anchor
     *            An anchor that appears in the buffer.
     * @return Iterator
     */
    public TextIter getIter(TextChildAnchor anchor) {
        return TextIter.getTextIter(gtk_text_buffer_get_iter_at_child_anchor(
                getHandle(), anchor.getHandle()));
    }

    /**
     * Returns an iterator for the start of the text
     * 
     * @return iterator
     */
    public TextIter getStartIter() {
        return TextIter
                .getTextIter(gtk_text_buffer_get_start_iter(getHandle()));
    }

    /**
     * Returns Iterator for the end of the text
     * 
     * @return Iterator for the end of the text.
     */
    public TextIter getEndIter() {
        return TextIter.getTextIter(gtk_text_buffer_get_end_iter(getHandle()));
    }

    /**
     * Indicates whether the buffer has been modified since the last call to
     * {@link #setModified(boolean)} set the modification flag to FALSE. Used
     * for example to enable a "save" function in a text editor.
     * 
     * @return TRUE if the buffer has been modified
     */
    public boolean getModified() {
        return gtk_text_buffer_get_modified(getHandle());
    }

    /**
     * Used to keep track of whether the buffer has been modified since the last
     * time it was saved. Whenever the buffer is saved to disk, call
     * setModified(FALSE). When the buffer is modified, it will automatically
     * toggled on the modified bit again. When the modified bit flips, the
     * buffer emits a "modified_changed" event.
     * 
     * @param setting
     *            Modification flag setting
     */
    public void setModified(boolean setting) {
        gtk_text_buffer_set_modified(getHandle(), setting);
    }

    /**
     * Deletes the range between the "insert" and "selection_bound" marks, that
     * is, the currently-selected text. If interactive is TRUE, the editability
     * of the selection will be considered (users can't delete uneditable text).
     * 
     * @param interactive
     *            Whether the deletion is caused by user interaction
     * @param defaultEditable
     *            Whether the buffer is editable by default
     * @return Whether there was a non-empty selection to delete
     */
    public boolean deleteSelection(boolean interactive, boolean defaultEditable) {
        return gtk_text_buffer_delete_selection(getHandle(), interactive,
                defaultEditable);
    }

    /**
     * Pastes the contents of a clipboard at location. (Note: pasting is
     * asynchronous, that is, we'll ask for the paste data and return, and at
     * some point later after the main loop runs, the paste data will be
     * inserted.)
     * 
     * @param clipboard
     *            The Clipboard to paste from
     * @param location
     *            Location to insert pasted text
     * @param defaultEditable
     *            Whether the buffer is editable by default
     */
    public void pasteClipboard(Clipboard clipboard, TextIter location,
            boolean defaultEditable) {
        gtk_text_buffer_paste_clipboard(getHandle(), clipboard.getHandle(),
                location.getHandle(), defaultEditable);
    }

    /**
     * Pastes the contents of a clipboard at the insertion point. (Note: pasting
     * is asynchronous, that is, we'll ask for the paste data and return, and at
     * some point later after the main loop runs, the paste data will be
     * inserted.)
     * 
     * @param clipboard
     *            The Clipboard to paste from
     * @param defaultEditable
     *            Whether the buffer is editable by default
     */
    public void pasteClipboard(Clipboard clipboard, boolean defaultEditable) {
        gtk_text_buffer_paste_clipboardInsertPosition(getHandle(), clipboard
                .getHandle(), defaultEditable);
    }

    /**
     * Copies the currently-selected text to a clipboard.
     * 
     * @param clip
     *            The clipboard to copy the text to
     */
    public void copyClipboard(Clipboard clip) {
        gtk_text_buffer_copy_clipboard(getHandle(), clip.getHandle());
    }

    /**
     * Copies the currently-selected text to a clipboard, then deletes said text
     * if it's editable.
     * 
     * @param clipboard
     *            The Clipboard object to cut to.
     * @param defaultEditable
     *            Default editability of the buffer
     */
    public void cutClipboard(Clipboard clipboard, boolean defaultEditable) {
        gtk_text_buffer_cut_clipboard(getHandle(), clipboard.getHandle(),
                defaultEditable);
    }

    /**
     * Returns true if an area of the buffer is selected
     * 
     * @return True if an area is selected
     */
    public boolean getSelected() {
        return gtk_text_buffer_get_selection_exists(getHandle());
    }

    /**
     * Returns an iterator for the start of the selected text, or
     * <code>null</code> if no text is selected.
     */
    public TextIter getSelectionStart() {
        if (getSelected()) {
            return TextIter
                    .getTextIter(gtk_text_buffer_get_selection_start(getHandle()));
        } else {
            return null;
        }
    }

    /**
     * Returns an iterator for the end of the selection, or <code>null</code>
     * if no text is selected.
     */
    public TextIter getSelectionEnd() {
        if (getSelected()) {
            return TextIter
                    .getTextIter(gtk_text_buffer_get_selection_end(getHandle()));
        } else {
            return null;
        }
    }

    /**
     * Called to indicate that the buffer operations between here and a call to
     * {@link #endUserAction()} are part of a single user-visible operation. The
     * operations between beginUserAction() and endUserAction() can then be
     * grouped when creating an undo stack. TextBuffer maintains a count of
     * calls to beginUserAction() that have not been closed with a call to
     * endUserAction(), and emits the "begin_user_action" and "end_user_action"
     * event only for the outermost pair of calls. This allows you to build user
     * actions from other user actions.
     * 
     * <p>
     * The "interactive" buffer mutation functions automatically call begin/end
     * user action around the buffer operations they perform, so there's no need
     * to add extra calls if you user action consists solely of a single call to
     * one of those functions.
     */
    public void beginUserAction() {
        gtk_text_buffer_begin_user_action(getHandle());
    }

    /**
     * Should be paired with a call to {@link #beginUserAction()}. See that
     * function for a full explanation.
     */
    public void endUserAction() {
        gtk_text_buffer_end_user_action(getHandle());
    }

    /**
     * Adds clipboard to the list of clipboards in which the selection contents
     * of buffer are available. In most cases, clipboard will be the
     * GtkClipboard of type GDK_SELECTION_PRIMARY for a view of buffer.
     * 
     * @param clipboard
     *            A ClipBoard
     */
    public void addClipboard(Clipboard clipboard) {
        gtk_text_buffer_add_selection_clipboard(getHandle(), clipboard
                .getHandle());
    }

    /**
     * Removes a clipboard added with {@link #addClipboard(Clipboard)}
     * 
     * @param clipboard
     *            The clipboard to remove
     */
    public void removeClipboard(Clipboard clipboard) {
        gtk_text_buffer_remove_selection_clipboard(getHandle(), clipboard
                .getHandle());
    }

    /**
     * This function moves the "insert" and "selection_bound" marks
     * simultaneously. If you move them in two steps with moveMark, you will
     * temporarily select a region in between their old and new locations, which
     * can be pretty inefficient since the temporarily-selected region will
     * force stuff to be recalculated. This function moves them as a unit, which
     * can be optimized.
     * 
     * @param ins
     *            where to put the "insert" mark
     * @param bound
     *            where to put the "selection_bound" mark
     * 
     * @since 2.4
     */
    public void selectRange(TextIter ins, TextIter bound) {
        gtk_text_buffer_select_range(getHandle(), ins.getHandle(), bound
                .getHandle());
    }

    /**
     * Performs the appropriate action as if the user hit the delete key with
     * the cursor at the position specified by iter. In the normal case a single
     * character will be deleted, but when combining accents are involved, more
     * than one character can be deleted, and when precomposed character and
     * accent combinations are involved, less than one character will be
     * deleted.
     * 
     * Because the buffer is modified, all outstanding iterators become invalid
     * after calling this function; however, the iter will be re-initialized to
     * point to the location where text was deleted.
     * 
     * @param iter
     *            A position in the buffer.
     * @param interactive
     *            Whether the deletion is caused by user interaction.
     * @param default_editable
     *            Whether the buffer is editable by default.
     * @return TRUE if the buffer was modified.
     */
    public boolean backspace(TextIter iter, boolean interactive,
            boolean default_editable) {
        return gtk_text_buffer_backspace(getHandle(), iter.getHandle(),
                interactive, default_editable);
    }

    /***************************************************************************
     * TextView event handling.
     **************************************************************************/

    public Class getEventListenerClass(String signal) {
        Class cls = evtMap.getEventListenerClass(signal);
        if (cls == null)
            cls = super.getEventListenerClass(signal);
        return cls;
    }

    public EventType getEventType(String signal) {
        EventType et = evtMap.getEventType(signal);
        if (et == null)
            et = super.getEventType(signal);
        return et;
    }

    private static EventMap evtMap = new EventMap();
    static {
        addEvents(evtMap);
    }

    private static void addEvents(EventMap evtMap) {
        evtMap.addEvent("apply_tag", "handleApplyTag",
                TextBufferEvent.Type.APPLY_TAG, TextBufferListener.class);
        evtMap.addEvent("changed", "handleChanged",
                TextBufferEvent.Type.CHANGED, TextBufferListener.class);
        evtMap.addEvent("delete_range", "handleDeleteRange",
                TextBufferEvent.Type.DELETE_RANGE, TextBufferListener.class);
        evtMap.addEvent("insert_child_anchor", "handleInsertChildAnchor",
                TextBufferEvent.Type.INSERT_CHILD_ANCHOR,
                TextBufferListener.class);
        evtMap.addEvent("insert_pixbuf", "handleInsertPixbuf",
                TextBufferEvent.Type.INSERT_PIXBUF, TextBufferListener.class);
        evtMap.addEvent("insert_text", "handleInsertText",
                TextBufferEvent.Type.INSERT_TEXT, TextBufferListener.class);
        evtMap.addEvent("mark_deleted", "handleMarkDelete",
                TextBufferEvent.Type.MARK_DELETED, TextBufferListener.class);
        evtMap.addEvent("mark_set", "handleMarkSet",
                TextBufferEvent.Type.MARK_SET, TextBufferListener.class);
        evtMap
                .addEvent("modified_changed", "handleModifiedChanged",
                        TextBufferEvent.Type.MODIFIED_CHANGED,
                        TextBufferListener.class);
        evtMap.addEvent("remove_tag", "handleRemoveTag",
                TextBufferEvent.Type.REMOVE_TAG, TextBufferListener.class);
    }

    /**
     * Register an object to receive text view event notification.
     * 
     * @param listener
     *            The object that has implemented the TextBufferListener
     *            interface that is to receive the text view events.
     */
    public void addListener(TextBufferListener listener) {
        // Don't add the listener a second time if it is in the Vector.
        int i = findListener(listeners, listener);
        if (i == -1) {
            if (null == listeners) {
                evtMap.initialize(this, TextBufferEvent.Type.APPLY_TAG);
                evtMap.initialize(this, TextBufferEvent.Type.CHANGED);
                evtMap.initialize(this, TextBufferEvent.Type.DELETE_RANGE);
                evtMap.initialize(this,
                        TextBufferEvent.Type.INSERT_CHILD_ANCHOR);
                evtMap.initialize(this, TextBufferEvent.Type.INSERT_PIXBUF);
                evtMap.initialize(this, TextBufferEvent.Type.INSERT_TEXT);
                evtMap.initialize(this, TextBufferEvent.Type.MARK_DELETED);
                evtMap.initialize(this, TextBufferEvent.Type.MARK_SET);
                evtMap.initialize(this, TextBufferEvent.Type.MODIFIED_CHANGED);
                evtMap.initialize(this, TextBufferEvent.Type.REMOVE_TAG);
                listeners = new Vector();
            }
            listeners.addElement(listener);
        }
    }

    /**
     * Unregister an object that was receiving text view event notification.
     * 
     * @param listener
     *            The object that is to no longer receive text view events.
     */
    public void removeListener(TextBufferListener listener) {
        int i = findListener(listeners, listener);
        if (i > -1) {
            listeners.remove(i);
        }
        if (0 == listeners.size()) {
            evtMap.uninitialize(this, TextBufferEvent.Type.APPLY_TAG);
            evtMap.uninitialize(this, TextBufferEvent.Type.CHANGED);
            evtMap.uninitialize(this, TextBufferEvent.Type.DELETE_RANGE);
            evtMap.uninitialize(this, TextBufferEvent.Type.INSERT_CHILD_ANCHOR);
            evtMap.uninitialize(this, TextBufferEvent.Type.INSERT_PIXBUF);
            evtMap.uninitialize(this, TextBufferEvent.Type.INSERT_TEXT);
            evtMap.uninitialize(this, TextBufferEvent.Type.MARK_DELETED);
            evtMap.uninitialize(this, TextBufferEvent.Type.MARK_SET);
            evtMap.uninitialize(this, TextBufferEvent.Type.MODIFIED_CHANGED);
            evtMap.uninitialize(this, TextBufferEvent.Type.REMOVE_TAG);
            listeners = null;
        }
    }

    protected void fireTextBufferEvent(TextBufferEvent event) {
        if (null == listeners) {
            return;
        }
        int size = listeners.size();
        int i = 0;
        while (i < size) {
            TextBufferListener tbc = (TextBufferListener) listeners
                    .elementAt(i);
            tbc.textBufferEvent(event);
            i++;
        }
    }

    /**
     * Give us a way to locate a specific listener in a Vector.
     * 
     * @param list
     *            The Vector of listeners to search.
     * @param listener
     *            The object that is to be located in the Vector.
     * @return Returns the index of the listener in the Vector, or -1 if the
     *         listener is not contained in the Vector.
     */
    protected static int findListener(Vector list, Object listener) {
        if (null == list || null == listener)
            return -1;
        return list.indexOf(listener);
    }

    private void handleApplyTag(Handle tag, Handle startIter, Handle endIter) {
        fireTextBufferEvent(new TextBufferEvent(this,
                TextBufferEvent.Type.APPLY_TAG));
    }

    private void handleChanged() {
        fireTextBufferEvent(new TextBufferEvent(this,
                TextBufferEvent.Type.CHANGED));
    }

    private void handleDeleteRange(Handle startIter, Handle endIter) {
        fireTextBufferEvent(new TextBufferEvent(this,
                TextBufferEvent.Type.DELETE_RANGE));
    }

    private void handleInsertChildAnchor(Handle position, Handle childAnchor) {
        fireTextBufferEvent(new TextBufferEvent(this,
                TextBufferEvent.Type.INSERT_CHILD_ANCHOR));
    }

    private void handleInsertPixbuf(Handle position, Handle pixbuf) {
        fireTextBufferEvent(new TextBufferEvent(this,
                TextBufferEvent.Type.INSERT_PIXBUF));
    }

    private void handleInsertText(Handle position, String text, int length) {
        fireTextBufferEvent(new TextBufferEvent(this,
                TextBufferEvent.Type.INSERT_TEXT));
    }

    private void handleMarkDelete(Handle mark) {
        fireTextBufferEvent(new TextBufferEvent(this,
                TextBufferEvent.Type.MARK_DELETED));
    }

    private void handleMarkSet(Handle location, Handle mark) {
        fireTextBufferEvent(new TextBufferEvent(this,
                TextBufferEvent.Type.MARK_SET));
    }

    private void handleModifiedChanged() {
        fireTextBufferEvent(new TextBufferEvent(this,
                TextBufferEvent.Type.MODIFIED_CHANGED));
    }

    private void handleRemoveTag(Handle tag, Handle startChar, Handle endChar) {
        fireTextBufferEvent(new TextBufferEvent(this,
                TextBufferEvent.Type.REMOVE_TAG));
    }

    /**
     * Retrieve the runtime type used by the GLib library.
     */
    public static Type getType() {
        return new Type(gtk_text_buffer_get_type());
    }

    /**
     * Creates a TextTag initialised for use in this TextBuffer. If a TextTag
     * already exists with this name null is returned.
     */
    public TextTag createTag(String name) {
        Handle hndl = create_tag(getHandle(), name);
        return TextTag.getTextTag(hndl);
    }

    native static final protected int gtk_text_buffer_get_type();

    native static final protected Handle gtk_text_buffer_new(Handle table);

    native static final protected int gtk_text_buffer_get_line_count(
            Handle buffer);

    native static final protected int gtk_text_buffer_get_char_count(
            Handle buffer);

    native static final protected Handle gtk_text_buffer_get_tag_table(
            Handle buffer);

    native static final protected void gtk_text_buffer_set_text(Handle buffer,
            String text);

    native static final protected void gtk_text_buffer_insert(Handle buffer,
            Handle iter, String text);

    native static final protected void gtk_text_buffer_insert_at_cursor(
            Handle buffer, String text);

    native static final protected boolean gtk_text_buffer_insert_interactive(
            Handle buffer, Handle iter, String text, boolean defaultEditable);

    native static final protected boolean gtk_text_buffer_insert_interactive_at_cursor(
            Handle buffer, String text, boolean defaultEditable);

    native static final protected void gtk_text_buffer_insert_range(
            Handle buffer, Handle iter, Handle start, Handle end);

    native static final protected boolean gtk_text_buffer_insert_range_interactive(
            Handle buffer, Handle iter, Handle start, Handle end,
            boolean defaultEditable);

    native static final protected void gtk_text_buffer_delete(Handle buffer,
            Handle start, Handle end);

    native static final protected boolean gtk_text_buffer_delete_interactive(
            Handle buffer, Handle start, Handle end, boolean defaultEditable);

    native static final protected String gtk_text_buffer_get_text(
            Handle buffer, Handle start, Handle end, boolean includeHiddenChars);

    native static final protected String gtk_text_buffer_get_slice(
            Handle buffer, Handle start, Handle end, boolean includeHiddenChars);

    native static final protected void gtk_text_buffer_insert_pixbuf(
            Handle buffer, Handle iter, Handle pixbuf);

    native static final protected void gtk_text_buffer_insert_child_anchor(
            Handle buffer, Handle iter, Handle anchor);

    native static final protected Handle gtk_text_buffer_create_child_anchor(
            Handle buffer, Handle iter);

    native static final protected Handle gtk_text_buffer_create_mark(
            Handle buffer, String markName, Handle where, boolean leftGravity);

    native static final protected void gtk_text_buffer_move_mark(Handle buffer,
            Handle mark, Handle where);

    native static final protected void gtk_text_buffer_delete_mark(
            Handle buffer, Handle mark);

    native static final protected Handle gtk_text_buffer_get_mark(
            Handle buffer, String name);

    native static final protected void gtk_text_buffer_move_mark_by_name(
            Handle buffer, String name, Handle where);

    native static final protected void gtk_text_buffer_delete_mark_by_name(
            Handle buffer, String name);

    native static final protected Handle gtk_text_buffer_get_insert(
            Handle buffer);

    native static final protected Handle gtk_text_buffer_get_selection_bound(
            Handle buffer);

    native static final protected void gtk_text_buffer_place_cursor(
            Handle buffer, Handle where);

    native static final protected void gtk_text_buffer_apply_tag(Handle buffer,
            Handle tag, Handle start, Handle end);

    native static final protected void gtk_text_buffer_remove_tag(
            Handle buffer, Handle tag, Handle start, Handle end);

    native static final protected void gtk_text_buffer_apply_tag_by_name(
            Handle buffer, String name, Handle start, Handle end);

    native static final protected void gtk_text_buffer_remove_tag_by_name(
            Handle buffer, String name, Handle start, Handle end);

    native static final protected void gtk_text_buffer_remove_all_tags(
            Handle buffer, Handle start, Handle end);

    // This method is not wrapped, and were it called would break (as the last
    // two params are supposed to be TextIters). 
//    native static final protected void gtk_text_buffer_get_bounds(
//            Handle buffer, int start, int end);

    native static final protected boolean gtk_text_buffer_get_modified(
            Handle buffer);

    native static final protected void gtk_text_buffer_set_modified(
            Handle buffer, boolean setting);

    native static final protected void gtk_text_buffer_add_selection_clipboard(
            Handle buffer, Handle clipboard);

    native static final protected void gtk_text_buffer_remove_selection_clipboard(
            Handle buffer, Handle clipboard);

    native static final protected void gtk_text_buffer_cut_clipboard(
            Handle buffer, Handle clipboard, boolean defaultEditable);

    native static final protected void gtk_text_buffer_copy_clipboard(
            Handle buffer, Handle clipboard);

    native static final protected void gtk_text_buffer_paste_clipboard(
            Handle buffer, Handle clipboard, Handle overrideLocation,
            boolean defaultEditable);

    // Broken, should take two TextIters, not publicly visible, commenting out
//    native static final protected boolean gtk_text_buffer_get_selection_bounds(
//            Handle buffer, int start, int end);

    native static final protected boolean gtk_text_buffer_delete_selection(
            Handle buffer, boolean interactive, boolean defaultEditable);

    native static final protected void gtk_text_buffer_begin_user_action(
            Handle buffer);

    native static final protected void gtk_text_buffer_end_user_action(
            Handle buffer);

    native static final protected Handle gtk_text_buffer_new_noTable();

    native static final protected Handle create_tag(Handle buffer, String name);

    native static final protected void insert_with_tag(Handle buffer,
            Handle iter, String text, String tag);

    native static final protected void gtk_text_buffer_select_range(
            Handle handle, Handle ins, Handle bound);

    native protected static final boolean gtk_text_buffer_get_selection_exists(
            Handle handle);

    native protected static final Handle gtk_text_buffer_get_selection_start(
            Handle handle);

    native protected static final Handle gtk_text_buffer_get_selection_end(
            Handle handle);

    native static final protected Handle gtk_text_buffer_get_iter_at_line_offset(
            Handle buffer, int lineNumber, int charOffset);

    native static final protected Handle gtk_text_buffer_get_iter_at_offset(
            Handle buffer, int charOffset);

    native static final protected Handle gtk_text_buffer_get_iter_at_line(
            Handle buffer, int line);

    native static final protected Handle gtk_text_buffer_get_iter_at_mark(
            Handle buffer, Handle mark);

    native static final protected Handle gtk_text_buffer_get_iter_at_child_anchor(
            Handle buffer, Handle anchor);

    native static final protected Handle gtk_text_buffer_get_start_iter(
            Handle buffer);

    native static final protected Handle gtk_text_buffer_get_end_iter(
            Handle buffer);

    native protected static final void gtk_text_buffer_paste_clipboardInsertPosition(
            Handle handle, Handle clip, boolean editable);

    // GTK 2.6 additions.
    native static final private boolean gtk_text_buffer_backspace(
            Handle buffer, Handle iter, boolean interactive,
            boolean default_editable);
}
