/* * mooterm/mootermselection.c * * Copyright (C) 2004-2007 by Yevgen Muntyan * * 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 MOOTERM_COMPILATION #include "mooterm/mooterm-text.h" #include "mooterm/mooterm-private.h" #include "mooterm/mootermbuffer-private.h" #include "mooterm/mooterm-selection.h" #include "mooterm/mootermline-private.h" #include "mooutils/mooutils-misc.h" typedef struct { MooTermIter start; MooTermIter end; } Segment; typedef enum { MOO_TERM_SELECT_CHARS, MOO_TERM_SELECT_WORDS, MOO_TERM_SELECT_LINES } MooTermSelectionType; #define GET_SELECTION(term__) ((Segment*)(term__)->priv->selection) #define ITER_TOTAL_HEIGHT(iter__) ((int)((iter__)->term->priv->height + buf_scrollback ((iter__)->buffer))) #define ITER_TERM(iter__) ((iter__)->term) #define ITER_ROW(iter__) ((iter__)->row) #define ITER_COL(iter__) ((iter__)->col) #define ITER_WIDTH(iter__) ((iter__)->width) #define FILL_ITER(iter__,term__,row__,col__) \ (iter__)->term = term__; \ (iter__)->buffer = term__->priv->buffer; \ (iter__)->line = buf_line ((iter__)->buffer, row__); \ (iter__)->row = row__; \ (iter__)->col = col__; \ (iter__)->width = term__->priv->width; \ CHECK_ITER(iter__) #ifdef MOO_DEBUG_ENABLED static void CHECK_ITER (const MooTermIter *iter) { g_assert (iter->term != NULL); g_assert ((int)iter->term->priv->width == iter->width); g_assert (iter->row >= 0); g_assert (iter->row < ITER_TOTAL_HEIGHT(iter)); g_assert (iter->col >= 0); g_assert (iter->col <= iter->width); } static void CHECK_SEGMENT (const Segment *segment) { CHECK_ITER (&segment->start); CHECK_ITER (&segment->end); } #else #define CHECK_ITER(iter__) #define CHECK_SEGMENT(segment__) #endif static gboolean _moo_term_extend_selection (MooTerm *term, MooTermSelectionType type, MooTermIter *end, MooTermIter *start); static void _moo_term_get_visible_rect (MooTerm *term, GdkRectangle *rect); static void _moo_term_iter_order (MooTermIter *first, MooTermIter *second); static void _moo_term_place_selection_end (MooTerm *term, const MooTermIter *where); static gboolean _moo_term_selection_empty (MooTerm *term); static void get_start_iter (MooTerm *term, MooTermIter *iter); static void get_end_iter (MooTerm *term, MooTermIter *iter); static int iter_cmp (const MooTermIter *first, const MooTermIter *second); static void iter_set_start (MooTermIter *iter); static char *segment_get_text (Segment *segment); static void invalidate_segment (Segment *segments, guint num); static void middle_button_click (MooTerm *term); static void right_button_click (MooTerm *term, GdkEventButton *event); #define MOUSE_STUFF(term__) (&(term__)->priv->mouse_stuff) gpointer _moo_term_selection_new (MooTerm *term) { Segment *sel = g_new0 (Segment, 1); FILL_ITER (&sel->start, term, 0, 0); FILL_ITER (&sel->end, term, 0, 0); return sel; } void _moo_term_selection_invalidate (MooTerm *term) { Segment *sel = term->priv->selection; invalidate_segment (sel, 1); FILL_ITER (&sel->start, term, 0, 0); FILL_ITER (&sel->end, term, 0, 0); _moo_term_release_selection (term); } static gboolean segment_empty (Segment *s) { CHECK_SEGMENT (s); return !iter_cmp (&s->start, &s->end); } static int segment_sym_diff (Segment *s1, Segment *s2, Segment *result) { Segment *left = result; Segment *right = &result[1]; CHECK_SEGMENT (s1); CHECK_SEGMENT (s2); if (segment_empty (s1)) { if (segment_empty (s2)) { return 0; } else { *left = *s2; CHECK_SEGMENT (left); return 1; } } else if (segment_empty (s2)) { *left = *s1; CHECK_SEGMENT (left); return 1; } else { Segment tmp; MooTermIter itmp; *left = *s1; *right = *s2; _moo_term_iter_order (&left->start, &left->end); _moo_term_iter_order (&right->start, &right->end); switch (iter_cmp (&left->start, &right->start)) { case 0: switch (iter_cmp (&left->end, &right->end)) { case 0: return 0; case 1: left->start = right->end; CHECK_SEGMENT (left); return 1; case -1: left->start = left->end; left->end = right->end; CHECK_SEGMENT (left); return 1; } case 1: tmp = *left; *left = *right; *right = tmp; break; } switch (iter_cmp (&left->end, &right->start)) { case 0: left->end = right->end; CHECK_SEGMENT (left); return 1; case -1: CHECK_SEGMENT (left); CHECK_SEGMENT (right); return 2; case 1: itmp = left->end; left->end = right->start; right->start = itmp; CHECK_SEGMENT (left); CHECK_SEGMENT (right); return 2; } } g_return_val_if_reached (0); } static void invalidate_segment (Segment *segm, guint num) { MooTerm *term; int top_line; guint i; if (!num) return; term = ITER_TERM (&segm->start); top_line = term_top_line (term); for (i = 0; i < num; ++i) { MooTermIter start = segm[i].start; MooTermIter end = segm[i].end; GdkRectangle rect; _moo_term_iter_order (&start, &end); if (ITER_ROW (&start) < ITER_ROW (&end)) { if (ITER_COL (&start) < (int)term->priv->width) { rect.x = ITER_COL (&start); rect.width = term->priv->width - rect.x; rect.y = ITER_ROW (&start) - top_line; rect.height = 1; _moo_term_invalidate_screen_rect (term, &rect); } if (ITER_ROW (&start) + 1 < ITER_ROW (&end)) { rect.x = 0; rect.width = term->priv->width; rect.y = ITER_ROW (&start) + 1 - top_line; rect.height = ITER_ROW (&end) - ITER_ROW (&start) - 1; _moo_term_invalidate_screen_rect (term, &rect); } if (ITER_COL (&end) > 0) { rect.x = 0; rect.width = ITER_COL (&end); rect.y = ITER_ROW (&end) - top_line; rect.height = 1; _moo_term_invalidate_screen_rect (term, &rect); } } else { if (ITER_COL (&start) < ITER_COL (&end)) { rect.x = ITER_COL (&start); rect.width = ITER_COL (&end) - ITER_COL (&start); rect.y = ITER_ROW (&start) - top_line; rect.height = 1; _moo_term_invalidate_screen_rect (term, &rect); } } } } static void _moo_term_select_range (MooTerm *term, const MooTermIter *start, const MooTermIter *end) { Segment diff[2]; Segment new_sel; Segment old_selection; gboolean inv = FALSE; CHECK_ITER (start); CHECK_ITER (end); old_selection = *GET_SELECTION (ITER_TERM (start)); CHECK_SEGMENT (&old_selection); new_sel.start = *start; new_sel.end = *end; switch (iter_cmp (start, end)) { case 0: iter_set_start (&new_sel.start); iter_set_start (&new_sel.end); break; case 1: inv = TRUE; break; } _moo_term_iter_order (&new_sel.start, &new_sel.end); if (ITER_COL(&new_sel.start) == ITER_WIDTH(&new_sel.start)) { g_assert (ITER_ROW(&new_sel.start) < ITER_TOTAL_HEIGHT(&new_sel.start)); if (ITER_ROW(&new_sel.start) + 1 < ITER_TOTAL_HEIGHT(&new_sel.start)) { ITER_ROW(&new_sel.start)++; ITER_COL(&new_sel.start) = 0; } } if (ITER_COL(&new_sel.end) == 0 && ITER_ROW(&new_sel.end) != 0) { ITER_ROW(&new_sel.end)--; ITER_COL(&new_sel.end) = ITER_WIDTH(&new_sel.start); } if (inv) _moo_term_iter_order (&new_sel.end, &new_sel.start); CHECK_SEGMENT (&new_sel); *GET_SELECTION (ITER_TERM (start)) = new_sel; invalidate_segment (diff, segment_sym_diff (&new_sel, &old_selection, diff)); if (_moo_term_selection_empty (term)) _moo_term_release_selection (term); else _moo_term_grab_selection (term); } void _moo_term_clear_selection (MooTerm *term) { MooTermIter iter; g_return_if_fail (MOO_IS_TERM (term)); get_start_iter (term, &iter); _moo_term_select_range (term, &iter, &iter); } static void get_start_iter (MooTerm *term, MooTermIter *iter) { FILL_ITER (iter, term, 0, 0); } static void get_end_iter (MooTerm *term, MooTermIter *iter) { int width = term->priv->width; int total_height = term->priv->height + buf_scrollback (term->priv->buffer); FILL_ITER (iter, term, total_height - 1, width); } /**************************************************************************/ /* mouse functionality */ static void start_drag_scroll (MooTerm *term); static void stop_drag_scroll (MooTerm *term); static gboolean drag_scroll_timeout_func (MooTerm *term); static void scroll_selection_end_onscreen (MooTerm *term); static void start_selection_dnd (MooTerm *term, const MooTermIter *iter, GdkEventMotion *event); #define SCROLL_TIMEOUT 100 gboolean _moo_term_button_press (GtkWidget *widget, GdkEventButton *event) { MooTermIter iter; int x, y; MooTerm *term = MOO_TERM (widget); moo_term_set_pointer_visible (term, TRUE); if (!GTK_WIDGET_HAS_FOCUS (widget)) gtk_widget_grab_focus (widget); moo_term_window_to_buffer_coords (term, event->x, event->y, &x, &y); moo_term_get_iter_at_location (term, &iter, x, y); if (event->type == GDK_BUTTON_PRESS) { if (event->button == 1) { #if 0 MooTermIter sel_start, sel_end; #endif MOUSE_STUFF(term)->drag_button = GDK_BUTTON_PRESS; MOUSE_STUFF(term)->drag_start_x = x; MOUSE_STUFF(term)->drag_start_y = y; #if 0 /* TODO: implement drag'n'drop */ /* if clicked in selected, start drag */ if (moo_term_get_selection_bounds (term, &sel_start, &sel_end)) { moo_term_iter_order (&sel_start, &sel_end); if (_moo_term_iter_in_range (&iter, &sel_start, &sel_end) && FALSE) { /* clicked inside of selection, * set up drag and return */ MOUSE_STUFF(term)->drag_type = DRAG_DRAG; return TRUE; } } #endif /* otherwise, clear selection, and position cursor at clicked point */ if (event->state & GDK_SHIFT_MASK) _moo_term_place_selection_end (term, &iter); else _moo_term_select_range (term, &iter, &iter); MOUSE_STUFF(term)->drag_type = DRAG_SELECT; } else if (event->button == 2) { middle_button_click (term); } else if (event->button == 3) { right_button_click (term, event); } else { g_warning ("got button %u in button_press callback", event->button); } } else if (event->type == GDK_2BUTTON_PRESS && event->button == 1) { MooTermIter bound; if (moo_term_get_selection_bounds (term, NULL, NULL)) { /* it may happen sometimes, if you click fast enough */ _moo_term_select_range (term, &iter, &iter); } MOUSE_STUFF(term)->drag_button = GDK_2BUTTON_PRESS; MOUSE_STUFF(term)->drag_start_x = x; MOUSE_STUFF(term)->drag_start_y = y; MOUSE_STUFF(term)->drag_type = DRAG_SELECT; bound = iter; if (_moo_term_extend_selection (term, MOO_TERM_SELECT_WORDS, &iter, &bound)) _moo_term_select_range (term, &bound, &iter); } else if (event->type == GDK_3BUTTON_PRESS && event->button == 1) { MooTermIter bound; MOUSE_STUFF(term)->drag_button = GDK_3BUTTON_PRESS; MOUSE_STUFF(term)->drag_start_x = x; MOUSE_STUFF(term)->drag_start_y = y; MOUSE_STUFF(term)->drag_type = DRAG_SELECT; bound = iter; if (_moo_term_extend_selection (term, MOO_TERM_SELECT_LINES, &iter, &bound)) _moo_term_select_range (term, &bound, &iter); } return TRUE; } gboolean _moo_term_button_release (GtkWidget *widget, G_GNUC_UNUSED GdkEventButton *event) { MooTerm *term = MOO_TERM (widget); MooTermIter iter; moo_term_set_pointer_visible (term, TRUE); switch (MOUSE_STUFF(term)->drag_type) { case DRAG_NONE: /* it may happen after right-click, or clicking outside * of widget or something like that * everything has been taken care of, so do nothing */ break; case DRAG_SELECT: /* everything should be done already in button_press and * motion_notify handlers */ stop_drag_scroll (term); break; case DRAG_DRAG: /* if we were really dragging, drop it * otherwise, it was just a single click in selected text */ g_assert (!MOUSE_STUFF(term)->drag_moved); /* parent should handle drag */ /* TODO ??? */ moo_term_get_iter_at_location (term, &iter, MOUSE_STUFF(term)->drag_start_x, MOUSE_STUFF(term)->drag_start_y); _moo_term_select_range (term, &iter, &iter); break; default: g_assert_not_reached (); } MOUSE_STUFF(term)->drag_moved = FALSE; MOUSE_STUFF(term)->drag_type = DRAG_NONE; MOUSE_STUFF(term)->drag_start_x = -1; MOUSE_STUFF(term)->drag_start_y = -1; MOUSE_STUFF(term)->drag_button = GDK_BUTTON_RELEASE; return TRUE; } #define OUTSIDE(x,y,rect) \ ((x) < (rect).x || \ (y) < (rect).y || \ (x) >= (rect).x + (rect).width || \ (y) >= (rect).y + (rect).height) gboolean _moo_term_motion_notify (GtkWidget *widget, GdkEventMotion *event) { MooTerm *term = MOO_TERM (widget); int x, y, event_x, event_y; MooTermIter iter; moo_term_set_pointer_visible (MOO_TERM (widget), TRUE); if (!MOUSE_STUFF(term)->drag_type) return FALSE; if (event->is_hint) { gdk_window_get_pointer (event->window, &event_x, &event_y, NULL); } else { event_x = (int) event->x; event_y = (int) event->y; } moo_term_window_to_buffer_coords (term, event_x, event_y, &x, &y); moo_term_get_iter_at_location (term, &iter, x, y); if (MOUSE_STUFF(term)->drag_type == DRAG_SELECT) { GdkRectangle rect; MooTermIter start; MooTermSelectionType t; MOUSE_STUFF(term)->drag_moved = TRUE; _moo_term_get_visible_rect (term, &rect); if (OUTSIDE (x, y, rect)) { start_drag_scroll (term); return TRUE; } else { stop_drag_scroll (term); } moo_term_get_iter_at_location (term, &start, MOUSE_STUFF(term)->drag_start_x, MOUSE_STUFF(term)->drag_start_y); switch (MOUSE_STUFF(term)->drag_button) { case GDK_BUTTON_PRESS: t = MOO_TERM_SELECT_CHARS; break; case GDK_2BUTTON_PRESS: t = MOO_TERM_SELECT_WORDS; break; default: t = MOO_TERM_SELECT_LINES; } if (_moo_term_extend_selection (term, t, &iter, &start)) _moo_term_select_range (term, &start, &iter); else _moo_term_select_range (term, &iter, &iter); } else { /* this piece is from gtktextview.c */ gdk_window_get_pointer (widget->window, &x, &y, NULL); if (gtk_drag_check_threshold (widget, MOUSE_STUFF(term)->drag_start_x, MOUSE_STUFF(term)->drag_start_y, x, y)) { int buffer_x, buffer_y; moo_term_window_to_buffer_coords (term, MOUSE_STUFF(term)->drag_start_x, MOUSE_STUFF(term)->drag_start_y, &buffer_x, &buffer_y); moo_term_get_iter_at_location (term, &iter, buffer_x, buffer_y); MOUSE_STUFF(term)->drag_type = DRAG_NONE; start_selection_dnd (term, &iter, event); } } return TRUE; } static void start_drag_scroll (MooTerm *term) { if (!MOUSE_STUFF(term)->drag_scroll_timeout) MOUSE_STUFF(term)->drag_scroll_timeout = _moo_timeout_add (SCROLL_TIMEOUT, (GSourceFunc) drag_scroll_timeout_func, term); drag_scroll_timeout_func (term); } static void stop_drag_scroll (MooTerm *term) { if (MOUSE_STUFF(term)->drag_scroll_timeout) g_source_remove (MOUSE_STUFF(term)->drag_scroll_timeout); MOUSE_STUFF(term)->drag_scroll_timeout = 0; } static gboolean drag_scroll_timeout_func (MooTerm *term) { int x, y, px, py; MooTermIter iter; MooTermIter start; MooTermSelectionType t; g_assert (MOUSE_STUFF(term)->drag_type == DRAG_SELECT); gdk_window_get_pointer (GTK_WIDGET(term)->window, &px, &py, NULL); moo_term_window_to_buffer_coords (term, px, py, &x, &y); moo_term_get_iter_at_location (term, &iter, x, y); moo_term_get_iter_at_location (term, &start, MOUSE_STUFF(term)->drag_start_x, MOUSE_STUFF(term)->drag_start_y); switch (MOUSE_STUFF(term)->drag_button) { case GDK_BUTTON_PRESS: t = MOO_TERM_SELECT_CHARS; break; case GDK_2BUTTON_PRESS: t = MOO_TERM_SELECT_WORDS; break; default: t = MOO_TERM_SELECT_LINES; } if (_moo_term_extend_selection (term, t, &iter, &start)) _moo_term_select_range (term, &start, &iter); else _moo_term_select_range (term, &iter, &iter); scroll_selection_end_onscreen (term); return TRUE; } static void scroll_selection_end_onscreen (MooTerm *term) { MooTermIter iter = GET_SELECTION(term)->end; guint top_line = term_top_line (term); if (ITER_ROW(&iter) < (int)top_line) moo_term_scroll_lines (term, ITER_ROW(&iter) - (int)top_line); else if (ITER_ROW(&iter) >= (int)top_line + (int)term->priv->height) moo_term_scroll_lines (term, ITER_ROW(&iter) + 1 - (int)top_line - (int)term->priv->height); } static void start_selection_dnd (G_GNUC_UNUSED MooTerm *term, G_GNUC_UNUSED const MooTermIter *iter, G_GNUC_UNUSED GdkEventMotion *event) { g_return_if_reached (); } static gboolean _moo_term_selection_empty (MooTerm *term) { return segment_empty (GET_SELECTION (term)); } gboolean _moo_term_get_selected_cells (MooTerm *term, guint *left_row, guint *left_col, guint *right_row, guint *right_col) { Segment *selection = GET_SELECTION (term); if (segment_empty (selection)) { return FALSE; } else { MooTermIter start = selection->start; MooTermIter end = selection->end; _moo_term_iter_order (&start, &end); if (left_row) *left_row = ITER_ROW (&start); if (left_col) *left_col = ITER_COL (&start); if (right_row) *right_row = ITER_ROW (&end); if (right_col) *right_col = ITER_COL (&end); return TRUE; } } void _moo_term_selection_clear (MooTerm *term) { MooTermIter i; FILL_ITER (&i, term, 0, 0); _moo_term_select_range (term, &i, &i); } static void middle_button_click (MooTerm *term) { moo_term_paste_clipboard (term, GDK_SELECTION_PRIMARY); } static void right_button_click (MooTerm *term, GdkEventButton *event) { _moo_term_do_popup_menu (term, event); } void moo_term_window_to_buffer_coords (MooTerm *term, int window_x, int window_y, int *buffer_x, int *buffer_y) { g_return_if_fail (MOO_IS_TERM (term)); g_return_if_fail (buffer_x != NULL && buffer_y != NULL); *buffer_x = window_x; *buffer_y = window_y + (term_top_line (term) * term_char_height (term)); } void moo_term_get_iter_at_location (MooTerm *term, MooTermIter *iter, int x, int y) { int char_width = term_char_width (term); int char_height = term_char_height (term); int scrollback = buf_scrollback (term->priv->buffer); g_return_if_fail (iter != NULL); y /= char_height; x = CLAMP (x, 0, (int)term->priv->width * char_width - 1); x = (x % char_width > char_width / 2) ? x / char_width + 1 : x / char_width; if (y < 0) { y = 0; x = 0; } else if (y > scrollback + (int)term->priv->height - 1) { y = scrollback + (int)term->priv->height - 1; x = term->priv->width; } FILL_ITER (iter, term, y, x); } void moo_term_get_iter_at_pos (MooTerm *term, MooTermIter *iter, int x, int y) { int char_width = term_char_width (term); int char_height = term_char_height (term); int scrollback = buf_scrollback (term->priv->buffer); g_return_if_fail (iter != NULL); y /= char_height; x /= char_width; x = CLAMP (x, 0, (int)term->priv->width); if (y < 0) { y = 0; x = 0; } else if (y > scrollback + (int)term->priv->height - 1) { y = scrollback + (int)term->priv->height - 1; x = term->priv->width; } FILL_ITER (iter, term, y, x); } gboolean moo_term_get_selection_bounds (MooTerm *term, MooTermIter *sel_start, MooTermIter *sel_end) { Segment *selection = term->priv->selection; g_return_val_if_fail (MOO_IS_TERM (term), FALSE); selection = term->priv->selection; if (sel_start) *sel_start = selection->start; if (sel_end) *sel_end = selection->end; return !_moo_term_selection_empty (term); } static int iter_cmp (const MooTermIter *first, const MooTermIter *second) { g_assert (ITER_TERM (first) == ITER_TERM (second)); if (ITER_ROW (first) < ITER_ROW (second)) return -1; else if (ITER_ROW (first) > ITER_ROW (second)) return 1; else if (ITER_COL (first) < ITER_COL (second)) return -1; else if (ITER_COL (first) > ITER_COL (second)) return 1; else return 0; } static void iter_set_start (MooTermIter *iter) { ITER_ROW (iter) = ITER_COL (iter) = 0; CHECK_ITER (iter); } static void _moo_term_iter_order (MooTermIter *first, MooTermIter *second) { if (iter_cmp (first, second) > 0) { MooTermIter tmp = *first; *first = *second; *second = tmp; } } #if 0 static gboolean _moo_term_iter_in_range (const MooTermIter *iter, const MooTermIter *start, const MooTermIter *end) { CHECK_ITER (iter); CHECK_ITER (start); CHECK_ITER (end); return iter_cmp (start, iter) <= 0 && iter_cmp (iter, end) <= 0; } #endif static void _moo_term_place_selection_end (MooTerm *term, const MooTermIter *where) { CHECK_ITER (where); _moo_term_select_range (term, &GET_SELECTION(term)->start, where); } static void _moo_term_get_visible_rect (MooTerm *term, GdkRectangle *rect) { int char_height = term_char_height (term); rect->x = 0; rect->width = term->priv->width * term_char_width (term); rect->y = term_top_line (term) * char_height; rect->height = term->priv->height * char_height; } gboolean _moo_term_cell_selected (MooTerm *term, guint row, guint col) { MooTermIter start, end, iter; if (moo_term_get_selection_bounds (term, &start, &end)) { _moo_term_iter_order (&start, &end); FILL_ITER (&iter, term, row, col); return iter_cmp (&start, &iter) <= 0 && iter_cmp (&iter, &end) < 0; } else { return FALSE; } } int _moo_term_row_selected (MooTerm *term, guint row) { guint l_row, l_col, r_row, r_col; if (_moo_term_get_selected_cells (term, &l_row, &l_col, &r_row, &r_col)) { if (row < l_row || row > r_row) return NOT_SELECTED; else if (l_row < row && row < r_row) return FULL_SELECTED; else return PART_SELECTED; } else { return NOT_SELECTED; } } char *moo_term_get_selection (MooTerm *term) { return segment_get_text (term->priv->selection); } static int char_class (MooTerm *term, const MooTermIter *iter); static gboolean iter_ends_line (const MooTermIter *iter); static gboolean iter_starts_line (const MooTermIter *iter); static void iter_forward_char (MooTermIter *iter); static void iter_backward_char (MooTermIter *iter); static void iter_set_line_offset (MooTermIter *iter, guint offset); static gboolean iter_forward_line (MooTermIter *iter); static gboolean is_space (const MooTermIter *iter); static gboolean is_word_char (const MooTermIter *iter); static gunichar iter_get_char (const MooTermIter *iter); static gboolean _moo_term_extend_selection (MooTerm *term, MooTermSelectionType type, MooTermIter *end, MooTermIter *start) { int order = iter_cmp (start, end); CHECK_ITER (start); CHECK_ITER (end); if (type == MOO_TERM_SELECT_CHARS) { return order; } if (order > 0) { MooTermIter *tmp = start; start = end; end = tmp; } if (type == MOO_TERM_SELECT_WORDS) { int ch_class; ch_class = char_class (term, end); while (!iter_ends_line (end) && char_class (term, end) == ch_class) { iter_forward_char (end); } ch_class = char_class (term, start); while (!iter_starts_line (start) && char_class (term, start) == ch_class) { iter_backward_char (start); } if (char_class (term, start) != ch_class) iter_forward_char (start); return iter_cmp (start, end); } if (type == MOO_TERM_SELECT_LINES) { iter_set_line_offset (start, 0); iter_forward_line (end); return iter_cmp (start, end); } g_return_val_if_reached (FALSE); } /* TODO */ static int char_class (MooTerm *term, const MooTermIter *iter) { if (iter_ends_line (iter)) return -1; else if (is_space (iter)) return 0; else if (is_word_char (iter)) return 1; else return 2; } static gboolean iter_ends_line (const MooTermIter *iter) { CHECK_ITER (iter); return iter->col == (int)iter->term->priv->width; } static gboolean iter_starts_line (const MooTermIter *iter) { CHECK_ITER (iter); return iter->col == 0; } static void iter_forward_char (MooTermIter *iter) { MooTerm *term = iter->term; int width = term->priv->width; int total_height = term->priv->height + buf_scrollback (term->priv->buffer); g_return_if_fail (iter->row < total_height || iter->col < width); if (iter->col < width) { iter->col++; } else { iter->col = 0; iter->row++; } CHECK_ITER (iter); } static void iter_backward_char (MooTermIter *iter) { MooTerm *term = iter->term; int width = term->priv->width; g_return_if_fail (iter->row > 0 || iter->col > 0); if (iter->col > 0) { iter->col--; } else { iter->row--; iter->col = width; } CHECK_ITER (iter); } static void iter_set_line_offset (MooTermIter *iter, guint offset) { g_return_if_fail (offset <= iter->term->priv->width); iter->col = offset; CHECK_ITER (iter); } static gboolean iter_forward_line (MooTermIter *iter) { MooTerm *term = iter->term; int width = term->priv->width; int total_height = term->priv->height + buf_scrollback (term->priv->buffer); if (iter->row == total_height - 1) { iter->col = width; CHECK_ITER (iter); return FALSE; } else { iter->col = 0; iter->row++; CHECK_ITER (iter); return TRUE; } } static gboolean is_space (const MooTermIter *iter) { gunichar c = iter_get_char (iter); g_return_val_if_fail (c != 0, FALSE); return g_unichar_isspace (c); } static gboolean is_word_char (const MooTermIter *iter) { gunichar c = iter_get_char (iter); g_return_val_if_fail (c != 0, FALSE); return g_unichar_isalnum (c) || c == '_'; } static gunichar iter_get_char (const MooTermIter *iter) { MooTermLine *line = buf_line (iter->term->priv->buffer, iter->row); return _moo_term_line_get_char (line, iter->col); } static char* segment_get_text (Segment *segment) { MooTerm *term = ITER_TERM (&segment->start); MooTermBuffer *buf = term->priv->buffer; MooTermIter start = segment->start; MooTermIter end = segment->end; GString *text; MooTermLine *line; int width = term->priv->width; int i, j; _moo_term_iter_order (&start, &end); if (!iter_cmp (&start, &end)) return NULL; text = g_string_new (""); if (ITER_ROW(&start) == ITER_ROW(&end)) { line = buf_line (buf, ITER_ROW(&start)); for (j = ITER_COL(&start); j < ITER_COL(&end) && j < (int) _moo_term_line_len (line); ++j) g_string_append_unichar (text, _moo_term_line_get_char (line, j)); if (iter_ends_line (&end) && !_moo_term_line_wrapped (line)) g_string_append_c (text, '\n'); } else { if (ITER_COL(&start) < width) { line = buf_line (buf, ITER_ROW(&start)); for (j = ITER_COL(&start); j < (int) _moo_term_line_len (line); ++j) g_string_append_unichar (text, _moo_term_line_get_char (line, j)); if (!_moo_term_line_wrapped (line)) g_string_append_c (text, '\n'); } for (i = ITER_ROW(&start) + 1; i < ITER_ROW(&end); ++i) { line = buf_line (buf, i); for (j = 0; j < (int) _moo_term_line_len (line); ++j) g_string_append_unichar (text, _moo_term_line_get_char (line, j)); if (!_moo_term_line_wrapped (line)) g_string_append_c (text, '\n'); } if (ITER_COL(&end) > 0) { line = buf_line (buf, ITER_ROW(&end)); for (j = 0; j < ITER_COL(&end) && j < (int) _moo_term_line_len (line); ++j) g_string_append_unichar (text, _moo_term_line_get_char (line, j)); if (iter_ends_line (&end) && !_moo_term_line_wrapped (line)) g_string_append_c (text, '\n'); } } return g_string_free (text, FALSE); } void moo_term_select_all (MooTerm *term) { MooTermIter start, end; get_start_iter (term, &start); get_end_iter (term, &end); _moo_term_select_range (term, &start, &end); } char *moo_term_get_content (MooTerm *term) { Segment segm; get_start_iter (term, &segm.start); get_end_iter (term, &segm.end); return segment_get_text (&segm); } char* moo_term_get_text (MooTermIter *start, MooTermIter *end) { /* XXX check iters */ Segment segm; segm.start = *start; segm.end = *end; return segment_get_text (&segm); } guint moo_term_get_line_count (MooTerm *term) { g_return_val_if_fail (MOO_IS_TERM (term), 0); return term->priv->height + buf_scrollback (term->priv->primary_buffer); } MooTermLine* moo_term_get_line (MooTerm *term, guint line_no) { g_return_val_if_fail (MOO_IS_TERM (term), NULL); g_return_val_if_fail (line_no < moo_term_get_line_count (term), NULL); return buf_line (term->priv->primary_buffer, line_no); } gboolean moo_term_get_iter_at_line (MooTerm *term, MooTermIter *iter, guint line) { g_return_val_if_fail (iter != NULL, FALSE); g_return_val_if_fail (MOO_IS_TERM (term), FALSE); if (line >= moo_term_get_line_count (term)) return FALSE; FILL_ITER (iter, term, line, 0); return TRUE; } gboolean moo_term_get_iter_at_line_offset (MooTerm *term, MooTermIter *iter, guint line_no, guint offset) { MooTermLine *line; g_return_val_if_fail (iter != NULL, FALSE); g_return_val_if_fail (MOO_IS_TERM (term), FALSE); if (line_no >= moo_term_get_line_count (term)) return FALSE; line = buf_line (term->priv->buffer, line_no); if (offset > _moo_term_line_width (line)) return FALSE; FILL_ITER (iter, term, line_no, offset); return TRUE; } void moo_term_get_iter_at_cursor (MooTerm *term, MooTermIter *iter) { g_return_if_fail (MOO_IS_TERM (term)); g_return_if_fail (iter != NULL); FILL_ITER (iter, term, buf_cursor_row_abs (term->priv->buffer), buf_cursor_col_display (term->priv->buffer)); } void moo_term_iter_forward_to_line_end (MooTermIter *iter) { MooTerm *term; g_return_if_fail (iter != NULL); term = ITER_TERM (iter); g_return_if_fail (MOO_IS_TERM (term)); ITER_COL (iter) = term->priv->width; } void moo_term_apply_tag (MooTerm *term, MooTermTag *tag, MooTermIter *start, MooTermIter *end) { MooTermBuffer *buf; MooTermLine *line; int i; Segment inv; g_return_if_fail (MOO_IS_TERM (term)); g_return_if_fail (MOO_IS_TERM_TAG (tag)); g_return_if_fail (start != NULL && end != NULL); g_return_if_fail (ITER_TERM(start) == term && ITER_TERM(end) == term); _moo_term_iter_order (start, end); buf = term->priv->primary_buffer; if (ITER_ROW (start) == ITER_ROW (end)) { line = buf_line (buf, ITER_ROW (start)); _moo_term_line_apply_tag (line, tag, ITER_COL (start), ITER_COL (end) - ITER_COL (start)); } else { line = buf_line (buf, ITER_ROW (start)); _moo_term_line_apply_tag (line, tag, ITER_COL (start), _moo_term_line_width (line) - ITER_COL (start)); if (ITER_COL (end)) { line = buf_line (buf, ITER_ROW (end)); _moo_term_line_apply_tag (line, tag, 0, ITER_COL (end)); } if (ITER_ROW (start) + 1 < ITER_ROW (end)) { for (i = ITER_ROW (start) + 1; i < ITER_ROW (end); ++i) { line = buf_line (buf, i); _moo_term_line_apply_tag (line, tag, 0, _moo_term_line_width (line)); } } } inv.start = *start; inv.end = *end; invalidate_segment (&inv, 1); } void moo_term_remove_tag (MooTerm *term, MooTermTag *tag, MooTermIter *start, MooTermIter *end) { MooTermBuffer *buf; MooTermLine *line; Segment inv; int i; g_return_if_fail (MOO_IS_TERM (term)); g_return_if_fail (MOO_IS_TERM_TAG (tag)); g_return_if_fail (start != NULL && end != NULL); g_return_if_fail (ITER_TERM(start) == term && ITER_TERM(end) == term); _moo_term_iter_order (start, end); buf = term->priv->primary_buffer; if (ITER_ROW (start) == ITER_ROW (end)) { line = buf_line (buf, ITER_ROW (start)); _moo_term_line_remove_tag (line, tag, ITER_COL (start), ITER_COL (end) - ITER_COL (start)); } else { line = buf_line (buf, ITER_ROW (start)); _moo_term_line_remove_tag (line, tag, ITER_COL (start), _moo_term_line_width (line) - ITER_COL (start)); line = buf_line (buf, ITER_ROW (end)); _moo_term_line_remove_tag (line, tag, 0, ITER_COL (end)); if (ITER_ROW (start) + 1 < ITER_ROW (end)) { for (i = ITER_ROW (start) + 1; i < ITER_ROW (end); ++i) { line = buf_line (buf, i); _moo_term_line_remove_tag (line, tag, 0, _moo_term_line_width (line)); } } } inv.start = *start; inv.end = *end; invalidate_segment (&inv, 1); } gboolean moo_term_iter_has_tag (MooTermIter *iter, MooTermTag *tag) { MooTermBuffer *buf; MooTerm *term; g_return_val_if_fail (iter != NULL, FALSE); g_return_val_if_fail (MOO_IS_TERM (ITER_TERM (iter)), FALSE); g_return_val_if_fail (MOO_IS_TERM_TAG (tag), FALSE); term = ITER_TERM (iter); buf = term->priv->primary_buffer; return _moo_term_line_has_tag (buf_line (buf, ITER_ROW (iter)), tag, ITER_COL (iter)); } gboolean moo_term_iter_get_tag_start (MooTermIter *iter, MooTermTag *tag) { MooTermBuffer *buf; MooTermLine *line; MooTerm *term; guint col; g_return_val_if_fail (iter != NULL, FALSE); g_return_val_if_fail (MOO_IS_TERM (ITER_TERM (iter)), FALSE); g_return_val_if_fail (MOO_IS_TERM_TAG (tag), FALSE); term = ITER_TERM (iter); buf = term->priv->primary_buffer; line = buf_line (buf, ITER_ROW (iter)); col = ITER_COL (iter); if (_moo_term_line_get_tag_start (line, tag, &col)) { ITER_COL (iter) = col; return TRUE; } else { return FALSE; } } gboolean moo_term_iter_get_tag_end (MooTermIter *iter, MooTermTag *tag) { MooTermBuffer *buf; MooTermLine *line; MooTerm *term; guint col; g_return_val_if_fail (iter != NULL, FALSE); g_return_val_if_fail (MOO_IS_TERM (ITER_TERM (iter)), FALSE); g_return_val_if_fail (MOO_IS_TERM_TAG (tag), FALSE); term = ITER_TERM (iter); buf = term->priv->primary_buffer; line = buf_line (buf, ITER_ROW (iter)); col = ITER_COL (iter); if (_moo_term_line_get_tag_end (line, tag, &col)) { ITER_COL (iter) = col; return TRUE; } else { return FALSE; } } void moo_term_set_line_data (MooTerm *term, MooTermLine *line, const char *key, gpointer data) { moo_term_set_line_data_full (term, line, key, data, NULL); } void moo_term_set_line_data_full (MooTerm *term, MooTermLine *line, const char *key, gpointer data, GDestroyNotify destroy) { g_return_if_fail (MOO_IS_TERM (term)); g_return_if_fail (key != NULL); g_return_if_fail (line != NULL); _moo_term_buffer_set_line_data (term->priv->primary_buffer, line, key, data, destroy); } gpointer moo_term_get_line_data (MooTerm *term, MooTermLine *line, const char *key) { g_return_val_if_fail (MOO_IS_TERM (term), NULL); g_return_val_if_fail (key != NULL, NULL); g_return_val_if_fail (line != NULL, NULL); return _moo_term_buffer_get_line_data (term->priv->primary_buffer, line, key); } MooTermIter* moo_term_iter_copy (const MooTermIter *iter) { g_return_val_if_fail (iter != NULL, NULL); return g_memdup (iter, sizeof (MooTermIter)); } void moo_term_iter_free (MooTermIter *iter) { g_free (iter); } GType moo_term_iter_get_type (void) { static GType type = 0; if (G_UNLIKELY (!type)) type = g_boxed_type_register_static ("MooTermIter", (GBoxedCopyFunc) moo_term_iter_copy, (GBoxedFreeFunc) moo_term_iter_free); return type; }