medit/moo/mooedit/mootextview.c

4709 lines
143 KiB
C

/*
* mootextview.c
*
* Copyright (C) 2004-2007 by Yevgen Muntyan <muntyan@math.tamu.edu>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation.
*
* See COPYING file that comes with this distribution.
*/
#define MOOEDIT_COMPILATION
#include "mooedit/mootextview-private.h"
#include "mooedit/mootextview.h"
#include "mooedit/mootextbuffer.h"
#include "mooedit/mootextfind.h"
#include "mooedit/mootext-private.h"
#include "mooedit/mooedit-enums.h"
#include "mooedit/quicksearch-glade.h"
#include "mooedit/mooeditprefs.h"
#include "mooedit/mootextbox.h"
#include "mooutils/moomarshals.h"
#include "mooutils/mooutils-gobject.h"
#include "mooutils/mooutils-misc.h"
#include "mooutils/mooundo.h"
#include "mooutils/mooentry.h"
#include "mooutils/mooi18n.h"
#include <gtk/gtk.h>
#include <glib/gregex.h>
#include <gdk/gdkkeysyms.h>
#include <string.h>
#define LIGHT_BLUE "#EEF6FF"
#define BOOL_CMP(b1,b2) ((b1 && b2) || (!b1 && !b2))
#define UPDATE_PRIORITY (GTK_TEXT_VIEW_PRIORITY_VALIDATE - 5)
#define MIN_LINE_MARK_WIDTH 6
#define DEFAULT_EXPANDER_SIZE 12
#define EXPANDER_PAD 1
static GtkTextWindowType window_types[4] = {
GTK_TEXT_WINDOW_LEFT,
GTK_TEXT_WINDOW_RIGHT,
GTK_TEXT_WINDOW_TOP,
GTK_TEXT_WINDOW_BOTTOM
};
static const GtkTargetEntry text_view_target_table[] = {
{ (char*)"GTK_TEXT_BUFFER_CONTENTS", GTK_TARGET_SAME_APP, 0 }
};
static GdkAtom moo_text_view_atom;
static GObject *moo_text_view_constructor (GType type,
guint n_construct_properties,
GObjectConstructParam *construct_param);
static void moo_text_view_finalize (GObject *object);
static void moo_text_view_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
static void moo_text_view_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
static void moo_text_view_realize (GtkWidget *widget);
static void moo_text_view_unrealize (GtkWidget *widget);
static gboolean moo_text_view_expose (GtkWidget *widget,
GdkEventExpose *event);
#if !GTK_CHECK_VERSION(2,12,0)
static gboolean moo_text_view_focus_in (GtkWidget *widget,
GdkEventFocus *event);
static gboolean moo_text_view_focus_out (GtkWidget *widget,
GdkEventFocus *event);
#endif
static void moo_text_view_style_set (GtkWidget *widget,
GtkStyle *previous_style);
static void moo_text_view_size_request (GtkWidget *widget,
GtkRequisition *requisition);
static void moo_text_view_size_allocate (GtkWidget *widget,
GtkAllocation *allocation);
static void moo_text_view_remove (GtkContainer *container,
GtkWidget *child);
static void moo_text_view_copy_clipboard (GtkTextView *text_view);
static void moo_text_view_cut_clipboard (GtkTextView *text_view);
static void moo_text_view_paste_clipboard (GtkTextView *text_view);
static void moo_text_view_populate_popup(GtkTextView *text_view,
GtkMenu *menu);
static void moo_text_view_apply_style_scheme (MooTextView *view,
MooTextStyleScheme *scheme);
static void invalidate_gcs (MooTextView *view);
static void update_gcs (MooTextView *view);
static void update_tab_width (MooTextView *view);
static void invalidate_right_margin (MooTextView *view);
static void update_right_margin (MooTextView *view);
static GtkTextBuffer *get_buffer (MooTextView *view);
static MooTextBuffer *get_moo_buffer (MooTextView *view);
static GtkTextMark *get_insert (MooTextView *view);
static void cursor_moved (MooTextView *view,
GtkTextIter *where);
static void proxy_prop_notify (MooTextView *view,
GParamSpec *pspec);
static void find_word_at_cursor (MooTextView *view,
gboolean forward);
static void find_interactive (MooTextView *view);
static void replace_interactive (MooTextView *view);
static void find_next_interactive (MooTextView *view);
static void find_prev_interactive (MooTextView *view);
static void goto_line_interactive (MooTextView *view);
static gboolean start_quick_search (MooTextView *view);
static void moo_text_view_stop_quick_search (MooTextView *view);
static void insert_text_cb (MooTextView *view,
GtkTextIter *iter,
gchar *text,
gint len);
static gboolean moo_text_view_char_inserted (MooTextView *view,
GtkTextIter *where,
guint character);
static void moo_text_view_delete_selection (MooTextView *view);
static void set_manage_clipboard (MooTextView *view,
gboolean manage);
static void selection_changed (MooTextView *view,
MooTextBuffer *buffer);
static void highlight_updated (GtkTextView *view,
const GtkTextIter *start,
const GtkTextIter *end);
#if !GTK_CHECK_VERSION(2,12,0)
static void overwrite_changed (MooTextView *view);
static void check_cursor_blink (MooTextView *view);
static void moo_text_view_draw_cursor (GtkTextView *view,
GdkEventExpose *event);
#endif
static void set_draw_tabs (MooTextView *view,
gboolean draw);
static void set_draw_trailing_spaces (MooTextView *view,
gboolean draw);
static void buffer_changed (MooTextView *view);
static void update_left_margin (MooTextView *view);
static void draw_left_margin (MooTextView *view,
GdkEventExpose *event);
static void draw_marks_background (MooTextView *view,
GdkEventExpose *event);
static gboolean update_n_lines_idle (MooTextView *view);
static void line_mark_added (MooTextView *view,
MooLineMark *mark);
static void line_mark_deleted (MooTextView *view,
MooLineMark *mark);
static void line_mark_changed (MooTextView *view,
MooLineMark *mark);
static void line_mark_moved (MooTextView *view,
MooLineMark *mark);
static void set_show_line_marks (MooTextView *view,
gboolean show);
static void fold_added (MooTextView *view,
MooFold *fold);
static void fold_deleted (MooTextView *view);
static void fold_toggled (MooTextView *view,
MooFold *fold);
static void set_enable_folding (MooTextView *view,
gboolean show);
static gboolean has_boxes (MooTextView *view);
static void update_box_tag (MooTextView *view);
static gboolean has_box_at_iter (MooTextView *view,
GtkTextIter *iter);
static gboolean text_iter_forward_visible_line (MooTextView *view,
GtkTextIter *iter,
int *line);
static int get_border_window_size (GtkTextView *text_view,
GtkTextWindowType type);
enum {
DELETE_SELECTION,
FIND_INTERACTIVE,
FIND_WORD_AT_CURSOR,
FIND_NEXT_INTERACTIVE,
FIND_PREV_INTERACTIVE,
REPLACE_INTERACTIVE,
GOTO_LINE_INTERACTIVE,
CURSOR_MOVED,
CHAR_INSERTED,
UNDO,
REDO,
LINE_MARK_CLICKED,
START_QUICK_SEARCH,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL];
enum {
PROP_0,
PROP_BUFFER,
PROP_INDENTER,
PROP_AUTO_INDENT,
PROP_BACKSPACE_INDENTS,
PROP_TAB_INDENTS,
PROP_RIGHT_MARGIN_OFFSET,
PROP_DRAW_RIGHT_MARGIN,
PROP_RIGHT_MARGIN_COLOR,
PROP_HIGHLIGHT_CURRENT_LINE,
PROP_HIGHLIGHT_MATCHING_BRACKETS,
PROP_HIGHLIGHT_MISMATCHING_BRACKETS,
PROP_CURRENT_LINE_COLOR,
PROP_TAB_WIDTH,
PROP_DRAW_TABS,
PROP_DRAW_TRAILING_SPACES,
PROP_HAS_TEXT,
PROP_HAS_SELECTION,
PROP_CAN_UNDO,
PROP_CAN_REDO,
PROP_MANAGE_CLIPBOARD,
PROP_SMART_HOME_END,
PROP_ENABLE_HIGHLIGHT,
PROP_SHOW_LINE_NUMBERS,
PROP_SHOW_LINE_MARKS,
PROP_ENABLE_FOLDING,
PROP_ENABLE_QUICK_SEARCH,
PROP_QUICK_SEARCH_FLAGS
};
/* MOO_TYPE_TEXT_VIEW */
G_DEFINE_TYPE (MooTextView, moo_text_view, GTK_TYPE_TEXT_VIEW)
static void moo_text_view_class_init (MooTextViewClass *klass)
{
gpointer ref_class;
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
GtkTextViewClass *text_view_class = GTK_TEXT_VIEW_CLASS (klass);
GtkBindingSet *binding_set;
ref_class = g_type_class_ref (MOO_TYPE_INDENTER);
g_type_class_unref (ref_class);
moo_text_view_atom = gdk_atom_intern ("MOO_TEXT_VIEW", FALSE);
gobject_class->set_property = moo_text_view_set_property;
gobject_class->get_property = moo_text_view_get_property;
gobject_class->constructor = moo_text_view_constructor;
gobject_class->finalize = moo_text_view_finalize;
widget_class->button_press_event = _moo_text_view_button_press_event;
widget_class->button_release_event = _moo_text_view_button_release_event;
widget_class->motion_notify_event = _moo_text_view_motion_event;
widget_class->key_press_event = _moo_text_view_key_press_event;
widget_class->realize = moo_text_view_realize;
widget_class->unrealize = moo_text_view_unrealize;
widget_class->expose_event = moo_text_view_expose;
#if !GTK_CHECK_VERSION(2,12,0)
widget_class->focus_in_event = moo_text_view_focus_in;
widget_class->focus_out_event = moo_text_view_focus_out;
#endif
widget_class->style_set = moo_text_view_style_set;
widget_class->size_request = moo_text_view_size_request;
widget_class->size_allocate = moo_text_view_size_allocate;
container_class->remove = moo_text_view_remove;
text_view_class->move_cursor = _moo_text_view_move_cursor;
#if !GTK_CHECK_VERSION(2,12,0)
text_view_class->page_horizontally = _moo_text_view_page_horizontally;
#endif
text_view_class->delete_from_cursor = _moo_text_view_delete_from_cursor;
text_view_class->copy_clipboard = moo_text_view_copy_clipboard;
text_view_class->cut_clipboard = moo_text_view_cut_clipboard;
text_view_class->paste_clipboard = moo_text_view_paste_clipboard;
text_view_class->populate_popup = moo_text_view_populate_popup;
klass->extend_selection = _moo_text_view_extend_selection;
klass->find_word_at_cursor = find_word_at_cursor;
klass->find_interactive = find_interactive;
klass->find_next_interactive = find_next_interactive;
klass->find_prev_interactive = find_prev_interactive;
klass->replace_interactive = replace_interactive;
klass->goto_line_interactive = goto_line_interactive;
klass->undo = moo_text_view_undo;
klass->redo = moo_text_view_redo;
klass->char_inserted = moo_text_view_char_inserted;
klass->apply_style_scheme = moo_text_view_apply_style_scheme;
g_type_class_add_private (klass, sizeof (MooTextViewPrivate));
g_object_class_install_property (gobject_class,
PROP_BUFFER,
g_param_spec_object ("buffer",
"buffer",
"buffer",
MOO_TYPE_TEXT_BUFFER,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property (gobject_class,
PROP_RIGHT_MARGIN_OFFSET,
g_param_spec_uint ("right-margin-offset",
"right-margin-offset",
"right-margin-offset",
1, G_MAXUINT, 80,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_DRAW_RIGHT_MARGIN,
g_param_spec_boolean ("draw-right-margin",
"draw-right-margin",
"draw-right-margin",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_RIGHT_MARGIN_COLOR,
g_param_spec_string ("right-margin-color",
"right-margin-color",
"right-margin-color",
LIGHT_BLUE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_HIGHLIGHT_CURRENT_LINE,
g_param_spec_boolean ("highlight-current-line",
"highlight-current-line",
"highlight-current-line",
FALSE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
g_object_class_install_property (gobject_class,
PROP_HIGHLIGHT_MATCHING_BRACKETS,
g_param_spec_boolean ("highlight-matching-brackets",
"highlight-matching-brackets",
"highlight-matching-brackets",
TRUE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_HIGHLIGHT_MISMATCHING_BRACKETS,
g_param_spec_boolean ("highlight-mismatching-brackets",
"highlight-mismatching-brackets",
"highlight-mismatching-brackets",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_CURRENT_LINE_COLOR,
g_param_spec_string ("current-line-color",
"current-line-color",
"current-line-color",
LIGHT_BLUE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_TAB_WIDTH,
g_param_spec_uint ("tab-width",
"tab-width",
"tab-width",
1, G_MAXUINT, 8,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_DRAW_TABS,
g_param_spec_boolean ("draw-tabs",
"draw-tabs",
"draw-tabs",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_DRAW_TRAILING_SPACES,
g_param_spec_boolean ("draw-trailing-spaces",
"draw-trailing-spaces",
"draw-trailing-spaces",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_INDENTER,
g_param_spec_object ("indenter",
"indenter",
"indenter",
MOO_TYPE_INDENTER,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_HAS_TEXT,
g_param_spec_boolean ("has-text",
"has-text",
"has-text",
FALSE,
G_PARAM_READABLE));
g_object_class_install_property (gobject_class,
PROP_HAS_SELECTION,
g_param_spec_boolean ("has-selection",
"has-selection",
"has-selection",
FALSE,
G_PARAM_READABLE));
g_object_class_install_property (gobject_class,
PROP_CAN_UNDO,
g_param_spec_boolean ("can-undo",
"can-undo",
"can-undo",
FALSE,
G_PARAM_READABLE));
g_object_class_install_property (gobject_class,
PROP_CAN_REDO,
g_param_spec_boolean ("can-redo",
"can-redo",
"can-redo",
FALSE,
G_PARAM_READABLE));
g_object_class_install_property (gobject_class,
PROP_MANAGE_CLIPBOARD,
g_param_spec_boolean ("manage-clipboard",
"manage-clipboard",
"manage-clipboard",
TRUE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
g_object_class_install_property (gobject_class,
PROP_SMART_HOME_END,
g_param_spec_boolean ("smart-home-end",
"smart-home-end",
"smart-home-end",
TRUE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
g_object_class_install_property (gobject_class,
PROP_ENABLE_HIGHLIGHT,
g_param_spec_boolean ("enable-highlight",
"enable-highlight",
"enable-highlight",
TRUE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
g_object_class_install_property (gobject_class,
PROP_SHOW_LINE_NUMBERS,
g_param_spec_boolean ("show-line-numbers",
"show-line-numbers",
"show-line-numbers",
FALSE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
g_object_class_install_property (gobject_class,
PROP_SHOW_LINE_MARKS,
g_param_spec_boolean ("show-line-marks",
"show-line-marks",
"show-line-marks",
FALSE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
g_object_class_install_property (gobject_class,
PROP_ENABLE_FOLDING,
g_param_spec_boolean ("enable-folding",
"enable-folding",
"enable-folding",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_ENABLE_QUICK_SEARCH,
g_param_spec_boolean ("enable-quick-search",
"enable-quick-search",
"enable-quick-search",
TRUE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
g_object_class_install_property (gobject_class,
PROP_QUICK_SEARCH_FLAGS,
g_param_spec_flags ("quick-search-flags",
"quick-search-flags",
"quick-search-flags",
MOO_TYPE_TEXT_SEARCH_FLAGS,
MOO_TEXT_SEARCH_CASELESS,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
g_object_class_install_property (gobject_class,
PROP_AUTO_INDENT,
g_param_spec_boolean ("auto-indent",
"auto-indent", "auto-indent",
TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
g_object_class_install_property (gobject_class,
PROP_BACKSPACE_INDENTS,
g_param_spec_boolean ("backspace-indents",
"backspace-indents", "backspace-indents",
FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
g_object_class_install_property (gobject_class,
PROP_TAB_INDENTS,
g_param_spec_boolean ("tab-indents",
"tab-indents", "tab-indents",
FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
gtk_widget_class_install_style_property (widget_class,
g_param_spec_int ("expander-size",
"Expander Size",
"Size of the expander arrow",
0,
G_MAXINT,
DEFAULT_EXPANDER_SIZE,
G_PARAM_READABLE));
signals[UNDO] =
g_signal_new ("undo",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (MooTextViewClass, undo),
g_signal_accumulator_true_handled, NULL,
_moo_marshal_BOOLEAN__VOID,
G_TYPE_BOOLEAN, 0);
signals[REDO] =
g_signal_new ("redo",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (MooTextViewClass, redo),
g_signal_accumulator_true_handled, NULL,
_moo_marshal_BOOLEAN__VOID,
G_TYPE_BOOLEAN, 0);
signals[DELETE_SELECTION] =
_moo_signal_new_cb ("delete-selection",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST,
G_CALLBACK (moo_text_view_delete_selection),
NULL, NULL,
_moo_marshal_VOID__VOID,
G_TYPE_NONE, 0);
signals[FIND_WORD_AT_CURSOR] =
g_signal_new ("find-word-at-cursor",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (MooTextViewClass, find_word_at_cursor),
NULL, NULL,
_moo_marshal_VOID__BOOLEAN,
G_TYPE_NONE, 1,
G_TYPE_BOOLEAN);
signals[FIND_INTERACTIVE] =
g_signal_new ("find-interactive",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (MooTextViewClass, find_interactive),
NULL, NULL,
_moo_marshal_VOID__VOID,
G_TYPE_NONE, 0);
signals[FIND_NEXT_INTERACTIVE] =
g_signal_new ("find-next-interactive",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (MooTextViewClass, find_next_interactive),
NULL, NULL,
_moo_marshal_VOID__VOID,
G_TYPE_NONE, 0);
signals[FIND_PREV_INTERACTIVE] =
g_signal_new ("find-prev-interactive",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (MooTextViewClass, find_prev_interactive),
NULL, NULL,
_moo_marshal_VOID__VOID,
G_TYPE_NONE, 0);
signals[REPLACE_INTERACTIVE] =
g_signal_new ("replace-interactive",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (MooTextViewClass, replace_interactive),
NULL, NULL,
_moo_marshal_VOID__VOID,
G_TYPE_NONE, 0);
signals[GOTO_LINE_INTERACTIVE] =
g_signal_new ("goto-line-interactive",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (MooTextViewClass, goto_line_interactive),
NULL, NULL,
_moo_marshal_VOID__VOID,
G_TYPE_NONE, 0);
signals[CHAR_INSERTED] =
g_signal_new ("char-inserted",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (MooTextViewClass, char_inserted),
g_signal_accumulator_true_handled, NULL,
_moo_marshal_BOOLEAN__BOXED_UINT,
G_TYPE_BOOLEAN, 2,
GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE,
G_TYPE_UINT);
signals[CURSOR_MOVED] =
_moo_signal_new_cb ("cursor-moved",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST,
NULL, NULL, NULL,
_moo_marshal_VOID__BOXED,
G_TYPE_NONE, 1,
GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE);
signals[LINE_MARK_CLICKED] =
g_signal_new ("line-mark-clicked",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (MooTextViewClass, line_mark_clicked),
g_signal_accumulator_true_handled, NULL,
_moo_marshal_BOOLEAN__INT,
G_TYPE_BOOLEAN, 1,
G_TYPE_INT);
signals[START_QUICK_SEARCH] =
_moo_signal_new_cb ("start-quick-search",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_CALLBACK (start_quick_search),
NULL, NULL,
_moo_marshal_BOOL__VOID,
G_TYPE_BOOLEAN, 0);
binding_set = gtk_binding_set_by_class (klass);
gtk_binding_entry_add_signal (binding_set, GDK_z, GDK_CONTROL_MASK,
"undo", 0);
gtk_binding_entry_add_signal (binding_set, GDK_z, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
"redo", 0);
}
static void
moo_text_view_init (MooTextView *view)
{
view->priv = G_TYPE_INSTANCE_GET_PRIVATE (view, MOO_TYPE_TEXT_VIEW, MooTextViewPrivate);
view->priv->colors[MOO_TEXT_VIEW_COLOR_CURRENT_LINE] = g_strdup (LIGHT_BLUE);
view->priv->colors[MOO_TEXT_VIEW_COLOR_RIGHT_MARGIN] = g_strdup (LIGHT_BLUE);
view->priv->right_margin_offset = 80;
view->priv->dnd.button = GDK_BUTTON_RELEASE;
view->priv->dnd.type = MOO_TEXT_VIEW_DRAG_NONE;
view->priv->dnd.start_x = -1;
view->priv->dnd.start_y = -1;
view->priv->last_search_stamp = -1;
#if !GTK_CHECK_VERSION(2,12,0)
view->priv->saved_cursor_visible = TRUE;
#endif
view->priv->tab_indents = FALSE;
view->priv->backspace_indents = FALSE;
view->priv->enter_indents = TRUE;
view->priv->ctrl_up_down_scrolls = TRUE;
view->priv->ctrl_page_up_down_scrolls = TRUE;
view->priv->smart_home_end = TRUE;
view->priv->highlight_matching_brackets = TRUE;
view->priv->highlight_mismatching_brackets = FALSE;
view->priv->tab_width = 8;
view->priv->qs.flags = MOO_TEXT_SEARCH_CASELESS;
view->priv->lm.show_icons = FALSE;
view->priv->lm.show_numbers = FALSE;
view->priv->lm.numbers_font = NULL;
view->priv->lm.show_folds = FALSE;
view->priv->n_lines = 1;
gtk_text_view_set_left_margin (GTK_TEXT_VIEW (view), 2);
gtk_text_view_set_right_margin (GTK_TEXT_VIEW (view), 2);
}
static GObject*
moo_text_view_constructor (GType type,
guint n_construct_properties,
GObjectConstructParam *construct_param)
{
GtkTargetList *target_list;
GObject *object;
MooTextView *view;
MooUndoStack *undo_stack;
GtkTextIter iter;
object = G_OBJECT_CLASS (moo_text_view_parent_class)->constructor (
type, n_construct_properties, construct_param);
view = MOO_TEXT_VIEW (object);
view->priv->constructed = TRUE;
g_object_set (get_buffer (view),
"highlight-matching-brackets", view->priv->highlight_matching_brackets,
"highlight-mismatching-brackets", view->priv->highlight_mismatching_brackets,
NULL);
g_signal_connect_swapped (get_buffer (view), "changed",
G_CALLBACK (buffer_changed), view);
g_signal_connect_swapped (get_buffer (view), "cursor_moved",
G_CALLBACK (cursor_moved), view);
g_signal_connect_swapped (get_buffer (view), "selection-changed",
G_CALLBACK (selection_changed), view);
g_signal_connect_swapped (get_buffer (view), "highlight-updated",
G_CALLBACK (highlight_updated), view);
g_signal_connect_swapped (get_buffer (view), "notify::has-selection",
G_CALLBACK (proxy_prop_notify), view);
g_signal_connect_swapped (get_buffer (view), "notify::has-text",
G_CALLBACK (proxy_prop_notify), view);
undo_stack = _moo_text_buffer_get_undo_stack (get_moo_buffer (view));
g_signal_connect_swapped (undo_stack, "notify::can-undo",
G_CALLBACK (proxy_prop_notify), view);
g_signal_connect_swapped (undo_stack, "notify::can-redo",
G_CALLBACK (proxy_prop_notify), view);
g_signal_connect_data (get_buffer (view), "insert-text",
G_CALLBACK (insert_text_cb), view,
NULL, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
g_signal_connect_swapped (get_buffer (view), "line-mark-added",
G_CALLBACK (line_mark_added), view);
g_signal_connect_swapped (get_buffer (view), "line-mark-moved",
G_CALLBACK (line_mark_moved), view);
g_signal_connect_swapped (get_buffer (view), "line-mark-deleted",
G_CALLBACK (line_mark_deleted), view);
g_signal_connect_swapped (get_buffer (view), "fold-added",
G_CALLBACK (fold_added), view);
g_signal_connect_swapped (get_buffer (view), "fold-deleted",
G_CALLBACK (fold_deleted), view);
g_signal_connect_swapped (get_buffer (view), "fold-toggled",
G_CALLBACK (fold_toggled), view);
gtk_text_buffer_get_start_iter (get_buffer (view), &iter);
view->priv->dnd_mark = gtk_text_buffer_create_mark (get_buffer (view), NULL, &iter, FALSE);
gtk_text_mark_set_visible (view->priv->dnd_mark, FALSE);
#if !GTK_CHECK_VERSION(2,12,0)
g_signal_connect (view, "notify::overwrite", G_CALLBACK (overwrite_changed), NULL);
#endif
target_list = gtk_target_list_new (text_view_target_table, G_N_ELEMENTS (text_view_target_table));
gtk_target_list_add_text_targets (target_list, 0);
gtk_drag_dest_set_target_list (GTK_WIDGET (view), target_list);
gtk_target_list_unref (target_list);
return object;
}
static void
moo_text_view_finalize (GObject *object)
{
int i;
GSList *l;
MooTextView *view = MOO_TEXT_VIEW (object);
for (i = 0; i < MOO_TEXT_VIEW_N_COLORS; ++i)
g_free (view->priv->colors[i]);
if (view->priv->indenter)
g_object_unref (view->priv->indenter);
for (l = view->priv->line_marks; l != NULL; l = l->next)
{
MooLineMark *mark = l->data;
_moo_line_mark_set_pretty (mark, FALSE);
g_signal_handlers_disconnect_by_func (mark, (gpointer) line_mark_changed, view);
g_object_unref (mark);
}
g_slist_free (view->priv->line_marks);
G_OBJECT_CLASS (moo_text_view_parent_class)->finalize (object);
}
GtkWidget *
moo_text_view_new (void)
{
return g_object_new (MOO_TYPE_TEXT_VIEW, NULL);
}
static void
moo_text_view_delete_selection (MooTextView *view)
{
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
gtk_text_buffer_delete_selection (get_buffer (view), TRUE, TRUE);
}
static void
moo_text_view_message (MooTextView *view,
const char *message)
{
GtkWidget *toplevel;
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
g_return_if_fail (message != NULL);
toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
if (MOO_IS_EDIT_WINDOW (toplevel))
moo_edit_window_message (MOO_EDIT_WINDOW (toplevel), message);
}
static void
msg_to_statusbar (const char *message,
gpointer data)
{
moo_text_view_message (data, message);
}
static void
find_word_at_cursor (MooTextView *view,
gboolean forward)
{
moo_text_view_run_find_current_word (GTK_TEXT_VIEW (view), forward,
msg_to_statusbar, view);
}
static void
find_interactive (MooTextView *view)
{
moo_text_view_run_find (GTK_TEXT_VIEW (view),
msg_to_statusbar, view);
}
static void
replace_interactive (MooTextView *view)
{
moo_text_view_run_replace (GTK_TEXT_VIEW (view),
msg_to_statusbar, view);
}
static void
find_next_interactive (MooTextView *view)
{
moo_text_view_run_find_next (GTK_TEXT_VIEW (view),
msg_to_statusbar, view);
}
static void
find_prev_interactive (MooTextView *view)
{
moo_text_view_run_find_prev (GTK_TEXT_VIEW (view),
msg_to_statusbar, view);
}
static void
goto_line_interactive (MooTextView *view)
{
moo_text_view_run_goto_line (GTK_TEXT_VIEW (view));
}
void
moo_text_view_set_font_from_string (MooTextView *view,
const char *font)
{
PangoFontDescription *font_desc = NULL;
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
if (font)
font_desc = pango_font_description_from_string (font);
gtk_widget_modify_font (GTK_WIDGET (view), font_desc);
if (font_desc)
pango_font_description_free (font_desc);
}
static MooUndoStack *
get_undo_stack (MooTextView *view)
{
return _moo_text_buffer_get_undo_stack (get_moo_buffer (view));
}
gboolean
moo_text_view_can_redo (MooTextView *view)
{
g_return_val_if_fail (MOO_IS_TEXT_VIEW (view), FALSE);
return moo_undo_stack_can_redo (get_undo_stack (view));
}
gboolean
moo_text_view_can_undo (MooTextView *view)
{
g_return_val_if_fail (MOO_IS_TEXT_VIEW (view), FALSE);
return moo_undo_stack_can_undo (get_undo_stack (view));
}
void
moo_text_view_begin_not_undoable_action (MooTextView *view)
{
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
moo_undo_stack_freeze (get_undo_stack (view));
}
void
moo_text_view_end_not_undoable_action (MooTextView *view)
{
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
moo_undo_stack_thaw (get_undo_stack (view));
}
gboolean
moo_text_view_redo (MooTextView *view)
{
g_return_val_if_fail (MOO_IS_TEXT_VIEW (view), FALSE);
if (moo_undo_stack_can_redo (get_undo_stack (view)) &&
gtk_text_view_get_editable (GTK_TEXT_VIEW (view)))
{
moo_text_buffer_freeze (get_moo_buffer (view));
moo_undo_stack_redo (get_undo_stack (view));
gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
get_insert (view),
0, FALSE, 0, 0);
moo_text_buffer_thaw (get_moo_buffer (view));
return TRUE;
}
else
{
return FALSE;
}
}
gboolean
moo_text_view_undo (MooTextView *view)
{
g_return_val_if_fail (MOO_IS_TEXT_VIEW (view), FALSE);
if (moo_undo_stack_can_undo (get_undo_stack (view)) &&
gtk_text_view_get_editable (GTK_TEXT_VIEW (view)))
{
moo_text_buffer_freeze (get_moo_buffer (view));
moo_undo_stack_undo (get_undo_stack (view));
gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
get_insert (view),
0, FALSE, 0, 0);
moo_text_buffer_thaw (get_moo_buffer (view));
return TRUE;
}
else
{
return FALSE;
}
}
void
moo_text_view_select_all (MooTextView *view)
{
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
g_signal_emit_by_name (view, "select-all", TRUE, NULL);
}
static void
moo_text_view_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
MooTextView *view = MOO_TEXT_VIEW (object);
GtkTextBuffer *buffer;
switch (prop_id)
{
case PROP_BUFFER:
buffer = g_value_get_object (value);
if (buffer && !MOO_IS_TEXT_BUFFER (buffer))
{
g_warning ("%s: ignoring buffer not of type MooTextBuffer", G_STRLOC);
buffer = moo_text_buffer_new (NULL);
}
else if (!buffer)
{
buffer = moo_text_buffer_new (NULL);
}
else
{
g_object_ref (buffer);
}
gtk_text_view_set_buffer (GTK_TEXT_VIEW (view), buffer);
g_object_unref (buffer);
break;
case PROP_INDENTER:
moo_text_view_set_indenter (view, g_value_get_object (value));
break;
case PROP_TAB_WIDTH:
moo_text_view_set_tab_width (view, g_value_get_uint (value));
break;
case PROP_HIGHLIGHT_MATCHING_BRACKETS:
view->priv->highlight_matching_brackets = g_value_get_boolean (value);
if (view->priv->constructed)
g_object_set (get_buffer (view), "highlight-matching-brackets",
view->priv->highlight_matching_brackets, NULL);
break;
case PROP_HIGHLIGHT_MISMATCHING_BRACKETS:
view->priv->highlight_mismatching_brackets = g_value_get_boolean (value);
if (view->priv->constructed)
g_object_set (get_buffer (view), "highlight-mismatching-brackets",
view->priv->highlight_mismatching_brackets, NULL);
break;
case PROP_HIGHLIGHT_CURRENT_LINE:
moo_text_view_set_highlight_current_line (view, g_value_get_boolean (value));
break;
case PROP_DRAW_RIGHT_MARGIN:
moo_text_view_set_draw_right_margin (view, g_value_get_boolean (value));
break;
case PROP_RIGHT_MARGIN_OFFSET:
moo_text_view_set_right_margin_offset (view, g_value_get_uint (value));
break;
case PROP_CURRENT_LINE_COLOR:
moo_text_view_set_current_line_color (view, g_value_get_string (value));
break;
case PROP_RIGHT_MARGIN_COLOR:
moo_text_view_set_right_margin_color (view, g_value_get_string (value));
break;
case PROP_DRAW_TABS:
set_draw_tabs (view, g_value_get_boolean (value));
break;
case PROP_DRAW_TRAILING_SPACES:
set_draw_trailing_spaces (view, g_value_get_boolean (value));
break;
case PROP_MANAGE_CLIPBOARD:
set_manage_clipboard (view, g_value_get_boolean (value));
break;
case PROP_SMART_HOME_END:
view->priv->smart_home_end = g_value_get_boolean (value) != 0;
g_object_notify (object, "smart-home-end");
break;
case PROP_ENABLE_HIGHLIGHT:
moo_text_buffer_set_highlight (get_moo_buffer (view),
g_value_get_boolean (value));
g_object_notify (object, "enable-highlight");
break;
case PROP_SHOW_LINE_NUMBERS:
moo_text_view_set_show_line_numbers (view, g_value_get_boolean (value));
break;
case PROP_SHOW_LINE_MARKS:
set_show_line_marks (view, g_value_get_boolean (value));
break;
case PROP_ENABLE_FOLDING:
set_enable_folding (view, g_value_get_boolean (value));
break;
case PROP_ENABLE_QUICK_SEARCH:
view->priv->qs.enable = g_value_get_boolean (value) != 0;
g_object_notify (object, "enable-quick-search");
break;
case PROP_QUICK_SEARCH_FLAGS:
view->priv->qs.flags = g_value_get_flags (value);
g_object_notify (object, "quick-search-flags");
break;
case PROP_AUTO_INDENT:
view->priv->enter_indents = g_value_get_boolean (value);
g_object_notify (object, "auto-indent");
break;
case PROP_TAB_INDENTS:
view->priv->tab_indents = g_value_get_boolean (value);
g_object_notify (object, "tab-indents");
break;
case PROP_BACKSPACE_INDENTS:
view->priv->backspace_indents = g_value_get_boolean (value);
g_object_notify (object, "backspace-indents");
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
moo_text_view_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
MooTextView *view = MOO_TEXT_VIEW (object);
gboolean val;
switch (prop_id)
{
case PROP_BUFFER:
g_value_set_object (value, get_buffer (view));
break;
case PROP_INDENTER:
g_value_set_object (value, view->priv->indenter);
break;
case PROP_TAB_WIDTH:
g_value_set_uint (value, view->priv->tab_width);
break;
case PROP_HIGHLIGHT_CURRENT_LINE:
g_value_set_boolean (value, view->priv->color_settings[MOO_TEXT_VIEW_COLOR_CURRENT_LINE]);
break;
case PROP_DRAW_RIGHT_MARGIN:
g_value_set_boolean (value, view->priv->color_settings[MOO_TEXT_VIEW_COLOR_RIGHT_MARGIN]);
break;
case PROP_RIGHT_MARGIN_OFFSET:
g_value_set_uint (value, view->priv->right_margin_offset);
break;
case PROP_HIGHLIGHT_MATCHING_BRACKETS:
g_object_get (get_buffer (view), "highlight-matching-brackets", &val, NULL);
g_value_set_boolean (value, val);
break;
case PROP_HIGHLIGHT_MISMATCHING_BRACKETS:
g_object_get (get_buffer (view), "highlight-mismatching-brackets", &val, NULL);
g_value_set_boolean (value, val);
break;
case PROP_CURRENT_LINE_COLOR:
g_value_set_string (value, view->priv->colors[MOO_TEXT_VIEW_COLOR_CURRENT_LINE]);
break;
case PROP_RIGHT_MARGIN_COLOR:
g_value_set_string (value, view->priv->colors[MOO_TEXT_VIEW_COLOR_RIGHT_MARGIN]);
break;
case PROP_DRAW_TABS:
g_value_set_boolean (value, view->priv->draw_tabs != 0);
break;
case PROP_DRAW_TRAILING_SPACES:
g_value_set_boolean (value, view->priv->draw_trailing_spaces != 0);
break;
case PROP_HAS_TEXT:
g_value_set_boolean (value, moo_text_view_has_text (view));
break;
case PROP_HAS_SELECTION:
g_value_set_boolean (value, moo_text_view_has_selection (view));
break;
case PROP_CAN_UNDO:
g_value_set_boolean (value, moo_text_view_can_undo (view));
break;
case PROP_CAN_REDO:
g_value_set_boolean (value, moo_text_view_can_redo (view));
break;
case PROP_MANAGE_CLIPBOARD:
g_value_set_boolean (value, view->priv->manage_clipboard != 0);
break;
case PROP_SMART_HOME_END:
g_value_set_boolean (value, view->priv->smart_home_end != 0);
break;
case PROP_ENABLE_HIGHLIGHT:
g_value_set_boolean (value, moo_text_buffer_get_highlight (get_moo_buffer (view)));
break;
case PROP_SHOW_LINE_NUMBERS:
g_value_set_boolean (value, view->priv->lm.show_numbers);
break;
case PROP_SHOW_LINE_MARKS:
g_value_set_boolean (value, view->priv->lm.show_icons);
break;
case PROP_ENABLE_FOLDING:
g_value_set_boolean (value, view->priv->enable_folding);
break;
case PROP_ENABLE_QUICK_SEARCH:
g_value_set_boolean (value, view->priv->qs.enable);
break;
case PROP_QUICK_SEARCH_FLAGS:
g_value_set_flags (value, view->priv->qs.flags);
break;
case PROP_AUTO_INDENT:
g_value_set_boolean (value, view->priv->enter_indents != 0);
break;
case PROP_TAB_INDENTS:
g_value_set_boolean (value, view->priv->tab_indents);
break;
case PROP_BACKSPACE_INDENTS:
g_value_set_boolean (value, view->priv->backspace_indents != 0);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
char *
moo_text_view_get_selection (gpointer view)
{
GtkTextBuffer *buf;
GtkTextIter start, end;
g_return_val_if_fail (GTK_IS_TEXT_VIEW (view), NULL);
buf = gtk_text_view_get_buffer (view);
if (gtk_text_buffer_get_selection_bounds (buf, &start, &end))
return gtk_text_buffer_get_slice (buf, &start, &end, TRUE);
else
return NULL;
}
gboolean
moo_text_view_has_selection (MooTextView *view)
{
g_return_val_if_fail (MOO_IS_TEXT_VIEW (view), FALSE);
return moo_text_buffer_has_selection (get_moo_buffer (view));
}
gboolean
moo_text_view_has_text (MooTextView *view)
{
g_return_val_if_fail (MOO_IS_TEXT_VIEW (view), FALSE);
return moo_text_buffer_has_text (get_moo_buffer (view));
}
char *
moo_text_view_get_text (gpointer view)
{
GtkTextBuffer *buf;
GtkTextIter start, end;
char *text;
g_return_val_if_fail (GTK_IS_TEXT_VIEW (view), NULL);
buf = get_buffer (view);
gtk_text_buffer_get_bounds (buf, &start, &end);
text = gtk_text_buffer_get_slice (buf, &start, &end, TRUE);
if (text && *text)
{
return text;
}
else
{
g_free (text);
return NULL;
}
}
static void
insert_text_cb (MooTextView *view,
GtkTextIter *iter,
gchar *text,
gint len)
{
if (view->priv->in_key_press && g_utf8_strlen (text, len) == 1)
{
view->priv->in_key_press = FALSE;
view->priv->char_inserted = g_utf8_get_char (text);
view->priv->char_inserted_offset = gtk_text_iter_get_offset (iter);
}
}
void
_moo_text_view_check_char_inserted (MooTextView *view)
{
if (view->priv->char_inserted)
{
gboolean result;
GtkTextIter iter;
gtk_text_buffer_get_iter_at_offset (get_buffer (view), &iter,
view->priv->char_inserted_offset);
g_signal_emit (view, signals[CHAR_INSERTED], 0,
&iter, (guint) view->priv->char_inserted,
&result);
view->priv->char_inserted = 0;
}
}
static gboolean
moo_text_view_char_inserted (MooTextView *view,
GtkTextIter *where,
guint character)
{
if (view->priv->indenter)
{
GtkTextBuffer *buffer = get_buffer (view);
gtk_text_buffer_begin_user_action (buffer);
moo_indenter_character (view->priv->indenter,
character, where);
gtk_text_buffer_end_user_action (buffer);
return TRUE;
}
return FALSE;
}
static void
cursor_moved (MooTextView *view,
GtkTextIter *where)
{
g_signal_emit (view, signals[CURSOR_MOVED], 0, where);
}
static void
proxy_prop_notify (MooTextView *view,
GParamSpec *pspec)
{
g_object_notify (G_OBJECT (view), pspec->name);
}
MooIndenter*
moo_text_view_get_indenter (MooTextView *view)
{
g_return_val_if_fail (MOO_IS_TEXT_VIEW (view), NULL);
return view->priv->indenter;
}
void
moo_text_view_set_indenter (MooTextView *view,
MooIndenter *indenter)
{
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
g_return_if_fail (!indenter || MOO_IS_INDENTER (indenter));
if (view->priv->indenter == indenter)
return;
if (view->priv->indenter)
g_object_unref (view->priv->indenter);
view->priv->indenter = indenter;
if (view->priv->indenter)
g_object_ref (view->priv->indenter);
g_object_notify (G_OBJECT (view), "indenter");
}
typedef struct {
GtkTextView *view;
int line;
int character;
gboolean visual;
} Scroll;
static int
iter_get_chars_in_line (const GtkTextIter *iter)
{
GtkTextIter i = *iter;
if (!gtk_text_iter_ends_line (&i))
gtk_text_iter_forward_to_line_end (&i);
return gtk_text_iter_get_line_offset (&i);
}
static gboolean
do_move_cursor (Scroll *scroll)
{
GtkTextBuffer *buffer;
GtkTextIter iter;
g_return_val_if_fail (scroll != NULL, FALSE);
g_return_val_if_fail (MOO_IS_TEXT_VIEW (scroll->view), FALSE);
g_return_val_if_fail (scroll->line >= 0, FALSE);
buffer = gtk_text_view_get_buffer (scroll->view);
if (scroll->line >= gtk_text_buffer_get_line_count (buffer))
scroll->line = gtk_text_buffer_get_line_count (buffer) - 1;
gtk_text_buffer_get_iter_at_line (buffer, &iter, scroll->line);
if (scroll->character >= 0)
{
if (scroll->visual)
{
int line_len = moo_text_iter_get_visual_line_length (&iter, 8);
if (scroll->character > line_len)
scroll->character = line_len;
else if (scroll->character < 0)
scroll->character = 0;
if (scroll->character == line_len)
{
if (!gtk_text_iter_ends_line (&iter))
gtk_text_iter_forward_to_line_end (&iter);
}
else
{
moo_text_iter_set_visual_line_offset (&iter, scroll->character, 8);
}
}
else
{
int line_len = iter_get_chars_in_line (&iter);
if (scroll->character > line_len)
scroll->character = line_len;
else if (scroll->character < 0)
scroll->character = 0;
if (scroll->character == line_len)
{
if (!gtk_text_iter_ends_line (&iter))
gtk_text_iter_forward_to_line_end (&iter);
}
else
{
gtk_text_iter_set_line_offset (&iter, scroll->character);
}
}
}
gtk_text_buffer_place_cursor (buffer, &iter);
gtk_text_view_scroll_to_mark (scroll->view,
gtk_text_buffer_get_insert (buffer),
0.2, FALSE, 0, 0);
return FALSE;
}
void
moo_text_view_move_cursor (gpointer view,
int line,
int offset,
gboolean offset_visual,
gboolean in_idle)
{
Scroll scroll;
g_return_if_fail (GTK_IS_TEXT_VIEW (view));
scroll.view = view;
scroll.line = line;
scroll.character = offset;
scroll.visual = offset_visual;
do_move_cursor (&scroll);
if (in_idle)
_moo_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
(GSourceFunc) do_move_cursor,
g_memdup (&scroll, sizeof scroll),
g_free);
}
void
moo_text_view_get_cursor (MooTextView *view,
GtkTextIter *iter)
{
GtkTextBuffer *buffer;
g_return_if_fail (GTK_IS_TEXT_VIEW (view));
g_return_if_fail (iter != NULL);
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
gtk_text_buffer_get_iter_at_mark (buffer, iter,
gtk_text_buffer_get_insert (buffer));
}
int
moo_text_view_get_cursor_line (MooTextView *view)
{
GtkTextIter iter;
g_return_val_if_fail (GTK_IS_TEXT_VIEW (view), -1);
moo_text_view_get_cursor (view, &iter);
return gtk_text_iter_get_line (&iter);
}
static GtkTextBuffer*
get_buffer (MooTextView *view)
{
return gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
}
static MooTextBuffer*
get_moo_buffer (MooTextView *view)
{
return MOO_TEXT_BUFFER (get_buffer (view));
}
static GtkTextMark*
get_insert (MooTextView *view)
{
return gtk_text_buffer_get_insert (get_buffer (view));
}
static void
moo_text_view_set_color_setting (MooTextView *view,
const char *prop,
gboolean setting,
MooTextViewColor color_num)
{
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
if (view->priv->color_settings[color_num] != setting)
{
view->priv->color_settings[color_num] = setting;
invalidate_gcs (view);
gtk_widget_queue_draw (GTK_WIDGET (view));
g_object_notify (G_OBJECT (view), prop);
}
}
void
moo_text_view_set_highlight_current_line (MooTextView *view,
gboolean highlight)
{
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
moo_text_view_set_color_setting (view, "highlight-current-line", highlight,
MOO_TEXT_VIEW_COLOR_CURRENT_LINE);
}
void
moo_text_view_set_draw_right_margin (MooTextView *view,
gboolean draw)
{
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
moo_text_view_set_color_setting (view, "draw-right-margin", draw,
MOO_TEXT_VIEW_COLOR_RIGHT_MARGIN);
}
void
moo_text_view_set_right_margin_offset (MooTextView *view,
guint offset)
{
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
g_return_if_fail (offset > 0);
if (view->priv->right_margin_offset != offset)
{
view->priv->right_margin_offset = offset;
invalidate_right_margin (view);
if (view->priv->color_settings[MOO_TEXT_VIEW_COLOR_RIGHT_MARGIN])
gtk_widget_queue_draw (GTK_WIDGET (view));
g_object_notify (G_OBJECT (view), "right-margin-offset");
}
}
static void
moo_text_view_set_color (MooTextView *view,
MooTextViewColor color_num,
const char *color,
const char *propname)
{
char *tmp;
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
g_return_if_fail (color_num < MOO_TEXT_VIEW_N_COLORS);
tmp = view->priv->colors[color_num];
view->priv->colors[color_num] = g_strdup (color);
g_free (tmp);
invalidate_gcs (view);
if (GTK_WIDGET_DRAWABLE (view) && view->priv->color_settings[color_num])
gtk_widget_queue_draw (GTK_WIDGET (view));
g_object_notify (G_OBJECT (view), propname);
}
void
moo_text_view_set_current_line_color (MooTextView *view,
const char *color)
{
moo_text_view_set_color (view, MOO_TEXT_VIEW_COLOR_CURRENT_LINE,
color, "current-line-color");
}
void
moo_text_view_set_right_margin_color (MooTextView *view,
const char *color)
{
moo_text_view_set_color (view, MOO_TEXT_VIEW_COLOR_RIGHT_MARGIN,
color, "right-margin-color");
}
/********************************************************************/
/* Clipboard
*/
enum {
TARGET_TEXT,
TARGET_MOO_TEXT_VIEW
};
static const GtkTargetEntry targets[] = {
{ (char*) "MOO_TEXT_VIEW", GTK_TARGET_SAME_APP, TARGET_MOO_TEXT_VIEW },
{ (char*) "STRING", 0, TARGET_TEXT },
{ (char*) "TEXT", 0, TARGET_TEXT },
{ (char*) "COMPOUND_TEXT", 0, TARGET_TEXT },
{ (char*) "UTF8_STRING", 0, TARGET_TEXT }
};
static void
add_selection_clipboard (MooTextView *view)
{
GtkClipboard *clipboard;
clipboard = gtk_widget_get_clipboard (GTK_WIDGET (view), GDK_SELECTION_PRIMARY);
gtk_text_buffer_add_selection_clipboard (get_buffer (view), clipboard);
}
static void
remove_selection_clipboard (MooTextView *view)
{
GtkClipboard *clipboard;
clipboard = gtk_widget_get_clipboard (GTK_WIDGET (view), GDK_SELECTION_PRIMARY);
gtk_text_buffer_remove_selection_clipboard (get_buffer (view), clipboard);
}
static void
clipboard_get_selection (G_GNUC_UNUSED GtkClipboard *clipboard,
GtkSelectionData *selection_data,
guint info,
gpointer data)
{
MooTextView *view = data;
GtkTextIter start, end;
if (info == TARGET_MOO_TEXT_VIEW)
{
_moo_selection_data_set_pointer (selection_data,
gdk_atom_intern ("MOO_TEXT_VIEW", FALSE),
data);
}
else if (gtk_text_buffer_get_selection_bounds (get_buffer (view), &start, &end))
{
char *text = gtk_text_iter_get_text (&start, &end);
gtk_selection_data_set_text (selection_data, text, -1);
g_free (text);
}
}
static void
clear_primary (MooTextView *view)
{
GtkClipboard *clipboard;
clipboard = gtk_widget_get_clipboard (GTK_WIDGET (view),
GDK_SELECTION_PRIMARY);
if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (view))
gtk_clipboard_clear (clipboard);
}
static void
selection_changed (MooTextView *view,
MooTextBuffer *buffer)
{
GtkClipboard *clipboard;
if (!view->priv->manage_clipboard)
return;
clipboard = gtk_widget_get_clipboard (GTK_WIDGET (view),
GDK_SELECTION_PRIMARY);
if (moo_text_buffer_has_selection (buffer))
gtk_clipboard_set_with_owner (clipboard, targets,
G_N_ELEMENTS (targets),
clipboard_get_selection,
NULL, G_OBJECT (view));
else
clear_primary (view);
}
static void
set_manage_clipboard (MooTextView *view,
gboolean manage)
{
if (BOOL_CMP (view->priv->manage_clipboard, manage))
return;
view->priv->manage_clipboard = manage;
if (GTK_WIDGET_REALIZED (view))
{
if (manage)
{
remove_selection_clipboard (view);
selection_changed (view, get_moo_buffer (view));
}
else
{
clear_primary (view);
add_selection_clipboard (view);
}
}
g_object_notify (G_OBJECT (view), "manage-clipboard");
}
static void
get_clipboard (G_GNUC_UNUSED GtkClipboard *clipboard,
GtkSelectionData *selection_data,
guint info,
gpointer view)
{
char **contents;
char *text;
if (info == TARGET_MOO_TEXT_VIEW)
{
_moo_selection_data_set_pointer (selection_data,
gdk_atom_intern ("MOO_TEXT_VIEW", FALSE),
view);
return;
}
contents = g_object_get_data (view, "moo-text-view-clipboard");
g_return_if_fail (contents != NULL);
text = g_strjoinv ("", contents);
gtk_selection_data_set_text (selection_data, text, -1);
g_free (text);
}
static void
clear_clipboard (G_GNUC_UNUSED GtkClipboard *clipboard,
gpointer view)
{
g_object_set_data (view, "moo-text-view-clipboard", NULL);
}
static void
moo_text_view_cut_or_copy (GtkTextView *text_view,
gboolean delete)
{
GtkTextBuffer *buffer;
GtkTextIter start, end;
char *text;
char **pieces;
GtkClipboard *clipboard;
buffer = gtk_text_view_get_buffer (text_view);
if (!gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
return;
text = gtk_text_buffer_get_slice (buffer, &start, &end, TRUE);
pieces = g_strsplit (text, MOO_TEXT_UNKNOWN_CHAR_S, 0);
g_object_set_data_full (G_OBJECT (text_view), "moo-text-view-clipboard",
pieces, (GDestroyNotify) g_strfreev);
g_free (text);
clipboard = gtk_widget_get_clipboard (GTK_WIDGET (text_view),
GDK_SELECTION_CLIPBOARD);
if (!gtk_clipboard_set_with_owner (clipboard, targets, G_N_ELEMENTS (targets),
get_clipboard, clear_clipboard,
G_OBJECT (text_view)))
return;
gtk_clipboard_set_can_store (clipboard, targets + 1,
G_N_ELEMENTS (targets) - 1);
if (delete)
{
gtk_text_buffer_begin_user_action (buffer);
gtk_text_buffer_delete (buffer, &start, &end);
gtk_text_buffer_end_user_action (buffer);
gtk_text_view_scroll_mark_onscreen (text_view,
gtk_text_buffer_get_insert (buffer));
}
}
static void
moo_text_view_copy_clipboard (GtkTextView *text_view)
{
moo_text_view_cut_or_copy (text_view, FALSE);
}
static void
moo_text_view_cut_clipboard (GtkTextView *text_view)
{
moo_text_view_cut_or_copy (text_view, TRUE);
}
static void
paste_moo_text_view_content (GtkTextView *target,
MooTextView *source)
{
GtkTextBuffer *buffer;
GtkTextIter start, end;
char **contents, **p;
g_return_if_fail (MOO_IS_TEXT_VIEW (source));
buffer = gtk_text_view_get_buffer (target);
contents = g_object_get_data (G_OBJECT (source), "moo-text-view-clipboard");
g_return_if_fail (contents != NULL);
if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
gtk_text_buffer_delete (buffer, &start, &end);
for (p = contents; *p; ++p)
{
if (p != contents)
moo_text_view_insert_placeholder (MOO_TEXT_VIEW (target), &end, NULL);
gtk_text_buffer_insert (buffer, &end, *p, -1);
}
}
static void
paste_text (GtkTextView *text_view,
const char *text)
{
GtkTextBuffer *buffer;
GtkTextIter start, end;
g_return_if_fail (text != NULL);
buffer = gtk_text_view_get_buffer (text_view);
if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
gtk_text_buffer_delete (buffer, &start, &end);
gtk_text_buffer_insert (buffer, &end, text, -1);
}
static void
moo_text_view_paste_clipboard (GtkTextView *text_view)
{
char *text;
GtkTextBuffer *buffer;
GtkClipboard *clipboard;
gboolean need_paste_text = TRUE;
buffer = gtk_text_view_get_buffer (text_view);
clipboard = gtk_widget_get_clipboard (GTK_WIDGET (text_view), GDK_SELECTION_CLIPBOARD);
gtk_text_buffer_begin_user_action (buffer);
if (gtk_clipboard_wait_is_target_available (clipboard, moo_text_view_atom))
{
MooTextView *source;
GtkSelectionData *data;
if ((data = gtk_clipboard_wait_for_contents (clipboard, moo_text_view_atom)) &&
(source = _moo_selection_data_get_pointer (data, moo_text_view_atom)))
{
need_paste_text = FALSE;
paste_moo_text_view_content (text_view, source);
}
if (data)
gtk_selection_data_free (data);
}
if (need_paste_text && (text = gtk_clipboard_wait_for_text (clipboard)))
{
paste_text (text_view, text);
g_free (text);
}
gtk_text_buffer_end_user_action (buffer);
gtk_text_view_scroll_mark_onscreen (text_view, gtk_text_buffer_get_insert (buffer));
}
/********************************************************************/
/* Drawing and stuff
*/
/* XXX: workaround for http://bugzilla.gnome.org/show_bug.cgi?id=336796 */
static void
lower_border_window (GtkTextView *view,
MooTextViewPos pos)
{
GdkWindow *window;
g_return_if_fail (pos < 4);
window = gtk_text_view_get_window (view, window_types[pos]);
if (window)
window = gdk_window_get_parent (window);
if (window)
gdk_window_lower (window);
}
static void
moo_text_view_realize (GtkWidget *widget)
{
MooTextView *view = MOO_TEXT_VIEW (widget);
guint i;
GTK_WIDGET_CLASS(moo_text_view_parent_class)->realize (widget);
update_tab_width (view);
update_left_margin (view);
if (view->priv->manage_clipboard)
{
remove_selection_clipboard (view);
selection_changed (view, get_moo_buffer (view));
}
/* workaround for http://bugzilla.gnome.org/show_bug.cgi?id=336796 */
for (i = 0; i < 4; ++i)
{
if (view->priv->children[i] && GTK_WIDGET_VISIBLE (view->priv->children[i]))
lower_border_window (GTK_TEXT_VIEW (view), i);
}
update_box_tag (view);
}
static void
moo_text_view_unrealize (GtkWidget *widget)
{
MooTextView *view = MOO_TEXT_VIEW (widget);
g_slist_foreach (view->priv->line_marks, (GFunc) _moo_line_mark_unrealize, NULL);
g_object_set_data (G_OBJECT (widget), "moo-line-mark-icons", NULL);
g_object_set_data (G_OBJECT (widget), "moo-line-mark-colors", NULL);
if (view->priv->update_idle)
g_source_remove (view->priv->update_idle);
view->priv->update_idle = 0;
if (view->priv->update_n_lines_idle)
g_source_remove (view->priv->update_n_lines_idle);
view->priv->update_n_lines_idle = 0;
g_free (view->priv->update_rectangle);
view->priv->update_rectangle = NULL;
invalidate_gcs (view);
if (view->priv->manage_clipboard)
{
clear_primary (view);
add_selection_clipboard (view);
}
#if !GTK_CHECK_VERSION(2,12,0)
if (view->priv->blink_timeout)
{
g_warning ("%s: oops", G_STRLOC);
g_source_remove (view->priv->blink_timeout);
view->priv->blink_timeout = 0;
}
#endif
if (view->priv->dnd.scroll_timeout)
{
g_source_remove (view->priv->dnd.scroll_timeout);
view->priv->dnd.scroll_timeout = 0;
}
GTK_WIDGET_CLASS(moo_text_view_parent_class)->unrealize (widget);
}
static void
invalidate_gcs (MooTextView *view)
{
int i;
for (i = 0; i < MOO_TEXT_VIEW_N_COLORS; ++i)
{
if (view->priv->gcs[i])
g_object_unref (view->priv->gcs[i]);
view->priv->gcs[i] = NULL;
}
}
static void
update_gc (MooTextView *view,
MooTextViewColor color_num)
{
GdkColor color;
GtkWidget *widget = GTK_WIDGET (view);
GdkColormap *colormap;
GdkWindow *window;
if (!GTK_WIDGET_REALIZED (widget))
return;
g_return_if_fail (color_num < MOO_TEXT_VIEW_N_COLORS);
if (!view->priv->color_settings[color_num])
{
if (view->priv->gcs[color_num])
{
g_object_unref (view->priv->gcs[color_num]);
view->priv->gcs[color_num] = NULL;
}
return;
}
if (view->priv->gcs[color_num])
return;
colormap = gtk_widget_get_colormap (widget);
g_return_if_fail (colormap != NULL);
window = gtk_text_view_get_window (GTK_TEXT_VIEW (view),
GTK_TEXT_WINDOW_TEXT);
g_return_if_fail (window != NULL);
if (view->priv->colors[color_num])
{
if (!gdk_color_parse (view->priv->colors[color_num], &color))
{
g_warning ("%s: could not parse color %s", G_STRLOC,
view->priv->colors[color_num]);
color = widget->style->bg[GTK_STATE_NORMAL];
}
else if (!gdk_colormap_alloc_color (colormap, &color, FALSE, TRUE))
{
g_warning ("%s: failed to allocate color", G_STRLOC);
color = widget->style->bg[GTK_STATE_NORMAL];
}
}
else
{
color = widget->style->bg[GTK_STATE_NORMAL];
}
if (!view->priv->gcs[color_num])
view->priv->gcs[color_num] = gdk_gc_new (window);
gdk_gc_set_foreground (view->priv->gcs[color_num], &color);
}
static void
update_gcs (MooTextView *view)
{
update_gc (view, MOO_TEXT_VIEW_COLOR_CURRENT_LINE);
update_gc (view, MOO_TEXT_VIEW_COLOR_RIGHT_MARGIN);
}
static void
moo_text_view_draw_right_margin (GtkTextView *text_view,
GdkEventExpose *event)
{
int x, y;
MooTextView *view = MOO_TEXT_VIEW (text_view);
update_right_margin (view);
gdk_drawable_get_size (event->window, NULL, &y);
x = view->priv->right_margin_pixel_offset + gtk_text_view_get_left_margin (text_view);
if (text_view->hadjustment)
x -= text_view->hadjustment->value;
gdk_draw_rectangle (event->window, view->priv->gcs[MOO_TEXT_VIEW_COLOR_RIGHT_MARGIN],
TRUE, x, 0, 1, y);
}
static void
moo_text_view_get_line_yrange (GtkTextView *text_view,
const GtkTextIter *iter,
int *y,
int *height)
{
if (gtk_text_view_get_wrap_mode (text_view) == GTK_WRAP_NONE)
{
gtk_text_view_get_line_yrange (text_view, iter, y, height);
}
else
{
GdkRectangle rect;
gtk_text_view_get_iter_location (text_view, iter, &rect);
*y = rect.y;
*height = rect.height;
}
}
static void
moo_text_view_draw_current_line (GtkTextView *text_view,
GdkEventExpose *event)
{
GdkRectangle visible_rect;
GdkRectangle redraw_rect;
GtkTextIter cur;
int y, height;
int win_y;
int margin;
gtk_text_buffer_get_iter_at_mark (text_view->buffer,
&cur,
gtk_text_buffer_get_insert (text_view->buffer));
moo_text_view_get_line_yrange (text_view, &cur, &y, &height);
gtk_text_view_get_visible_rect (text_view, &visible_rect);
gtk_text_view_buffer_to_window_coords (text_view,
GTK_TEXT_WINDOW_TEXT,
visible_rect.x,
visible_rect.y,
&redraw_rect.x,
&redraw_rect.y);
gtk_text_view_buffer_to_window_coords (text_view,
GTK_TEXT_WINDOW_TEXT,
0,
y,
NULL,
&win_y);
redraw_rect.width = visible_rect.width;
redraw_rect.height = visible_rect.height;
if (text_view->hadjustment)
margin = gtk_text_view_get_left_margin (text_view) - (int) text_view->hadjustment->value;
else
margin = gtk_text_view_get_left_margin (text_view);
gdk_draw_rectangle (event->window,
MOO_TEXT_VIEW(text_view)->priv->gcs[MOO_TEXT_VIEW_COLOR_CURRENT_LINE],
TRUE,
redraw_rect.x + MAX (0, margin - 1),
win_y,
redraw_rect.width,
height);
}
static void
draw_tab_at_iter (GtkTextView *text_view,
GdkEventExpose *event,
GtkTextIter *iter)
{
GdkRectangle rect;
GdkPoint points[3];
gtk_text_view_get_iter_location (text_view, iter, &rect);
gtk_text_view_buffer_to_window_coords (text_view, GTK_TEXT_WINDOW_TEXT,
rect.x, rect.y + rect.height - 2,
&points[0].x, &points[0].y);
points[1] = points[0];
points[2] = points[0];
points[1].y += 1;
points[2].x += 1;
points[2].y += 1;
gdk_draw_polygon (event->window,
GTK_WIDGET(text_view)->style->text_gc[GTK_STATE_NORMAL],
FALSE, points, 3);
}
static void
moo_text_view_draw_tabs (GtkTextView *text_view,
GdkEventExpose *event,
const GtkTextIter *start,
const GtkTextIter *end)
{
GtkTextIter iter = *start;
while (gtk_text_iter_compare (&iter, end) < 0)
{
if (gtk_text_iter_get_char (&iter) == '\t')
draw_tab_at_iter (text_view, event, &iter);
if (!gtk_text_iter_forward_char (&iter))
break;
}
}
static void
draw_box (GtkTextView *text_view,
GdkEventExpose *event,
const GtkTextIter *iter)
{
GtkTextBuffer *buffer;
GtkTextIter sel_start, sel_end;
gboolean selected = FALSE;
GdkGC *gc;
GdkRectangle rect;
buffer = gtk_text_view_get_buffer (text_view);
if (gtk_text_buffer_get_selection_bounds (buffer, &sel_start, &sel_end) &&
gtk_text_iter_compare (&sel_start, iter) <= 0 &&
gtk_text_iter_compare (iter, &sel_end) < 0)
selected = TRUE;
gtk_text_view_get_iter_location (text_view, iter, &rect);
gtk_text_view_buffer_to_window_coords (text_view, GTK_TEXT_WINDOW_TEXT,
rect.x, rect.y, &rect.x, &rect.y);
rect.x += 2;
rect.y += 2;
rect.width -= 4;
rect.height -= 4;
if (selected)
gc = GTK_WIDGET(text_view)->style->base_gc[GTK_STATE_NORMAL];
else
gc = GTK_WIDGET(text_view)->style->text_gc[GTK_STATE_NORMAL];
gdk_draw_rectangle (event->window, gc, FALSE,
rect.x, rect.y, rect.width, rect.height);
rect.x += 1;
rect.y += 1;
rect.width -= 2;
rect.height -= 2;
gdk_draw_rectangle (event->window, gc, FALSE,
rect.x, rect.y, rect.width, rect.height);
}
static void
moo_text_view_draw_boxes (GtkTextView *text_view,
GdkEventExpose *event,
const GtkTextIter *start,
const GtkTextIter *end)
{
GtkTextIter iter = *start;
while (gtk_text_iter_compare (&iter, end) < 0)
{
if (has_box_at_iter (MOO_TEXT_VIEW (text_view), &iter))
draw_box (text_view, event, &iter);
if (!gtk_text_iter_forward_char (&iter))
break;
}
}
static void
moo_text_view_draw_trailing_spaces (GtkTextView *text_view,
GdkEventExpose *event,
const GtkTextIter *start,
const GtkTextIter *end)
{
GtkTextIter iter = *start;
do
{
if (!gtk_text_iter_ends_line (&iter))
gtk_text_iter_forward_to_line_end (&iter);
while (!gtk_text_iter_starts_line (&iter))
{
gunichar c;
gtk_text_iter_backward_char (&iter);
c = gtk_text_iter_get_char (&iter);
if (g_unichar_isspace (c))
draw_tab_at_iter (text_view, event, &iter);
else
break;
}
gtk_text_iter_forward_line (&iter);
}
while (gtk_text_iter_compare (&iter, end) < 0);
}
static gboolean
moo_text_view_expose (GtkWidget *widget,
GdkEventExpose *event)
{
gboolean handled;
MooTextView *view = MOO_TEXT_VIEW (widget);
GtkTextView *text_view = GTK_TEXT_VIEW (widget);
GdkWindow *text_window = gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT);
GdkWindow *left_window = gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_LEFT);
GtkTextIter start, end;
update_gcs (view);
view->priv->in_expose = TRUE;
if (view->priv->update_n_lines_idle)
update_n_lines_idle (view);
if (GTK_WIDGET_SENSITIVE (view) && event->window == text_window)
{
if (view->priv->color_settings[MOO_TEXT_VIEW_COLOR_CURRENT_LINE] &&
view->priv->gcs[MOO_TEXT_VIEW_COLOR_CURRENT_LINE])
moo_text_view_draw_current_line (text_view, event);
if (view->priv->color_settings[MOO_TEXT_VIEW_COLOR_RIGHT_MARGIN] &&
view->priv->gcs[MOO_TEXT_VIEW_COLOR_RIGHT_MARGIN])
moo_text_view_draw_right_margin (text_view, event);
}
if (event->window == left_window)
draw_left_margin (view, event);
else if (event->window == text_window)
draw_marks_background (view, event);
if (event->window == text_window)
{
GdkRectangle visible_rect;
gtk_text_view_get_visible_rect (text_view, &visible_rect);
gtk_text_view_get_line_at_y (text_view, &start, visible_rect.y, NULL);
gtk_text_iter_backward_line (&start);
gtk_text_view_get_line_at_y (text_view, &end, visible_rect.y + visible_rect.height, NULL);
gtk_text_iter_forward_line (&end);
_moo_text_buffer_update_highlight (get_moo_buffer (view), &start, &end, FALSE);
}
handled = GTK_WIDGET_CLASS(moo_text_view_parent_class)->expose_event (widget, event);
if (event->window == text_window)
{
int first_line = gtk_text_iter_get_line (&start);
int last_line = gtk_text_iter_get_line (&end);
if (last_line - first_line < 2000)
{
if (view->priv->draw_tabs)
moo_text_view_draw_tabs (text_view, event, &start, &end);
if (view->priv->draw_trailing_spaces)
moo_text_view_draw_trailing_spaces (text_view, event, &start, &end);
if (has_boxes (view))
moo_text_view_draw_boxes (text_view, event, &start, &end);
}
#if !GTK_CHECK_VERSION(2,12,0)
if (view->priv->cursor_visible)
moo_text_view_draw_cursor (text_view, event);
#endif
}
view->priv->in_expose = FALSE;
return handled;
}
static gboolean
invalidate_rectangle (MooTextView *view)
{
GdkWindow *window;
GdkRectangle *rect = view->priv->update_rectangle;
view->priv->update_rectangle = NULL;
view->priv->update_idle = 0;
gtk_text_view_buffer_to_window_coords (GTK_TEXT_VIEW (view),
GTK_TEXT_WINDOW_TEXT,
rect->x, rect->y,
&rect->x, &rect->y);
window = gtk_text_view_get_window (GTK_TEXT_VIEW (view), GTK_TEXT_WINDOW_TEXT);
gdk_window_invalidate_rect (window, rect, FALSE);
g_free (rect);
return FALSE;
}
static void
highlight_updated (GtkTextView *text_view,
const GtkTextIter *start,
const GtkTextIter *end)
{
GdkRectangle visible, changed, update;
int y, height;
MooTextView *view = MOO_TEXT_VIEW (text_view);
if (!GTK_WIDGET_DRAWABLE (text_view))
return;
gtk_text_view_get_visible_rect (text_view, &visible);
gtk_text_view_get_line_yrange (text_view, start, &changed.y, &height);
gtk_text_view_get_line_yrange (text_view, end, &y, &height);
changed.height = y - changed.y + height;
changed.x = visible.x;
changed.width = visible.width;
if (!gdk_rectangle_intersect (&changed, &visible, &update))
return;
if (view->priv->update_rectangle)
{
gdk_rectangle_union (view->priv->update_rectangle, &update,
view->priv->update_rectangle);
}
else
{
view->priv->update_rectangle = g_new (GdkRectangle, 1);
*view->priv->update_rectangle = update;
}
if (view->priv->in_expose)
{
g_critical ("%s: oops\n", G_STRLOC);
if (!view->priv->update_idle)
view->priv->update_idle =
_moo_idle_add_full (G_PRIORITY_HIGH_IDLE,
(GSourceFunc) invalidate_rectangle,
view, NULL);
}
else
{
if (view->priv->update_idle)
g_source_remove (view->priv->update_idle);
view->priv->update_idle = 0;
invalidate_rectangle (view);
}
}
static void
set_draw_tabs (MooTextView *view,
gboolean draw)
{
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
if (BOOL_CMP (view->priv->draw_tabs, draw))
return;
view->priv->draw_tabs = draw != 0;
g_object_notify (G_OBJECT (view), "draw-tabs");
if (GTK_WIDGET_DRAWABLE (view))
gtk_widget_queue_draw (GTK_WIDGET (view));
}
static void
set_draw_trailing_spaces (MooTextView *view,
gboolean draw)
{
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
if (BOOL_CMP (view->priv->draw_trailing_spaces, draw))
return;
view->priv->draw_trailing_spaces = draw != 0;
g_object_notify (G_OBJECT (view), "draw-trailing-spaces");
if (GTK_WIDGET_DRAWABLE (view))
gtk_widget_queue_draw (GTK_WIDGET (view));
}
GtkTextTag *
moo_text_view_lookup_tag (MooTextView *view,
const char *name)
{
GtkTextBuffer *buffer;
GtkTextTagTable *table;
g_return_val_if_fail (MOO_IS_TEXT_VIEW (view), NULL);
g_return_val_if_fail (name != NULL, NULL);
buffer = get_buffer (view);
table = gtk_text_buffer_get_tag_table (buffer);
return gtk_text_tag_table_lookup (table, name);
}
void
moo_text_view_set_lang (MooTextView *view,
MooLang *lang)
{
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
moo_text_buffer_set_lang (get_moo_buffer (view), lang);
gtk_widget_queue_draw (GTK_WIDGET (view));
}
void
moo_text_view_set_lang_by_id (MooTextView *view,
const char *lang_id)
{
MooEditor *editor;
MooLangMgr *mgr;
MooLang *lang;
MooTextStyleScheme *scheme;
editor = moo_editor_instance ();
g_return_if_fail (editor != NULL);
mgr = moo_editor_get_lang_mgr (editor);
lang = moo_lang_mgr_get_lang (mgr, lang_id);
scheme = moo_lang_mgr_get_active_scheme (mgr);
moo_text_view_set_style_scheme (view, scheme);
moo_text_view_set_lang (view, lang);
}
MooLang*
moo_text_view_get_lang (MooTextView *view)
{
g_return_val_if_fail (MOO_IS_TEXT_VIEW (view), NULL);
return moo_text_buffer_get_lang (get_moo_buffer (view));
}
static void
moo_text_view_apply_style_scheme (MooTextView *view,
MooTextStyleScheme *scheme)
{
_moo_text_style_scheme_apply (scheme, GTK_WIDGET (view));
_moo_text_buffer_set_style_scheme (get_moo_buffer (view), scheme);
}
void
moo_text_view_set_style_scheme (MooTextView *view,
MooTextStyleScheme *scheme)
{
g_return_if_fail (MOO_IS_TEXT_STYLE_SCHEME (scheme));
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
if (view->priv->style_scheme == scheme)
return;
if (view->priv->style_scheme)
g_object_unref (view->priv->style_scheme);
view->priv->style_scheme = g_object_ref (scheme);
MOO_TEXT_VIEW_GET_CLASS (view)->apply_style_scheme (view, scheme);
}
MooTextStyleScheme *
moo_text_view_get_style_scheme (MooTextView *view)
{
g_return_val_if_fail (MOO_IS_TEXT_VIEW (view), NULL);
return view->priv->style_scheme;
}
void
moo_text_view_strip_whitespace (MooTextView *view)
{
GtkTextBuffer *buffer;
GtkTextIter iter;
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
buffer = get_buffer (view);
gtk_text_buffer_begin_user_action (buffer);
for (gtk_text_buffer_get_start_iter (buffer, &iter);
!gtk_text_iter_is_end (&iter);
gtk_text_iter_forward_line (&iter))
{
GtkTextIter end;
char *slice, *p;
int len;
if (gtk_text_iter_ends_line (&iter))
continue;
end = iter;
gtk_text_iter_forward_to_line_end (&end);
slice = gtk_text_buffer_get_slice (buffer, &iter, &end, TRUE);
len = strlen (slice);
g_assert (len > 0);
for (p = slice + len; p > slice && (p[-1] == ' ' || p[-1] == '\t'); --p) ;
if (*p)
{
gtk_text_iter_forward_chars (&iter, g_utf8_pointer_to_offset (slice, p));
gtk_text_buffer_delete (buffer, &iter, &end);
}
g_free (slice);
}
gtk_text_buffer_end_user_action (buffer);
}
static void
moo_text_view_populate_popup (GtkTextView *text_view,
GtkMenu *menu)
{
MooTextView *view = MOO_TEXT_VIEW (text_view);
GtkWidget *item;
item = gtk_separator_menu_item_new ();
gtk_widget_show (item);
gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
item = gtk_image_menu_item_new_from_stock (GTK_STOCK_REDO, NULL);
gtk_widget_show (item);
gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
g_signal_connect_swapped (item, "activate", G_CALLBACK (moo_text_view_redo), view);
gtk_widget_set_sensitive (item, moo_text_view_can_redo (view));
item = gtk_image_menu_item_new_from_stock (GTK_STOCK_UNDO, NULL);
gtk_widget_show (item);
gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
g_signal_connect_swapped (item, "activate", G_CALLBACK (moo_text_view_undo), view);
gtk_widget_set_sensitive (item, moo_text_view_can_undo (view));
}
#if !GTK_CHECK_VERSION(2,12,0)
static void
overwrite_changed (MooTextView *view)
{
GtkTextView *text_view;
text_view = GTK_TEXT_VIEW (view);
if (text_view->overwrite_mode)
{
view->priv->saved_cursor_visible = text_view->cursor_visible != 0;
gtk_text_view_set_cursor_visible (text_view, FALSE);
view->priv->overwrite_mode = TRUE;
}
else
{
gtk_text_view_set_cursor_visible (text_view,
view->priv->saved_cursor_visible);
view->priv->overwrite_mode = FALSE;
}
check_cursor_blink (view);
}
static gboolean
moo_text_view_focus_in (GtkWidget *widget,
GdkEventFocus *event)
{
gboolean ret;
ret = GTK_WIDGET_CLASS(moo_text_view_parent_class)->focus_in_event (widget, event);
check_cursor_blink (MOO_TEXT_VIEW (widget));
return ret;
}
static gboolean
moo_text_view_focus_out (GtkWidget *widget,
GdkEventFocus *event)
{
gboolean ret;
ret = GTK_WIDGET_CLASS(moo_text_view_parent_class)->focus_out_event (widget, event);
check_cursor_blink (MOO_TEXT_VIEW (widget));
return ret;
}
#define CURSOR_ON_MULTIPLIER 0.66
#define CURSOR_OFF_MULTIPLIER 0.34
#define CURSOR_PEND_MULTIPLIER 0.5
static gboolean
get_cursor_rectangle (GtkTextView *view,
GdkRectangle *cursor_rect)
{
GtkTextIter iter;
GdkRectangle visible_rect;
GtkTextBuffer *buffer;
GtkTextMark *insert;
buffer = gtk_text_view_get_buffer (view);
insert = gtk_text_buffer_get_insert (buffer);
gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
gtk_text_view_get_iter_location (view, &iter, cursor_rect);
gtk_text_view_get_visible_rect (view, &visible_rect);
if (gtk_text_iter_ends_line (&iter))
cursor_rect->width = 6;
if (gdk_rectangle_intersect (cursor_rect, &visible_rect, cursor_rect))
{
gtk_text_view_buffer_to_window_coords (view, GTK_TEXT_WINDOW_TEXT,
cursor_rect->x, cursor_rect->y,
&cursor_rect->x, &cursor_rect->y);
return TRUE;
}
else
{
return FALSE;
}
}
static void
moo_text_view_draw_cursor (GtkTextView *view,
GdkEventExpose *event)
{
GdkRectangle cursor_rect;
if (!get_cursor_rectangle (view, &cursor_rect))
return;
if (!gdk_rectangle_intersect (&cursor_rect, &event->area, &cursor_rect))
return;
gdk_draw_rectangle (event->window,
GTK_WIDGET(view)->style->text_gc[GTK_STATE_NORMAL],
TRUE,
cursor_rect.x,
cursor_rect.y,
cursor_rect.width,
cursor_rect.height);
}
static void
invalidate_cursor (GtkTextView *view)
{
GdkRectangle rect;
if (get_cursor_rectangle (view, &rect))
{
GdkWindow *window = gtk_text_view_get_window (view, GTK_TEXT_WINDOW_TEXT);
g_return_if_fail (window != NULL);
gdk_window_invalidate_rect (window, &rect, FALSE);
}
}
static gboolean
cursor_blinks (GtkWidget *widget)
{
gboolean blink;
GtkSettings *settings = gtk_widget_get_settings (widget);
g_object_get (settings, "gtk-cursor-blink", &blink, NULL);
return blink;
}
static int
get_cursor_time (GtkWidget *widget)
{
int time;
GtkSettings *settings = gtk_widget_get_settings (widget);
g_object_get (settings, "gtk-cursor-blink-time", &time, NULL);
return time;
}
static gboolean
blink_cb (MooTextView *view)
{
GtkTextView *text_view = GTK_TEXT_VIEW (view);
int time;
g_return_val_if_fail (view->priv->overwrite_mode, FALSE);
time = get_cursor_time (GTK_WIDGET (view));
if (view->priv->cursor_visible)
time *= CURSOR_OFF_MULTIPLIER;
else
time *= CURSOR_ON_MULTIPLIER;
view->priv->blink_timeout = _moo_timeout_add (time, (GSourceFunc) blink_cb, view);
view->priv->cursor_visible = !view->priv->cursor_visible;
invalidate_cursor (text_view);
return FALSE;
}
static void
stop_cursor_blink (MooTextView *view)
{
if (view->priv->blink_timeout)
{
g_source_remove (view->priv->blink_timeout);
view->priv->blink_timeout = 0;
}
}
static void
check_cursor_blink (MooTextView *view)
{
GtkTextView *text_view = GTK_TEXT_VIEW (view);
if (view->priv->overwrite_mode && GTK_WIDGET_HAS_FOCUS (view))
{
if (cursor_blinks (GTK_WIDGET (view)))
{
if (!view->priv->blink_timeout)
{
int time = get_cursor_time (GTK_WIDGET (view)) * CURSOR_OFF_MULTIPLIER;
view->priv->cursor_visible = TRUE;
view->priv->blink_timeout = _moo_timeout_add (time, (GSourceFunc) blink_cb, view);
}
}
else
{
view->priv->cursor_visible = TRUE;
}
}
else
{
view->priv->cursor_visible = FALSE;
stop_cursor_blink (view);
if (GTK_WIDGET_DRAWABLE (text_view))
invalidate_cursor (text_view);
}
}
void
_moo_text_view_pend_cursor_blink (MooTextView *view)
{
if (view->priv->overwrite_mode &&
GTK_WIDGET_HAS_FOCUS (view) &&
cursor_blinks (GTK_WIDGET (view)))
{
int time;
if (view->priv->blink_timeout != 0)
{
g_source_remove (view->priv->blink_timeout);
view->priv->blink_timeout = 0;
}
view->priv->cursor_visible = TRUE;
time = get_cursor_time (GTK_WIDGET (view)) * CURSOR_PEND_MULTIPLIER;
view->priv->blink_timeout = _moo_timeout_add (time, (GSourceFunc) blink_cb, view);
}
}
#else /* GTK_CHECK_VERSION(2,12,0) */
void
_moo_text_view_pend_cursor_blink (G_GNUC_UNUSED MooTextView *view)
{
}
#endif
int
_moo_text_view_get_line_height (MooTextView *view)
{
PangoContext *ctx;
PangoLayout *layout;
PangoRectangle rect;
int height;
g_return_val_if_fail (MOO_IS_TEXT_VIEW (view), 10);
ctx = gtk_widget_get_pango_context (GTK_WIDGET (view));
g_return_val_if_fail (ctx != NULL, 10);
layout = pango_layout_new (ctx);
pango_layout_set_text (layout, "AA", -1);
pango_layout_get_extents (layout, NULL, &rect);
height = rect.height / PANGO_SCALE;
g_object_unref (layout);
return height;
}
/*****************************************************************************/
/* Left margin
*/
#define MARK_ICON_LPAD 0
#define MARK_ICON_RPAD 0
#define LINE_NUMBER_LPAD 0
#define LINE_NUMBER_RPAD 3
static PangoLayout *
create_line_numbers_layout (MooTextView *view)
{
PangoLayout *layout;
layout = gtk_widget_create_pango_layout (GTK_WIDGET (view), NULL);
if (view->priv->lm.numbers_font)
pango_layout_set_font_description (layout, view->priv->lm.numbers_font);
return layout;
}
static int
get_n_digits (MooTextView *view)
{
int i, n;
int lines;
lines = MAX (99, gtk_text_buffer_get_line_count (get_buffer (view)));
for (i = 2, n = 100; ; ++i, n *= 10)
if (lines < n)
return i;
}
static void
update_line_mark_icons (MooTextView *view)
{
int i;
char str[32];
GtkSettings *settings;
PangoLayout *layout;
g_return_if_fail (GTK_WIDGET_REALIZED (view));
settings = gtk_widget_get_settings (GTK_WIDGET (view));
if (!gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_MENU,
&view->priv->lm.icon_width, NULL))
view->priv->lm.icon_width = 16;
layout = gtk_widget_create_pango_layout (GTK_WIDGET (view), NULL);
for (i = 1; i < 10; ++i)
{
PangoRectangle rect;
g_snprintf (str, sizeof str, "<b>%d</b>", i);
pango_layout_set_markup (layout, str, -1);
pango_layout_get_pixel_extents (layout, &rect, NULL);
view->priv->lm.icon_width = MAX (view->priv->lm.icon_width, rect.width);
}
g_object_unref (layout);
}
static void
update_digit_width (MooTextView *view)
{
int i;
char str[32];
PangoLayout *layout;
layout = create_line_numbers_layout (view);
view->priv->lm.digit_width = 0;
view->priv->lm.numbers_width = 0;
for (i = 0; i < 10; ++i)
{
int width;
g_snprintf (str, sizeof str, "%d", i);
pango_layout_set_text (layout, str, -1);
pango_layout_get_pixel_size (layout, &width, NULL);
view->priv->lm.digit_width = MAX (view->priv->lm.digit_width, width);
}
g_object_unref (layout);
}
static void
update_fold_width (MooTextView *view)
{
gtk_widget_style_get (GTK_WIDGET (view), "expander-size",
&view->priv->lm.fold_width, NULL);
view->priv->lm.fold_width += 2*EXPANDER_PAD;
}
static void
update_left_margin (MooTextView *view)
{
int margin_size;
int old_size;
GtkTextView *text_view = GTK_TEXT_VIEW (view);
if (!GTK_WIDGET_REALIZED (view))
return;
margin_size = 0;
if (view->priv->lm.show_icons)
{
update_line_mark_icons (view);
margin_size += view->priv->lm.icon_width + MARK_ICON_LPAD + MARK_ICON_RPAD;
}
if (view->priv->lm.show_numbers)
{
update_digit_width (view);
view->priv->lm.numbers_width = view->priv->lm.digit_width * get_n_digits (view);
margin_size += view->priv->lm.numbers_width + LINE_NUMBER_LPAD + LINE_NUMBER_RPAD;
}
if (view->priv->lm.show_folds)
{
update_fold_width (view);
margin_size += view->priv->lm.fold_width;
}
old_size = get_border_window_size (text_view, GTK_TEXT_WINDOW_LEFT);
gtk_text_view_set_border_window_size (text_view,
GTK_TEXT_WINDOW_LEFT,
margin_size);
if (old_size == 0 && margin_size != 0)
{
GdkWindow *window = gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_LEFT);
GdkCursor *cursor = gdk_cursor_new (GDK_RIGHT_PTR);
gdk_window_set_cursor (window, cursor);
gdk_cursor_unref (cursor);
}
/* XXX do not invalidate whole widget */
gtk_widget_queue_draw (GTK_WIDGET (view));
}
static gboolean
update_n_lines_idle (MooTextView *view)
{
int n_lines;
view->priv->update_n_lines_idle = 0;
n_lines = gtk_text_buffer_get_line_count (get_buffer (view));
if (n_lines != view->priv->n_lines)
{
view->priv->n_lines = n_lines;
update_left_margin (view);
}
return FALSE;
}
static void
buffer_changed (MooTextView *view)
{
if (GTK_WIDGET_REALIZED (view) &&
view->priv->lm.show_numbers &&
!view->priv->update_n_lines_idle)
{
view->priv->update_n_lines_idle =
_moo_idle_add_full (G_PRIORITY_HIGH,
(GSourceFunc) update_n_lines_idle,
view, NULL);
}
}
static void
draw_marks (MooTextView *view,
GdkEventExpose *event,
GSList *marks,
int line_y,
int line_height)
{
g_return_if_fail (marks != NULL);
while (marks)
{
MooLineMark *mark;
GdkPixbuf *pixbuf;
const char *markup;
mark = marks->data;
marks = marks->next;
if (!_moo_line_mark_get_pretty (mark))
continue;
pixbuf = moo_line_mark_get_pixbuf (mark);
if (pixbuf)
{
int pw, ph;
int x, y;
pw = gdk_pixbuf_get_width (pixbuf);
ph = gdk_pixbuf_get_height (pixbuf);
x = MARK_ICON_LPAD + (view->priv->lm.icon_width - pw) / 2;
y = line_y + (line_height - ph) / 2;
gdk_draw_pixbuf (event->window, NULL, pixbuf,
0, 0, x, y, -1, -1,
GDK_RGB_DITHER_NORMAL, 0, 0);
}
markup = moo_line_mark_get_markup (mark);
if (markup)
{
PangoLayout *layout;
PangoRectangle rect;
int x, y;
layout = gtk_widget_create_pango_layout (GTK_WIDGET (view), NULL);
pango_layout_set_markup (layout, markup, -1);
pango_layout_get_pixel_extents (layout, &rect, NULL);
x = MARK_ICON_LPAD + (view->priv->lm.icon_width - rect.width) / 2;
y = line_y;
gdk_draw_layout (event->window,
GTK_WIDGET(view)->style->fg_gc[GTK_WIDGET_STATE(view)],
x, y, layout);
g_object_unref (layout);
}
}
}
static void
draw_fold_mark (MooTextView *view,
GdkEventExpose *event,
MooFold *fold,
int y,
int height,
int window_width)
{
gtk_paint_expander (GTK_WIDGET(view)->style,
event->window,
GTK_WIDGET_STATE (view),
&event->area,
GTK_WIDGET(view),
"fold",
window_width - view->priv->lm.fold_width / 2,
y + height / 2,
fold->collapsed ? GTK_EXPANDER_COLLAPSED : GTK_EXPANDER_EXPANDED);
}
static void
draw_left_margin (MooTextView *view,
GdkEventExpose *event)
{
GtkTextView *text_view;
GtkTextBuffer *buffer;
PangoLayout *layout = NULL;
int line, last_line, current_line, text_width, window_width, mark_icon_width;
GdkRectangle area;
GtkTextIter iter;
char str[32];
text_view = GTK_TEXT_VIEW (view);
buffer = gtk_text_view_get_buffer (text_view);
gdk_drawable_get_size (event->window, &window_width, NULL);
text_width = 0;
mark_icon_width = 0;
if (view->priv->lm.show_numbers)
{
layout = create_line_numbers_layout (view);
text_width = view->priv->lm.numbers_width;
}
if (view->priv->lm.show_icons)
mark_icon_width = view->priv->lm.icon_width + MARK_ICON_LPAD + MARK_ICON_RPAD;
gtk_text_buffer_get_iter_at_mark (buffer, &iter, gtk_text_buffer_get_insert (buffer));
current_line = gtk_text_iter_get_line (&iter);
area = event->area;
gtk_text_view_window_to_buffer_coords (text_view,
GTK_TEXT_WINDOW_LEFT,
area.x, area.y,
&area.x, &area.y);
gtk_text_view_get_line_at_y (text_view, &iter, area.y + area.height - 1, NULL);
last_line = gtk_text_iter_get_line (&iter);
gtk_text_view_get_line_at_y (text_view, &iter, area.y, NULL);
line = gtk_text_iter_get_line (&iter);
while (TRUE)
{
int y, height;
gtk_text_view_get_line_yrange (text_view, &iter, &y, &height);
if (y > area.y + area.height)
break;
gtk_text_view_buffer_to_window_coords (text_view, GTK_TEXT_WINDOW_LEFT,
0, y, NULL, &y);
if (view->priv->lm.show_numbers)
{
int x, w;
if (line == current_line)
g_snprintf (str, sizeof str, "<b>%d</b>", line + 1);
else
g_snprintf (str, sizeof str, "%d", line + 1);
pango_layout_set_markup (layout, str, -1);
pango_layout_get_pixel_size (layout, &w, NULL);
x = mark_icon_width + LINE_NUMBER_LPAD + text_width - w;
gtk_paint_layout (GTK_WIDGET (view)->style,
event->window,
GTK_WIDGET_STATE (view),
TRUE, &event->area,
GTK_WIDGET(view), NULL,
x, y, layout);
}
if (view->priv->lm.show_folds)
{
MooFold *fold = moo_text_buffer_get_fold_at_line (get_moo_buffer (view), line);
if (fold)
draw_fold_mark (view, event, fold, y, height, window_width);
}
if (view->priv->lm.show_icons)
{
GSList *marks = moo_text_buffer_get_line_marks_at_line (get_moo_buffer (view), line);
if (marks)
draw_marks (view, event, marks, y, height);
g_slist_free (marks);
}
if (!text_iter_forward_visible_line (view, &iter, &line))
break;
}
if (layout)
g_object_unref (layout);
}
static void
draw_fold_background (MooTextView *view,
GdkEventExpose *event,
MooFold *fold,
int y,
int height,
int window_width)
{
if (fold->collapsed)
gdk_draw_line (event->window,
GTK_WIDGET(view)->style->text_gc[GTK_STATE_NORMAL],
gtk_text_view_get_left_margin (GTK_TEXT_VIEW (view)),
y + height - 1,
gtk_text_view_get_left_margin (GTK_TEXT_VIEW (view)) + window_width,
y + height - 1);
}
static void
draw_marks_background (MooTextView *view,
GdkEventExpose *event)
{
GtkTextView *text_view;
int line, last_line, window_width;
GdkRectangle area;
GtkTextIter iter;
text_view = GTK_TEXT_VIEW (view);
area = event->area;
gtk_text_view_window_to_buffer_coords (text_view,
GTK_TEXT_WINDOW_TEXT,
area.x, area.y,
&area.x, &area.y);
gdk_drawable_get_size (event->window, &window_width, NULL);
window_width -= gtk_text_view_get_left_margin (text_view);
gtk_text_view_get_line_at_y (text_view, &iter, area.y + area.height - 1, NULL);
last_line = gtk_text_iter_get_line (&iter);
gtk_text_view_get_line_at_y (text_view, &iter, area.y, NULL);
line = gtk_text_iter_get_line (&iter);
while (TRUE)
{
int y, height;
gtk_text_view_get_line_yrange (text_view, &iter, &y, &height);
if (y > area.y + area.height)
break;
gtk_text_view_buffer_to_window_coords (text_view, GTK_TEXT_WINDOW_TEXT,
0, y, NULL, &y);
if (view->priv->enable_folding)
{
MooFold *fold = moo_text_buffer_get_fold_at_line (get_moo_buffer (view), line);
if (fold)
draw_fold_background (view, event, fold, y, height, window_width);
}
if (TRUE)
{
MooLineMark *mark;
GdkGC *gc = NULL;
GSList *marks = moo_text_buffer_get_line_marks_at_line (get_moo_buffer (view), line);
if (marks)
{
while (marks)
{
mark = marks->data;
if (_moo_line_mark_get_pretty (mark))
gc = moo_line_mark_get_background_gc (mark);
if (!gc)
marks = g_slist_delete_link (marks, marks);
else
break;
}
/* XXX compose colors */
if (gc)
{
gdk_gc_set_clip_rectangle (gc, &event->area);
gdk_draw_rectangle (event->window,
gc,
TRUE,
gtk_text_view_get_left_margin (text_view),
y,
window_width,
height);
}
}
g_slist_free (marks);
}
if (!text_iter_forward_visible_line (view, &iter, &line))
break;
}
}
void
moo_text_view_set_show_line_numbers (MooTextView *view,
gboolean show)
{
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
show = show != 0;
if (view->priv->lm.show_numbers != show)
{
view->priv->lm.show_numbers = show;
update_left_margin (view);
g_object_notify (G_OBJECT (view), "show-line-numbers");
}
}
void
_moo_text_view_set_line_numbers_font (MooTextView *view,
const char *name)
{
PangoFontDescription *font;
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
if (name)
font = pango_font_description_from_string (name);
else
font = NULL;
if (font == view->priv->lm.numbers_font ||
(font != NULL && view->priv->lm.numbers_font != NULL &&
pango_font_description_equal (font, view->priv->lm.numbers_font)))
{
pango_font_description_free (font);
return;
}
if (view->priv->lm.numbers_font)
pango_font_description_free (view->priv->lm.numbers_font);
view->priv->lm.numbers_font = font;
update_left_margin (view);
}
static void
set_show_line_marks (MooTextView *view,
gboolean show)
{
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
show = show != 0;
if (view->priv->lm.show_icons != show)
{
view->priv->lm.show_icons = show;
update_left_margin (view);
g_object_notify (G_OBJECT (view), "show-line-marks");
}
}
static void
moo_text_view_style_set (GtkWidget *widget,
GtkStyle *prev_style)
{
MooTextView *view = MOO_TEXT_VIEW (widget);
invalidate_gcs (view);
invalidate_right_margin (view);
update_box_tag (view);
update_tab_width (view);
update_left_margin (view);
GTK_WIDGET_CLASS(moo_text_view_parent_class)->style_set (widget, prev_style);
}
static void
set_enable_folding (MooTextView *view,
gboolean show)
{
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
show = show != 0;
if (show == view->priv->enable_folding)
return;
if (show && !moo_text_view_lookup_tag (view, MOO_FOLD_TAG))
gtk_text_buffer_create_tag (get_buffer (view), MOO_FOLD_TAG,
"invisible", TRUE, NULL);
view->priv->lm.show_folds = show;
view->priv->enable_folding = show;
update_left_margin (view);
g_object_notify (G_OBJECT (view), "enable-folding");
}
static void
update_tab_width (MooTextView *view)
{
PangoTabArray *tabs;
PangoLayout *layout;
int tab_width;
char *string;
if (!GTK_WIDGET_REALIZED (view))
return;
g_return_if_fail (view->priv->tab_width > 0);
g_return_if_fail (GTK_WIDGET (view)->style != NULL);
string = g_strnfill (view->priv->tab_width, ' ');
layout = gtk_widget_create_pango_layout (GTK_WIDGET (view), string);
pango_layout_get_size (layout, &tab_width, NULL);
tabs = pango_tab_array_new (2, FALSE);
pango_tab_array_set_tab (tabs, 0, PANGO_TAB_LEFT, 0);
pango_tab_array_set_tab (tabs, 1, PANGO_TAB_LEFT, tab_width);
gtk_text_view_set_tabs (GTK_TEXT_VIEW (view), tabs);
pango_tab_array_free (tabs);
g_object_unref (layout);
g_free (string);
}
void
moo_text_view_set_tab_width (MooTextView *view,
guint width)
{
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
g_return_if_fail (width > 0);
if (width == view->priv->tab_width)
return;
view->priv->tab_width = width;
update_tab_width (view);
g_object_notify (G_OBJECT (view), "tab-width");
}
static void
invalidate_right_margin (MooTextView *view)
{
view->priv->right_margin_pixel_offset = -1;
}
static void
update_right_margin (MooTextView *view)
{
PangoLayout *layout;
char *string;
if (view->priv->right_margin_pixel_offset > 0 ||
!GTK_WIDGET_REALIZED (view))
return;
g_return_if_fail (view->priv->right_margin_offset > 0 &&
view->priv->right_margin_offset < 1000);
string = g_strnfill (view->priv->right_margin_offset, '_');
layout = gtk_widget_create_pango_layout (GTK_WIDGET (view), string);
pango_layout_get_pixel_size (layout, &view->priv->right_margin_pixel_offset, NULL);
g_object_unref (layout);
g_free (string);
}
static gboolean
text_iter_forward_visible_line (MooTextView *view,
GtkTextIter *iter,
int *line)
{
if (!view->priv->enable_folding)
{
if (!gtk_text_iter_forward_line (iter))
{
if (gtk_text_iter_get_line (iter) == *line)
return FALSE;
}
*line += 1;
return TRUE;
}
else
{
GtkTextTagTable *table = gtk_text_buffer_get_tag_table (get_buffer (view));
GtkTextTag *tag = gtk_text_tag_table_lookup (table, MOO_FOLD_TAG);
g_return_val_if_fail (tag != NULL, FALSE);
while (TRUE)
{
if (!gtk_text_iter_forward_line (iter))
{
if (gtk_text_iter_get_line (iter) == *line)
return FALSE;
}
*line += 1;
if (!gtk_text_iter_has_tag (iter, tag) && !gtk_text_iter_begins_tag (iter, tag))
return TRUE;
}
}
}
static void
invalidate_line (MooTextView *view,
int line,
gboolean left,
gboolean text)
{
GtkTextIter iter;
GdkRectangle rect = {0, 0, 0, 0};
if (!GTK_WIDGET_DRAWABLE (view))
return;
gtk_text_buffer_get_iter_at_line (get_buffer (view), &iter, line);
gtk_text_view_get_line_yrange (GTK_TEXT_VIEW (view), &iter, &rect.y, &rect.height);
gtk_text_view_buffer_to_window_coords (GTK_TEXT_VIEW (view),
GTK_TEXT_WINDOW_TEXT,
0, rect.y, NULL, &rect.y);
if (left)
{
GdkWindow *window = gtk_text_view_get_window (GTK_TEXT_VIEW (view),
GTK_TEXT_WINDOW_LEFT);
if (window)
{
gdk_drawable_get_size (window, &rect.width, NULL);
gdk_window_invalidate_rect (window, &rect, FALSE);
}
}
if (text)
{
GdkWindow *window = gtk_text_view_get_window (GTK_TEXT_VIEW (view),
GTK_TEXT_WINDOW_TEXT);
if (window)
{
gdk_drawable_get_size (window, &rect.width, NULL);
gdk_window_invalidate_rect (window, &rect, FALSE);
}
}
}
static void
line_mark_deleted (MooTextView *view,
MooLineMark *mark)
{
if (_moo_line_mark_get_pretty (mark))
{
_moo_line_mark_set_pretty (mark, FALSE);
view->priv->line_marks = g_slist_remove (view->priv->line_marks, mark);
g_signal_handlers_disconnect_by_func (mark, (gpointer) line_mark_changed, view);
g_object_unref (mark);
gtk_widget_queue_draw (GTK_WIDGET (view));
}
}
static void
line_mark_changed (MooTextView *view,
MooLineMark *mark)
{
invalidate_line (view, moo_line_mark_get_line (mark), TRUE, TRUE);
}
static void
line_mark_moved (MooTextView *view,
MooLineMark *mark)
{
/* XXX */
if (_moo_line_mark_get_pretty (mark))
gtk_widget_queue_draw (GTK_WIDGET (view));
}
static void
line_mark_added (MooTextView *view,
MooLineMark *mark)
{
g_return_if_fail (!g_slist_find (view->priv->line_marks, mark));
if (!moo_line_mark_get_visible (mark))
return;
_moo_line_mark_set_pretty (mark, TRUE);
view->priv->line_marks = g_slist_prepend (view->priv->line_marks,
g_object_ref (mark));
g_signal_connect_swapped (mark, "changed",
G_CALLBACK (line_mark_changed), view);
if (GTK_WIDGET_REALIZED (view))
_moo_line_mark_realize (mark, GTK_WIDGET (view));
invalidate_line (view, moo_line_mark_get_line (mark), TRUE, TRUE);
}
static void
fold_added (MooTextView *view,
MooFold *fold)
{
if (view->priv->enable_folding)
invalidate_line (view, _moo_fold_get_start (fold), TRUE, fold->collapsed);
}
static void
fold_deleted (MooTextView *view)
{
if (view->priv->enable_folding)
gtk_widget_queue_draw (GTK_WIDGET (view));
}
static void
fold_toggled (MooTextView *view,
MooFold *fold)
{
if (view->priv->enable_folding)
invalidate_line (view, _moo_fold_get_start (fold), TRUE, TRUE);
}
/***************************************************************************/
/* Children
*/
/* http://bugzilla.gnome.org/show_bug.cgi?id=323843 */
static int
get_border_window_size (GtkTextView *text_view,
GtkTextWindowType type)
{
if (GTK_WIDGET_REALIZED (text_view) && gtk_text_view_get_window (text_view, type))
return gtk_text_view_get_border_window_size (text_view, type);
else
return 0;
}
void
moo_text_view_add_child_in_border (MooTextView *view,
GtkWidget *widget,
GtkTextWindowType which_border)
{
MooTextViewPos pos = MOO_TEXT_VIEW_POS_INVALID;
GtkWidget **child;
GtkRequisition child_req = {0, 0};
int border_size = 0;
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (widget->parent == NULL);
switch (which_border)
{
case GTK_TEXT_WINDOW_LEFT:
pos = MOO_TEXT_VIEW_POS_LEFT;
break;
case GTK_TEXT_WINDOW_RIGHT:
pos = MOO_TEXT_VIEW_POS_RIGHT;
break;
case GTK_TEXT_WINDOW_TOP:
pos = MOO_TEXT_VIEW_POS_TOP;
break;
case GTK_TEXT_WINDOW_BOTTOM:
pos = MOO_TEXT_VIEW_POS_BOTTOM;
break;
default:
g_return_if_reached ();
}
g_return_if_fail (pos < MOO_TEXT_VIEW_POS_INVALID);
child = &view->priv->children[pos];
g_return_if_fail (*child == NULL);
MOO_OBJECT_REF_SINK (widget);
*child = widget;
if (GTK_WIDGET_VISIBLE (widget))
{
gtk_widget_size_request (widget, &child_req);
switch (which_border)
{
case GTK_TEXT_WINDOW_LEFT:
case GTK_TEXT_WINDOW_RIGHT:
border_size = child_req.width;
break;
case GTK_TEXT_WINDOW_TOP:
case GTK_TEXT_WINDOW_BOTTOM:
border_size = child_req.height;
break;
default:
g_assert_not_reached ();
}
gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view),
which_border, MIN (1, border_size));
lower_border_window (GTK_TEXT_VIEW (view), pos);
}
gtk_text_view_add_child_in_window (GTK_TEXT_VIEW (view), widget,
GTK_TEXT_WINDOW_WIDGET, 0, 0);
}
static void
moo_text_view_size_request (GtkWidget *widget,
GtkRequisition *requisition)
{
guint i;
MooTextView *view;
GtkTextView *text_view;
view = MOO_TEXT_VIEW (widget);
text_view = GTK_TEXT_VIEW (widget);
for (i = 0; i < 4; i++)
{
int border_size = 0;
GtkWidget *child = view->priv->children[i];
GtkRequisition child_req;
if (child && GTK_WIDGET_VISIBLE (child))
gtk_widget_size_request (child, &child_req);
else
child_req.width = child_req.height = 0;
if (child)
{
int old_size;
switch (i)
{
case MOO_TEXT_VIEW_POS_LEFT:
case MOO_TEXT_VIEW_POS_RIGHT:
border_size = child_req.width;
break;
case MOO_TEXT_VIEW_POS_TOP:
case MOO_TEXT_VIEW_POS_BOTTOM:
border_size = child_req.height;
break;
}
old_size = get_border_window_size (text_view,
window_types[i]);
gtk_text_view_set_border_window_size (text_view,
window_types[i],
border_size);
if (!old_size)
lower_border_window (GTK_TEXT_VIEW (view), i);
}
}
GTK_WIDGET_CLASS(moo_text_view_parent_class)->size_request (widget, requisition);
}
static void
moo_text_view_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
guint i;
int right, left, bottom, top, border_width;
MooTextView *view;
GtkTextView *text_view;
view = MOO_TEXT_VIEW (widget);
text_view = GTK_TEXT_VIEW (widget);
GTK_WIDGET_CLASS(moo_text_view_parent_class)->size_allocate (widget, allocation);
border_width = GTK_CONTAINER(widget)->border_width;
right = get_border_window_size (text_view, GTK_TEXT_WINDOW_RIGHT);
left = get_border_window_size (text_view, GTK_TEXT_WINDOW_LEFT);
bottom = get_border_window_size (text_view, GTK_TEXT_WINDOW_BOTTOM);
top = get_border_window_size (text_view, GTK_TEXT_WINDOW_TOP);
for (i = 0; i < 4; i++)
{
GtkWidget *child = view->priv->children[i];
GtkAllocation child_alloc = {0, 0, 0, 0};
GtkRequisition child_req;
if (!child || !GTK_WIDGET_VISIBLE (child))
continue;
child_alloc.x = left + border_width;
child_alloc.y = top + border_width;
gtk_widget_get_child_requisition (child, &child_req);
switch (i)
{
case MOO_TEXT_VIEW_POS_RIGHT:
child_alloc.x = MAX (allocation->width - border_width - right, 0);
case MOO_TEXT_VIEW_POS_LEFT:
child_alloc.width = child_req.width;
child_alloc.height = MAX (allocation->height - 2*border_width - top - bottom, 1);
break;
case MOO_TEXT_VIEW_POS_BOTTOM:
child_alloc.y = MAX (allocation->height - bottom - border_width, 0);
case MOO_TEXT_VIEW_POS_TOP:
child_alloc.height = child_req.height;
child_alloc.width = MAX (allocation->width - 2*border_width - left - right, 1);
break;
}
gtk_text_view_move_child (text_view, child, child_alloc.x, child_alloc.y);
gtk_widget_size_allocate (child, &child_alloc);
}
}
static void
moo_text_view_remove (GtkContainer *container,
GtkWidget *widget)
{
guint i;
MooTextView *view = MOO_TEXT_VIEW (container);
if (MOO_IS_TEXT_BOX (widget))
view->priv->boxes = g_slist_remove (view->priv->boxes, widget);
if (widget == view->priv->qs.evbox)
{
view->priv->qs.in_search = FALSE;
view->priv->qs.evbox = NULL;
view->priv->qs.entry = NULL;
view->priv->qs.case_sensitive = NULL;
view->priv->qs.regex = NULL;
}
for (i = 0; i < 4; ++i)
{
if (view->priv->children[i] == widget)
{
view->priv->children[i] = NULL;
g_object_unref (widget);
gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view),
window_types[i], 0);
break;
}
}
GTK_CONTAINER_CLASS(moo_text_view_parent_class)->remove (container, widget);
}
/***************************************************************************/
/* Search
*/
static void
quick_search_set_widgets_from_flags (MooTextView *view);
static void
moo_text_view_set_quick_search_flags (MooTextView *view,
MooTextSearchFlags flags)
{
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
if (flags != view->priv->qs.flags)
{
view->priv->qs.flags = flags;
if (view->priv->qs.evbox)
quick_search_set_widgets_from_flags (view);
g_object_notify (G_OBJECT (view), "quick-search-flags");
}
}
static void
quick_search_option_toggled (MooTextView *view)
{
MooTextSearchFlags flags = 0;
if (!gtk_toggle_button_get_active (view->priv->qs.case_sensitive))
flags |= MOO_TEXT_SEARCH_CASELESS;
if (gtk_toggle_button_get_active (view->priv->qs.regex))
flags |= MOO_TEXT_SEARCH_REGEX;
moo_text_view_set_quick_search_flags (view, flags);
if (MOO_IS_EDIT (view))
moo_prefs_set_flags (moo_edit_setting (MOO_EDIT_PREFS_QUICK_SEARCH_FLAGS), flags);
}
static void
quick_search_set_widgets_from_flags (MooTextView *view)
{
g_signal_handlers_block_by_func (view->priv->qs.case_sensitive,
quick_search_option_toggled, view);
g_signal_handlers_block_by_func (view->priv->qs.regex,
quick_search_option_toggled, view);
gtk_toggle_button_set_active (view->priv->qs.case_sensitive,
!(view->priv->qs.flags & MOO_TEXT_SEARCH_CASELESS));
gtk_toggle_button_set_active (view->priv->qs.regex,
view->priv->qs.flags & MOO_TEXT_SEARCH_REGEX);
g_signal_handlers_unblock_by_func (view->priv->qs.case_sensitive,
quick_search_option_toggled, view);
g_signal_handlers_unblock_by_func (view->priv->qs.regex,
quick_search_option_toggled, view);
}
static void
scroll_selection_onscreen (GtkTextView *text_view)
{
GtkTextIter iter;
GtkTextBuffer *buffer;
GdkRectangle rect, visible_rect;
buffer = gtk_text_view_get_buffer (text_view);
gtk_text_buffer_get_iter_at_mark (buffer, &iter,
gtk_text_buffer_get_selection_bound (buffer));
gtk_text_view_scroll_to_iter (text_view, &iter, 0.0, FALSE, 0.0, 0.0);
gtk_text_buffer_get_iter_at_mark (buffer, &iter,
gtk_text_buffer_get_insert (buffer));
gtk_text_view_get_iter_location (text_view, &iter, &rect);
gtk_text_view_get_visible_rect (text_view, &visible_rect);
if (rect.x < visible_rect.x || rect.y < visible_rect.y ||
rect.x + rect.width > visible_rect.x + visible_rect.width ||
rect.y + rect.height > visible_rect.y + visible_rect.height)
gtk_text_view_scroll_to_iter (text_view, &iter, 0.0, FALSE, 0.0, 0.0);
}
static void
quick_search_message (MooTextView *view,
const char *msg)
{
GtkWidget *window;
window = gtk_widget_get_toplevel (GTK_WIDGET (view));
if (MOO_IS_EDIT_WINDOW (window))
moo_edit_window_message (MOO_EDIT_WINDOW (window), msg);
}
static void
quick_search_find_from (MooTextView *view,
const char *text,
GtkTextIter *start)
{
gboolean found;
GtkTextIter match_start, match_end;
GtkTextBuffer *buffer;
if (view->priv->qs.flags & MOO_TEXT_SEARCH_REGEX)
{
GError *error = NULL;
GRegex *re = g_regex_new (text, 0, 0, &error);
if (!re)
{
char *msg = g_strdup_printf ("Invalid pattern '%s'", text);
quick_search_message (view, msg);
g_free (msg);
return;
}
g_regex_unref (re);
}
buffer = get_buffer (view);
found = moo_text_search_forward (start, text, view->priv->qs.flags,
&match_start, &match_end, NULL);
if (!found)
{
GtkTextIter iter;
gtk_text_buffer_get_start_iter (buffer, &iter);
if (!gtk_text_iter_equal (start, &iter))
found = moo_text_search_forward (&iter, text, view->priv->qs.flags,
&match_start, &match_end, NULL);
}
if (found)
{
gtk_text_buffer_select_range (buffer, &match_end, &match_start);
scroll_selection_onscreen (GTK_TEXT_VIEW (view));
}
else
{
char *message;
if (view->priv->qs.flags & MOO_TEXT_SEARCH_REGEX)
message = g_strdup_printf ("Pattern '%s' not found", text);
else
message = g_strdup_printf ("Text '%s' not found", text);
quick_search_message (view, message);
g_free (message);
}
}
static void
quick_search_find (MooTextView *view,
const char *text)
{
GtkTextIter iter1, iter2, insert;
GtkTextBuffer *buffer = get_buffer (view);
if (gtk_text_buffer_get_selection_bounds (buffer, &iter1, &iter2))
{
gtk_text_buffer_get_iter_at_mark (buffer, &insert, gtk_text_buffer_get_insert (buffer));
if (gtk_text_iter_equal (&iter1, &insert))
{
gtk_text_buffer_place_cursor (buffer, &insert);
quick_search_find_from (view, text, &insert);
return;
}
}
quick_search_find_from (view, text, &iter1);
}
static void
quick_search_find_next (MooTextView *view,
GtkEntry *entry,
gboolean from_start)
{
const char *text;
GtkTextIter start;
GtkTextBuffer *buffer = get_buffer (view);
text = gtk_entry_get_text (entry);
if (text[0])
{
if (from_start)
gtk_text_buffer_get_start_iter (buffer, &start);
else
gtk_text_buffer_get_iter_at_mark (buffer, &start, gtk_text_buffer_get_insert (buffer));
quick_search_find_from (view, text, &start);
}
}
static void
search_entry_changed (MooTextView *view,
GtkEntry *entry)
{
const char *text;
if (!view->priv->qs.in_search)
return;
text = gtk_entry_get_text (entry);
if (text[0])
quick_search_find (view, text);
}
static gboolean
search_entry_focus_out (MooTextView *view)
{
moo_text_view_stop_quick_search (view);
return FALSE;
}
static gboolean
search_entry_key_press (MooTextView *view,
GdkEventKey *event,
GtkEntry *entry)
{
if (!view->priv->qs.in_search)
return FALSE;
switch (event->keyval)
{
case GDK_Escape:
moo_text_view_stop_quick_search (view);
return TRUE;
case GDK_Return:
case GDK_KP_Enter:
quick_search_find_next (view, entry,
event->state & GDK_CONTROL_MASK);
return TRUE;
}
return FALSE;
}
static void
moo_text_view_start_quick_search (MooTextView *view)
{
char *text = NULL;
GtkTextIter iter1, iter2;
GtkTextBuffer *buffer;
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
if (view->priv->qs.in_search)
return;
if (!view->priv->qs.entry)
{
MooGladeXML *xml;
xml = moo_glade_xml_new_empty (GETTEXT_PACKAGE);
moo_glade_xml_map_class (xml, "GtkEntry", MOO_TYPE_ENTRY);
moo_glade_xml_parse_memory (xml, quicksearch_glade_xml, -1, "evbox", NULL);
view->priv->qs.evbox = moo_glade_xml_get_widget (xml, "evbox");
g_return_if_fail (view->priv->qs.evbox != NULL);
view->priv->qs.entry = moo_glade_xml_get_widget (xml, "entry");
view->priv->qs.case_sensitive = moo_glade_xml_get_widget (xml, "case_sensitive");
view->priv->qs.regex = moo_glade_xml_get_widget (xml, "regex");
g_signal_connect_swapped (view->priv->qs.entry, "changed",
G_CALLBACK (search_entry_changed), view);
g_signal_connect_swapped (view->priv->qs.entry, "focus-out-event",
G_CALLBACK (search_entry_focus_out), view);
g_signal_connect_swapped (view->priv->qs.entry, "key-press-event",
G_CALLBACK (search_entry_key_press), view);
g_signal_connect_swapped (view->priv->qs.case_sensitive, "toggled",
G_CALLBACK (quick_search_option_toggled), view);
g_signal_connect_swapped (view->priv->qs.regex, "toggled",
G_CALLBACK (quick_search_option_toggled), view);
quick_search_set_widgets_from_flags (view);
moo_text_view_add_child_in_border (view, view->priv->qs.evbox,
GTK_TEXT_WINDOW_BOTTOM);
g_object_unref (xml);
}
buffer = get_buffer (view);
if (gtk_text_buffer_get_selection_bounds (buffer, &iter1, &iter2) &&
gtk_text_iter_get_line (&iter1) == gtk_text_iter_get_line (&iter2))
{
text = gtk_text_buffer_get_slice (buffer, &iter1, &iter2, TRUE);
}
if (text)
gtk_entry_set_text (GTK_ENTRY (view->priv->qs.entry), text);
gtk_widget_show (view->priv->qs.evbox);
gtk_widget_grab_focus (view->priv->qs.entry);
view->priv->qs.in_search = TRUE;
g_free (text);
}
static void
moo_text_view_stop_quick_search (MooTextView *view)
{
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
if (view->priv->qs.in_search)
{
view->priv->qs.in_search = FALSE;
gtk_widget_hide (view->priv->qs.evbox);
gtk_widget_grab_focus (GTK_WIDGET (view));
}
}
static gboolean
start_quick_search (MooTextView *view)
{
if (view->priv->qs.enable)
{
moo_text_view_start_quick_search (view);
return TRUE;
}
else
{
return FALSE;
}
}
/*****************************************************************************/
/* Placeholders
*/
static void
update_box_tag (MooTextView *view)
{
GtkTextTag *tag = moo_text_view_lookup_tag (view, "moo-text-box");
if (tag && GTK_WIDGET_REALIZED (view))
{
PangoContext *ctx;
PangoLayout *layout;
PangoLayoutLine *line;
PangoRectangle rect;
int rise;
ctx = gtk_widget_get_pango_context (GTK_WIDGET (view));
g_return_if_fail (ctx != NULL);
layout = pango_layout_new (ctx);
pango_layout_set_text (layout, "AA", -1);
line = pango_layout_get_line (layout, 0);
pango_layout_line_get_extents (line, NULL, &rect);
rise = rect.y + rect.height;
if (tag)
g_object_set (tag, "rise", -rise, NULL);
g_object_unref (layout);
}
}
static GtkTextTag *
create_box_tag (MooTextView *view)
{
GtkTextTag *tag;
tag = moo_text_view_lookup_tag (view, "moo-text-box");
if (!tag)
{
GtkTextBuffer *buffer = get_buffer (view);
tag = gtk_text_buffer_create_tag (buffer, "moo-text-box", NULL);
update_box_tag (view);
}
return tag;
}
static GtkTextTag *
get_placeholder_tag (MooTextView *view)
{
return moo_text_view_lookup_tag (view, MOO_PLACEHOLDER_TAG);
}
static GtkTextTag *
create_placeholder_tag (MooTextView *view)
{
GtkTextTag *tag;
tag = moo_text_view_lookup_tag (view, MOO_PLACEHOLDER_TAG);
if (!tag)
{
GtkTextBuffer *buffer = get_buffer (view);
tag = gtk_text_buffer_create_tag (buffer, MOO_PLACEHOLDER_TAG, NULL);
g_object_set (tag, "background", "yellow", NULL);
}
return tag;
}
static void
moo_text_view_insert_box (MooTextView *view,
GtkTextIter *iter)
{
GtkTextBuffer *buffer;
GtkTextChildAnchor *anchor;
GtkWidget *box;
GtkTextTag *tag;
GtkTextIter start;
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
g_return_if_fail (iter != NULL);
anchor = g_object_new (MOO_TYPE_TEXT_ANCHOR, NULL);
box = g_object_new (MOO_TYPE_TEXT_BOX, NULL);
MOO_TEXT_ANCHOR (anchor)->widget = box;
buffer = get_buffer (view);
gtk_text_buffer_insert_child_anchor (buffer, iter, anchor);
tag = create_box_tag (view);
start = *iter;
gtk_text_iter_backward_char (&start);
gtk_text_buffer_apply_tag (buffer, tag, &start, iter);
gtk_widget_show (box);
gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view), box, anchor);
view->priv->boxes = g_slist_prepend (view->priv->boxes, box);
g_object_unref (anchor);
}
void
moo_text_view_insert_placeholder (MooTextView *view,
GtkTextIter *iter,
const char *text)
{
MooTextBuffer *buffer;
GtkTextTag *tag;
g_return_if_fail (MOO_IS_TEXT_VIEW (view));
g_return_if_fail (iter != NULL);
if (!text || !text[0])
{
moo_text_view_insert_box (view, iter);
return;
}
tag = create_placeholder_tag (view);
buffer = get_moo_buffer (view);
gtk_text_buffer_insert_with_tags (GTK_TEXT_BUFFER (buffer),
iter, text, -1, tag, NULL);
}
static gboolean
has_boxes (MooTextView *view)
{
return view->priv->boxes != NULL;
}
static gboolean
has_box_at_iter (MooTextView *view,
GtkTextIter *iter)
{
GtkTextChildAnchor *anchor;
g_return_val_if_fail (MOO_IS_TEXT_VIEW (view), FALSE);
g_return_val_if_fail (iter != NULL, FALSE);
if (gtk_text_iter_get_char (iter) != MOO_TEXT_UNKNOWN_CHAR)
return FALSE;
anchor = gtk_text_iter_get_child_anchor (iter);
return MOO_IS_TEXT_ANCHOR (anchor) &&
MOO_IS_TEXT_BOX (MOO_TEXT_ANCHOR(anchor)->widget);
}
static gboolean
moo_text_view_find_box_forward (MooTextView *view,
GtkTextIter *match_start,
GtkTextIter *match_end)
{
GtkTextIter start;
GtkTextBuffer *buffer;
g_return_val_if_fail (MOO_IS_TEXT_VIEW (view), FALSE);
buffer = get_buffer (view);
gtk_text_buffer_get_selection_bounds (buffer, NULL, &start);
while (gtk_text_iter_forward_search (&start, MOO_TEXT_UNKNOWN_CHAR_S,
0, match_start, match_end, NULL))
{
if (has_box_at_iter (view, match_start))
return TRUE;
else
start = *match_end;
}
return FALSE;
}
static gboolean
moo_text_view_find_placeholder_forward (MooTextView *view,
GtkTextIter *match_start,
GtkTextIter *match_end)
{
GtkTextBuffer *buffer;
GtkTextTag *tag;
g_return_val_if_fail (MOO_IS_TEXT_VIEW (view), FALSE);
if (!(tag = get_placeholder_tag (view)))
return FALSE;
buffer = get_buffer (view);
gtk_text_buffer_get_selection_bounds (buffer, NULL, match_start);
if (gtk_text_iter_has_tag (match_start, tag))
{
if (gtk_text_iter_begins_tag (match_start, tag))
{
*match_end = *match_start;
gtk_text_iter_forward_to_tag_toggle (match_end, tag);
return TRUE;
}
if (!gtk_text_iter_forward_to_tag_toggle (match_start, tag))
return FALSE;
}
if (!gtk_text_iter_forward_to_tag_toggle (match_start, tag))
return FALSE;
g_assert (gtk_text_iter_begins_tag (match_start, tag));
*match_end = *match_start;
gtk_text_iter_forward_to_tag_toggle (match_end, tag);
return TRUE;
}
static gboolean
moo_text_view_find_placeholder_backward (MooTextView *view,
GtkTextIter *match_start,
GtkTextIter *match_end)
{
GtkTextBuffer *buffer;
GtkTextTag *tag;
g_return_val_if_fail (MOO_IS_TEXT_VIEW (view), FALSE);
if (!(tag = get_placeholder_tag (view)))
return FALSE;
buffer = get_buffer (view);
gtk_text_buffer_get_selection_bounds (buffer, match_start, NULL);
if (gtk_text_iter_has_tag (match_start, tag))
{
if (!gtk_text_iter_begins_tag (match_start, tag))
gtk_text_iter_backward_to_tag_toggle (match_start, tag);
}
else if (gtk_text_iter_ends_tag (match_start, tag))
{
*match_end = *match_start;
gtk_text_iter_backward_to_tag_toggle (match_end, tag);
return TRUE;
}
if (!gtk_text_iter_backward_to_tag_toggle (match_start, tag))
return FALSE;
g_assert (gtk_text_iter_ends_tag (match_start, tag));
*match_end = *match_start;
gtk_text_iter_backward_to_tag_toggle (match_end, tag);
return TRUE;
}
static gboolean
moo_text_view_find_box_backward (MooTextView *view,
GtkTextIter *match_start,
GtkTextIter *match_end)
{
GtkTextIter start;
GtkTextBuffer *buffer;
g_return_val_if_fail (MOO_IS_TEXT_VIEW (view), FALSE);
buffer = get_buffer (view);
gtk_text_buffer_get_selection_bounds (buffer, &start, NULL);
while (gtk_text_iter_backward_search (&start, MOO_TEXT_UNKNOWN_CHAR_S,
0, match_start, match_end, NULL))
{
if (has_box_at_iter (view, match_start))
{
start = *match_start;
*match_start = *match_end;
*match_end = start;
return TRUE;
}
else
{
start = *match_start;
}
}
return FALSE;
}
static gboolean
moo_text_view_find_placeholder (MooTextView *view,
gboolean forward)
{
GtkTextIter box_start, box_end, ph_start, ph_end;
GtkTextIter *start, *end;
GtkTextBuffer *buffer;
gboolean found_box, found_ph;
g_return_val_if_fail (MOO_IS_TEXT_VIEW (view), FALSE);
buffer = get_buffer (view);
if (forward)
{
found_box = moo_text_view_find_box_forward (view, &box_start, &box_end);
found_ph = moo_text_view_find_placeholder_forward (view, &ph_start, &ph_end);
}
else
{
found_box = moo_text_view_find_box_backward (view, &box_start, &box_end);
found_ph = moo_text_view_find_placeholder_backward (view, &ph_start, &ph_end);
}
if (!found_box && !found_ph)
{
moo_text_view_message (view, "No placeholder found");
return FALSE;
}
if (found_box && found_ph)
{
if (forward)
found_box = gtk_text_iter_compare (&box_start, &ph_start) < 0;
else
found_box = gtk_text_iter_compare (&box_start, &ph_start) > 0;
}
if (found_box)
{
start = &box_start;
end = &box_end;
}
else
{
start = &ph_start;
end = &ph_end;
}
if (forward)
gtk_text_buffer_select_range (buffer, start, end);
else
gtk_text_buffer_select_range (buffer, end, start);
scroll_selection_onscreen (GTK_TEXT_VIEW (view));
return TRUE;
}
gboolean
moo_text_view_prev_placeholder (MooTextView *view)
{
g_return_val_if_fail (MOO_IS_TEXT_VIEW (view), FALSE);
return moo_text_view_find_placeholder (view, FALSE);
}
gboolean
moo_text_view_next_placeholder (MooTextView *view)
{
g_return_val_if_fail (MOO_IS_TEXT_VIEW (view), FALSE);
return moo_text_view_find_placeholder (view, TRUE);
}