Create  Edit  FrontPage  Index  Search  Changes  History  RSS  Login

tut-gtk2-txtw-itrsmrks

Text Iterators and Marks

When manipulating text within a Gtk::TextBuffer object, there are two different objects that can help you track the position within the buffer: Gtk::TextIter and Gtk::TextMark. Gtk classes provide methods to translate between these two objects, albeit, as you will learn, the conversion is not as straight forward in both directions, nevertheless, always possible.

What are Text Iterators
Text iterators represent a position between two characters in a text buffer. If I borrow the terminology from geometry, we can say that text iterators are used to define a single spot (a point) in a text buffer or to define an interval or a contiguous block of text in a text buffer. They are transient or ephemeral in nature, because they become invalid as soon as the text buffer is edited i.e. when text is inserted or deleted in the buffer. Iterators are better than offset or byte index into a buffer, because they are guarantied to point at the beginning of an UTF-8 character, which may be a string of multiple bytes.
What are Text Marks

For keeping track of the position in a text buffer throughout the editing process Gtk::TextMark object is provided. Text marks remain intact even when text buffer is changed, and will after insertions or deletions point to the new position based on how and where the text buffer is manipulated. You can retrieve an iterator position at a text mark with Gtk::TextBuffer#get_iter_at_mark(mark):

get_iter_at_mark(mark)
    Gets the iter with the current position at mark.
    * mark: a Gtk::TextMark in buffer 
    * Returns: a Gtk::TextIter


Sometimes you would like to refer to a position within a text buffer by means of index and offset, which in the early days of programming, before multi-byte characters were introduced, used to be the rather natural way to navigate the buffer. If you wish you can still navigate text buffer this way, though in the case you use UTF-8 character encoding, this can become increasingly unreliable. Should you have a need to do so, you can employ Gtk::TextBuffer#get_iter_at_offset(char_offset), which gets an iterator pointing tochar_offsetwithin the text buffer, and Gtk::TextIter#offset which returns the character offset of an iterator (note in both cases we mean character offset, not byte offset).

There is another method, which for measuring distance from the beginning of a line (note, the beginning of a line, not the beginning of the text buffer) does not count characters, and for which the offset does not necessarily start at the beginning of the text buffer, but instead at the beginning of a particular line. It is: Gtk::TextBuffer#get_iter_at_line_index(line_number, byte_index). This method obtains an iterator pointing to byte_index within the given line.byte_indexmust point at the start of a UTF-8 character, and must not be within a multi-byte character.

CAUTION:
The wordindexrefers to an individual byte, you need to be careful when stepping through a text buffer in this way. In particular you can not refer to an index pointing to the position between two characters.


Text Marks

Text marks (Gtk::TextMark) act as if they were invisible cursors within the text, changing position depending on how the text is edited. For instance, if the text is added before the mark, it (the mark) will move to the right so that it remains at the same textual position.

By default, text marks havegravityset to the right, hence a text mark will move to the right as new text is added at the mark. If text is removed the mark will move between the two pieces of text on either side of the removed text. Then if the text is inserted at that position, because of its right gravity setting, the mark will remain on the right side of the inserted text. You can see that the text mark behaves just like a cursor in most text editors.

Text Mark Visibility:
By default, text marks are invisible within the text, however, you can make them visible by setting Gtk::TextMark#visible=(boolean) to true.

Text marks can be accessed in two ways. You can retrieve a text mark at a specific Gtk::TextIter location. It is also possible to set up text mark with a string as its name, which makes it easy to keep track of. Here the word "retrieve" is not very exact, since there really is no way to "retrieve a mark at an iter location". Instead you can give a mark a name when you create a new mark for an iter, and only then later, you can truly retrieve it if you wish by its name. But this detail is rally not that important, I just want to be accurate. In fact there are two default marks always provided by GTK+. They are selection_bound andinsert. You can always retrieve these two marks, and of course any marks you yourself created with names, via Gtk::TextBuffer#get_mark(name).

Creating a Text Mark:

Gtk::TextBuffer#create_mark(mark_name, iter, left_gravity)
Creates a mark at position iter. If mark_name is nil, the mark is anonymous; otherwise, the mark can be retrieved by name using Gtk::TextBuffer#get_mark. If a mark has left gravity, and text is inserted at the mark's current location, the mark will be moved to the left of the newly-inserted text. If the mark has right gravity (left_gravity = false), the mark will end up on the right of newly-inserted text. The standard left-to-right cursor is a mark with right gravity (when you type, the cursor stays on the right side of the text you're typing). The caller of this method does not own a reference to the returned Gtk::TextMark, so you can ignore the return value if you like. Marks are owned by the buffer and go away when the buffer does. Emits the "mark_set" signal as notification of the mark's initial placement.
  • mark_name: name for mark(String), or nil
  • iter: location to place mark (Gtk::TextIter)
  • left_gravity: true if the mark has left gravity, false if the mark has right gravity
  • Returns: the new Gtk::TextMark


As mentioned above, two default text marks are always available in every Gtk::TextBuffer: insert and selection_bound. The "insert" mark refers to the current cursor position within the buffer. The "selection_bound" text mark refers to the starting position of the boundary for the selected (highlighted) text if there is any. If there isn't any text selected these two marks (insert and selection_bound) will point to the same position.

Theinsertandselection_boundtext marks are extremely useful. They can be set to automatically select or deselect text within a buffer and help you figure out where text should be inserted within the buffer.

Note:
Pay attention to the spelling, namely Gtk::TextBuffer::selection_bound is singular, and returns a single Gtk::TextMark. There exists also Gtk::TextBuffer::selection_bounds - plural. The latter returns an array of two iters and the Boolean value in this order:[start, end, selected], if some text is selected. In other words Gtk::TextBuffer::selection_bounds returns the bounds of the selection in "start" and "end" and true in the flag called "selected".

Editing the Text Buffer

txtw-itrsmrks-01.png

GTK+ provides a wide variety of methods for retrieving text iterators as well as manipulating text buffers. Not to overwhelm ourselves we will first start exploring a few, and later some more. The next program example is an application capable of retrieving and inserting a text into the text buffer:


iterators.rb

#!/usr/bin/env ruby
require 'gtk2'

# Insert the text from the GtkEntry into the GtkTextView.
def insert_text(ent, txtvu)
  mark = txtvu.buffer.selection_bound
  iter = txtvu.buffer.get_iter_at_mark(mark)
  txtvu.buffer.insert(iter, ent.text)
end

# Retrieve the selected text from the GtkTextView and
# display it to the user.
def retrieve_text(tw)

  start_iter, end_iter, selected  = tw.buffer.selection_bounds
  if !selected
    start_iter, end_iter = tw.buffer.bounds
  end

  text = tw.buffer.get_text(start_iter, end_iter)
  puts "selected=#{selected}; TEXT=#{text}"
end

window = Gtk::Window.new("Text Iterators")
window.resizable = true
window.border_width = 10
window.signal_connect('destroy') { Gtk.main_quit }
window.set_size_request(250, 150)

textview = Gtk::TextView.new

entry    = Gtk::Entry.new
insert   = Gtk::Button.new("Insert Text")
retrieve = Gtk::Button.new("Get Text")

insert.signal_connect('clicked') { insert_text(entry, textview) }
retrieve.signal_connect('clicked') { retrieve_text(textview) }

scrolled_win = Gtk::ScrolledWindow.new
scrolled_win.border_width = 5
scrolled_win.add(textview)
scrolled_win.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS)

hbox = Gtk::HBox.new(false, 5)
hbox.pack_start_defaults(entry)
hbox.pack_start_defaults(insert)
hbox.pack_start_defaults(retrieve)
vbox = Gtk::VBox.new(false, 5)
vbox.pack_start(scrolled_win, true,  true, 0)
vbox.pack_start(hbox,         false, true, 0)
window.add(vbox)
window.show_all
Gtk.main

It is not to be expected that a serious reader of this tutorial would have any difficulty understanding the main body of this example program. You may have guessed that the most important processing is happening in two top level methods (defined for Object object)insert_text andretrieve_text.

In insert_text(entry, textview) we first have to retrieve the text mark, which is next translated into text iterator, and which is then in turn used to insert a string from Gtk::Entry text field into the text buffer.

In the retrieve_text(textview) we first obtain two text iterators representing start/end positions of the selected (highlighted) text, which are subsequently used to obtain the marked (selected) text from the text buffer if any selection was made, else the entire buffer is retrieved. In both cases the results are printed on the console.

Retrieving Text Iterators and Marks

In the above example program we first obtain the text mark with Gtk::TextBuffer#selection_bound. Note that the following two lines have identical effect and meaning:

mark = textview.buffer.selection_bound
mark = textview.buffer.get_mark("selection_bound")

These methods return Gtk::TextMark object. Once we have the have a text mark, we have to translate it into text iterator with Gtk::TextBuffer#get_iter_at_mark(mark). This method returns a Gtk::TextIter object.

iter = textview.buffer.get_iter_at_mark(mark)

In the event that no text was selected we call another, different method for retrieving text iterators. Its name is Gtk::TextBuffer#bounds. It returns an array with the starting and ending Gtk::TextIter objects, which identify the starting and ending points of the entire text buffer.

Note:
In Gtk::TextBuffer there are three methods with a semantically similar name pertaining toBOUND/S. Do not confuse them. They are:


Back in our example program, when we obtained the two iterators we supply them to the Gtk::TextBuffer#get_text(start, end, include_hidden_chars=false) method, to retrieve the selection.

Caution:

When using the above Gtk::TextBuffer#get_text, you should be careful that there are no images or other non-textual object in the text buffer, since the character positions will, because of this, most likely be off. In such cases you should use Gtk::TextBuffer#get_slice instead.

Also, recall that positions refer to character and not byte positions.

Lets repeat that GTK+ also provides Gtk::TextBuffer#get_iter_at_line_index(line_number, byte_index), which will choose a position of an individual byte on the specified line. You should be extremely careful when using this function, because the index must always point to the beginning of a UTF-8 character, which may be more than one byte wide.

Rather than choosing a character offset (at an index), you may retrieve the iterator of a specified line with Gtk::TextBuffer#get_iter_at_line(line_number).

You can also get an iterator at an offset from the beginning of a line Gtk::TextBuffer#get_iter_at_line_offset(line_number, char_offset).

Changing Text Buffer Contents

So far we have been using the text buffer without mentioning the obvious changes that occur when a user is using the keyboard. Every character user types triggers the insert-text signal to be emitted. We also did not mention the changes a user can do using options from the context menu. Though all these things modify a text buffer we do not want to overwhelm you with unnecessary details and mostly ignore them, since at the end they all utilize the same text buffer editing philosophy. Up to now we have been dealing with the contents of an entire text buffer. In fact the first thing you learned was how to reset the buffer to a new value with:

Gtk::TextBuffer#.text = "Some text" 

Subsequently we've learned how to manipulate the entire buffer. However, it is also useful to edit just a portion of a document. There are quite a number of such methods available to you. The simplest of all is Gtk::TextBuffer#insert(iter, text). It will insert the text at the insertion point pointed at by the iter, and emit the insert-text signal. After insertion the iter will be invalidated, however, default signal handler revalidates it to point to the end of the inserted text. Do not miss this last point, namely, that iter will finally point at the position immediately after the inserted text!

Following is the collection of insert methods:

Revised and more complete example dealing with marks and iterators

Now that we have learned the basics, let us look at the revised "iterators.rb" example which, in addition to simple insertions at a single selected location, also deals correctly with some arbitrarily selected text. That is, you can highlight a block of consecutive characters and replace them with the text from the Gtk::Entry widget when clicking "Insert" button. The solution with buttons is not the proper way to implement this feature (more appropriate would be the use of some kind of context menu), however, the mechanics of replacing selected text using of text buffer marks and iters would be identical.

I have also included two simple debugging code segments, to additionally expose the use of Gtk::TextIter and Gtk::TextMark objects. You may have noticed that it is very easy to obtain or convert amarkto aniterwith Gtk::TextBuffer#get_iter_at_mark(mark), and that there is no equivalent feature to convert an iter to mark, though you can always create a new mark from an existing iter with Gtk::TextBuffer#create_mark(mark_name, iter, left_gravity). The debug sections also show you how the values of iterators change when you delete or insert text by printing character offset boundaries for the selection in the text buffer, i.e. selection we are replacing with the text entered in the entry widget.

But more important is the part demonstrating steps taken to replace the highlighted selection in the text buffer, where we first, if theselectedflag returned by Gtk::TextBuffer#selection_bounds contains true delete the selection in the text buffer, retrieve now by the deletion invalidated iter from the mark, and then use it to insert the string from entry widget.


Revised iterators.rb

#!/usr/bin/env ruby
require 'gtk2'

# Insert the text from the GtkEntry into the GtkTextView.
def insert_text(ent, txtvu)
  mark = txtvu.buffer.selection_bound
  iter = txtvu.buffer.get_iter_at_mark(mark)
  first, last, selected = txtvu.buffer.selection_bounds
  # ----DEBUG:1-(s)-------------------------------
    puts "first = #{first.offset}"
    puts "last  = #{last.offset}"
    puts "selected=#{selected}"   #=> selected = true or false

    mark_j = txtvu.buffer.create_mark("jack", iter, left_gravity=false)
    puts "mark_j = #{mark_j.name}"  #=> mark_j = jack
    puts "mark   = #{mark.name}"    #=> mark   = selection_bound
  # ----DEBUG:1-(e)--------------------------------
  if selected
    txtvu.buffer.delete(first, last)
    iter = txtvu.buffer.get_iter_at_mark(mark) # restore invalidated stored value
    # ----DEBUG:2-(s)--------------------------------
      puts "Selection iter after deletion: first = #{first.offset}"
      puts "Selection iter after deletion: last  = #{last.offset}"
    # ----DEBUG:2-(e)--------------------------------
  end
  txtvu.buffer.insert(iter, ent.text)
end

# Retrieve the selected text from the GtkTextView and
# display it to the user.
def retrieve_text(tw)

  start_iter, end_iter, selected  = tw.buffer.selection_bounds
  if !selected
    start_iter, end_iter = tw.buffer.bounds
  end

  text = tw.buffer.get_text(start_iter, end_iter)
  puts "selected=#{selected}; TEXT=#{text}"
end

window = Gtk::Window.new("Text Iterators")
window.resizable = true
window.border_width = 10
window.signal_connect('destroy') { Gtk.main_quit }
window.set_size_request(250, 150)

textview = Gtk::TextView.new

entry    = Gtk::Entry.new
insert   = Gtk::Button.new("Insert Text")
retrieve = Gtk::Button.new("Get Text")

insert.signal_connect('clicked') { insert_text(entry, textview) }
retrieve.signal_connect('clicked') { retrieve_text(textview) }

scrolled_win = Gtk::ScrolledWindow.new
scrolled_win.border_width = 5
scrolled_win.add(textview)
scrolled_win.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS)

hbox = Gtk::HBox.new(false, 5)
hbox.pack_start_defaults(entry)
hbox.pack_start_defaults(insert)
hbox.pack_start_defaults(retrieve)
vbox = Gtk::VBox.new(false, 5)
vbox.pack_start(scrolled_win, true,  true, 0)
vbox.pack_start(hbox,         false, true, 0)
window.add(vbox)
window.show_all
Gtk.main



Cutting, Copying and Pasting

When you right-click a Gtk::TextView widget, you are presented with a pop-up menu containing multiple options, of which the tree we are interested in here are Cut, Copy, and Paste. These three options also have assigned to them a set of built-in accelerator keys Ctrl-X, Ctrl+C, and Ctrl-V respectively. In the next example program (cutcopypaste.rb) we will explore these three options:

txtw-itrsmrks-02.png

cutcopypaste.rb

#!/usr/bin/env ruby
require 'gtk2'

# Copy the selected text to the clipboard and remove it from the buffer.
def cut_clicked (txtvu)
  clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
  txtvu.buffer.cut_clipboard(clipboard, true)
end
# Copy the selected text to the clipboard.
def copy_clicked (txtvu)
  clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
  txtvu.buffer.copy_clipboard(clipboard)
end
# Insert the text from the clipboard into the text buffer.
def paste_clicked (txtvu)
  clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
  txtvu.buffer.paste_clipboard(clipboard, nil, true)
end

window = Gtk::Window.new(Gtk::Window::TOPLEVEL)
window.resizable = true
window.title = "Cut, Copy & Paste"
window.border_width = 10
window.signal_connect('delete_event') { Gtk.main_quit }

textview = Gtk::TextView.new

cut    = Gtk::Button.new(Gtk::Stock::CUT)
copy   = Gtk::Button.new(Gtk::Stock::COPY)
paste  = Gtk::Button.new(Gtk::Stock::PASTE)
cut.signal_connect('clicked') { cut_clicked(textview) }
copy.signal_connect('clicked') { copy_clicked(textview) }
paste.signal_connect('clicked') { paste_clicked(textview) }

scrolled_win = Gtk::ScrolledWindow.new
scrolled_win.set_size_request(300, 200)
scrolled_win.add(textview)

hbox = Gtk::HBox.new(true, 5)
hbox.pack_start(cut,   true, true, 0)
hbox.pack_start(copy,  true, true, 0)
hbox.pack_start(paste, true, true, 0)
vbox = Gtk::VBox.new(false, 5)
vbox.pack_start(scrolled_win, true,  true, 0)
vbox.pack_start(hbox,         false, true, 0)

window.add(vbox)
window.show_all
Gtk.main

Gtk::Clipboard? is the central class to or from where data can be transferred hence, shared between different applications. To retrieve a clipboard that has already been created you should use:

clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)

As you have seen the other methods we used are Gtk::TextBuffer instance methods and are related to the three Cut, Copy and Paste behaviours we are exploring here. They are as follows:

txtvu.buffer.cut_clipboard(clipboard, true)
txtvu.buffer.copy_clipboard(clipboard)
txtvu.buffer.paste_clipboard(clipboard, nil, true)

Following is the API for these methods:

copy_clipboard(clipboard)
Copies the currently-selected text to a clipboard.
  • clipboard: the Gtk::Clipboard? object to copy to.
  • Returns: self
cut_clipboard(clipboard, default_editable)
Copies the currently-selected text to a clipboard, then deletes said text if it's editable.
  • clipboard: the Gtk::Clipboard? object to cut to
  • default_editable : true if the buffer is editable by default
  • Returns: self
paste_clipboard(clipboard, override_location, default_editable)
Pastes the contents of a clipboard at the insertion point, or at override_location. (Note: pasting is asynchronous, that is, we'll ask for the paste data and return, and at some point later after the main loop runs, the paste data will be inserted.)
  • clipboard : the Gtk::Clipboard? to paste from
  • override_location : location to insert pasted text, or nil for at the cursor
  • default_editable : true if the buffer is editable by default
  • Returns: self

Though it is possible to manipulate the clipboard directly, for the simplest clipboard actions such as cuting, copying and pasting text it makes more sense to use just mentioned Gtk::TextBuffer's built-in instance methods.



Searching in the Text Buffer

In many applications that use the Gtk::TextView widget, you will need to search through a text buffer. GTK+ provides two search functions, to find a string in a text buffer: Gtk::TextIter#forward_search, and Gtk::TextIter#backward_search.

forward_search(str, flags, limit)
Searches forward for str. Any match is returned by an array of Gtk::TextIter [match_start, match_end]. match_start is set to the first character of the match, and match_end to the first character after the match. The search will not continue past limit. You may need Gtk::TextIter flags (GtkTextSearchFlags). The flags are needed among other things to manage embedded pixbufs or child widgets. If these flags are not given, the match must be exact.
  • str: a search string
  • flags: flags affecting how the search is done (GtkTextSearchFlags)
  • limit: bound for the search, or nil for the end of the buffer
  • Returns: an array of Gtk::TextIter or nil
backward_search(str, flags, limit)
Same as Gtk::TextIter#forward_search, but moves backward.
  • str: search string
  • flags: bitmask of flags affecting the search (GtkTextSearchFlags)
  • limit: location of last possible match_start, or nil for start of buffer
  • Returns: an array of Gtk::TextIter or nil

GtkTextSearchFlags

Gtk::TextIter::SEARCH_TEXT_ONLY
Ignore images, child widgets, or any other type of nontextual objects when searching.
Gtk::TextIter::SEARCH_VISIBLE_ONLY
Do not search for hidden (invisible) elements.
Gtk::TextIter::SEARCH_CASE_INSENSITIVE
Though the meaning is self-explanatory, this flag is not supported at this point. (By default all searching is case sensitive).

The following example demonstrates the use of the first of the above two methods to search for a text string in a Gtk::TextBuffer. The search begins when the user clicks the Gtk::Stock::FIND button.

txtw-itrsmrks-03.png

find.rb

#!/usr/bin/env ruby
require 'gtk2'

# Search for the entered string within the GtkTextView.
# Then tell the user how many times it was found.
def search(ent, txtvu)
  start = txtvu.buffer.start_iter
  #                   forward_search(find, flags, limit=(nil==entire text buffer))
  first, last = start.forward_search(ent.text, Gtk::TextIter::SEARCH_TEXT_ONLY, nil)
  count = 0
  while (first)
    start.forward_char
    first, last = start.forward_search(ent.text, Gtk::TextIter::SEARCH_TEXT_ONLY, nil)
    start = first
    count += 1
  end
  # Gtk::MessageDialog.new(parent, flags, message_type, button_type, message = nil)
  dialogue = Gtk::MessageDialog.new(
            nil,
            Gtk::Dialog::MODAL,
            Gtk::MessageDialog::INFO, 
            Gtk::MessageDialog::BUTTONS_OK,
             "The string #{ent.text} was found #{count} times!"
  )
  dialogue.run
  dialogue.destroy
end

window = Gtk::Window.new(Gtk::Window::TOPLEVEL)
window.resizable = true
window.title = "Searching Buffers"
window.border_width = 10
window.signal_connect('delete_event') { Gtk.main_quit }

textview = Gtk::TextView.new

entry      = Gtk::Entry.new
entry.text = "Search for ..."
find       = Gtk::Button.new(Gtk::Stock::FIND)

find.signal_connect('clicked') { search(entry, textview) }

scrolled_win = Gtk::ScrolledWindow.new
window.set_size_request(250, 200)
scrolled_win.add(textview)
scrolled_win.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS)

hbox = Gtk::HBox.new(false, 5)
hbox.pack_start_defaults(entry)
hbox.pack_start_defaults(find)
vbox = Gtk::VBox.new(false, 5)
vbox.pack_start(scrolled_win, true,  true, 0)
vbox.pack_start(hbox,         false, true, 0)
window.add(vbox)
window.show_all
Gtk.main

The first thing the search procedure has to do is retrieve the lower search bound of the document with Gtk::TextBuffer#start_iter. We do not need the bounding position of the buffer because by leaving the search unbound, it will automatically set the end of the document as the limit for the search.

Forward search through the buffer is performed with Gtk::TextIter#forward_search(find, flags, limit). Note difference in types (classes) of objects for instance methods Gtk::TextBuffer#start_iter and Gtk::TextIter#forward_search. The former is performed on a text buffer while the latter operates on an iterator. When you think of it this is quite logical. You should get well acquainted with the arguments and return values of the Gtk::TextIter#forward_search:

first, last = start.forward_search(ent.text, Gtk::TextIter::SEARCH_TEXT_ONLY, nil)

Note also thatstart iterator is advanced for one character before the forward_search is run inside the while loop.

start.forward_char

This is a rather profound move since one may conclude that the very first match, should it be at the very beginning of the text buffer, would never be found. That is not the case, however, since the Gtk::TextBuffer#start_iter returns the iter at text buffer offset 0 (ZERO), which just like the last position in the text buffer should never be populated with any text.

start = first

Similarly, when viewed after the execution of start.forward_char the statement start = first which advances the search to the next match deserves a special attention. Namely, the "start" and "first" iterators after start = first is executed become one and the same iterator. If it were important that the two variables would remain different start = first.clone should be called instead.

Following is a brief summary for this iterator instance method. Pay attention to return values:

Gtk::TextIter#forward_search(str, flags, limit)
Searches forward for str. Any match is returned by an array of Gtk::TextIter [match_start, match_end]. match_start is set to the first character of the match and match_end to the first character after the match. The search will not continue past limit. If you specify Gtk::TextIter::SEARCH_TEXT_ONLY flag, the match may have pixbufs or child widgets mixed inside the matched range. If these flags are not given, the match must be exact.
  • str: a search string
  • flags: flags affecting how the search is done (GtkTextSearchFlags)
  • limit: bound for the search, or nil for the end of the buffer
  • Returns: an array of Gtk::TextIter or nil

Let us revisit the GtkTextSearchFlags

Gtk Text Search Flags


If you do not specify the Gtk::TextIter::SEARCH_TEXT_ONLY flag, you will need to use the special 0xfffc character to represent embedded child widgets and pixbufs. Match must be exact, so ignoring non-textual elements in the text buffer with this flag is probably a good idea.


Improved find.rb program

It would be nice to highlight all found text instances that we find in our text buffer. But this requires that we introduce a new concept, namely, the Gtk::TextTag. We do this on the following page under the title Text Tags. Nevertheless, I decided to include here the improved "find.rb" program version in which the highlighting feature is added. (If you wish to first learn about Gtk::TextTag, by all means jump first to the next page and read the article Text Tags and then return here.)

findNtextTags.png

findNtextTags.rb

#!/usr/bin/env ruby
require 'gtk2'

# Search for the entered string within the GtkTextView.
# Then tell the user how many times it was found.
def search(ent, txtvu)
  start = txtvu.buffer.start_iter
  first, last = start.forward_search(ent.text, Gtk::TextIter::SEARCH_TEXT_ONLY, nil)
  count = 0
  while (first)
    start.forward_char
    first, last = start.forward_search(ent.text, Gtk::TextIter::SEARCH_TEXT_ONLY, nil)
    start = first
    txtvu.buffer.apply_tag("highlight", first, last)
    count += 1
  end

  dialogue = Gtk::MessageDialog.new(
            nil,
            Gtk::Dialog::MODAL,
            Gtk::MessageDialog::INFO, 
            Gtk::MessageDialog::BUTTONS_OK,
             "The string #{ent.text} was found #{count} times!"
  )
  dialogue.run
  dialogue.destroy
end

window = Gtk::Window.new("Searching & Text Tags")
window.resizable = true
window.border_width = 10
window.signal_connect('destroy') { Gtk.main_quit }
window.set_size_request(250, 200)

textview = Gtk::TextView.new

textview.buffer.create_tag("highlight", {"background" => "#f8d60d", "foreground" => "red"})
entry      = Gtk::Entry.new
entry.text = "Search for ..."
find       = Gtk::Button.new(Gtk::Stock::FIND)

find.signal_connect('clicked') { search(entry, textview) }

scrolled_win = Gtk::ScrolledWindow.new
scrolled_win.add(textview)
scrolled_win.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS)

hbox = Gtk::HBox.new(false, 5)
hbox.pack_start_defaults(entry)
hbox.pack_start_defaults(find)
vbox = Gtk::VBox.new(false, 5)
vbox.pack_start(scrolled_win, true,  true, 0)
vbox.pack_start(hbox,         false, true, 0)
window.add(vbox)
window.show_all
Gtk.main

As mentioned above, you should consult the next page (Text Tags) to understand all the code here. Let me only point out the two relevant pieces of code:

textview.buffer.create_tag("highlight", {"background" => "#f8d60d", "foreground" => "red"})

and in the method search(ent, txtvu):

txtvu.buffer.apply_tag("highlight", first, last)



Scrolling the Text Buffer

When you are searching through a text buffer with either Gtk::TextIter#forward_search or Gtk::TextIter#forward_search or Gtk::TextIter#backward_search, GTK+ will not automatically scroll to search matches that you select. To do this, you need to first call Gtk::TextBuffer#create_mark to create a temporary Gtk::TextMark at the location of the found text:

create_mark(mark_name, iter, left_gravity)

Creates a mark at position iter. If mark_name is nil, the mark is anonymous; otherwise, the mark can be retrieved by name using Gtk::TextBuffer#get_mark. If a mark has left gravity, and text is inserted at the mark's current location, the mark will be moved to the left of the newly-inserted text. If the mark has right gravity (left_gravity = false), the mark will end up on the right of newly-inserted text. The standard left-to-right cursor is a mark with right gravity (when you type, the cursor stays on the right side of the text you're typing).


Emits the "mark_set" signal as notification of the mark's initial placement.

  • mark_name: name for mark(String), or nil
  • iter: location to place mark (Gtk::TextIter)
  • left_gravity: true if the mark has left gravity, false if the mark has right gravity
  • Returns: the new Gtk::TextMark

Next you have to scroll to the mark with Gtk::TextView#scroll_mark_onscreen:

scroll_mark_onscreen(mark)
Scrolls the Gtk::TextView the minimum distance such that mark is contained within the visible area of the widget.

The problem with Gtk::TextView#scroll_mark_onscreen is that it will only scroll the minimum distance to show the mark on the screen. However, you may want the mark to be centred within the visible buffer. To specify the alignment parameters for where the mark appears within the visible buffer, call Gtk::TextView#scroll_to_mark:

scroll_to_mark(mark, within_margin, use_align, xalign, yalign)
Scrolls the Gtk::TextView so that mark is on the screen in the position indicated by xalign and yalign. An alignment of 0.0 indicates left or top, 1.0 indicates right or bottom, 0.5 means center. If use_align is false, the text scrolls the minimal distance to get the mark onscreen, possibly not scrolling at all. The effective screen for purposes of this method is reduced by a margin of size within_margin.
  • mark: a Gtk::TextMark
  • within_margin: margin as a 0.0, 0.5 fraction of screen size
  • use_align: whether to use alignment arguments (if false, just get the mark onscreen)
  • xalign: horizontal alignment of mark within visible area.
  • yalign: vertical alignment of mark within visible area
  • Returns: self

You begin by setting a margin, which reduces the scrollable area. The margin must be a floating-point number, to reduce the area by a factor. In most cases you will want to use 0.0 as the margin, so the area is not reduced at all.

If you set use_align to false, the method will scroll the minimal distance to get mark on the screen. Otherwise, the method will use the two alignment parameters as guides, which allows you to specify horizontal and vertical alignment of the mark within the visible area.

An alignment 0.0 refers to the left or top, 1.0 refers to the right or bottom, and 0.5 refers to the centre.

There is yet another instance method Gtk::TextView#scroll_to_iter, which behaves in a similar manner as Gtk::TextView#scroll_to_mark. The difference is obvious one receives mark the other iterator for the location. In most cases you should use mark.

Last modified:2012/09/16 06:20:21
Keyword(s):
References:[tut-gtk2-txtw] [tut-gtk] [tut-gtk2-txtw-itrsmrks-cgtk-01] [tut-gtk2-txtw-ttags]