Create  Edit  FrontPage  Index  Search  Changes  History  RSS  Login

tut-gtk2-mnstbs-tb

Toolbars

A Gtk::Toolbar is a type of container that holds a number of widgets in a horizontal or vertical row. It is meant to allow easier customization of large number of widgets with very little trouble. Typically toolbars hold tool buttons that can display an image along with a text string. However, toolbars are actually able to hold any type of widget.

mnstbs-tb-01.png

In the example program here, a simple toolbars is created that shows five tool items in a horizontal row. Each toolbar item, except the separator, displays an icon and a label that describes the purpose of the item. The toolbar is also set to display an arrow that will provide access to toolbar items that do not fit in the menu.

In this example, a toolbar is used to provide cut, copy paste, and select-all functionality to the Gtk::Entry widget. The main program body creates the toolbar, packing it above the entry widget. It then calls "create_toolbar", which populates the toolbar with tool items and connects the needed signals.

mnstbs-tb-02.png There are two images here so you can see all the features this toolbar provides, in particular I wanted you to see the arrow, indicating that there are additional menu items available when top window is smaller than is the size of the toolbar.


toolbars.rb

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

# Create a toolbar with Cut, Copy, Paste and Select All
# toolbar items.
def create_toolbar(tb, ent)
  cut       = Gtk::ToolButton.new(Gtk::Stock::CUT)
  copy      = Gtk::ToolButton.new(Gtk::Stock::COPY)
  paste     = Gtk::ToolButton.new(Gtk::Stock::PASTE)
  selectall = Gtk::ToolButton.new(Gtk::Stock::SELECT_ALL)
  separator = Gtk::SeparatorToolItem.new

  cut.tooltip_text   = "Cut saves the selected text in the clipboard,\n" +
                       "and removes it from the editable widget," 
  copy.tooltip_text  = "Copy saves the selected text in the clipboard."
  paste.tooltip_text = "Paste retrieves last text saved in the clipboard\n" +
                       "and places it at the cursor position in the edit field."
  selectall.tooltip_text = "Select all the text in the edit field."

  tb.show_arrow = true
  tb.toolbar_style = Gtk::Toolbar::Style::BOTH

  # NOTE: tool-tips are only part of the deprecated interface!
  tb.insert(0, cut)
  tb.insert(1, copy)
  tb.insert(2, paste)
  tb.insert(3, separator)
  tb.insert(4, selectall)

  cut.signal_connect('clicked')       { ent.cut_clipboard; p "Cut" }
  copy.signal_connect('clicked')      { ent.copy_clipboard; p "Copy" }
  paste.signal_connect('clicked')     { ent.paste_clipboard; p "Paste" }
  # Select all of the text in the editable (Gtk::Editable#select_region)
  selectall.signal_connect('clicked') { ent.select_region(0, -1); p "Sel. All" }
end

window = Gtk::Window.new("Toolbars")
window.resizable = true
window.border_width = 10
window.signal_connect('destroy') { Gtk.main_quit }
window.set_size_request(350, -1)

entry   = Gtk::Entry.new
toolbar = Gtk::Toolbar.new
create_toolbar(toolbar, entry)

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

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

New toolbars are created with Gtk::Toolbar.new. This creates an empty toolbar. We created our toolbar before calling our own method create_toolbar(toolbar, entry), which populates the toolbar.

Gtk::Toolbar class provides a number of properties for customizing how it appears and interacts with the user including the orientation, button style, and the ability to give access to items that do not fit in the toolbar. The one property managing instance method worth mentioning here is the Gtk::Toolbar#toolbar_style, because it requires you to be aware of the appropriate constants (GtkToolbarStyle). Also note the Gtk::Toolbar#show_arrow method

tb.show_arrow = true
tb.toolbar_style = Gtk::Toolbar::Style::BOTH

Signal handling callbacks are straight forward, except that we are using methods defined in the Gtk::Editable module such as 'cut_clipboard', ... 'select_region'.

Gtk::Editable:
The Gtk::Editable is a module for widgets for editing text, such as Gtk::Entry. The editable class contains methods for generically manipulating an editable widget, a large number of action signals used for key bindings, and several signals that an application can connect to modify the behaviour of a widget.

Another thing worth mentioning is that you should avid using the deprecated Gtk::Toolbar instance methods which use the deprecated Gtk::Tooltips class. Instead you should be using thetooltip_*instance methods defined in Gtk::Widget.

Note also, that you can use only one Gtk::SeparatorToolItem object on the toolbar.

tb.insert(2, separator)


Menus And Sub-menus As Toolbar Items

toolb-L-menus-s1.png

It is also possible to add menus and sub-menu's as toolbar items, as well as you can implement tear offs. However, notice that menu items emit a different signal'activate'than tool-menu items'clicked'when clicked. Following is expanded example program including the menu toolbar item with the language sub-menu, you already saw in previous section. In addition to the toolbar menu item, we also included two Gtk::ToolButton objects with user supplied icon image.

gnu-baby-32x32.jpggnu-head-42x42.jpg For your convenience I have included the two images here on the left. Their names are 'gnu-baby-32x32.jpg' and 'gnu-head-42x42.jpg' respectively. You should copy these image files into the directory, where you plan to run our 'toolbar-item-menu.rb' program example.

The image can be passed to the Gtk::ToolButton constructor as animage fileor as apixbuf.Our 'toolbar-item-menu.rb' example shows both of these two ways of supplying image objects to the toolbar item constructor.

toolb-tearoff.png

Note also our custom toolbar menu under 'Preferences' includes the tear-off menu item shown here on the image on the left.

Note:

When you include custom toolbar menus on your toolbars, one thing you should pay attention to is where you place these custom menu items on the toolbar. Namely, if they are close to the end of your toolbar (the right or the bottom side, depending on the orientation you set for your toolbar), chances are that they will be chopped of when you resize the window containing your toolbar. The chopped-off toolbar items end-up in Gtk supplied rightmost arrow item sub-menu. If your custom toolbar sub-menu ends up in that Gtk arrow sub-menu of chopped off menu items, you will not be able to open that custom toolbar sub-menu.

Therefore, the prudent thing to do, is to place your custom toolbar menu items at the beginning of the toolbar, where the resizing is unlikely to effect them!


toolbar-item-menu.rb

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

# Create a toolbar with Cut, Copy, Paste and Select All
# toolbar items.
def create_toolbar(tb, ent)
  cut       = Gtk::ToolButton.new(Gtk::Stock::CUT)
  copy      = Gtk::ToolButton.new(Gtk::Stock::COPY)
  paste     = Gtk::ToolButton.new(Gtk::Stock::PASTE)
  selectall = Gtk::ToolButton.new(Gtk::Stock::SELECT_ALL)
  separator = Gtk::SeparatorToolItem.new

  mytb_menu = Gtk::MenuToolButton.new(Gtk::Stock::PREFERENCES)
  # To be sure file exists use more elaborate check making the bixbuf
  pixbuf = get_pixbuf_if_file_exists("gnu-baby-32x32.jpg")
  my_stuff  = Gtk::ToolButton.new(icon_widget=Gtk::Image.new(pixbuf), label="My Stuff")
  # or if you are sure you you really have the image file:
  my_stuff1 = Gtk::ToolButton.new(icon_widget=Gtk::Image.new("gnu-head-42x42.jpg"), label="My Stuff-1")

  # use Gtk::Widget#tooltip_text= instead of deprecated Gtk::Toolbar 
  # methods utilizing deprecated Gtk::Tooltips class!
  mytb_menu.tooltip_text = "Demonstrate that menus with submenus work."
  cut.tooltip_text   = "Cut saves the selected text in the clipboard,\n" +
                       "and removes it from the editable widget," 
  copy.tooltip_text  = "Copy saves the selected text in the clipboard."
  paste.tooltip_text = "Paste retrieves last text saved in the clipboard\n" +
                       "and places it at the cursor position in the edit field."
  selectall.tooltip_text = "Select all the text in the edit field."

  tb.show_arrow = true
  tb.toolbar_style = Gtk::Toolbar::Style::BOTH

  tb.insert(0, mytb_menu)
  tb.insert(1, my_stuff)
  tb.insert(2, cut)
  tb.insert(3, copy)
  tb.insert(4, paste)
  tb.insert(5, separator)
  tb.insert(6, selectall)
  tb.insert(7, my_stuff1)

  cut.signal_connect('clicked')       { ent.cut_clipboard; p "Cut" }
  copy.signal_connect('clicked')      { ent.copy_clipboard; p "Copy" }
  paste.signal_connect('clicked')     { ent.paste_clipboard; p "Paste" }
  # Select all of the text in the editable (Gtk::Editable#select_region)
  selectall.signal_connect('clicked') { ent.select_region(0, -1); p "Sel. All" }
  my_stuff.signal_connect('clicked')  { p "My Stuff selected." }
  my_stuff1.signal_connect('clicked')  { p "My Stuff-1 selected." }

  menutearoff = Gtk::TearoffMenuItem.new
  testi1 = Gtk::MenuItem.new("Test Item #1")
  testi2 = Gtk::MenuItem.new("Test Item #2")
  testi3 = Gtk::MenuItem.new("Test Item #3")
  langi  = Gtk::MenuItem.new("Languages")
  testi1.signal_connect('activate') { |w| puts "w=#{w.class}:Test Item-1 selected" }
  testi2.signal_connect('activate') { |w| puts "w=#{w.class}:Test Item-2 selected" }
  testi3.signal_connect('activate') { |w| puts "w=#{w.class}:Test Item-3 selected" }

  # Create Test Menu
  testmenu = Gtk::Menu.new
  testmenu.append(menutearoff)
  testmenu.append(testi1)
  testmenu.append(testi2)
  testmenu.append(testi3)
  testmenu.append(langi)

  langmenu = Gtk::Menu.new
  langi.submenu = langmenu

  english = Gtk::MenuItem.new("English")
  french  = Gtk::MenuItem.new("French")
  german  = Gtk::MenuItem.new("German")
  russian = Gtk::MenuItem.new("Russian")
  italian = Gtk::MenuItem.new("Italian")
  langmenu.append(english)
  langmenu.append(french)
  langmenu.append(german)
  langmenu.append(russian)
  langmenu.append(italian)

  english.signal_connect('activate') { |w| puts "w=#{w.class}:English selected" }
  french.signal_connect('activate')  { |w| puts "w=#{w.class}:French selected" }
  german.signal_connect('activate')  { |w| puts "w=#{w.class}:German selected" }
  russian.signal_connect('activate') { |w| puts "w=#{w.class}:Russian selected" }
  italian.signal_connect('activate') { |w| puts "w=#{w.class}:Italian selected" }

  testmenu.show_all
  mytb_menu.menu = testmenu

end

# Turn image file into pixbuf, and return nil in case of file error. 
def get_pixbuf_if_file_exists(file)
  begin
    pixbuf = Gdk::Pixbuf.new(file)
    rescue GLib::FileError => err
      print "I/O ERROR (%s): %s\n" % [err.class, err]
      pixbuf = nil
  end
  pixbuf
end

window = Gtk::Window.new("Toolbars w/menus")
window.resizable = true
window.border_width = 10
window.signal_connect('destroy') { Gtk.main_quit }
window.set_size_request(350, -1)

entry   = Gtk::Entry.new
toolbar = Gtk::Toolbar.new
create_toolbar(toolbar, entry)

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

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


Some 'toolbar-item-menu.rb' Program Notes

toolb-vertical-s1.png

You can set the toolbar orientation by setting the Gtk::Toolbar.orientation= to one of the constants defined in Gtk::Orientation. By default it is set to Gtk::ORIENTATION_HORIZONTAL. To change that, all you need to do is include the following line in your code:

toolbar.orientation = Gtk::Orientation::VERTICAL

To place your toolbar on the side vertically, requires a different screen layout organization. In our example program we use the following layout:

toolbar.orientation = Gtk::ORIENTATION_VERTICAL
hbox = Gtk::HBox.new(false, 5)
toolbar.set_size_request(50, 350)
toolframe = Gtk::Frame.new("Tools")
toolframe.add(toolbar)
hbox.pack_start_defaults(toolframe)

right_vbox =  Gtk::VBox.new(false, 5)
emtyl = Gtk::Label.new
right_vbox.pack_start(child=entry, expand=false, fill=false, padding=5)
right_vbox.pack_start(child=emtyl, expand=true,  fill=true,  padding=5)
hbox.pack_start_defaults(right_vbox)

window.add(hbox)

Tool Bar Style

The toolbar style can be set with Gtk::Toolbar#toolbar_style=(style), where the 'style' is one of the following:

# Gtk::Toolbar#toolbar_style=(style)
# toolbar.toolbar_style = Gtk::Toolbar::Style::ICONS		# ok (works)
# toolbar.toolbar_style = Gtk::Toolbar::Style::TEXT		# ok (works)
# toolbar.toolbar_style = Gtk::Toolbar::Style::BOTH		# ok (default)
# toolbar.toolbar_style = Gtk::Toolbar::Style::BOTH_HORIZ	# nok., like ICONS 

As you can see, the default is Gtk::Toolbar::Style::BOTH. Note that setting the toolbar style overrides the user's preferences for the default toolbar style. You will find the documentation at Gtk::Toolbar#GtkToolbarStyle

Icon Size On Your Tool Bars

To control the size of the icons on the toolbar use Gtk::Toolbar#icon_size=(icon_size), where the 'icon_size' is one of the following:

# toolbar.icon_size = Gtk::IconSize::MENU		# the smallest same as button ... (grade:1)
# toolbar.icon_size = Gtk::IconSize::BUTTON		# the smallest same as menu ..... (grade:1)
# toolbar.icon_size = Gtk::IconSize::SMALL_TOOLBAR	# small ......................... (grade:1.2)
# toolbar.icon_size = Gtk::IconSize::INVALID		# Run-time warning .............. (grade:3)
# toolbar.icon_size = Gtk::IconSize::LARGE_TOOLBAR	# large ......................... (grade:3)
# toolbar.icon_size = Gtk::IconSize::DND		# one before the largest ........ (grade:4)
# toolbar.icon_size = Gtk::IconSize::DIALOG		# the largest ................... (grade:5)

Note that I have graded the icon sizes above. In reality the differences are not very obvious, but they may be important for some applications. Indeed, the icons you created from your own image files are not affected by setting the Gtk::Toolbar#icon_size= value. For that you'd have to create a new image file and/or pixbuf with different sizes. For icon size values you will find the documentation at: Gtk::IconSize#GtkIconSize.


Dragging And Dropping Tool Bars

dnd-toolbar-colage-w-frame-placeholders.png

There are two ways you would want to move toolbars around. One is to detach a toolbar from the window, and the other (the drag-and-drop method) is to place it to a different place (usually the edge) inside the window. The two techniques require two different program solutions. We will look at both programming techniques in two separate example programs. It would be hard, though perhaps not impossible, to allow one and the same toolbar to be either detached from or dragged to a different location in the window. Most likely you are familiar with GUIs that provide such facilities, but if not the introductory images here, along with the explanations and, indeed most importantly, the example programs should give you enough info to start playing with these facilities.

detaching-toolbar-n-handlebox-s1.png

The image above on the right shows the toolbar dragged to a different location within the window, whereas the image here on the left shows you from the main window detached toolbar. The latter technique of detaching the toolbar from the window is rather useless, since unlike the other 'drag-and-drop' (dnd) method, it does not recover by the toolbar vacated window real estate (space). In both cases though, there are a few details, you should be aware of if you'd like to drag the toolbar to a new location, and indeed, to drag it back to its original location. To start dragging a toolbar to a new location you have to click on a particular drag-sensitive spot on the toolbar, and keep pressing the mouse button while dragging. However, the two methods differ in presenting that drag-sensitive spot. handle-indicator.png The detaching method provides a rather obvious handle, depicted as three vertical bars. Even though however, these handles are hard to spot. On the images here, they are marked with red circles. Note, that this handle is not on the toolbar itself but rather on the wrapper widget called Gtk::HandleBox. Our first example program 'detaching-toolbar-w-HandleBox.rb' demonstrates the use of this widget.

Just a reminder:
The following three paragraphs pertain to our second drag-and-drop example, but since here we are comparing the two toolbar moving techniques, you should keep on reading.

drag-document-cursor.png

On the other hand, as demonstrated later in the second example program, to initiate the dragging of a toolbar within the same window, you need to click and hold any mouse button on the edge of the frame in which toolbar resides, or on an empty spot within the toolbar. When you have clicked on a drag-sensitive spot the cursor would indicate that by changing to adrag-documentshape (see the small image here on the left).

The blue colour arrows, on the large image above on the right, show the position of the empty frame in which you'd release the mouse button you are holding down since you started to drag the toolbar to its new location. However, it is more tricky to discover a sensitive spot at the edges of toolbar, which responds to a grab. To help you with this, Gtk changes the cursor when you find it. I included the enlarged images of different cursors, conveying to the user the type of the action she may continue to carry out or abort in the case an incorrect spot was chosen.

You can drop the toolbar only at the designated edges of the window with the toolbar. It is up to the program designer to decide whether it would be possible to drag a toolbar to any window edge, or to limit this action to a particular edge. In our example program, we decided to use only the top and left window sides as possible locations for our toolbar. ask-to-drop-document-cursor.png If you look closely, you will see a tiny empty rectangular shape (a.k.a. toolbar's frame placeholder) stretched along the side where you can drop the toolbar once you started to drag it. When your 'drag-document' cursor reaches this 'toolbar placeholder' and hovers over it it adds a little question mark to thedrag-documentshape. You can see enlarged image of the drop cursor here on the right.


Detached Toolbar Example

As you see, for our toolbar detachment and drag-and-drop examples we are using the 'toolbar-item-menu.rb' program from the beginning of this section (9.6), in which we introduced custom designed toolbar-menu-items, hence you should be sufficiently familiar with the menu itself as well as its behaviour. From here on we only add the features allowing you to move the toolbar to different paces, but particularly in our next 'detaching-toolbar-w-HandleBox.rb' program example, to detach the menu from the including window.

detached-vert-n-horiz-tb-s1.png

I included a new image to start discussing the following program listing. On the image you can see the detached toolbar in both horizontal and vertical form. The second thing you should notice is that there is also a detached (teared-off) 'toolbar item sub-menu', and the last thing you can see is that the size of the detached toolbar affects how the toolbar is displayed, and that you have access to the cut-off toolbar items via the Gtk supplied last toolbar 'arrow' item.

What you can not see is that once it is detached from the main window there is no way for the operator to resize the toolbar. This calls for some planning when you design this feature, or you will cause the detached toolbar to be rather awkward if not useless. Namely, had we designed our toolbar with the 'Preferences' toolbar item sub-menu positioned at the end of the toolbar, it would be impossible to access the Preferences sub-menus, since the Gtk supplied 'arrow sub-menu' does not provide sub-menu expansions, and there is no way to resize a toolbar once it is detached from the containing window. We will address programming issues related to the menu detachment features after the program listing in program notes.

Perhaps you recall, that to detach a toolbar from the containing window, you use Gtk::HandleBox widget. We encountered this widget already in chapter 3 under the title 'Handle Boxes'. However that early in our learning process we did not have enough material, to demonstrate its full power, and indeed, its drawbacks. Hopefully, the following program example will rectify this 'minor inconvenience':)

Let's look at the code now.


detaching-toolbar-w-HandleBox.rb

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

# Create a toolbar with Cut, Copy, Paste and Select All
# toolbar items.
def create_toolbar(tb, ent)
  cut       = Gtk::ToolButton.new(Gtk::Stock::CUT)
  copy      = Gtk::ToolButton.new(Gtk::Stock::COPY)
  paste     = Gtk::ToolButton.new(Gtk::Stock::PASTE)
  selectall = Gtk::ToolButton.new(Gtk::Stock::SELECT_ALL)
  separator = Gtk::SeparatorToolItem.new

  mytb_menu = Gtk::MenuToolButton.new(Gtk::Stock::PREFERENCES)
  # To be sure file exists use more elaborate check making the bixbuf
  pixbuf = get_pixbuf_if_file_exists("gnu-baby-32x32.jpg")
  my_stuff  = Gtk::ToolButton.new(icon_widget=Gtk::Image.new(pixbuf), label="My Stuff")
  # or if you are sure you you really have the image file:
  my_stuff1 = Gtk::ToolButton.new(icon_widget=Gtk::Image.new("gnu-head-42x42.jpg"), label="My Stuff-1")

  # use Gtk::Widget#tooltip_text= instead of deprecated Gtk::Toolbar 
  # methods utilizing deprecated Gtk::Tooltips class!
  mytb_menu.tooltip_text = "Demonstrate that menus with submenus work."
  cut.tooltip_text   = "Cut saves the selected text in the clipboard,\n" +
                       "and removes it from the editable widget," 
  copy.tooltip_text  = "Copy saves the selected text in the clipboard."
  paste.tooltip_text = "Paste retrieves last text saved in the clipboard\n" +
                       "and places it at the cursor position in the edit field."
  selectall.tooltip_text = "Select all the text in the edit field."

  tb.show_arrow = true
  tb.toolbar_style = Gtk::Toolbar::Style::BOTH

  tb.insert(0, mytb_menu)
  tb.insert(1, my_stuff)
  tb.insert(2, cut)
  tb.insert(3, copy)
  tb.insert(4, paste)
  tb.insert(5, separator)
  tb.insert(6, selectall)
  tb.insert(7, my_stuff1)

  cut.signal_connect('clicked')       { ent.cut_clipboard; p "Cut" }
  copy.signal_connect('clicked')      { ent.copy_clipboard; p "Copy" }
  paste.signal_connect('clicked')     { ent.paste_clipboard; p "Paste" }
  # Select all of the text in the editable (Gtk::Editable#select_region)
  selectall.signal_connect('clicked') { ent.select_region(0, -1); p "Sel. All" }
  my_stuff.signal_connect('clicked')  { p "My Stuff selected." }
  my_stuff1.signal_connect('clicked')  { p "My Stuff-1 selected." }

  menutearoff = Gtk::TearoffMenuItem.new
  testi1 = Gtk::MenuItem.new("Test Item #1")
  testi2 = Gtk::MenuItem.new("Test Item #2")
  testi3 = Gtk::MenuItem.new("Test Item #3")
  langi  = Gtk::MenuItem.new("Languages")
  testi1.signal_connect('activate') { |w| puts "w=#{w.class}:Test Item-1 selected" }
  testi2.signal_connect('activate') { |w| puts "w=#{w.class}:Test Item-2 selected" }
  testi3.signal_connect('activate') { |w| puts "w=#{w.class}:Test Item-3 selected" }

  # Create Test Menu
  testmenu = Gtk::Menu.new
  testmenu.append(menutearoff)
  testmenu.append(testi1)
  testmenu.append(testi2)
  testmenu.append(testi3)
  testmenu.append(langi)

  langmenu = Gtk::Menu.new
  langi.submenu = langmenu

  english = Gtk::MenuItem.new("English")
  french  = Gtk::MenuItem.new("French")
  german  = Gtk::MenuItem.new("German")
  russian = Gtk::MenuItem.new("Russian")
  italian = Gtk::MenuItem.new("Italian")
  langmenu.append(english)
  langmenu.append(french)
  langmenu.append(german)
  langmenu.append(russian)
  langmenu.append(italian)

  english.signal_connect('activate') { |w| puts "w=#{w.class}:English selected" }
  french.signal_connect('activate')  { |w| puts "w=#{w.class}:French selected" }
  german.signal_connect('activate')  { |w| puts "w=#{w.class}:German selected" }
  russian.signal_connect('activate') { |w| puts "w=#{w.class}:Russian selected" }
  italian.signal_connect('activate') { |w| puts "w=#{w.class}:Italian selected" }

  testmenu.show_all
  mytb_menu.menu = testmenu

end

# Turn image file into pixbuf, and return nil in case of file error.
def get_pixbuf_if_file_exists(file)
  begin
    pixbuf = Gdk::Pixbuf.new(file)
    rescue GLib::FileError => err
      print "I/O ERROR (%s): %s\n" % [err.class, err]
      pixbuf = nil
  end
  pixbuf
end

window = Gtk::Window.new("Toolbars in Gtk::HandleBox")
window.resizable = true
window.border_width = 10
window.signal_connect('destroy') { Gtk.main_quit }
window.set_size_request(650, -1)

entry   = Gtk::Entry.new
toolbar = Gtk::Toolbar.new
create_toolbar(toolbar, entry)

# Create handle box wrapper
# -----------------------------------------------------------------------
handle = Gtk::HandleBox.new

# Add a shadow to the handle box, set the handle position
# on the left and set the snap edge to the top of the widget.
handle.shadow_type = Gtk::SHADOW_IN
handle.handle_position = Gtk::POS_TOP   # or ... Gtk::POS_LEFT
handle.snap_edge = Gtk::POS_TOP         # or ... Gtk::POS_LEFT

# ---- handle's signal handlers are optional ----- (start) --
handle.signal_connect('child-detached') do |w, child|
  child.set_size_request(80, 450)
  child.orientation = Gtk::Orientation::VERTICAL
end
handle.signal_connect('child-attached') do |w, child|
  child.orientation = Gtk::Orientation::HORIZONTAL
  puts "attached: child=#{child.class}"
end
# ---- handle's signal handlers are optional ----- (end) --

# -- If you comment out the above tear-off orientation signal handling
#    code make sure you uncomment the following line! 
#toolbar.set_size_request(600, -1)	# -- Important, otherwise only arrow 
					#    item box would be moved out!
handle.add(toolbar)

vbox = Gtk::VBox.new(false, 5)
vbox.pack_start_defaults(handle)
# -----------------------------------------------------------------------
vbox.pack_start_defaults(entry)

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


Orientation Of Detached Toolbar

If you just used the Gtk::HandleBox widget to detach your toolbar without any additional code that alters the properties of your toolbar object, particularly if you neglected setting its size with Gtk::Widget#size_request method, it would detach into a default detached Gtk arrow toolbar item form that is most likely also rather useless. So if you plan to detach your toolbar you should make sure it would have a acceptable size. To warn you about this eventuality, in our code you will find the following:

# ... make sure you uncomment the following line! 
#toolbar.set_size_request(600, -1)	# -- Important, otherwise only arrow 
					#    item box would be moved out!

Moving the toolbar out of the window, when by this action you do not gain a bigger working area, is most likely a result of your desire to visually rearrange the screen layout. You accomplish this by setting toolbar's orientation attribute. That is what we did in our program example above. What is interesting is, that we did this in the signal handler callbacks, reacting to'child-detached'and 'child-attached'signals. When you think about it, this makes perfect sence. However for learning purposes you may try to simply detach your toolbar from the window without rearranging the orientation. To do this you can comment out the signal handling code as shown with the '=begin' and '=end' keywords in the following code snippet. However, make sure you do not forget or overlook the 'set_size_request' method.

# Create handle box wrapper
# -----------------------------------------------------------------------
handle = Gtk::HandleBox.new

# Add a shadow to the handle box, set the handle position
# on the left and set the snap edge to the top of the widget.
handle.shadow_type = Gtk::SHADOW_IN
handle.handle_position = Gtk::POS_TOP		# or ... Gtk::POS_LEFT
handle.snap_edge = Gtk::POS_TOP			# or ... Gtk::POS_LEFT

=begin
# ---- handle's signal handlers are optional ----- (start) --
handle.signal_connect('child-detached') do |w, child|
  puts "dettached: child=#{child.class}"
  child.set_size_request(80, 450)
  child.orientation = Gtk::Orientation::VERTICAL
end
handle.signal_connect('child-attached') do |w, child|
  child.set_size_request(400, -1)
  child.orientation = Gtk::Orientation::HORIZONTAL
  puts "attached: child=#{child.class}"
end
# ---- handle's signal handlers are optional ----- (end) --
=end

toolbar.set_size_request(300, -1)	# -- Important, otherwise only arrow 
					#    item box would be moved out!
handle.add(toolbar)
vbox = Gtk::VBox.new(false, 5)
vbox.pack_start_defaults(handle)
# -----------------------------------------------------------------------


Dragging a Toolbar Within a Window

Most of contemporary GUI systems allow users to rearrange their display by dragging some of the displayed widgets to new locations within a window and if it makes sense even to a new widget or a location in a different window all together.

This feature is commonly called 'drag-and-drop', or short, 'dnd' feature. We devoted entire chapter 10, following this one, to document and explain the dnd facilities and features. The above 'detaching-toolbar-w-HandleBox.rb' example program does not really fall within the drag-and-drop categories, but not surprisingly, our next 'dnd-toolbar.rb' example does. Though, this next example logically rather nicely fits into this chapter, no attempt will be made here to explain the Gtk dnd metaphor. As mentioned, that is the topics of the next chapter - you are encouraged to read at least the 'DnD Introduction' article there, before you attempt to unravel the mysteries of the dnd features in the following example program here. In it, we revisit the 'dnd-toolbar.rb' program, albeit in a bit different (object-oriented) form, and add proper explanations of the included dnd features.

Since this is the chapter about toolbars, this version of 'dnd-toolbar.rb' program is included here for the completion sake, so you can run and compare it, not so much programmatically but operationally, to the above 'detaching-toolbar-w-HandleBox.rb' example.

Providing extra drag-sensitive spot on the toolbar:

Perhaps you remember we mentioned that users may find discovering a 'dnd-sensitive' spot on a toolbar a bit tricky. Well, we can alleviate this problem by making a toolbar item that will be used as an 'auxiliary drag-and drop handle'. menui-as-aux-drag-sensitive-spot.png In our 'dnd-toolbar.rb' example program you can do this by turning our tool button with the the 'baby-GNU' image labelled 'My stuff' into such a handle (see the small image on the left). However this will make this tool-button useless for any other actions, you may have planed for it before. You can try it out by uncommenting the 'my_stuff.use_drag_window = true' in our example program. Search for the following code segment.

# -- Provide additional drag-sensitive spot on the toolbar ----
#    However the original intent (action) of this button is lost!!!
#my_stuff.use_drag_window = true

Following is the procedural version of 'dnd-toolbar.rb':

dnd-toolbar.rb

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

TBDND_ACTIONS = actions=Gdk::DragContext::ACTION_ASK   # _ASK,_COPY,_LINK,_MOVE,_DEFAULT,_PRIVATE
TBDND_TARGETS = [["toolbar", Gtk::Drag::TARGET_SAME_APP, 100]]

window = Gtk::Window.new("Drag-gable Toolbox")
window.resizable = true
window.border_width = 10
window.signal_connect('destroy') { Gtk.main_quit }
window.set_size_request(450, 400)
window.border_width = 5

# Create a toolbar with Cut, Copy, Paste and Select All
# toolbar items.
def create_toolbar(tb, entf)
  cut       = Gtk::ToolButton.new(Gtk::Stock::CUT)
  copy      = Gtk::ToolButton.new(Gtk::Stock::COPY)
  paste     = Gtk::ToolButton.new(Gtk::Stock::PASTE)
  selectall = Gtk::ToolButton.new(Gtk::Stock::SELECT_ALL)

# -- Depending on the status in the entry field cut, copy, paste and 
# -- select menu items should be made sensitive or insensitive, however
# -- that would introduce too much overhead to our example program here.

#  cut.sensitive = false
#  copy.sensitive = false
#  paste.sensitive = false
#  selectall.sensitive = false

  separator = Gtk::SeparatorToolItem.new
  mytb_menu = Gtk::MenuToolButton.new(Gtk::Stock::PREFERENCES)

  # To be sure file exists use more elaborate check making the bixbuf
  pixbuf = get_pixbuf_if_file_exists("gnu-baby-32x32.jpg")
  my_stuff  = Gtk::ToolButton.new(
      icon_widget=Gtk::Image.new(pixbuf), label="My Stuff")
  # or if you are sure you you really have the image file:
  my_stuff1 = Gtk::ToolButton.new(
      icon_widget=Gtk::Image.new("gnu-head-42x42.jpg"), label="My Stuff-1")

  # -- Provide additional drag-sensitive spot on the toolbar ----
  #    However the original intent (action) of this button is lost!!!
  #my_stuff.use_drag_window = true

  # use Gtk::Widget#tooltip_text= instead of deprecated Gtk::Toolbar 
  # methods utilizing deprecated Gtk::Tooltips class!
  mytb_menu.tooltip_text = "Demonstrate that menus with submenus work."
  cut.tooltip_text       = "Cut saves the selected text in the clipboard,\n" +
                           "and removes it from the editable widget," 
  copy.tooltip_text      = "Copy saves the selected text in the clipboard."
  paste.tooltip_text     = "Paste retrieves last text saved in the clipboard\n" +
                           "and places it at the cursor position in the edit field."
  selectall.tooltip_text = "Select all the text in the edit field."

  tb.show_arrow = true
  tb.toolbar_style = Gtk::Toolbar::Style::BOTH

  tb.insert(0, mytb_menu)
  tb.insert(1, my_stuff)
  tb.insert(2, cut)
  tb.insert(3, copy)
  tb.insert(4, paste)
  tb.insert(5, separator)
  tb.insert(6, selectall)
  tb.insert(7, my_stuff1)

  cut.signal_connect('clicked')       { entf.cut_clipboard; p "Cut" }
  copy.signal_connect('clicked')      { entf.copy_clipboard; p "Copy" }
  paste.signal_connect('clicked')     { entf.paste_clipboard; p "Paste" }
  # Select all of the text in the editable (Gtk::Editable#select_region)
  selectall.signal_connect('clicked') { entf.select_region(0, -1); p "Sel. All" }

  my_stuff.signal_connect('clicked')  { p "My Stuff selected." }
  my_stuff1.signal_connect('clicked') { p "My Stuff-1 selected." }

  menutearoff = Gtk::TearoffMenuItem.new
  testi1 = Gtk::MenuItem.new("Test Item #1")
  testi2 = Gtk::MenuItem.new("Test Item #2")
  testi3 = Gtk::MenuItem.new("Test Item #3")
  langi  = Gtk::MenuItem.new("Languages")
  testi1.signal_connect('activate') { |w| puts "w=#{w.class}:Test Item-1 selected" }
  testi2.signal_connect('activate') { |w| puts "w=#{w.class}:Test Item-2 selected" }
  testi3.signal_connect('activate') { |w| puts "w=#{w.class}:Test Item-3 selected" }

  # Create Test Menu
  testmenu = Gtk::Menu.new
  testmenu.append(menutearoff)
  testmenu.append(testi1)
  testmenu.append(testi2)
  testmenu.append(testi3)
  testmenu.append(langi)

  langmenu = Gtk::Menu.new
  langi.submenu = langmenu

  english = Gtk::MenuItem.new("English")
  french  = Gtk::MenuItem.new("French")
  german  = Gtk::MenuItem.new("German")
  russian = Gtk::MenuItem.new("Russian")
  italian = Gtk::MenuItem.new("Italian")
  langmenu.append(english)
  langmenu.append(french)
  langmenu.append(german)
  langmenu.append(russian)
  langmenu.append(italian)

  english.signal_connect('activate') { |w| puts "w=#{w.class}:English selected" }
  french.signal_connect('activate')  { |w| puts "w=#{w.class}:French selected" }
  german.signal_connect('activate')  { |w| puts "w=#{w.class}:German selected" }
  russian.signal_connect('activate') { |w| puts "w=#{w.class}:Russian selected" }
  italian.signal_connect('activate') { |w| puts "w=#{w.class}:Italian selected" }

  testmenu.show_all
  mytb_menu.menu = testmenu
end
# Turn image file into pixbuf, and return nil in case of file error.
def get_pixbuf_if_file_exists(file)
  begin
    pixbuf = Gdk::Pixbuf.new(file)
    rescue GLib::FileError => err
      print "I/O ERROR (%s): %s\n" % [err.class, err]
      pixbuf = nil
  end
  pixbuf
end
## ----------
## DnD Source
## ----------

def set_dnd_source_frame_widget(widg)

 ### ----------------------------------------------------------------------(start)--
 ### NOTE: -- the following is not really necessary, if commented out all 
 ###          still works fine!
 # Gtk::Drag.dest_unset(widg)     # cancel earlier dnd destination setting 
 ### ----------------------------------------------------------------------(end)----

  widg.add_events(Gdk::Event::BUTTON_PRESS_MASK)
  widg.signal_connect('button-press-event') do |w, e|

    Gtk::Drag.begin(
	widget=w,
	targets=Gtk::TargetList.new(TBDND_TARGETS), # [["toolbar", Gtk::Drag::TARGET_SAME_APP, 100]]
	actions=TBDND_ACTIONS,                      # Gdk::DragContext::ACTION_ASK
	button=e.button,
	event=e                              # Gdk::Event::DRAG_LEAVE
    )
  end
end

## ---------------
## DnD Destination
## ---------------

def reverse_dnd_source_n_destination(src_widg, toolbar, dest_widg)

  # Remember in the continuation of this method the destination and
  # source widgets are to be reversed
  set_dnd_source_frame_widget(dest_widg)
  set_dnd_destination_frame_widget(dest_widg, toolbar, src_widg)
end

def set_dnd_destination_frame_widget(src_widg, toolbar, dest_widg)

  Gtk::Drag.dest_set(
		widget=dest_widg,
		flags=Gtk::Drag::DEST_DEFAULT_MOTION,
		targets=TBDND_TARGETS,  # [["toolbar", Gtk::Drag::TARGET_SAME_APP, 100]]
		actions=TBDND_ACTIONS,  # Gdk::DragContext::ACTION_ASK
	)

  dest_widg.signal_connect('drag-drop') do |w, e| 

    src_widg.remove(toolbar)

    if toolbar.orientation == Gtk::Orientation::HORIZONTAL
      dest_widg.set_size_request(80, 350)
      new_orientation = Gtk::Orientation::VERTICAL
      src_widg.set_size_request(350, 10) # x,y ... reduce size of horizontal old source
    else
      dest_widg.set_size_request(350, -1)
      new_orientation = Gtk::Orientation::HORIZONTAL
      src_widg.set_size_request(10, 350) # x,y ... reduce size of vertical old source
    end

##    src_widg.parent.show_all
    # src_widg.shadow_type = Gtk::SHADOW_NONE	# Gtk::SHADOW_IN

    toolbar.orientation = new_orientation

    dest_widg.add(toolbar)
##    dest_widg.show_all

    Gtk::Drag.source_unset(dest_widg)	# cancel earlier dnd source setting

    # NOTE: by now source and destination are already reversed.
    #       The next reverse will allow next dnd to start all
    #       over again like nothing ever happened.
    reverse_dnd_source_n_destination(src_widg, toolbar, dest_widg)
  end
end

entry   = Gtk::Entry.new
entry.set_size_request(100, -1)
toolbar = Gtk::Toolbar.new

vtbframe = Gtk::Frame.new.set_size_request(10, -1)
htbframe = Gtk::Frame.new.set_size_request(200, 10)

vbox = Gtk::VBox.new(false, 0)               # (homogeneous, spacing)
hbox = Gtk::HBox.new(false, 0)

vbox.pack_start(htbframe,  false,  false, 0) # (child, expand, fill,  padding)
vbox.pack_start(hbox,      true,   true,  0)
hbox.pack_start(vtbframe,  false,  false, 0)

workspace = Gtk::VBox.new(false, 5)
workspace.pack_start(entry,                     false, false, 0)
workspace.pack_start(Gtk::Label.new("D-e-m-o"), true,  true,  0)
hbox.pack_start(workspace,                      true,  true,  0)

create_toolbar(toolbar, entry)

toolbar.set_size_request(300, -1)
htbframe.set_size_request(300, -1)
htbframe.add(toolbar)

set_dnd_source_frame_widget(htbframe)
set_dnd_destination_frame_widget(htbframe, toolbar, vtbframe)	# (src_tbfr, toolbar, dest_tbfr)

window.add(vbox)
window.show_all
Gtk.main
Last modified:2012/12/12 14:01:58
Keyword(s):
References:[tut-gtk2-mnstbs] [tut-gtk2-dnd-intro] [tut-gtk] [tut-gtk2-dnd]