medit/moo/mooedit/mooeditkeys.c

729 lines
20 KiB
C

/*
* mooedit/mooeditkeys.c
*
* Copyright (C) 2004-2005 by Yevgen Muntyan <muntyan@math.tamu.edu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* See COPYING file that comes with this distribution.
*/
#define MOOEDIT_COMPILATION
#include "mooedit/mooedit-private.h"
#include "mooedit/mootextiter.h"
#include "mooutils/moocompat.h"
#include <gdk/gdkkeysyms.h>
#include <string.h>
inline G_GNUC_CONST static GtkWidgetClass *parent_class (void)
{
static gpointer sourceview_class = NULL;
if (!sourceview_class)
sourceview_class = GTK_WIDGET_CLASS (gtk_type_class (GTK_TYPE_SOURCE_VIEW));
return GTK_WIDGET_CLASS (sourceview_class);
}
/* these two functions are taken from gtk/gtktextview.c */
static void text_view_obscure_mouse_cursor (GtkTextView *text_view);
static void set_invisible_cursor (GdkWindow *window);
static gboolean handle_tab (MooEdit *edit,
GdkEventKey *event);
static gboolean handle_backspace (MooEdit *edit,
GdkEventKey *event);
static gboolean handle_enter (MooEdit *edit,
GdkEventKey *event);
static gboolean handle_shift_tab (MooEdit *edit,
GdkEventKey *event);
static gboolean handle_ctrl_up (MooEdit *edit,
GdkEventKey *event,
gboolean up);
static gboolean handle_ctrl_pgup (MooEdit *edit,
GdkEventKey *event,
gboolean up);
static void indent_line (MooEdit *edit,
GtkTextIter *iter);
static void unindent_line (MooEdit *edit,
GtkTextIter *iter);
inline static void get_iter_at_selection_bound (GtkTextBuffer *buffer,
GtkTextIter *iter)
{
gtk_text_buffer_get_iter_at_mark (buffer, iter,
gtk_text_buffer_get_selection_bound (buffer));
}
inline static void get_iter_at_insert (GtkTextBuffer *buffer,
GtkTextIter *iter)
{
gtk_text_buffer_get_iter_at_mark (buffer, iter,
gtk_text_buffer_get_insert (buffer));
}
inline static void move_selection_bound (GtkTextBuffer *buffer,
GtkTextIter *iter)
{
gtk_text_buffer_move_mark (buffer,
gtk_text_buffer_get_selection_bound (buffer),
iter);
}
inline static void move_insert (GtkTextBuffer *buffer,
GtkTextIter *iter)
{
gtk_text_buffer_move_mark (buffer,
gtk_text_buffer_get_insert (buffer),
iter);
}
int _moo_edit_key_press_event (GtkWidget *widget,
GdkEventKey *event)
{
MooEdit *edit;
GtkTextView *text_view;
GtkTextBuffer *buffer;
gboolean obscure = TRUE;
gboolean handled = FALSE;
int keyval = event->keyval;
GdkModifierType mods =
event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK);
edit = MOO_EDIT (widget);
text_view = GTK_TEXT_VIEW (widget);
buffer = edit->priv->text_buffer;
if (!mods)
{
switch (keyval)
{
case GDK_Tab:
case GDK_KP_Tab:
handled = handle_tab (edit, event);
break;
case GDK_BackSpace:
handled = handle_backspace (edit, event);
break;
case GDK_KP_Enter:
case GDK_Return:
handled = handle_enter (edit, event);
break;
}
}
else if (mods == GDK_SHIFT_MASK)
{
switch (keyval)
{
/* TODO TODO stupid X and gtk !!! */
case GDK_ISO_Left_Tab:
case GDK_KP_Tab:
handled = handle_shift_tab (edit, event);
break;
}
}
else if (mods == GDK_CONTROL_MASK)
{
switch (keyval)
{
case GDK_Up:
case GDK_KP_Up:
handled = handle_ctrl_up (edit, event, TRUE);
/* if we scroll, let mouse cursor stay */
obscure = FALSE;
break;
case GDK_Down:
case GDK_KP_Down:
handled = handle_ctrl_up (edit, event, FALSE);
obscure = FALSE;
break;
case GDK_Page_Up:
case GDK_KP_Page_Up:
handled = handle_ctrl_pgup (edit, event, TRUE);
obscure = FALSE;
break;
case GDK_Page_Down:
case GDK_KP_Page_Down:
handled = handle_ctrl_pgup (edit, event, FALSE);
obscure = FALSE;
break;
}
}
else
{
obscure = FALSE;
}
if (obscure && handled)
text_view_obscure_mouse_cursor (text_view);
return handled ||
parent_class()->key_press_event (widget, event);
}
static void text_view_obscure_mouse_cursor (GtkTextView *text_view)
{
if (!text_view->mouse_cursor_obscured)
{
GdkWindow *window =
gtk_text_view_get_window (text_view,
GTK_TEXT_WINDOW_TEXT);
set_invisible_cursor (window);
text_view->mouse_cursor_obscured = TRUE;
}
}
static void set_invisible_cursor (GdkWindow *window)
{
GdkBitmap *empty_bitmap;
GdkCursor *cursor;
GdkColor useless;
char invisible_cursor_bits[] = { 0x0 };
useless.red = useless.green = useless.blue = 0;
useless.pixel = 0;
empty_bitmap =
gdk_bitmap_create_from_data (window,
invisible_cursor_bits,
1, 1);
cursor = gdk_cursor_new_from_pixmap (empty_bitmap,
empty_bitmap,
&useless,
&useless, 0, 0);
gdk_window_set_cursor (window, cursor);
gdk_cursor_unref (cursor);
g_object_unref (empty_bitmap);
}
static gboolean handle_tab (MooEdit *edit,
G_GNUC_UNUSED GdkEventKey *event)
{
GtkTextBuffer *buffer = edit->priv->text_buffer;
GtkTextIter insert, bound, start, end;
gboolean starts_line, insert_last;
int first_line, last_line, i;
if (!edit->priv->tab_indents ||
!gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
return FALSE;
get_iter_at_insert (buffer, &insert);
get_iter_at_selection_bound (buffer, &bound);
insert_last = (gtk_text_iter_compare (&insert, &bound) > 0);
starts_line = gtk_text_iter_starts_line (&start);
first_line = gtk_text_iter_get_line (&start);
last_line = gtk_text_iter_get_line (&end);
if (gtk_text_iter_starts_line (&end))
last_line--;
gtk_text_buffer_begin_user_action (buffer);
for (i = first_line; i <= last_line; ++i)
{
indent_line (edit, &start);
gtk_text_iter_forward_line (&start);
}
if (starts_line)
{
if (insert_last)
{
get_iter_at_selection_bound (buffer, &insert);
gtk_text_iter_set_line_offset (&insert, 0);
move_selection_bound (buffer, &insert);
}
else
{
get_iter_at_insert (buffer, &insert);
gtk_text_iter_set_line_offset (&insert, 0);
move_insert (buffer, &insert);
}
}
gtk_text_buffer_end_user_action (buffer);
return TRUE;
}
static gboolean handle_shift_tab (MooEdit *edit,
G_GNUC_UNUSED GdkEventKey *event)
{
GtkTextBuffer *buffer = edit->priv->text_buffer;
GtkTextIter start, end;
int first_line, last_line, i;
if (!edit->priv->shift_tab_unindents)
return FALSE;
gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
first_line = gtk_text_iter_get_line (&start);
last_line = gtk_text_iter_get_line (&end);
if (gtk_text_iter_starts_line (&end) && first_line != last_line)
last_line--;
gtk_text_buffer_begin_user_action (buffer);
for (i = first_line; i <= last_line; ++i)
{
unindent_line (edit, &start);
gtk_text_iter_forward_line (&start);
}
gtk_text_buffer_end_user_action (buffer);
return TRUE;
}
static void indent_line (MooEdit *edit,
GtkTextIter *iter)
{
gboolean use_spaces;
char *text;
guint len;
use_spaces = gtk_source_view_get_insert_spaces_instead_of_tabs (GTK_SOURCE_VIEW (edit));
if (use_spaces)
{
len = gtk_source_view_get_tabs_width (GTK_SOURCE_VIEW (edit));
text = g_strnfill (len, ' ');
}
else
{
len = 1;
text = g_strdup ("\t");
}
gtk_text_iter_set_line_offset (iter, 0);
gtk_text_buffer_insert (edit->priv->text_buffer, iter,
text, len);
g_free (text);
}
static void unindent_line (MooEdit *edit,
GtkTextIter *iter)
{
GtkTextBuffer *buffer;
GtkTextIter end;
int deleted;
guint tab_width;
gunichar c;
buffer = edit->priv->text_buffer;
tab_width = gtk_source_view_get_tabs_width (GTK_SOURCE_VIEW (edit));
gtk_text_iter_set_line_offset (iter, 0);
end = *iter;
deleted = 0;
while (TRUE)
{
if (gtk_text_iter_ends_line (&end))
break;
c = gtk_text_iter_get_char (&end);
if (c == ' ')
{
gtk_text_iter_forward_char (&end);
deleted += 1;
}
else if (c == '\t')
{
gtk_text_iter_forward_char (&end);
deleted += tab_width;
}
else
{
break;
}
if (deleted >= (int) tab_width)
break;
}
gtk_text_buffer_delete (buffer, iter, &end);
deleted -= tab_width;
if (deleted > 0)
{
char *text = g_strnfill (deleted, ' ');
gtk_text_buffer_insert (buffer, iter, text, deleted);
g_free (text);
}
}
static guint get_text_width_offset (const GtkTextIter *iter,
guint tab_width)
{
GtkTextIter i;
guint offset;
if (gtk_text_iter_starts_line (iter))
return 0;
i = *iter;
gtk_text_iter_set_line_offset (&i, 0);
offset = 0;
while (gtk_text_iter_compare (&i, iter))
{
gunichar c = gtk_text_iter_get_char (&i);
if (c == '\t')
{
guint add = offset - (offset / tab_width) * tab_width;
if (!add)
add = tab_width;
offset += add;
}
else if (g_unichar_iswide (c))
{
offset += 2;
}
else
{
offset += (g_unichar_iswide (c) ? 2 : 1);
}
gtk_text_iter_forward_char (&i);
}
return offset;
}
/* computes offset of start and returns offset or -1 if there are
non-whitespace characters before start */
static int compute_offset (const GtkTextIter *start,
guint tab_width)
{
GtkTextIter iter;
guint offset;
if (gtk_text_iter_starts_line (start))
return 0;
iter = *start;
gtk_text_iter_set_line_offset (&iter, 0);
offset = 0;
while (gtk_text_iter_compare (&iter, start))
{
gunichar c = gtk_text_iter_get_char (&iter);
if (c == ' ')
{
offset += 1;
}
else if (c == '\t')
{
guint add = offset - (offset / tab_width) * tab_width;
if (!add)
add = tab_width;
offset += add;
}
else
{
return -1;
}
gtk_text_iter_forward_char (&iter);
}
return offset;
}
/* computes amount of leading white space on the line containing
start; returns TRUE if line contains some non-whitespace chars */
static gboolean compute_line_indent (const GtkTextIter *line,
guint tab_width,
guint *indent)
{
GtkTextIter iter;
*indent = 0;
iter = *line;
gtk_text_iter_set_line_offset (&iter, 0);
if (gtk_text_iter_ends_line (&iter))
return FALSE;
while (TRUE)
{
gunichar c = gtk_text_iter_get_char (&iter);
if (c == ' ')
{
*indent += 1;
}
else if (c == '\t')
{
guint add = *indent - (*indent / tab_width) * tab_width;
if (!add)
add = tab_width;
*indent += add;
}
else
{
return TRUE;
}
gtk_text_iter_forward_char (&iter);
if (gtk_text_iter_ends_line (&iter))
return FALSE;
}
}
/* computes where cursor should jump when backspace is pressed
<-- result -->
blah blah blah
blah
| offset
*/
static guint compute_next_stop (const GtkTextIter *start,
guint tab_width,
guint offset,
gboolean same_line)
{
GtkTextIter iter;
guint indent;
iter = *start;
gtk_text_iter_set_line_offset (&iter, 0);
if (!same_line)
{
if (gtk_text_iter_is_start (&iter))
return 0;
gtk_text_iter_backward_line (&iter);
}
while (TRUE)
{
if (compute_line_indent (&iter, tab_width, &indent) &&
indent && indent <= offset)
return indent;
if (!gtk_text_iter_backward_line (&iter))
return 0;
}
}
static char *compute_indent_string (guint offset,
guint tab_width,
gboolean use_spaces)
{
if (!offset)
return NULL;
else if (use_spaces)
return g_strnfill (offset, ' ');
else
{
guint num_tabs, num_spaces;
char *string;
num_tabs = offset / tab_width;
num_spaces = offset - num_tabs * tab_width;
string = g_new (char, num_tabs + num_spaces + 1);
if (num_tabs)
memset (string, '\t', num_tabs);
if (num_spaces)
memset (string + num_tabs, ' ', num_spaces);
string[num_tabs + num_spaces] = 0;
return string;
}
}
static gboolean handle_backspace (G_GNUC_UNUSED MooEdit *edit,
G_GNUC_UNUSED GdkEventKey *event)
{
GtkTextBuffer *buffer;
GtkTextIter start, end;
int offset;
guint new_offset;
guint tab_width;
char *insert = NULL;
buffer = edit->priv->text_buffer;
get_iter_at_insert (buffer, &end);
if (!edit->priv->backspace_indents)
return FALSE;
if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL))
return FALSE;
if (gtk_text_iter_starts_line (&end))
return FALSE;
tab_width = gtk_source_view_get_tabs_width (GTK_SOURCE_VIEW (edit));
offset = compute_offset (&end, tab_width);
if (offset < 0)
return FALSE;
if (!offset)
new_offset = 0;
else
new_offset = compute_next_stop (&end, tab_width, offset - 1, FALSE);
gtk_text_buffer_begin_user_action (buffer);
start = end;
gtk_text_iter_set_line_offset (&start, 0);
gtk_text_buffer_delete (buffer, &start, &end);
insert = compute_indent_string (new_offset, tab_width,
gtk_source_view_get_insert_spaces_instead_of_tabs (GTK_SOURCE_VIEW (edit)));
if (insert)
{
gtk_text_buffer_insert (buffer, &start, insert, -1);
g_free (insert);
}
gtk_text_buffer_end_user_action (buffer);
return TRUE;
}
static gboolean handle_enter (MooEdit *edit,
G_GNUC_UNUSED GdkEventKey *event)
{
GtkTextBuffer *buffer;
GtkTextIter start, end;
gboolean has_selection;
guint indent, tab_width;
char *indent_string;
if (!edit->priv->auto_indent)
return FALSE;
buffer = edit->priv->text_buffer;
has_selection =
gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
tab_width = gtk_source_view_get_tabs_width (GTK_SOURCE_VIEW (edit));
indent = compute_next_stop (&start, tab_width,
get_text_width_offset (&start, tab_width),
TRUE);
indent_string = compute_indent_string (indent, tab_width,
gtk_source_view_get_insert_spaces_instead_of_tabs (GTK_SOURCE_VIEW (edit)));
gtk_text_buffer_begin_user_action (buffer);
if (has_selection)
gtk_text_buffer_delete (buffer, &start, &end);
gtk_text_buffer_insert (buffer, &start, "\n", 1);
if (indent_string)
{
gtk_text_buffer_insert (buffer, &start, indent_string, -1);
g_free (indent_string);
}
gtk_text_buffer_end_user_action (buffer);
gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (edit),
gtk_text_buffer_get_insert (buffer));
return TRUE;
}
static gboolean handle_ctrl_up (MooEdit *edit,
G_GNUC_UNUSED GdkEventKey *event,
gboolean up)
{
GtkTextView *text_view;
GtkAdjustment *adjustment;
double value;
if (!edit->priv->ctrl_up_down_scrolls)
return FALSE;
text_view = GTK_TEXT_VIEW (edit);
adjustment = text_view->vadjustment;
if (!adjustment)
return FALSE;
if (up)
{
value = adjustment->value - adjustment->step_increment;
if (value < adjustment->lower)
value = adjustment->lower;
gtk_adjustment_set_value (adjustment, value);
}
else
{
value = adjustment->value + adjustment->step_increment;
if (value > adjustment->upper - adjustment->page_size)
value = adjustment->upper - adjustment->page_size;
gtk_adjustment_set_value (adjustment, value);
}
return TRUE;
}
static gboolean handle_ctrl_pgup (MooEdit *edit,
G_GNUC_UNUSED GdkEventKey *event,
gboolean up)
{
GtkTextView *text_view;
GtkAdjustment *adjustment;
double value;
if (!edit->priv->ctrl_page_up_down_scrolls)
return FALSE;
text_view = GTK_TEXT_VIEW (edit);
adjustment = text_view->vadjustment;
if (!adjustment)
return FALSE;
if (up)
{
value = adjustment->value - adjustment->page_increment;
if (value < adjustment->lower)
value = adjustment->lower;
gtk_adjustment_set_value (adjustment, value);
}
else
{
value = adjustment->value + adjustment->page_increment;
if (value > adjustment->upper - adjustment->page_size)
value = adjustment->upper - adjustment->page_size;
gtk_adjustment_set_value (adjustment, value);
}
return TRUE;
}