Create  Edit  FrontPage  Index  Search  Changes  History  RSS  Login

tut-gtk2-mnstbs-statb

Status Bar

Status bar is a widget used to provide information about the application status for the time its user interface is active and displayed to the user. It appears at the bottom of the application window, though there is nothing preventing you from placing it anywhere else on the screen. To start this chapter we will add status bar to our earlier rather simple programs from chapter 3, segment called 'Tables', to demonstrate how to write a minimal status bar implementation. The program originally demonstrated the use of a Gtk::Table object, into which we placed two labels and an entry widget. The table was then added as the top widget to the main window. We wish to associate the entry object in the table with the status bar, and whenever a user moves a cursor into the entry field, display the message "Entering text" in the status bar, and removing this message, if the user moves mouse cursor out of the entry field.

To add a status bar widget we need to create a vertical box, and place the table with entry object and the labels above the newly created status bar in it. Finally we need to connect the Gtk::Widget's 'enter-notify-event'and'leave-notify-event'signal handlers to the entry object. Let's look at the example program:

simple-statusbar.rb

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

table  = Gtk::Table.new(rows=2, columns=2, homogeneous=true)
label1 = Gtk::Label.new("Enter the name here ...")
label2 = Gtk::Label.new("Name: ")
name   = Gtk::Entry.new

## -- Status bar -------------------------------- (start #1) ----------------------
def show_entry_on_sb(sbar, event)
  cid = sbar.get_context_id("StatBarTest")
  event.event_type == Gdk::Event::ENTER_NOTIFY ?
  	 sbar.push(cid, "Entering text") : sbar.pop(cid)
  return
end
statusbar = Gtk::Statusbar.new
name.signal_connect('enter_notify_event') { |w, e| show_entry_on_sb(statusbar, e) }
name.signal_connect('leave_notify_event') { |w, e| show_entry_on_sb(statusbar, e) }
## -- Status bar -------------------------------- (end #1) ------------------------

table.attach_defaults(label1, left=0, right=2, top=0, bottom=1)
table.attach_defaults(label2, left=0, right=1, top=1, bottom=2)
table.attach_defaults(name,   left=1, right=2, top=1, bottom=2)
table.row_spacings = 20
table.column_spacings = 5

vbox = Gtk::VBox.new(false, 5)
vbox.pack_start_defaults(table)

## -- Status bar  -------------------------------- (start #2) -----
vbox.pack_start_defaults(statusbar)
## -- Status bar  -------------------------------- (end #2) -------

window = Gtk::Window.new("Simpe Status Bar")
window.border_width = 10
window.set_size_request(250, -1)
window.signal_connect('destroy') { Gtk.main_quit }
window.add(vbox)
window.show_all
Gtk.main

The above introduction covered the pure mechanics of setting up the status bar, and set the grounds for more detailed study of related issues, which follow.


Status Bar Hints

Usually placed along the bottom of the window, the Gtk::Statusbar widget is used to display the current state in which your application is at any point in time. In addition to this it can also be used to give the user further information about what is going on in the application. This is what we here refer to as instantaneous or temporary'status bar hints'. They most often manifest themselves as temporary messages associated to individual menu items explaining their actions, whereby they provide more information to the user about the functionality of the menu item that the mouse cursor is hovering over.

The Status Bar Widget

While status bar can only display one short message at the time, the widget also stores a stack of messages. The currently displayed message is on the top of the stack. When you pop a message from the stack, the previous message is displayed. If the stack is empty after you pop a message from it no message is displayed any more on the status bar.

Status Bar Context and Message Identifiers

A new status bar widget is created with Gtk::Statusbar.new. Newly created status bar will have empty message stack. Before you are able to add or remove a message to or from the new status bar's stack, you must retrieve a context identifier(context_id)with Gtk::Statusbar#get_context_id(context_description). The API documentation may help you see how this associations are related:

get_context_id(context_description)
Returns a new context identifier, given a description of the actual context.
  • context_description : textual description of what context the new message is being used in.
  • Returns : an integer id.

The context identifier is a unique integer that is associated with a context description string. This identifier will be used for all messages of a specific type, which allows you to categorize messages on the stack. For example, if your status bar will hold hyperlinks and "IP" addresses, you could create two context identifiers from the strings "URL" and "IP". When you push or pop messages to and from the stack, you have to specify a context identifier. Messages are added to the bar's stack with Gtk::Statusbar#push(context_id, text). Pushing a message onto a status bar message stack returnsmessage_id.However, there are two ways to remove a message from status bar message stack. The usual way to remove currently displayed message from top of the stack with Gtk::Statusbar#pop(context_id), or to remove a specific message from any location within the stack. The latter is accomplished with Gtk::Statusbar#remove(context_id, message_id). This status bar stack message strategy allows separate parts parts of your application to push and pop messages to or from the the status bar stack without affecting each other.

Let's look at the API documentation:

push(context_id, text)
Pushes a new message onto a statusbar's stack.
pop(context_id)
Removes the message at the top of a Gtk::Statusbar's stack.
  • context_id : a context identifier.
  • Returns: self
remove(context_id, message_id)
Forces the removal of a message from a statusbar's stack. The exact context_id and message_id must be specified.
  • context_id: a context identifier.
  • message_id: a message identifier, as returned by Gtk::Statusbar#push.
  • Returns: self

statbar-resize-grip.png

Status bar has one property that you will most likely want to set. It is Gtk::Statusbar#has_resize_grip=(Boolean). If set to true a graphic will be placed in the right corner of the status bar, allowing used to resize the window. You can see the enlarged resize grip in the image on the left here.



Menu Item Information

statbar-hints-n-state-00.png

Beside displaying the current state your application is in at any moment, another useful role of the status bar is to give the user more information about the menu item the mouse cursor is hovering over. An example of the later is shown on the figure here on the right, while the application status (the string "Pulsating") is shown on the figure below on the left.

In our example program we keep two kinds of messages on our status bar's stack: (1) the temporary hints, describing each individual menu option, and (2) a bit more persistent messages reflecting the most recent user selection indicating the latest state our application is in. We created two context identifiers from the strings 'StatBarHints' and 'CurrentStatus', using the Gtk::Statusbar#get_context_id(context_description) method. The first is created from the string 'StatBarHints' in the 'statusbar_hint' method and the second from the 'CurrentStatus' string in three methods all of which are handling the'activate'signal, and which are called 'pulse_activated', 'fill_activated' and 'clear_activated'.

statbar-state.png

Theenter-notify-eventandleave-notify-eventsignals in our example program here are handled by the callback 'statusbar_hint' method. For both signals, the handler receives Gdk::Event object as a parameter which, allows us to use for both of these events the same 'statusbar_hint' signal handler method. In it we use the Gdk::Event object and Gdk::Event#event_type instance method to identify whether the cursor is entering the menu item (Gdk::Event::ENTER_NOTIFY) or is on its way out (see: Gdk::Event::GdkEventType). We can use this information to, for as long as the cursor is hovering above the menu item, display the description of the menu selection in the status bar, by pushing our menu item description message onto the status bar stack, hence, for the time being, hiding whatever was displayed there before the cursor entered the menu item. When the cursor leaves the menu item area, this time theleave-notify-eventsignal is emitted, carrying Gdk::Event::LEAVE_NOTIFY flag with it in Gdk::Event object, so we pop-off the stack the message we pushed onto when the cursor appeared above our menu item, which finally restores whatever message was on the status bar stack before the two consecutive '*-notify-event'signal emissions.

Note:
The 'statusbar_hint' method handles theenter-notify-eventandleave-notify-eventsignals. If this method is missing thereturnstatement the '*-notify-event' signals will interfere with the'activate'signal used in the other three just mentioned '{pulse|fill|clear}_activated' methods, by disabling the highlighting menu feature when browsing the menu (see the comment before the return statement in 'statusbar_hint' method.)

Let's look at the listing:


statbhints-n-state.rb

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

# Create the poup menu with three items and a separator.
# Then, attach it to the progress bar and show it to the
# user.
def create_popup_menu(menu, progb, sbar)
  pulse  = Gtk::MenuItem.new("Pulse Progress")
  separator = Gtk::MenuItem.new
  fill   = Gtk::MenuItem.new("Set as Complete")
  clear  = Gtk::MenuItem.new("_Clear Progress")

  pulse.name = "pulse"
  fill.name  = "fill"
  clear.name = "clear"

  menu_hint_msgs = { "pulse" => "Pulse the progress bar one step.",
            "fill"  => "Set the progress bar to 100%.",
            "clear" => "Clear the progress bar to 0%."
  }
  menu.append(pulse)
  menu.append(separator)
  menu.append(fill)
  menu.append(clear)

  # -- 'activate' is Gtk::MenuItem's signal
  pulse.signal_connect('activate') { pulse_activated(sbar, progb) }
  fill.signal_connect('activate')  { fill_activated(sbar, progb) }
  clear.signal_connect('activate') { clear_activated(sbar, progb) }

  # -- 'enter-notify-event'is Gtk::Widget's signal
  pulse.signal_connect('enter_notify_event') { |w, e| statusbar_hint(w, e, sbar, menu_hint_msgs) }
  fill.signal_connect('enter_notify_event')  { |w, e| statusbar_hint(w, e, sbar, menu_hint_msgs) }
  clear.signal_connect('enter_notify_event') { |w, e| statusbar_hint(w, e, sbar, menu_hint_msgs) }

  # -- 'leave-notify-event'is Gtk::Widget's signal
  pulse.signal_connect('leave_notify_event') { |w, e| statusbar_hint(w, e, sbar, menu_hint_msgs) }
  fill.signal_connect('leave_notify_event')  { |w, e| statusbar_hint(w, e, sbar, menu_hint_msgs) }
  clear.signal_connect('leave_notify_event') { |w, e| statusbar_hint(w, e, sbar, menu_hint_msgs) }

  menu.show_all
end

# Show in status bar what this menu item will do. 
def statusbar_hint(menui, event, sbar, hints)
  cntxt_id = sbar.get_context_id("StatBarHints")
  if event.event_type == Gdk::Event::ENTER_NOTIFY
    sbar.push(cntxt_id, hints[menui.name])
  else
    sbar.pop(cntxt_id)
  end
  return  # Mandatory, if used together with another signal handler that
          # needs to update screen activity, like browsing menu choices! 
end

# Update curently selected state in progres bar 
def pulse_activated(sbar, progbar)
  progbar.pulse
  return if progbar.text == "Pulse!"
  progbar.text = "Pulse!"

  cntxt_id = sbar.get_context_id("CurrentStatus")
  sbar.pop(cntxt_id)
  sbar.push(cntxt_id, "Pulsating")
end

# Update curently selected state in progres bar 
def fill_activated(sbar, progbar)
  return if progbar.text == "One Hundred Percent"
  progbar.fraction = 1.0
  progbar.text = "One Hundred Percent"

  cntxt_id = sbar.get_context_id("CurrentStatus")
  sbar.pop(cntxt_id)
  sbar.push(cntxt_id, "Set to filled")
end

# Update curently selected state in progres bar 
def clear_activated(sbar, progbar)
  return if progbar.text == "Reset to Zero"
  progbar.fraction = 0.0
  progbar.text = "Reset to Zero"

  cntxt_id = sbar.get_context_id("CurrentStatus")
  sbar.pop(cntxt_id)
  sbar.push(cntxt_id, "Cleared (ready to pulsate)")
end

window = Gtk::Window.new("Status Bar Hints & State")
window.resizable = true
window.border_width = 10
window.signal_connect('destroy') { Gtk.main_quit }
window.set_size_request(275, -1)

# Create all of the necessary widgets and initialize the popup menu.
menu = Gtk::Menu.new
eventbox = Gtk::EventBox.new
progress = Gtk::ProgressBar.new
progress.text = "Nothing yet happened"
progress.pulse
progress.pulse_step = 0.05
statusbar = Gtk::Statusbar.new

create_popup_menu(menu, progress, statusbar)

eventbox = Gtk::EventBox.new
eventbox.events = Gdk::Event::BUTTON_PRESS_MASK
eventbox.signal_connect('button_press_event') do |w, event|
  if event.event_type == Gdk::Event::BUTTON_PRESS
    if event.button == 3   # right mouse button
      menu.popup(nil, nil, event.button, event.time)
    end
  end
end
eventbox.add(progress)

vbox = Gtk::VBox.new(false, 5)
vbox.pack_start_defaults(eventbox)
vbox.pack_start_defaults(statusbar)

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


When working with signals we usually have to be concerned about the callback block parameters that are passed along when a callback is invoked. Especially when we use a single callback method for more than one different signals, like in ourstatusbar_hintmethod, we should rely on these parameters to identify which event occurred, and most likely which device (in our case menu item) emitted it. Both the'enter-notify-event'and'leave-notify-event'signals pass the signal emitting widget, as well as the event object whose type or class is Gdk::EventCrossing, the subclass of Gdk::Event class. This event object will provide us with the information, that will allow us to define whether we are dealing with a cursor entering or leaving a menu item. To identify whether the event was emitted by an entering or a leaving cursor in our signal handler callback we employ the Gdk::Event#event_type accessor method retrieving either Gdk::Event::ENTER_NOTIFY or Gdk::Event::LEAVE_NOTIFY event id value.

If you remember we have created two different categories of status bar's stack messages, namely the temporary messages which we also call hints, and the more permanent application state messages. The former, menu hint messages, are created in 'create_popup_menu' method, where we store status messages in a hash using menu item's name as the key, and processed in the signal handler callback method called 'statusbar_hint'. And the latter 'application state messages' are maintained and managed in 'pulse_activated', 'fill_activated' and 'clear_activated' methods.

Note however, that eventually both of these two categories of messages identified by the 'StatBarHints' and 'CurrentStatus' strings respectively, end up, though not in our case, nevertheless potentially intermingled, on the same status bar's message stack. The rendering of these status messages is managed exclusively by pushing and popping the messages from this stack, where you always have to identify the massage context id:

cntxt_id = sbar.get_context_id("CurrentStatus")
sbar.pop(cntxt_id)
sbar.push(cntxt_id, "Pulsating")

or

cntxt_id = sbar.get_context_id("StatBarHints")
if event.event_type == Gdk::Event::ENTER_NOTIFY
  sbar.push(cntxt_id, hints[menui.name])
else
  sbar.pop(cntxt_id)
end
Hint Status Messages:
To identify the menu item's hint status messages in our 'create_popup_menu' and finally in the callback 'statusbar_hint' method, we had to do some preparation work at the time when menu items were created. First we assigned the name to all our menu items by calling Gtk::Widget#name= on these objects. As mentioned above we use this name in two places: (1) 'create_popup_menu' method, and (2) in the callback - signal handler method (statusbar_hint), where we use this hash to retrieve the appropriate message, extracting the key for the hash entry from the signal emitting widget name, and then if the event type indicates cursor entering a menu item push it onto the status bar's message stack by first identifying its category or context id as 'StatBarHints', and finally when 'leave-notify-event'signal triggers invocation of 'statusbar_hint' method, we pop the message belonging to 'StatBarHints' category off the stack. Note, that before you push a message of certain context id onto the stack, there may have been other messages belonging to different categories with different context ids already on the stack. So despite very orderly behaviour of messages belonging to 'StatBarHints', namely, every invocation of push of 'StatBarHints' context id is always followed exclusively by the invocation of pop for the same context id, you can have messages belonging to 'CurrentStatus' category present on stack before you pushed onto it your 'StatBarHints' context id. When you pop the message belonging to 'StatBarHints' category, whatever was there on the stack before is then rendered in the status bar.
Application State Messages:
The application state messages are simple. They are hard-coded in their respective methods, and each time we are about to push a new message on the stack, we remove the previous message belonging to 'CurrentStatus' category.

You have to be careful that you do not pile messages onto the stack, without properly removing them. Check our example program again to see that we are meticulously removing the messages for each of our two categories.

Last modified:2012/10/29 08:31:42
Keyword(s):
References:[tut-gtk2-mnstbs] [tut-gtk2-mnstbs-mnui] [tut-gtk]