/* * File: selection.c * * Copyright 2003 Sebastian Geerken , * Eric Gaudet * * 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 doc/Selection.txt for informations about this module. */ #include "selection.h" #include "strbuf.h" #include "dw_gtk_viewport.h" #include #include #include /*#define DEBUG_LEVEL 2*/ #include "debug.h" static void Selection_reset_selection (Selection *selection); static void Selection_reset_link (Selection *selection); static void Selection_switch_link_to_selection (Selection *selection, DwIterator *it, gint32 char_pos); static void Selection_adjust_selection (Selection *selection, DwIterator *it, gint32 char_pos); static gint Selection_correct_char_pos (DwExtIterator *it, gint32 char_pos); static void Selection_highlight (Selection *selection, gboolean fl); static void Selection_highlight0 (gboolean fl, DwExtIterator *from, gint from_char, DwExtIterator *to, gint to_char); static void Selection_copy (Selection *selection); /* Not defined as static, to avoid compiler warnings. */ char *selection_state_descr[] = { "none", "selecting", "selected" }; Selection *a_Selection_new (void) { Selection *selection; selection = g_new (Selection, 1); selection->selection_state = SELECTION_NONE; selection->from = NULL; selection->to = NULL; selection->link_state = SELECTION_LINK_NONE; selection->link = NULL; selection->dclick_callback = NULL; selection->callback_data = NULL; selection->owner = NULL; return selection; } /* * Sets a callback function for double click, this is normally * the function toggling the full screen mode. * (More than one is never needed, so this instead of a signal.) */ void a_Selection_set_dclick_callback (Selection *selection, void (*fn) (gpointer data), gpointer data) { g_return_if_fail (selection->dclick_callback == NULL); selection->dclick_callback = fn; selection->callback_data = data; } /* * Set the widget that owns the selection. */ void a_Selection_set_owner(Selection *selection, gpointer owner) { selection->owner = owner; a_Selection_init_selection(owner); } void a_Selection_reset (Selection *selection) { Selection_reset_selection (selection); Selection_reset_link (selection); } void a_Selection_free (Selection *selection) { a_Selection_reset (selection); g_free (selection); } static void Selection_reset_selection (Selection *selection) { if (selection->from) a_Dw_ext_iterator_free(selection->from); selection->from = NULL; if (selection->to) a_Dw_ext_iterator_free(selection->to); selection->to = NULL; selection->selection_state = SELECTION_NONE; } static void Selection_reset_link (Selection *selection) { if (selection->link) a_Dw_ext_iterator_free(selection->link); selection->link = NULL; selection->link_state = SELECTION_LINK_NONE; } gint a_Selection_button_press (Selection *selection, DwIterator *it, gint char_pos, gint link, GdkEventButton *event, gboolean within_content) { DwWidget *it_widget = it->widget; gboolean ret = FALSE, dummy; DEBUG_MSG (3, "PRESS%s(%s, %d, %d): %s -> ...\n", (event && event->type == GDK_2BUTTON_PRESS) ? "2" : "", a_Dw_iterator_text (it), char_pos, link, selection_state_descr[selection->selection_state]); #if defined (DEBUG_LEVEL) && 1 >= DEBUG_LEVEL a_Dw_widget_print_tree (GTK_DW_VIEWPORT(it->widget->viewport)->child); #endif if (event && event->button == 1 && !within_content && event->type == GDK_2BUTTON_PRESS) { /* When the user double-clicks on empty parts, call the callback * function instead of normal processing. Used for full screen * mode. */ if (selection->dclick_callback) selection->dclick_callback (selection->callback_data); /* Reset everything, so that a_Selection_release will ignore the * "release" event following soon. */ Selection_highlight (selection, FALSE); a_Selection_reset (selection); ret = TRUE; } else { if (link != -1) { /* link handling */ if (event) { gtk_signal_emit_by_name (GTK_OBJECT (it_widget), "link_pressed", link, -1, -1, event, &dummy); Selection_reset_link (selection); selection->link_state = SELECTION_LINK_PRESSED; selection->link_button = event->button; selection->link = a_Dw_ext_iterator_new (it); /* It may be that the user has pressed on something activatable * (link != -1), but there is no contents, e.g. with images * without ALTernative text. */ if (selection->link) { selection->link_char = Selection_correct_char_pos (selection->link, char_pos); selection->link_number = link; } /* We do not return the value of the signal function (dummy), * but we do actually process this event. */ ret = TRUE; } } else { /* normal selection handling */ if (event && event->button == 1) { Selection_highlight (selection, FALSE); Selection_reset_selection (selection); selection->from = a_Dw_ext_iterator_new (it); /* a_Dw_ext_iterator_new may return NULL, if the page has no * contents. */ if (selection->from) { selection->selection_state = SELECTION_SELECTING; selection->from_char = Selection_correct_char_pos (selection->from, char_pos); selection->to = a_Dw_ext_iterator_clone (selection->from); selection->to_char = Selection_correct_char_pos (selection->to, char_pos); ret = TRUE; } else /* if there is no content */ ret = FALSE; } } } DEBUG_MSG (3, " ... -> %s, processed = %s\n", selection_state_descr[selection->selection_state], ret ? "TRUE" : "FALSE"); return ret; } gint a_Selection_button_release (Selection *selection, DwIterator *it, gint char_pos, gint link, GdkEventButton *event, gboolean within_content) { DwWidget *it_widget = it->widget; gboolean ret = FALSE, dummy; DEBUG_MSG (3, "RELEASE(%s, %d, %d): %s -> ...\n", a_Dw_iterator_text (it), char_pos, link, selection_state_descr[selection->selection_state]); if (selection->link_state == SELECTION_LINK_PRESSED && event && event->button == selection->link_button) { /* link handling */ ret = TRUE; if (link != -1) gtk_signal_emit_by_name (GTK_OBJECT (it_widget), "link_released", link, -1, -1, event, &dummy); /* The link where the user clicked the mouse button? */ if (link == selection->link_number) { Selection_reset_link (selection); gtk_signal_emit_by_name (GTK_OBJECT (it_widget), "link_clicked", link, -1, -1, event, &dummy); } else { if (event->button == 1) /* Reset links and switch to selection mode. The selection * state will be set to SELECTING, which is handled some lines * below. */ Selection_switch_link_to_selection (selection, it, char_pos); } } if (selection->selection_state == SELECTION_SELECTING && event && event->button == 1) { /* normal selection */ ret = TRUE; Selection_adjust_selection (selection, it, char_pos); if (a_Dw_ext_iterator_compare (selection->from, selection->to) == 0 && selection->from_char == selection->to_char) /* nothing selected */ Selection_reset_selection (selection); else { Selection_copy (selection); selection->selection_state = SELECTION_SELECTED; } } DEBUG_MSG (3, " ... -> %s, processed = %s\n", selection_state_descr[selection->selection_state], ret ? "TRUE" : "FALSE"); return ret; } gint a_Selection_button_motion (Selection *selection, DwIterator *it, gint char_pos, gint link, GdkEventButton *event, gboolean within_content) { DEBUG_MSG (3, "MOTION (%s, %d, %d): %s -> ...\n", a_Dw_iterator_text (it), char_pos, link, selection_state_descr[selection->selection_state]); if (selection->link_state == SELECTION_LINK_PRESSED) { /* link handling */ if (link != selection->link_number) /* No longer the link where the user clicked the mouse button. * Reset links and switch to selection mode. */ Selection_switch_link_to_selection (selection, it, char_pos); /* Still in link: do nothing. */ } else if (selection->selection_state == SELECTION_SELECTING) { /* selection */ Selection_adjust_selection (selection, it, char_pos); } DEBUG_MSG (3, " ... -> %s, processed = TRUE\n", selection_state_descr[selection->selection_state]); return TRUE; } /* * This function is called when the user decides not to activate a link, * but instead select text. */ static void Selection_switch_link_to_selection (Selection *selection, DwIterator *it, gint32 char_pos) { /* It may be that selection->link is NULL, see a_Selection_button_press. */ if (selection->link) { /* Reset old selection. */ Selection_highlight (selection, FALSE); Selection_reset_selection (selection); /* Transfer link state into selection state. */ selection->from = a_Dw_ext_iterator_clone (selection->link); selection->from_char = selection->link_char; selection->to = a_Dw_ext_iterator_new_variant (selection->from, it); selection->to_char = Selection_correct_char_pos (selection->to, char_pos); selection->selection_state = SELECTION_SELECTING; /* Reset link status. */ Selection_reset_link (selection); Selection_highlight (selection, TRUE); DEBUG_MSG (2, " after switching: from (%s, %d) to (%s, %d)\n", a_Dw_ext_iterator_text (selection->from), selection->from_char, a_Dw_ext_iterator_text (selection->to), selection->to_char); } else { /* A link was pressed on, but there is nothing to select. Reset * everything. */ Selection_reset_selection (selection); Selection_reset_link (selection); } } /* * This function is used by a_Selection_button_motion and * a_Selection_button_release, and changes the second limit of the already * existing selection region. */ static void Selection_adjust_selection (Selection *selection, DwIterator *it, gint32 char_pos) { DwExtIterator *new_to; gint new_to_char, cmp_old, cmp_new, cmp_diff, len; gboolean brute_highlighting = FALSE; new_to = a_Dw_ext_iterator_new_variant (selection->to, it); new_to_char = Selection_correct_char_pos (new_to, char_pos); cmp_old = a_Dw_ext_iterator_compare (selection->to, selection->from); cmp_new = a_Dw_ext_iterator_compare (new_to, selection->from); if (cmp_old == 0 || cmp_new == 0) { /* Either before, or now, the limits differ only by the character * position. */ brute_highlighting = TRUE; } else if (cmp_old * cmp_new < 0) { /* The selection order has changed, i.e. the user moved the selection * end again beyond the position he started. */ brute_highlighting = TRUE; } else { /* Here, cmp_old and cmp_new are equivalent and != 0. */ cmp_diff = a_Dw_ext_iterator_compare (new_to, selection->to); DEBUG_MSG (2, "Selection_adjust_selection: cmp_old = cmp_new = %d, " "cmp_diff = %d\n", cmp_old, cmp_diff); if (cmp_old * cmp_diff > 0) { /* The user has enlarged the selection. Highlight the difference. */ if (cmp_diff < 0) { len = Selection_correct_char_pos (selection->to, SELECTION_EOW); Selection_highlight0 (TRUE, new_to, new_to_char, selection->to, len + 1); } else { Selection_highlight0 (TRUE, selection->to, 0, new_to, new_to_char); } } else { if (cmp_old * cmp_diff < 0) { /* The user has reduced the selection. Unighlight the difference. */ Selection_highlight0 (FALSE, selection->to, 0, new_to, 0); } /* Otherwise, the user has changed the position only slightly. * In both cases, re-highlight the new position. */ if (cmp_old < 0) { len = Selection_correct_char_pos (new_to, SELECTION_EOW); a_Dw_ext_iterator_highlight (new_to, new_to_char, len + 1, DW_HIGHLIGHT_SELECTION); } else a_Dw_ext_iterator_highlight (new_to, 0, new_to_char, DW_HIGHLIGHT_SELECTION); } } if (brute_highlighting) Selection_highlight (selection, FALSE); a_Dw_ext_iterator_free(selection->to); selection->to = new_to; selection->to_char = new_to_char; if (brute_highlighting) Selection_highlight (selection, TRUE); DEBUG_MSG (2, " selection now from (%s, %d) to (%s, %d)\n", a_Dw_ext_iterator_text (selection->from), selection->from_char, a_Dw_ext_iterator_text (selection->to), selection->to_char); } /* * This function deals especially with the case that a widget passes * SELECTION_EOW. See Selection.txt in the doc dir for more informations. */ static gint Selection_correct_char_pos (DwExtIterator *it, gint32 char_pos) { DwIterator *top = it->stack[it->stack_top]; gint len; if (top->content.type == DW_CONTENT_TEXT) len = strlen(top->content.data.text); else len = 1; return MIN(char_pos, len); } static void Selection_highlight (Selection *selection, gboolean fl) { Selection_highlight0 (fl, selection->from, selection->from_char, selection->to, selection->to_char); } static void Selection_highlight0 (gboolean fl, DwExtIterator *from, gint from_char, DwExtIterator *to, gint to_char) { DwExtIterator *a, *b, *i; gint cmp, a_char, b_char; gboolean start; if (from && to) { DEBUG_MSG (2, " %shighlighting from %s / %d to %s / %d\n", fl ? "" : "un", a_Dw_ext_iterator_text (from), from_char, a_Dw_ext_iterator_text (to), to_char); cmp = a_Dw_ext_iterator_compare (from, to); if (cmp == 0) { if (fl) { if (from_char < to_char) a_Dw_ext_iterator_highlight (from, from_char, to_char, DW_HIGHLIGHT_SELECTION); else a_Dw_ext_iterator_highlight (from, to_char, from_char, DW_HIGHLIGHT_SELECTION); } else a_Dw_ext_iterator_unhighlight (from, DW_HIGHLIGHT_SELECTION); return; } if (cmp < 0) { a = from; a_char = from_char; b = to; b_char = to_char; } else { a = to; a_char = to_char; b = from; b_char = from_char; } for (i = a_Dw_ext_iterator_clone (a), start = TRUE; (cmp = a_Dw_ext_iterator_compare (i, b)) <= 0; a_Dw_ext_iterator_next (i), start = FALSE) { if (i->content.type == DW_CONTENT_TEXT) { if (fl) { if (start) { DEBUG_MSG (2, " highlighting %s from %d to %d\n", a_Dw_ext_iterator_text (i), a_char, strlen (i->content.data.text) + 1); a_Dw_ext_iterator_highlight (i, a_char, strlen (i->content.data.text) + 1, DW_HIGHLIGHT_SELECTION); } else if (cmp == 0) { /* the end */ DEBUG_MSG (2, " highlighting %s from %d to %d\n", a_Dw_ext_iterator_text (i), 0, b_char); a_Dw_ext_iterator_highlight (i, 0, b_char, DW_HIGHLIGHT_SELECTION); } else { DEBUG_MSG (2, " highlighting %s from %d to %d\n", a_Dw_ext_iterator_text (i), 0, strlen (i->content.data.text) + 1); a_Dw_ext_iterator_highlight (i, 0, strlen (i->content.data.text) + 1, DW_HIGHLIGHT_SELECTION); } } else { DEBUG_MSG (2, " unhighlighting %s\n", a_Dw_ext_iterator_text (i)); a_Dw_ext_iterator_unhighlight (i, DW_HIGHLIGHT_SELECTION); } } } a_Dw_ext_iterator_free (i); } } static void Selection_copy(Selection *selection) { DwIterator *si; DwExtIterator *a, *b, *i; gint cmp, a_char, b_char; gboolean start; gchar *tmp; Strbuf_t *strbuf; if (selection->from && selection->to) { strbuf = a_Strbuf_new (); cmp = a_Dw_ext_iterator_compare (selection->from, selection->to); if (cmp == 0) { if (selection->from->content.type == DW_CONTENT_TEXT) { si = selection->from->stack[selection->from->stack_top]; if (selection->from_char < selection->to_char) tmp = g_strndup(si->content.data.text + selection->from_char, selection->to_char - selection->from_char); else tmp = g_strndup(si->content.data.text + selection->to_char, selection->from_char - selection->to_char); a_Strbuf_append (strbuf, tmp); g_free (tmp); } } else { if (cmp < 0) { a = selection->from; a_char = selection->from_char; b = selection->to; b_char = selection->to_char; } else { a = selection->to; a_char = selection->to_char; b = selection->from; b_char = selection->from_char; } for (i = a_Dw_ext_iterator_clone (a), start = TRUE; (cmp = a_Dw_ext_iterator_compare (i, b)) <= 0; a_Dw_ext_iterator_next (i), start = FALSE) { si = i->stack[i->stack_top]; switch (si->content.type) { case DW_CONTENT_TEXT: if (start) { tmp = g_strndup(si->content.data.text + a_char, strlen (i->content.data.text) - a_char); a_Strbuf_append (strbuf, tmp); g_free (tmp); } else if (cmp == 0) { /* the end */ tmp = g_strndup(si->content.data.text, b_char); a_Strbuf_append (strbuf, tmp); g_free (tmp); } else a_Strbuf_append (strbuf, si->content.data.text); if (si->content.space && cmp != 0) a_Strbuf_append (strbuf, " "); break; case DW_CONTENT_BREAK: if (si->content.data.break_space > 0) a_Strbuf_append (strbuf, "\n\n"); else a_Strbuf_append (strbuf, "\n"); break; default: /* Make pedantic compilers happy. Especially * DW_CONTENT_WIDGET is never returned by a DwExtIterator. */ break; } } a_Dw_ext_iterator_free (i); } a_Selection_set_selection(selection->owner, a_Strbuf_chars(strbuf)); a_Strbuf_free (strbuf); } } /* GENERAL SELECTION STUFF */ /* * Local data */ static char *selection = NULL; /* * Sets the widget to copy the selection from. */ void a_Selection_init_selection(GtkWidget *widget) { gtk_signal_connect(GTK_OBJECT(widget), "selection_clear_event", GTK_SIGNAL_FUNC(a_Selection_clear_selection_callback), NULL); gtk_signal_connect(GTK_OBJECT(widget), "selection_get", GTK_SIGNAL_FUNC(a_Selection_give_selection_callback), NULL); gtk_selection_add_target(widget, GDK_SELECTION_PRIMARY, GDK_SELECTION_TYPE_STRING, 1); } /* * Sets the selection, and associates it with the current widget for * copying. */ void a_Selection_set_selection(GtkWidget *widget, gchar *str) { if (gtk_selection_owner_set(widget, GDK_SELECTION_PRIMARY, GDK_CURRENT_TIME)) { /* --EG: Why would it fail? (this question points to that the * GTK+ example tests the return value.) */ /* As the clear-selection callback is not being called automatically * before setting the new value, it'll be cleared here. --Jcid * todo: this selection code needs a revision */ g_free(selection); selection = g_strdup(str); } } /* * Callback for Paste (when another application wants the selection) */ void a_Selection_give_selection_callback(GtkWidget *widget, GtkSelectionData *data, guint info, guint time) { gtk_selection_data_set(data, GDK_SELECTION_TYPE_STRING, 8, selection, strlen(selection)); } /* * Clear selection */ gint a_Selection_clear_selection_callback(GtkWidget *widget, GdkEventSelection *event) { g_free(selection); selection = NULL; /* here we should un-highlight the selected text */ return TRUE; }