Create  Edit  FrontPage  Index  Search  Changes  History  RSS  Login

tut-gtk2-txtw-ttags

Text Tags

There are many methods provided for changing properties of the entire text within the text buffer. But as already mentioned, it is also possible to change the display properties only of a portion of text in the text buffer. You do this with a new class Gtk::TextTag.

Note:
As this tutorial is built on the Andrew Krause's book "Foundations of GTK+ Development", which explains the original C GTK+ API, sometimes the example programs he chose I would hesitate to use in Ruby text. Such is the case with the following program, which in C GTK+ version is much more illuminating than in Ruby. For an introduction to Gtk::TextTag I believe a much simpler Ruby example can be beneficial. I have devised just such an example in previous chapter called findNtextTags.rb. So if you find the following original Amndrew's example program (texttags.rb) a bit overwhelming, I encourage you to revisit the example Improved "find.rb" program on previous page, where, I assure you, you will have no trouble understanding how to implement Gtk::TextTag. On second reading though, I believe Andrew's program is a much better learning material.


txtw-ttags.png

As hinted in the above note, this program example unfortunately deals with an abundance of peripheral issues, which obfuscate the simple Gtk::TextTag issues. The most prominent of these side issues is how the "tag property names" are handled in the code. For the tutorial's sake it would be possible to simplify the code significantly if we didn't insist that the tag property names like "bold", "italic", etc., would not be called as it is appropriate, but wold just keep their little deformed stock-item names ("gtk-bold", "gtk-italic", etc). Or if instead of stock-items on button labels we would just use plain strings like "bold", "italic", etc. Yet another notch of complications represents the code optimization with the ts(text scale) array which is used to automate the tag creation process for the items in the combo box. For the sake of the tutorial it would be just as good, if we manually coded the font sizes for the selections in the combo box. However, the suggested simplifications would make the example less realistic and more dry or theoretical. The texttags.rb example, I must underscore, is quite good, however it is not best suited for a quick tutorial, that is why I am referring you to Improved "find.rb" program on previous page, though you may first read the narrative in this chapter to get acquainted with the Gtk::TextTag.


texttags.rb

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

# Retrieve the tag from the "tag" object data and apply it
# to the selection.
def format(b, txtvu)
  s, e = txtvu.buffer.selection_bounds

  # Our buttons contain Gtk::Stock#Items. Their label values
  # begin with "gtk-", ie: gtk-bold, gtk-italic, ... We need
  # the word after "gtk-" ie.: bold, italic ... which are used
  # as tag property names; Hence Ruby's gsub (sub-string) 
  # extracts the 2nd string
  tag_property_name = b.label.gsub(/(gtk-)([a-z]+)/, '\2')

  txtvu.buffer.apply_tag(tag_property_name, s, e)
end

# Apply the selected text size property as the tag.
def scale_changed(combo, txtvu)
  return if combo.active == -1
  s, e = txtvu.buffer.selection_bounds
  txtvu.buffer.apply_tag(combo.active_text, s, e)
  combo.active = -1
end

# Remove all of the tags from the selected text.
def clear_clicked(txtvu) 
  s, e = txtvu.buffer.selection_bounds
  txtvu.buffer.remove_all_tags(s, e)
end

class TextToDouble
  attr_accessor :str, :scale
  def initialize(str, scale)
    @str, @scale = str, scale
  end
end

ts = Array.new
ts[0] = TextToDouble.new("Quarter Sized",      0.25)
ts[1] = TextToDouble.new("Double Extra Small", Pango::SCALE_XX_SMALL)
ts[2] = TextToDouble.new("Extra Small",        Pango::SCALE_X_SMALL)
ts[3] = TextToDouble.new("Small",              Pango::SCALE_SMALL)
ts[4] = TextToDouble.new("Medium",             Pango::SCALE_MEDIUM)
ts[5] = TextToDouble.new("Large",              Pango::SCALE_LARGE)
ts[6] = TextToDouble.new("Extra Large",        Pango::SCALE_X_LARGE)
ts[7] = TextToDouble.new("Double Extra Large", Pango::SCALE_XX_LARGE)
ts[8] = TextToDouble.new("Double Sized",       2.0)

window = Gtk::Window.new(Gtk::Window::TOPLEVEL)
window.resizable = true
window.title = "Text Tags"
window.border_width = 10
window.signal_connect('delete_event') { Gtk.main_quit }
window.set_size_request(500, -1)

textview = Gtk::TextView.new

buffer = textview.buffer

# Note: the tag names are stored with buttons in their labels, where
# each label has string form "gtk-bold", "gtk-italic", ...
buffer.create_tag("bold",          {"weight"        => Pango::WEIGHT_BOLD})
buffer.create_tag("italic",        {"style"         => Pango::STYLE_ITALIC})
buffer.create_tag("strikethrough", {"strikethrough" => true})
buffer.create_tag("underline",     {"underline"     => Pango::UNDERLINE_SINGLE})

bold      = Gtk::Button.new(Gtk::Stock::BOLD)
italic    = Gtk::Button.new(Gtk::Stock::ITALIC)
underline = Gtk::Button.new(Gtk::Stock::UNDERLINE)
strike    = Gtk::Button.new(Gtk::Stock::STRIKETHROUGH)
clear     = Gtk::Button.new(Gtk::Stock::CLEAR)
scale     = Gtk::ComboBox.new      # (text=true)

# Add choices to the GtkComboBox widget.
ts.each do |e|
  scale.append_text(e.str)
  buffer.create_tag(e.str, { "scale" => e.scale } )
end

# Connect each of the buttons and the combo box to the necessary signals.
bold.signal_connect("clicked")      { |w| format(w, textview) }
italic.signal_connect("clicked")    { |w| format(w, textview) }
underline.signal_connect("clicked") { |w| format(w, textview) }
strike.signal_connect("clicked")    { |w| format(w, textview) }
scale.signal_connect("changed")     { |w| scale_changed(w, textview) }
clear.signal_connect("clicked")     {     clear_clicked(textview) }

# Pack the widgets into a GtkVBox, GtkHBox, and then into the window. */
vbox = Gtk::VBox.new(true, 5)
vbox.pack_start(bold,      false, false, 0)
vbox.pack_start(italic,    false, false, 0)
vbox.pack_start(underline, false, false, 0)
vbox.pack_start(strike,    false, false, 0)
vbox.pack_start(scale,     false, false, 0)
vbox.pack_start(clear,     false, false, 0)

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(scrolled_win, true,  true, 0)
hbox.pack_start(vbox,         false, true, 0)

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

When you create a text tag, it is added to a Gtk::TextBuffer's tag table, an object that holds all the text buffer's tags. This happens behind the scene, when you execute Gtk::TextBuffer#create_tag command. This command has two arguments: tag_name, properties. The tag_name can be either a string or nil. If tag_name is nil, the tag is anonymous.

create_tag(tag_name, properties)
Creates a tag and adds it to the tag table for buffer. If tag_name is non-nil, a tag called tag_name must not already exist in the tag table for this buffer.
  • tag_name: name of the new tag, or nil
  • properties: a hash of property names and values {name1 => value1, name2 => value2, ...}
  • Returns: a new Gtk::TextTag

The first argument tag_name is simple. It is an optional tag name. However, the second argument is more involved. It is a hash that may contain any number of name-value pairs representing tag's properties. For example you may have a tag called "colors", and two properties "background" and "foreground" with their respective values:

buff.create_tag("colors",   
                { "foreground" => "#FFFFFF",
                  "background" => "#000000" }
)

It should be pointed out that in the above code snippet the tag_name is an arbitrary string value, however the properties in the hash array can only be those found in Gtk::TextTag#Properties. In the API you can look up the properties you can define on the just mentioned link (Gtk::TextTag#Properties), and for your convenience I list them also here below:

Text Tag Properties
  • background: - String (Write); Background color as a string
  • background-full-height: - true or false (Read/Write); Whether the background color fills the entire line height or only the height of the tagged characters
  • background-full-height-set: - true or false (Read/Write); Whether this tag affects background height
  • background-gdk: - Gdk::Color (Read/Write); Background color as a (possibly unallocated) Gdk::Color
  • background-set: - true or false (Read/Write); Whether this tag affects the background color
  • background-stipple: - Gdk::Pixmap (Read/Write); Bitmap to use as a mask when drawing the text background
  • background-stipple-set: - true or false (Read/Write); Whether this tag affects the background stipple
  • direction: - Integer (Read/Write); Text direction, e.g. right-to-left or left-to-right
  • editable: - true or false (Read/Write); Whether the text can be modified by the user
  • editable-set: - true or false (Read/Write); Whether this tag affects text editability
  • family: - String (Read/Write); Name of the font family, e.g. Sans, Helvetica, Times, Monospace
  • family-set: - true or false (Read/Write); Whether this tag affects the font family
  • font: - String (Read/Write); Font description as a string, e.g. "Sans Italic 12"
  • font-desc: - Pango::FontDescription (Read/Write); Font description as a Pango::FontDescription struct
  • foreground: - String (Write); Foreground color as a string
  • foreground-gdk: - Gdk::Color (Read/Write); Foreground color as a (possibly unallocated) Gdk::Color
  • foreground-set: - true or false (Read/Write); Whether this tag affects the foreground color
  • foreground-stipple: - Gdk::Pixmap (Read/Write); Bitmap to use as a mask when drawing the text foreground
  • foreground-stipple-set: - true or false (Read/Write); Whether this tag affects the foreground stipple
  • indent: - Integer (Read/Write); Amount to indent the paragraph, in pixels
  • indent-set: - true or false (Read/Write); Whether this tag affects indentation
  • invisible: - true or false (Read/Write); Whether this text is hidden. Not implemented in GTK 2.0
  • invisible-set: - true or false (Read/Write); Whether this tag affects text visibility
  • justification: - Integer (Read/Write); Left, right, or center justification
  • justification-set: - true or false (Read/Write); Whether this tag affects paragraph justification
  • language: - String (Read/Write); The language this text is in, as an ISO code. Pango can use this as a hint when rendering the text. If you don't understand this parameter, you probably don't need it
  • language-set: - true or false (Read/Write); Whether this tag affects the language the text is rendered as
  • left-margin: - Integer (Read/Write); Width of the left margin in pixels
  • left-margin-set: - true or false (Read/Write); Whether this tag affects the left margin
  • name: - String (Read/Write); Name used to refer to the text tag. nil for anonymous tags
  • pixels-above-lines: - Integer (Read/Write); Pixels of blank space above paragraphs
  • pixels-above-lines-set: - true or false (Read/Write); Whether this tag affects the number of pixels above lines
  • pixels-below-lines: - Integer (Read/Write); Pixels of blank space below paragraphs
  • pixels-below-lines-set: - true or false (Read/Write); Whether this tag affects the number of pixels above lines
  • pixels-inside-wrap: - Integer (Read/Write); Pixels of blank space between wrapped lines in a paragraph
  • pixels-inside-wrap-set: - true or false (Read/Write); Whether this tag affects the number of pixels between wrapped lines
  • right-margin: - Integer (Read/Write); Width of the right margin in pixels
  • right-margin-set: - true or false (Read/Write); Whether this tag affects the right margin
  • rise: - Integer (Read/Write); Offset of text above the baseline (below the baseline if rise is negative) in pixels
  • rise-set: - true or false (Read/Write); Whether this tag affects the rise
  • scale: - Float (Read/Write); Font size as a scale factor relative to the default font size. This properly adapts to theme changes etc. so is recommended. Pango predefines some scales such as Pango::AttrScale::X_LARGE
  • scale-set: - true or false (Read/Write); Whether this tag scales the font size by a factor
  • size: - Integer (Read/Write); Font size in Pango units
  • size-points: - Float (Read/Write); Font size in points
  • size-set: - true or false (Read/Write); Whether this tag affects the font size
  • stretch: - Integer (Read/Write); Font stretch as a PangoStretch, e.g. Pango::FontDescription::STRETCH_CONDENSED
  • stretch-set: - true or false (Read/Write); Whether this tag affects the font stretch
  • strikethrough: - true or false (Read/Write); Whether to strike through the text
  • strikethrough-set: - true or false (Read/Write); Whether this tag affects strikethrough
  • style: - Integer (Read/Write); Font style as a PangoStyle, e.g. Pango::FontDescription::STYLE_ITALIC
  • style-set: - true or false (Read/Write); Whether this tag affects the font style
  • tabs: - Pango::TabArray (Read/Write); Custom tabs for this text
  • tabs-set: - true or false (Read/Write); Whether this tag affects tabs
  • underline: - Integer (Read/Write); Style of underline for this text
  • underline-set: - true or false (Read/Write); Whether this tag affects underlining
  • variant: - Integer (Read/Write); Font variant as a PangoVariant, e.g. Pango::FontDescription::VARIANT_SMALL_CAPS
  • variant-set: - true or false (Read/Write); Whether this tag affects the font variant
  • weight: - Integer (Read/Write); Font weight as an integer, see predefined values in PangoWeight; for example, Pango::FontDescription::WEIGHT_BOLD
  • weight-set: - true or false (Read/Write); Whether this tag affects the font weight
  • wrap-mode: - Integer (Read/Write); Whether to wrap lines never, at word boundaries, or at character boundaries
  • wrap-mode-set: - true or false (Read/Write); Whether this tag affects line wrap mode
  • accumulative-margin: - true or false (Read/Write); Whether left and right margins accumulate.
  • paragraph-background: - String (Write); Paragraph background color as a string
  • paragraph-background-gdk: - Gdk::Color (Read/Write); Paragraph background color as a (possibly unallocated) Gdk::Color
  • paragraph-background-set: - true or false (Read/Write); Whether this tag affects the paragraph background color


But lets return back to our study ot thetexttags.rbprogram:

Once you created a tag with its properties you will, later on - most likely in a callback, need to retrieve it from the text buffer's tag table by specifying its tag_name. At the same time you apply it to a segment or portion of the text buffer defined by start and end iterators. In the following snippet of code we create a an entry in tag table for the Gtk::TextBuffer object called buff. We create tag called "bold" and add for it a property name/value pair as hash {"weight" => Pango::WEIGHT_BOLD}. Subsequently (later in the program; in our case in a callback) we extract and apply the property value pair for this tag:

buff.create_tag("bold",   {"weight" => Pango::WEIGHT_BOLD})
     ...
buff.apply_tag("bold", start_iter, end_iter)

NOTE: in our example program we used a trick to obtain tag property names from button labels, where they are stored as Gtk::Stock items. If you need to store some new property such as a tag value in your widgets you could simply add it to the widget class as needed. For instance you could expand the Gtk::Button class to include a new property called ttag_prop.

Just as a picture is worth a thousand words so is perhaps a listing of an executable program. Let me repeat the above example program this time with code that modifies Gtk::Button class to include a new property to store a tag_name, used in Gtk::TextBuffer's tag table:

texttags-button-ttag_prop.rb

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

# Retrieve the tag from the "tag" object data and apply it
# to the selection.
def format(b, txtvu)
  s, e = txtvu.buffer.selection_bounds
  txtvu.buffer.apply_tag(b.ttag_prop, s, e)
end

# Apply the selected text size property as the tag.
def scale_changed(combo, txtvu)
  return if combo.active == -1
  s, e = txtvu.buffer.selection_bounds
  txtvu.buffer.apply_tag(combo.active_text, s, e)
  combo.active = -1
end 

# Remove all of the tags from the selected text.
def clear_clicked(txtvu)
  s, e = txtvu.buffer.selection_bounds
  txtvu.buffer.remove_all_tags(s, e)
end

class TextToDouble
  attr_accessor :str, :scale
  def initialize(str, scale)
    @str, @scale = str, scale
  end
end

ts = Array.new
ts[0] = TextToDouble.new("Quarter Sized",      0.25)
ts[1] = TextToDouble.new("Double Extra Small", Pango::SCALE_XX_SMALL)
ts[2] = TextToDouble.new("Extra Small",        Pango::SCALE_X_SMALL)
ts[3] = TextToDouble.new("Small",              Pango::SCALE_SMALL)
ts[4] = TextToDouble.new("Medium",             Pango::SCALE_MEDIUM)
ts[5] = TextToDouble.new("Large",              Pango::SCALE_LARGE)
ts[6] = TextToDouble.new("Extra Large",        Pango::SCALE_X_LARGE)
ts[7] = TextToDouble.new("Double Extra Large", Pango::SCALE_XX_LARGE)
ts[8] = TextToDouble.new("Double Sized",       2.0)

window = Gtk::Window.new(Gtk::Window::TOPLEVEL)
window.resizable = true
window.title = "Text Tags"
window.border_width = 10
window.signal_connect('delete_event') { Gtk.main_quit }
window.set_size_request(500, -1)

textview = Gtk::TextView.new

buffer = textview.buffer
buffer.create_tag("bold",          {"weight"        => Pango::WEIGHT_BOLD})
buffer.create_tag("italic",        {"style"         => Pango::STYLE_ITALIC})
buffer.create_tag("strikethrough", {"strikethrough" => true})
buffer.create_tag("underline",     {"underline"     => Pango::UNDERLINE_SINGLE})

bold      = Gtk::Button.new(Gtk::Stock::BOLD);
italic    = Gtk::Button.new(Gtk::Stock::ITALIC);
underline = Gtk::Button.new(Gtk::Stock::UNDERLINE);
strike    = Gtk::Button.new(Gtk::Stock::STRIKETHROUGH);
clear     = Gtk::Button.new(Gtk::Stock::CLEAR);

scale     = Gtk::ComboBox.new      # (text=true)

# Our little addition to the Button class, in order to
# save what usually is stored as label, however we use
# stock item rather than a label.
class Gtk::Button; attr_accessor :ttag_prop; end

# Add the name of the text tag as a data parameter of the object.
bold.ttag_prop      = "bold"
italic.ttag_prop    = "italic"
underline.ttag_prop = "underline"
strike.ttag_prop    = "strikethrough"

# Add choices to the GtkComboBox widget.
ts.each do |e|
  scale.append_text(e.str)
  buffer.create_tag(e.str, { "scale" => e.scale } )
end

# Connect each of the buttons and the combo box to the necessary signals.
bold.signal_connect("clicked")      { |w| format(w, textview) }
italic.signal_connect("clicked")    { |w| format(w, textview) }
underline.signal_connect("clicked") { |w| format(w, textview) }
strike.signal_connect("clicked")    { |w| format(w, textview) }
scale.signal_connect("changed")     { |w| scale_changed(w, textview) }
clear.signal_connect("clicked")     {     clear_clicked(textview) }

# Pack the widgets into a GtkVBox, GtkHBox, and then into the window. */
vbox = Gtk::VBox.new(true, 5)
vbox.pack_start(bold,      false, false, 0)
vbox.pack_start(italic,    false, false, 0)
vbox.pack_start(underline, false, false, 0)
vbox.pack_start(strike,    false, false, 0)
vbox.pack_start(scale,     false, false, 0)
vbox.pack_start(clear,     false, false, 0)

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(scrolled_win, true,  true, 0)
hbox.pack_start(vbox,         false, true, 0)

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

From the programming point of view the new version of this program is really not a great improvement. However, for someone who just started learning Ruby and GTK+ the modification of a built in class Gtk::Button makes it a valuable learning exercise. In the above code pay attention to the following:

# Our little addition to the Button class, in order to
# save what usually is stored as label, however we use
# stock item rather than a label.
class Gtk::Button; attr_accessor :ttag_prop; end
Last modified:2012/09/16 07:47:43
Keyword(s):
References:[tut-gtk2-txtw-itrsmrks] [tut-gtk2-txtw] [tut-gtk]