Logo Search packages:      
Sourcecode: bibledit version File versions  Download package

highlight.cpp

/*
** Copyright (C) 2003-2006 Teus Benschop.
**  
** 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.
**  
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**  
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**  
*/


#include "libraries.h"
#include <libgen.h>
#include <glib.h>
#include "highlight.h"
#include "utilities.h"
#include "gwrappers.h"
#include "usfmtools.h"
#include "session.h"
#include "categorize.h"


Highlight::Highlight (GtkTextBuffer * buffer, GtkWidget * textview)
{
  // Save variables.
  mybuffer = buffer;
  mytextview_edit = GTK_TEXT_VIEW (textview);
}


Highlight::~Highlight ()
{
}


void Highlight::line_at_cursor (GtkTextTag * tag)
// Highlights the line the cursor is on.
{
  // Remove previous highlight, if any.
  GtkTextIter startiter;
  GtkTextIter enditer;
  gtk_text_buffer_get_start_iter (mybuffer, &startiter);
  gtk_text_buffer_get_end_iter (mybuffer, &enditer);
  gtk_text_buffer_remove_tag (mybuffer, tag, &startiter, &enditer);
  // Hightlight the line number the cursor is on.
  gtk_text_buffer_get_iter_at_mark (mybuffer, &startiter, gtk_text_buffer_get_insert (mybuffer));
  int line_of_cursor = gtk_text_iter_get_line (&startiter);
  gtk_text_buffer_get_iter_at_line (mybuffer, &startiter, line_of_cursor);
  gtk_text_buffer_get_iter_at_line (mybuffer, &enditer, ++line_of_cursor);
  gtk_text_buffer_apply_tag (mybuffer, tag, &startiter, &enditer);
}


void Highlight::searchwords (GtkTextTag * tag, const ustring& verse)
/*
  This highlights all the words in the verse that agree to the word
  we search for.
*/
{
  // Remove any previous highlights.
  GtkTextIter startiter;
  GtkTextIter enditer;
  gtk_text_buffer_get_start_iter (mybuffer, &startiter);
  gtk_text_buffer_get_end_iter (mybuffer, &enditer);
  gtk_text_buffer_remove_tag (mybuffer, tag, &startiter, &enditer);

  // Determine the boundaries between which to highlight.
  GtkTextIter starthighlightiter = startiter;
  GtkTextIter endhighlightiter = enditer;
  {
    GtkTextIter iter = startiter;
    bool started = false;
    bool ended = false;
    while (!gtk_text_iter_is_end (&iter)) {
      GSList *tags = NULL, *tagp = NULL;
      tags = gtk_text_iter_get_tags (&iter);
      for (tagp = tags; tagp != NULL; tagp = tagp->next) {
        GtkTextTag *tag = (GtkTextTag *) tagp->data;
        gint id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tag), "verse-tag"));
        if (id == 1) {
          gchar *gverse;
          g_object_get (tag, "name", &gverse, NULL);
          if (verse == gverse) {
            if (!started) {
              started = true;
              starthighlightiter = iter;
            }
          } else {
            if (started) {
              if (!ended) {
                ended = true;
                endhighlightiter = iter;
              }
            }            
          }
          if (gverse) g_free (gverse);
        }
      }
      gtk_text_iter_forward_char (&iter);    
    }
  }

  // Find the boundaries for highlighting search words within the iterators.
  Session session (0);
  vector <ustring> searchword = session.highlight_words();
  vector <bool> casesensitive = session.highlight_casesensitives();
  vector <bool> globbing = session.highlight_globbings();
  vector <bool> startmatch = session.highlight_matchbegins();
  vector <bool> endmatch = session.highlight_matchends();
  vector <AreaType> areatype = session.highlight_area_type ();
  vector <bool> area_id = session.highlight_area_id ();
  vector <bool> area_intro = session.highlight_area_intro ();
  vector <bool> area_heading = session.highlight_area_heading ();
  vector <bool> area_chapter = session.highlight_area_chapter ();
  vector <bool> area_study = session.highlight_area_study ();
  vector <bool> area_notes = session.highlight_area_notes ();
  vector <bool> area_xref = session.highlight_area_xref ();
  vector <bool> area_verse = session.highlight_area_verse (); 
  vector<GtkTextIter> wordbegin_all;
  vector<GtkTextIter> wordend_all;
  for (unsigned int i = 0; i < session.highlight_words().size (); i++) {
    vector<GtkTextIter> wordbegin;
    vector<GtkTextIter> wordend;
    if (globbing[i] || startmatch[i] || endmatch[i]) {
      // Advanced and slow highlighting.
      searchwords_find_slow (&starthighlightiter, &endhighlightiter, searchword[i], 
                             casesensitive[i], globbing[i], startmatch[i], endmatch[i],
                             wordbegin, wordend);
    } else {
      // Basic and fast highlighting.
      searchwords_find_fast (&starthighlightiter, &endhighlightiter, searchword[i],
                             casesensitive[i], wordbegin, wordend);
    }
    // If we do searching in areas, check that the boundaries are inside the areas.
    if (areatype[i] == atSelection) {
      searchwords_in_area (wordbegin, wordend, area_id[i], area_intro[i], area_heading[i], area_chapter[i], area_study[i], area_notes[i], area_xref[i], area_verse[i]); 
    } 
    // Copy any remaining iterators to the main containers.
    for (unsigned int i = 0; i < wordbegin.size(); i++) {
      wordbegin_all.push_back (wordbegin[i]);
      wordend_all.push_back (wordend[i]);
    }
  }
  // Do the actual visible highlighting.
  bool cursor_set = false;
  searchwords_highlight (wordbegin_all, wordend_all, cursor_set, tag);
}


void Highlight::searchwords_find_slow (GtkTextIter * beginbound, GtkTextIter * endbound,
                                       const ustring & searchword,
                                       bool casesensitive, bool globbing, bool matchbegin, bool matchend,
                                       vector <GtkTextIter>& wordstart, vector<GtkTextIter>& wordend)
/*
Searches for words to highlight.
Problem when case insensitive searching:
  Character ffi was changed to ffi after casefolding, and as that one is 2
  characters longer than the original ffi, we ran in problems of searching
  past the line, which gave an exception in Gtk.
The solution was to determine the length of the word from the ones that are 
to highlight, not from the casefolded searchword, but the original one.
*/
{
  // Variable.
  GtkTextIter begin;
  GtkTextIter end;
  // Extract the line.
  ustring line = gtk_text_buffer_get_text (mybuffer, beginbound, endbound, false);
  // Find all places in this line that have the search word.
  /*
  To do that properly for glob-style pattern matching, for begin/end word
  matching and case (in)sensitivity, we need to open the box of tricks.
  We produce all possible combinations for characters and lengths, e.g.
  We have this text:
    he is
  We then make the following strings from it, and see whether they match:
    "h"
    "he"
    "he "
    "he i"
    "he is"
    "e"
    "e "
    "e i"
    "e is"
    " "
    " i"
    " is"
    "i"
    "is"
    "s"
  Any string matching will then be highlighted.
  */
  // Deal with case sensitivity.
  ustring case_considerate_search_word (searchword);
  if (!casesensitive)
    case_considerate_search_word = case_considerate_search_word.casefold();
  for (unsigned int i = 0; i < line.length(); i++) {
    ustring line2 (line.substr (0, i + 1));
    for (unsigned int offposition = 0; offposition < line2.length(); offposition++) {
      // Get the line as described above.
      // We use optimization here to get the speed acceptable when we have 
      // long lines. But when globbing is done, because of the characters of 
      // glob-style matching, we don't know how long the searchword might be,
      // we do not use that optimization.
      unsigned int linelength = line2.length() - offposition;
      if (!globbing)
        if (linelength > searchword.length()) 
          continue;
      ustring compareline (line2.substr (offposition, linelength));
      // Deal with case sensitivity.
      if (!casesensitive)
        compareline = compareline.casefold();
      // Now compare.
      bool match = false;
      if (globbing) {
        if (g_pattern_match_simple (case_considerate_search_word.c_str(), compareline.c_str()))
          match = true;        
      } else {
        if (case_considerate_search_word == compareline)
          match = true;
      }
      // Get the iterators in the textbuffer that belong to this possible match.
      if (match) {
        begin = * beginbound;
        gtk_text_iter_forward_chars (&begin, offposition);
        end = begin;
        gtk_text_iter_forward_chars (&end, searchword.length ());
      }
      // Deal with begin-word matching.
      if (match) {
        if (matchbegin) {
          if (!gtk_text_iter_starts_word (&begin))
            match = false;
        }
      }
      // Deal with end-word matching.
      if (match) {
        if (matchend) {
          if (!gtk_text_iter_ends_word (&end))
            match = false;
        }
      }
      // Add the boundaries of the word to highlight.
      if (match) {
        wordstart.push_back (begin);
        wordend.push_back (end);
      }
    }
  }
}


void Highlight::searchwords_find_fast (GtkTextIter * beginbound, GtkTextIter * endbound,
                                       const ustring & searchword, bool casesensitive,
                                       vector <GtkTextIter>& wordstart, vector<GtkTextIter>& wordend)
// Searches for words to highlight. For simple highligthing. 
// Is much faster than the slow routine, see there fore more information.
{
  // Variable.
  GtkTextIter begin;
  GtkTextIter end;
  // Extract the line.
  ustring line = gtk_text_buffer_get_text (mybuffer, beginbound, endbound, false);
  // Deal with case sensitivity.
  ustring case_considerate_search_word (searchword);
  if (!casesensitive)
    case_considerate_search_word = case_considerate_search_word.casefold();
  // Go through the line looking for matches.
  for (unsigned int i = 0; i < line.length(); i++) {
    ustring compareline (line.substr (i, searchword.length()));
    // Deal with case sensitivity.
    if (!casesensitive)
      compareline = compareline.casefold();
    // Now compare.
    if (case_considerate_search_word == compareline) {
      // Get the iterators in the textbuffer that belong to this possible match.
      begin = * beginbound;
      gtk_text_iter_forward_chars (&begin, i);
      end = begin;
      gtk_text_iter_forward_chars (&end, searchword.length ());
      // Add the boundaries of the word to highlight.
      wordstart.push_back (begin);
      wordend.push_back (end);
    }
  }
}


GtkTextTag * Highlight::get_tag (const ustring& tagname)
/*
This returns the tag named "tagname", and this tagname is created if it wasn't
there.
This tag is used to retrieve verse location information from the editor, i.e.
"on which verse are we now?".
*/
{
  // Get the tables of tags.
  GtkTextTagTable *table;
  table = gtk_text_buffer_get_tag_table (mybuffer);
  GtkTextTag *tag;
  // Get the named tag.
  tag = gtk_text_tag_table_lookup (table, tagname.c_str());
  // If the tag was not there, create it.
  if (!tag) {
    tag = gtk_text_buffer_create_tag (mybuffer, tagname.c_str(), NULL);
    // Note this int value of "1" is needed later to indicate to us this is the tag we need.
    g_object_set_data (G_OBJECT (tag), "verse-tag", GINT_TO_POINTER (1));
  }    
  return tag;
}


void Highlight::searchwords_highlight (vector <GtkTextIter>& start, vector<GtkTextIter>& end,
                                       bool& cursor_set, GtkTextTag * tag)
// Executes the actual visible highlighting.
// start: contains the iterators where highlighting should start.
// end: contains the iterators where highlighting should end.
{
  for (unsigned int i = 0; i < start.size(); i++) {
    gtk_text_buffer_apply_tag (mybuffer, tag, &start[i], &end[i]);
    if (!cursor_set) {
      gtk_text_buffer_place_cursor (mybuffer, &start[i]);
      gtk_text_view_scroll_to_iter (mytextview_edit, &start[i], 0.1, false, 0, 0);
      cursor_set = true;
    }
  }
}


void Highlight::searchwords_in_area (vector <GtkTextIter>& start, vector<GtkTextIter>& end,
                                     bool area_id, bool area_intro, bool area_heading, bool area_chapter, 
                                     bool area_study, bool area_notes, bool area_xref, bool area_verse)
/*
Finds out whether the text within the "start" and "end" iterators is inside
one of the given areas. If not, it removes the iterator from the containers.
*/
{
  // Categorization data
  CategorizeLine categorize ("");
  // Go through the iterators, starting at the end (to make erasing it easier).
  for (int it = start.size() - 1; it >= 0; it--) {
    // Get line number of the iterator.
    gint linenumber = gtk_text_iter_get_line (&start[it]);
    // Get the usfm this line starts with.
    ustring usfm;
    {
      GtkTextIter line1;
      gtk_text_buffer_get_iter_at_line (mybuffer, &line1, linenumber);
      GtkTextIter line2 = line1;
      gtk_text_iter_forward_chars (&line2, 10);
      ustring line = gtk_text_iter_get_text (&line1, &line2);
      usfm = usfm_extract_marker (line);
    }
    // See if this usfm is in one of the areas given.
    bool in_area = false;
    if (area_id)
      if (categorize.is_id_marker (usfm))
        in_area = true;
    if (area_intro)
      if (categorize.is_intro_marker (usfm))
        in_area = true;
    if (area_heading)
      if (categorize.is_head_marker (usfm))
        in_area = true;
    if (area_chapter)
      if (categorize.is_chap_marker (usfm))
        in_area = true;
    if (area_study)
      if (categorize.is_study_marker (usfm))
        in_area = true;
    // The variables "area_notes" and "area_xref" are not relevant.
    if (area_verse)
      if (categorize.is_verse_marker (usfm))
        in_area = true;
    // If not in one of the areas, remove this iterator from the container.
    if (!in_area) {
      vector <GtkTextIter>::iterator startiter = start.begin();
      vector <GtkTextIter>::iterator enditer = end.begin();
      for (int i = 0; i < it; i++) {
        startiter++;
        enditer++;
      }
      start.erase (startiter);
      end.erase (enditer);
    }
  }
}

Generated by  Doxygen 1.6.0   Back to index