Create  Edit  FrontPage  Index  Search  Changes  History  RSS  Login

tut-gtk2-txtw-textview

Text Views

txtw-textview-01.png

The Gtk::TextView widget is used to display multiple lines of a text document. It provides many ways to customize the text within the widget. You can even insert Gdk::Pixbuf objects and other child widgets into a document. Gtk::TextView is the first reasonably involved widget we've encountered so far and in this session we will explore most of its interesting features. It is a versatile widget that you will exploit in many GTK+ applications.

As just hinted, this widget is not only used to display simple text, it can also be used to display many types of rich text, and interactive documents used by a variety of different applications. This is what we will learn here and in the following pages.

Let us start with the simple minimal example of Gtk::TextView widget, that we borrowed in the introduction to scrolling on the previous page. The first thing to remember is that text view has a built in scrolling support and that it has to be added to the Gtk::ScrolledWindow widget. The "textview.rb" listing illustrates the simplest text view example that you could create:

textview.rb

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

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

textview = Gtk::TextView.new
textview.buffer.text = "Your 1st Gtk::TextView widget!"

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

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

Text Buffers

Each text view is used to display the contents of a class called Gtk::TextBuffer. Text buffers are used to store the current state of the container within the text view. You can guess this from the following line in our example program above.

textview.buffer.text = "Your 1st Gtk::TextView widget!"

Here the buffer component is the Gtk::TextBuffer. Text buffers hold text, images, child widgets, text tags and other information necessary for rendering the documents. A Gtk::TextBuffer buffer is an independent object and can be displayed by many text view widgets. However any text view has only a single text buffer associated with it. Most programmers do not take advantage of this feature, but it will become important latter when you learn how to embed child widgets into a text buffer.

A Short D-tour:

If you understand the relationship between the two Gtk classes, namely between Gtk::TextView and Gtk::TextBuffer, you will not miss anything by ignoring this D-tour. But if you are not exactly sure what was just said in the above paragraph, then following short demonstration simulating how Gtk::TextView and Gtk::TextBuffer are implemented, may help you understand this relationship and show you how to exploit this design to create some text views that share the same Gtk::TextBuffer and some that do not.

In tv-N-tb-example.rb sample program pay attention to three different tview_instance_# TextView instances. The first one, designated with number zero (0) has its own (not shared) TextBuffer, however, the other two (#1 and #2) share the TextBuffer instance:

tv-N-tb-example.rb

#!/usr/bin/env ruby
class TextView
  attr_accessor :buffer
  def initialize(buffer=nil); @buffer = (buffer) ? buffer : "Auto-Initialzed"; end
end
class TextBuffer
  attr_accessor :text
  def initialize(text); @text = text; end
  def to_s; "Class TextBuffer: text=#@text"; end
end
def show_text_views_buffer(id, tv_inst)
  puts "Buffer ID=#{id} - #{tv_inst.class}: #{tv_inst.buffer.to_s}"
end

tview_instance_0 = TextView.new    # (nil)
show_text_views_buffer(0, tview_instance_0)
tb=TextBuffer.new("Some initial text")
tview_instance_1 = TextView.new(tb)
tview_instance_2 = TextView.new(tb)
show_text_views_buffer(1, tview_instance_1)
tview_instance_2.buffer.text = "Override the initial text with new text."
puts "----- after overriding text buffer -------"
show_text_views_buffer(0, tview_instance_0)
show_text_views_buffer(1, tview_instance_1)
show_text_views_buffer(2, tview_instance_2)  

I suggest you run this program and experiment with it, until the design similarity with Gtk::TextView and Gtk::TextBuffer becomes apparent.

Most new text views are created with the Gtk::TextView.new(buffer=nil). When using this method with the default nil argument an empty Gtk::TextBuffer is created for you. This empty text buffer can be replaced or retrieved by "Gtk::TextView#buffer=(buff)", or "Gtk::TextView#set_buffer(buff)", and "buff = Gtk::TextView#buffer" respectively. With the former two methods the contents of the buffer gets completely replaced. However, as we will see on the following page in paragraphs entitledText Iterators and Marks,one can also make changes only to small segments of a text buffer, i.e. change text buffer partially.

Recall that we have pointed out that a few widgets provide native scrolling support. Well, Gtk::TextView is one of them. This means you should use Gtk::Container#add method, rather than Gtk::ScrolledWindow#add_with_viewport(child), to add Gtk::TextView to the scrolled windows.



txtv-utf-8.png

When handling text buffers, you will be dealing with terms such asofsetandindex.As with all text widgets in GTK+, text is stored in UTF-8 strings, hence individual characters may be more than one byte long. This may effect the overall meaning or value that offset and index will represent. You can tell what is the size in bytes for an UTF-8 character by looking at the initial bit(s) of the character. Consult the table on the left for details.

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 View Properties

txtw-textview-02.png

Gtk::TextView widget was created to be very versatile. Because of this, many properties are provided for the widget. In the next example program we will look only at the properties that apply changes to the whole buffer. We will look at the possibility to affect only part of the text buffer shortly when discussingtags.

Let's have a look at the following example program demonstrating how properties can be used to customize the text:


textview2.rb

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

def destroy; Gtk.main_quit; end

window = Gtk::Window.new(Gtk::Window::TOPLEVEL)
window.resizable = true
window.title = "Text View Properties"
window.border_width = 10
window.signal_connect('delete_event') { destroy }
window.set_size_request(250, 150)

textview = Gtk::TextView.new
font = Pango::FontDescription.new("Monospace Bold 10")
textview.modify_font(font)
textview.wrap_mode = Gtk::TextTag::WRAP_WORD
textview.justification = Gtk::JUSTIFY_RIGHT
textview.editable =  true
textview.cursor_visible =  true
textview.pixels_above_lines = 5
textview.pixels_below_lines = 5
textview.pixels_inside_wrap = 5
textview.left_margin = 10
textview.right_margin = 10
textview.buffer.text = "This is some text!\nChange me!\nPlease!"

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

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

The following two properties need the reference to GTK+ constants:

textview.wrap_mode = Gtk::TextTag::WRAP_WORD

For your convenience I list all the relevant GTK constants also here:

You can find the constants for the above line at: Gtk::TextTag#GtkWrapMode

textview.justification = Gtk::JUSTIFY_RIGHT

For your convenience:

And again, you can find these constants for the above code segment at: Gtk#GtkJustification

Gtk::TextView#editable? and Gtk::Widget#sensitive?:
Sometimes you may want to prevent users from editing the document. You accomplish this with the Gtk::TextView#editable= method to set the editable property. Though this property is used to set this behaviour for the entire text buffer, with text tags you can override this to affect only a part of the text. Compare this with Gtk::Widget#sensitive= which is even more restrictive than Gtk::TextView#editable=. With sensitive you can also disallow a text to be selected and perform cut-and-paste operations, which are still allowed if you set the text only not to be editable.

You will often want to change font

Changing font is easy. You first need to obtain a valid font name, sometimes also called description, since it describes font properties such as "[FAMILY-LIST] [STYLE-OPTIONS] [SIZE]". In API we find the following for Pango::FontDescription:

Pango::FontDescription.new(str = nil):
Creates a new font description from a string representation in the form "[FAMILY-LIST] [STYLE-OPTIONS] [SIZE]", where FAMILY-LIST is a comma separated list of families optionally terminated by a comma, STYLE_OPTIONS is a whitespace separated list of words where each WORD describes one of style, variant, weight, or stretch, and SIZE is an decimal number (size in points). Any one of the options may be absent. If FAMILY-LIST is absent, then the family_name field of the resulting font description will be initialized to nil. If STYLE-OPTIONS is missing, then all style options will be set to the default values. If SIZE is missing, the size in the resulting font description will be set to 0. If str is nil, constructor creates a new font description structure with all fields unset.

In our example we set the font with the following code:

font = Pango::FontDescription.new("Monospace Bold 10")
textview.modify_font(font)

Sometimes you may want to give user the control over which font style and size they would like to set the font to. In that event you would most likely employFont Selection Dialogand then initialiye the font in a way reminiscent of the code we have just seen. Let's run our imagination and pretend we needed something like the following:

font = dialog.font_name
desc = Pango::FontDescription.new(font)
textview.modify_font(desc)  

For a few more explanations and examples check the Font Manipulation Methods in this tutorial.

Pango Tab Arrays

You' ve seen Pango a few times already in our tutorials. Lets first see, how it is defined on Wiki pages:

What is Pango:
Pango is an LGPL licensed free software computing library used by software developers for laying out and rendering text in high quality, emphasising support for multilingual text. Different font backends can be used, allowing cross-platform support, so that Pango-rendered text will appear similar under different operating systems, such as Linux, Apple's Mac OS, and Microsoft Windows.

txtw-textview-03.png

Here we are discussingPango Tab Arraysin relation to editing a text buffer.TABsare a special kind of character that can seem to behave a little bit differently, depending on which font is currently active. The most consistent behavior producemonospacedfonts. This is because all characters for such a font take up the same amount of space on a graphical display. On the image depicting our next program example, here on the right, you can see that cursor is at the position fifteen spaces to the right from the left margin. Incidentally this is also the TAB size. You can run the program and see for yourself, that this indeed is where the TAB key would position the cursor if you pressed it after you started the sample program.

Tabs added to a text view are set to a default width, but there are times when this is not what you want. GTK+ provides Pango::TabArray object, which can help you define a new tab size. In order to change the tab size you must first calculate the number of horizontal pixels the tab will take up based on the current font. Let us expand our first simple example (textview.rb) by adding a tab size altering function called make_tab_array. In this program the tab size is arbitrarily restricted be less than 20 characters.


textview-pango-tabs.rb

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

def make_tab_array(textview, tab_size, font_desc)
  raise "Tab size can't be more than 20" if tab_size > 20
  tab_string = " " * tab_size
  layout = textview.create_pango_layout(tab_string)
  layout.font_description = font_desc
  width, height = layout.pixel_size
  tab_array = Pango::TabArray.new(1, true)
  tab_array.set_tab(0, Pango::TAB_LEFT, width)
  textview.set_tabs(tab_array)
end

window = Gtk::Window.new(Gtk::Window::TOPLEVEL)
window.resizable = true
window.title = "TABs in Text Views"
window.border_width = 10
window.signal_connect('delete_event') { Gtk.main_quit }
window.set_size_request(250, 150)

textview = Gtk::TextView.new
textview.buffer.text = "Tab is now set to 15!\n" +
                       "You can hit the TAB key\n" +
                       "and see for yourself:\n" +
                       "123456789012345678901234567890\n"

# For font styles "Italic", "Bold", "Bold Italic" and
# "Regular" currently, all except "Regular" work fine.
# However, this is really not a Ruby but general version
# "C" GTK+ problem!
font = Pango::FontDescription.new("Monospace Italic 8")
textview.modify_font(font)
make_tab_array(textview, 15, font)

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

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

The Pango::Layout object is used to represent a whole paragraph of text. Normally, Pango uses it internally for laying out text within a widget. However, it can be employed as in our example in the make_tab_array function to calculate the width of the tab string. Note that using tab, let alone changing it makes sense only when you have a mono-spaced text as well when you use the same font through out the text.

In make_tab_arraymethod we use Pango::Layout object and a set of other Pango devices to set up our TAB.We start up with Gtk::Widget#create_pango_layout(text=nil). In general Pango::Layout object is created by Pango::Layout.new, which creates a new Pango::Layout object with attributes initialized to default values for a particular Pango::Context. However, in our sample program we are employing Gtk::Widget#create_pango_layout, with a string parameter containing as many spaces as we would like our TAB to expand. instead. API documentation tells us that Gtk::Widget#create_pango_layout(text=nil) creates a new Pango::Layout with the appropriate colormap, font description, and base direction for drawing text for this widget.

Next we need to tell our Pango::Layout object what is the font we are using in the text buffer, for which we wish to calculate TAB size. At this point we can request the pixel size, which returns an array of width and height elements. As TAB is concerned with the horizontal layout only, we are interested in width only. We will need to convey the size to Pango::TabArray, which we create next. We will accomplish this with Pango::TabArray#set_tab to which just obtained pixel width is passed as the third argument. Finally we set the tabs for our text buffer and pass it our tab array object as the argument.

Between the following are the descriptions of the methods we used in order to implement our tab size altering functionality:

Gtk::Widget#create_pango_layout(text=nil)
Creates a new Pango::Layout with the appropriate colormap, font description, and base direction for drawing text for this widget. If you keep a Pango::Layout created in this way around, in order notify the layout of changes to the base direction or font of this widget, you must call Pango::Layout#context_changed in response to the ::style_set and ::direction_set signals for the widget.
  • text: text to set on the layout (can be nil)
  • Returns: the new Pango::Layout
Pango::Layout#font_description
Gets the font description for the layout, if any.
  • Returns: the layout's font description(Pango::FontDescription), or nil if the font description from the layout's context is inherited.
Pango::Layout#font_description=(desc)
Sets the default font description for the layout. If no font description is set on the layout, the font description from the layout's context is used.
Pango::Layout#pixel_size
Determines the logical width and height of a Pango::Layout in device units. (Pango::Layout#size returns the width and height in thousandths of a device unit.) This is simply a convenience method around Pango::Layout#extents.
  • Returns: [width, height]
    • width: the logical width
    • height: the logical height
Pango::TabArray#set_tab(0, Pango::TAB_LEFT, width)
Before applying the tab array, you need to add the width.
  • 0: refers to the first element in the Pango::TabArray, the only one that should ever exist.
  • Pango::TAB_LEFT - must always be specified; it is the only constant in Pango#TabAlign
  • width: the width of the tab in pixels
  • Returns: self: FIXME ???
Gtk::TextView#tabs
Gets the default tabs for text_view. Tags in the buffer may override the defaults. The returned array will be nil if "standard" (8-space) tabs are used.
  • Returns: copy of default tab array, or nil if "standard" tabs are used
Gtk::TextView#tabstabs=(tabs)
Sets the default tab stops for paragraphs in the Gtk::TextView. Tags in the buffer may override the default.
Gtk::TextView#tabsset_tabs(tabs)
Same as Gtk::TextView#tabs=.
Last modified:2012/09/15 06:35:11
Keyword(s):
References:[tut-gtk] [tut-gtk2-txtw]