Palette d'outils
L'objet ToolPalette est semblable à un objet Toolbar, mais il peut comporter un quadrillage d'éléments classés en groupements. L'utilisateur peut masquer ou développer chaque groupement. Comme dans une barre d'outils, les éléments sont affichés sous forme d'icônes uniquement, texte uniquement ou icônes avec texte.
Il est possible de faire glisser, ou simplement activer, les éléments d'un objet ToolPalette. Par exemple, l'utilisateur peut faire glisser des objets sur un canevas pour y créer de nouveaux éléments, ou bien, il peut cliquer sur un élément pour activer une taille de plume donnée dans une application de tracé.
ToolItemGroups should be added to the tool palette via the base class's Gtk::Container::add() method, for instance like so:

Gtk::ToolItemGroup* group_brushes =
  Gtk::manage(new Gtk::ToolItemGroup("Brosses"));
m_ToolPalette.add(*group_brushes);

Les éléments Gtk::ToolItem sont ensuite ajoutés au groupement. Ainsi :

Gtk::ToolButton* button = Gtk::manage(new Gtk::ToolButton(icon, "Grande"));
button->set_tooltip_text("Grande brosse);
group_brushes->insert(*button);

Vous pouvez après cela gérer le signal clicked sur l'objet ToolButton. Autre possibilité, vous pouvez autoriser le glisser-déposer de l'élément sur un autre élément graphique avec Gtk::ToolPalette::add_drag_dest(), puis en faisant appel à Gtk::ToolPalette::get_drag_item() dans le gestionnaire du signal drag_data_received de l'autre élément graphique.
ToolPalette Reference
ToolItemGroup Reference
ToolItem Reference

Glisser-déposer
Call add_drag_dest() to allow items or groups to be dragged from the tool palette to a particular destination widget. You can then use get_drag_item() to discover which ToolItem or ToolItemGroup is being dragged. You can use dynamic_cast to discover whether it is an item or a group. For instance, you might use this in your drag_data_received signal handler, to add a dropped item, or to show a suitable icon while dragging.
Consultez le chapitre Glisser-déposer pour des considérations générales sur cette fonctionnalité avec gtkmm.

Exemple de palette d'outils
Dans cet exemple, des objets ToolPalette et DrawingArea sont associés à une fenêtre. L'utilisateur est autorisé à faire un glisser-déposer depuis la palette d'outils sur la surface de tracé. La palette d'outils contient divers groupes d'éléments. Des boîtes combinées permettent à l'utilisateur de changer le style et l'orientation de la palette d'outils.

Palette d'outils
Source Code
File: canvas.h (For use with gtkmm 3, not gtkmm 2)

#ifndef GTKMM_EXAMPLE_CANVAS_H
#define GTKMM_EXAMPLE_CANVAS_H

#include <gtkmm.h>

// This little canvas class is only here 
// because gtkmm does not have a canvas class yet.
// Applications should probably use GooCanvas::Canvas (goocanvasmm) instead.
class Canvas : public Gtk::DrawingArea
{
public:
  Canvas();
  virtual ~Canvas();

private:

  class CanvasItem
  {
  public:
    CanvasItem(Gtk::Widget* canvas, Gtk::ToolButton* button, double x, double y)
    {
      Glib::ustring icon_name(button->get_icon_name());
      if (icon_name.empty())
        icon_name = button->get_label();

      Glib::RefPtr<Gtk::IconTheme> icon_theme = Gtk::IconTheme::get_for_screen(canvas->get_screen());
      int width = 0;
      int height = 0; //ignored
      Gtk::IconSize::lookup(Gtk::ICON_SIZE_DIALOG, width, height);
      this->pixbuf = icon_theme->load_icon(icon_name, width, Gtk::ICON_LOOKUP_GENERIC_FALLBACK);
      this->x = x;
      this->y = y;
    }

    Glib::RefPtr<Gdk::Pixbuf> pixbuf;
    double x, y;
  };

  void item_draw(const CanvasItem *item,
    const Cairo::RefPtr<Cairo::Context>& cr,
    bool preview);

  virtual bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr);
  virtual void on_drag_data_received(const Glib::RefPtr<Gdk::DragContext>& context, 
    int x, int y, const Gtk::SelectionData& selection_data, guint info, guint time);
  virtual bool on_drag_motion(const Glib::RefPtr<Gdk::DragContext>& context, int x, int y, guint time);
  virtual bool on_drag_drop(const Glib::RefPtr<Gdk::DragContext>& context, int x, int y, guint time);
  virtual void on_drag_leave(const Glib::RefPtr<Gdk::DragContext>& context, guint time);

  bool m_drag_data_requested_for_drop; //So we know what to do in on_drag_data_received().
  CanvasItem* m_drop_item;
  
  typedef std::vector<CanvasItem*> type_vec_items;
  type_vec_items m_canvas_items;
};

#endif //GTKMM_EXAMPLE_CANVAS_H

File: examplewindow.h (For use with gtkmm 3, not gtkmm 2)

#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H

#include <gtkmm.h>
#include "canvas.h"

class ExampleWindow : public Gtk::Window
{
public:
  ExampleWindow();
  virtual ~ExampleWindow();

private:

  void load_icon_items();
  void load_toggle_items();
  void load_special_items();

  //Signal handlers:
  void on_combo_orientation_changed();
  void on_combo_style_changed();

  //Tree model columns:
  class ModelColumnsOrientation : public Gtk::TreeModel::ColumnRecord
  {
  public:

    ModelColumnsOrientation()
    { add(m_col_value); add(m_col_name); }

    Gtk::TreeModelColumn<Gtk::Orientation> m_col_value;
    Gtk::TreeModelColumn<Glib::ustring> m_col_name;
  };

  ModelColumnsOrientation m_ColumnsOrientation;

  //Tree model columns:
  class ModelColumnsStyle : public Gtk::TreeModel::ColumnRecord
  {
  public:

    ModelColumnsStyle()
    { add(m_col_value); add(m_col_name); }

    Gtk::TreeModelColumn<int> m_col_value; //We use int to also allow -1
    Gtk::TreeModelColumn<Glib::ustring> m_col_name;
  };

  ModelColumnsStyle m_ColumnsStyle;


  //Child widgets:
  Gtk::Box m_VBox;
  Gtk::Box m_HBox;
  Gtk::ComboBox m_ComboOrientation;
  Glib::RefPtr<Gtk::ListStore> m_refTreeModelOrientation;
  Gtk::ComboBox m_ComboStyle;
  Glib::RefPtr<Gtk::ListStore> m_refTreeModelStyle;
  Gtk::ToolPalette m_ToolPalette;
  Gtk::ScrolledWindow m_ScrolledWindowPalette;
  Gtk::ScrolledWindow m_ScrolledWindowCanvas;
  Canvas m_Canvas;
};

#endif //GTKMM_EXAMPLEWINDOW_H

File: examplewindow.cc (For use with gtkmm 3, not gtkmm 2)

#include "examplewindow.h"


void ExampleWindow::load_icon_items()
{
  Glib::RefPtr<Gtk::IconTheme> icon_theme = Gtk::IconTheme::get_for_screen(get_screen());

  typedef std::vector<Glib::ustring> type_stringvec;
  type_stringvec icon_names = icon_theme->list_icons();

  // Obtain the names of all contexts, and the icon names per context.
  type_stringvec contexts = icon_theme->list_contexts();
  std::sort(contexts.begin(), contexts.end());

  int requested_icon_size = 0;
  int requested_icon_height = 0; //ignored
  Gtk::IconSize::lookup(Gtk::ICON_SIZE_BUTTON, requested_icon_size, requested_icon_height);
  const guint max_icons_per_group = 10;

  for (type_stringvec::const_iterator iter = contexts.begin(); iter != contexts.end(); ++iter)
  {
    const Glib::ustring context_name = *iter;
    Gtk::ToolItemGroup* group =
      Gtk::manage(new Gtk::ToolItemGroup(context_name));
    m_ToolPalette.add(*group);

    // Iterate through the icon names, populating the ToolItemGroup as appropriate.
    type_stringvec icon_names = icon_theme->list_icons(context_name);
    std::sort(icon_names.begin(), icon_names.end());
    guint icons_count = 0;
    for (type_stringvec::const_iterator iconiter = icon_names.begin(); iconiter != icon_names.end(); ++iconiter)
    {
      const Glib::ustring icon_name = *iconiter;
      Glib::RefPtr<Gdk::Pixbuf> pixbuf;
      try
      {
        pixbuf = icon_theme->load_icon(icon_name, requested_icon_size, Gtk::ICON_LOOKUP_GENERIC_FALLBACK);
      }
      catch (const Gtk::IconThemeError& /* ex */)
      {
        // Gtk::IconTheme::list_icons() may return some names of icons
        // that can't be loaded.
        continue;
      }

      // Skip large icons, just to make the ToolPalette look better.
      if (pixbuf->get_width() > 2*requested_icon_size ||
          pixbuf->get_height() > 2*requested_icon_size)
        continue;

      Gtk::Image* image = Gtk::manage(new Gtk::Image(pixbuf));
      Gtk::ToolButton* button = Gtk::manage(new Gtk::ToolButton(*image, icon_name));
      button->set_tooltip_text(icon_name);
      button->set_is_important();
      group->insert(*button);

      // Prevent us having an insane number of icons:
      ++icons_count;
      if(icons_count >= max_icons_per_group)
        break;
    }
  }
}


void ExampleWindow::load_toggle_items()
{
  Gtk::ToolItemGroup* group = 
    Gtk::manage(new Gtk::ToolItemGroup("Radio Item"));
  m_ToolPalette.add(*group);

  Gtk::RadioToolButton::Group radio_group;

  for(int i = 1; i <= 10; ++i)
  {
    const Glib::ustring label = Glib::ustring::compose("#%1", i);
    Gtk::RadioToolButton* button = Gtk::manage(new Gtk::RadioToolButton());
    button->set_group(radio_group);
    button->set_label(label);
   
    group->insert(*button);
  }
}


static Gtk::ToolItem* create_entry_item(const Glib::ustring& text)
{
  Gtk::Entry* entry = Gtk::manage(new Gtk::Entry());
  entry->set_text(text);
  entry->set_width_chars(5);

  Gtk::ToolItem* item = Gtk::manage(new Gtk::ToolItem());
  item->add(*entry);

  return item;
}

void ExampleWindow::load_special_items()
{
  Gtk::ToolItemGroup* group = Gtk::manage(new Gtk::ToolItemGroup());

  Gtk::Button *label_button = Gtk::manage(new Gtk::Button("Advanced Features"));
  label_button->show();
  group->set_label_widget(*label_button);
  m_ToolPalette.add(*group);

  Gtk::ToolItem* item = create_entry_item ("homogeneous=false");
  group->insert(*item);
  //TODO: Add Gtk::Container::set_child_property().
  gtk_container_child_set (GTK_CONTAINER (group->gobj()), GTK_WIDGET (item->gobj()),
                           "homogeneous", FALSE, NULL);

  item = create_entry_item ("homogeneous=FALSE, expand=TRUE");
  group->insert(*item);
  gtk_container_child_set (GTK_CONTAINER (group->gobj()), GTK_WIDGET (item->gobj()),
                           "homogeneous", FALSE, "expand", TRUE,
                           NULL);

  item = create_entry_item ("homogeneous=FALSE, expand=TRUE, fill=FALSE");
  group->insert(*item);
  gtk_container_child_set (GTK_CONTAINER (group->gobj()), GTK_WIDGET (item->gobj()),
                           "homogeneous", FALSE, "expand", TRUE,
                           "fill", FALSE, NULL);

  item = create_entry_item ("homogeneous=FALSE, expand=TRUE, new-row=TRUE");
  group->insert(*item);
  gtk_container_child_set (GTK_CONTAINER (group->gobj()), GTK_WIDGET (item->gobj()),
                           "homogeneous", FALSE, "expand", TRUE,
                           "new-row", TRUE, NULL);

  Gtk::ToolButton *button = Gtk::manage(new Gtk::ToolButton());
  button->set_icon_name("go-up");
  button->set_tooltip_text("Show on vertical palettes only");
  group->insert(*button);
  button->set_visible_horizontal(false);

  button = Gtk::manage(new Gtk::ToolButton());
  button->set_icon_name("go-next");
  button->set_tooltip_text("Show on horizontal palettes only");
  group->insert(*button);
  button->set_visible_vertical(false);

  button = Gtk::manage(new Gtk::ToolButton());
  button->set_icon_name("edit-delete");
  button->set_tooltip_text("Do not show at all");
  button->set_no_show_all();
  group->insert(*button);
  button->set_visible_vertical(false);

  button = Gtk::manage(new Gtk::ToolButton());
  button->set_icon_name("view-fullscreen");
  button->set_tooltip_text("Expanded this item");
  group->insert(*button);
  gtk_container_child_set (GTK_CONTAINER (group->gobj()), GTK_WIDGET (button->gobj()),
                           "homogeneous", FALSE,
                           "expand", TRUE,
                           NULL);

  button = Gtk::manage(new Gtk::ToolButton());
  button->set_icon_name("help-contents");
  button->set_tooltip_text("A regular item");
  group->insert(*button);
}

ExampleWindow::ExampleWindow()
: m_VBox(Gtk::ORIENTATION_VERTICAL, 6),
  m_HBox(Gtk::ORIENTATION_HORIZONTAL, 6)
{
  set_title("Gtk::ToolPalette example");
  set_size_request(600, 600);
  set_border_width(6);

  add(m_VBox);

  //The Orientation ComboBox:
  m_refTreeModelOrientation = Gtk::ListStore::create(m_ColumnsOrientation);
  Gtk::TreeModel::Row row = *(m_refTreeModelOrientation->append());
  row[m_ColumnsOrientation.m_col_value] = Gtk::ORIENTATION_HORIZONTAL;
  row[m_ColumnsOrientation.m_col_name] = "Horizontal";\
  row = *(m_refTreeModelOrientation->append());
  row[m_ColumnsOrientation.m_col_value] = Gtk::ORIENTATION_VERTICAL;
  row[m_ColumnsOrientation.m_col_name] = "Vertical";
  m_ComboOrientation.set_model(m_refTreeModelOrientation);
  m_VBox.pack_start(m_ComboOrientation, Gtk::PACK_SHRINK);
  m_ComboOrientation.pack_start(m_ColumnsOrientation.m_col_name);
  m_ComboOrientation.signal_changed().connect(
    sigc::mem_fun(*this, &ExampleWindow::on_combo_orientation_changed) );
  m_ComboOrientation.set_active(row);

  //The Style ComboBox:
  m_refTreeModelStyle = Gtk::ListStore::create(m_ColumnsStyle);
  row = *(m_refTreeModelStyle->append());
  row[m_ColumnsStyle.m_col_value] = Gtk::TOOLBAR_TEXT;
  row[m_ColumnsStyle.m_col_name] = "Text";\
  row = *(m_refTreeModelStyle->append());
  row[m_ColumnsStyle.m_col_value] = Gtk::TOOLBAR_BOTH;
  row[m_ColumnsStyle.m_col_name] = "Both";
  row = *(m_refTreeModelStyle->append());
  row[m_ColumnsStyle.m_col_value] = Gtk::TOOLBAR_BOTH_HORIZ;
  row[m_ColumnsStyle.m_col_name] = "Both: Horizontal";
  row = *(m_refTreeModelStyle->append());
  row[m_ColumnsStyle.m_col_value] = Gtk::TOOLBAR_ICONS;
  row[m_ColumnsStyle.m_col_name] = "Icons";
  row = *(m_refTreeModelStyle->append());
  row[m_ColumnsStyle.m_col_value] = -1; // A custom meaning for this demo.
  row[m_ColumnsStyle.m_col_name] = "Default";
  m_ComboStyle.set_model(m_refTreeModelStyle);
  m_VBox.pack_start(m_ComboStyle, Gtk::PACK_SHRINK);
  m_ComboStyle.pack_start(m_ColumnsStyle.m_col_name);
  m_ComboStyle.signal_changed().connect(
    sigc::mem_fun(*this, &ExampleWindow::on_combo_style_changed) );
  m_ComboStyle.set_active(row);

  //Add and fill the ToolPalette:
  load_icon_items();
  load_toggle_items();
  load_special_items();

  m_VBox.pack_start(m_HBox, Gtk::PACK_EXPAND_WIDGET);

  m_ScrolledWindowPalette.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
  m_ScrolledWindowPalette.set_border_width(6);
  m_ScrolledWindowPalette.add(m_ToolPalette);
  m_HBox.pack_start(m_ScrolledWindowPalette);

  on_combo_orientation_changed();

  m_ScrolledWindowCanvas.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS);
  m_ScrolledWindowCanvas.set_border_width(6);
  m_ScrolledWindowCanvas.add(m_Canvas);
  m_ScrolledWindowCanvas.set_size_request(200, -1);
  m_HBox.pack_start(m_ScrolledWindowCanvas);

  m_ToolPalette.add_drag_dest(m_Canvas,
    Gtk::DEST_DEFAULT_HIGHLIGHT, Gtk::TOOL_PALETTE_DRAG_ITEMS, Gdk::ACTION_COPY);

  show_all_children();
}

ExampleWindow::~ExampleWindow()
{
}

void ExampleWindow::on_combo_orientation_changed()
{
  Gtk::TreeModel::iterator iter = m_ComboOrientation.get_active();
  if(!iter)
    return;

  Gtk::TreeModel::Row row = *iter;
  const Gtk::Orientation value = row[m_ColumnsOrientation.m_col_value];
 
  m_ToolPalette.set_orientation(value);

  if(value == Gtk::ORIENTATION_HORIZONTAL)
    m_ScrolledWindowPalette.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_NEVER);
  else
    m_ScrolledWindowPalette.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
}

void ExampleWindow::on_combo_style_changed()
{
  Gtk::TreeModel::iterator iter = m_ComboStyle.get_active();
  if(!iter)
    return;

  Gtk::TreeModel::Row row = *iter;
  const int value = row[m_ColumnsStyle.m_col_value];
 
  if(value == -1)
    m_ToolPalette.unset_style();
  else
    m_ToolPalette.set_style((Gtk::ToolbarStyle)value);
}

File: canvas.cc (For use with gtkmm 3, not gtkmm 2)

#include "canvas.h"
#include <iostream>

Canvas::Canvas()
: m_drag_data_requested_for_drop(false),
  m_drop_item(0)
{
  set_app_paintable();
}

Canvas::~Canvas()
{
  while(!m_canvas_items.empty())
  {
    type_vec_items::iterator iter = m_canvas_items.begin();
    CanvasItem* item = *iter;
    delete item;
    m_canvas_items.erase(iter);
  }

  delete m_drop_item;
}

void Canvas::item_draw(const CanvasItem *item,
  const Cairo::RefPtr<Cairo::Context>& cr,
  bool preview)
{
  if(!item || !item->pixbuf)
    return;

  const double cx = item->pixbuf->get_width();
  const double cy = item->pixbuf->get_height();

  Gdk::Cairo::set_source_pixbuf(cr,
    item->pixbuf,
    item->x - cx * 0.5, item->y - cy * 0.5);

  if(preview)
    cr->paint_with_alpha(0.6);
  else
    cr->paint();
}

bool Canvas::on_draw(const Cairo::RefPtr<Cairo::Context>& cr)
{
  cr->set_source_rgb(1.0, 1.0, 1.0);
  const Gtk::Allocation allocation = get_allocation();
  cr->rectangle(0, 0, allocation.get_width(), allocation.get_height());
  cr->fill();

  for(type_vec_items::iterator iter = m_canvas_items.begin();
    iter != m_canvas_items.end(); ++iter )
  {
    item_draw(*iter, cr, false);
  }

  if(m_drop_item)
    item_draw (m_drop_item, cr, true);

  return true;
}


bool Canvas::on_drag_motion(const Glib::RefPtr<Gdk::DragContext>& context,
  int x, int y, guint time)
{
  m_drag_data_requested_for_drop = false; //It's for drag-motion instead.

  if(m_drop_item)
  {
    // We already have a drop indicator so just update its position.

    m_drop_item->x = x;
    m_drop_item->y = y;

    queue_draw();
    context->drag_status(Gdk::ACTION_COPY, time);
  }
  else
  {
    // Request DnD data for creating a drop indicator.
    // This will cause on_drag_data_received() to be called.
    const Glib::ustring target = drag_dest_find_target(context);

    if (target.empty())
      return false;

    drag_get_data(context, target, time);
  }

  Gtk::DrawingArea::on_drag_motion(context, x, y, time);
  return true;
}


void Canvas::on_drag_data_received(const Glib::RefPtr<Gdk::DragContext>& context,
  int x, int y, const Gtk::SelectionData& selection_data, guint info, guint time)
{
  // Find the tool button which is the source of this DnD operation.
  Gtk::Widget* widget = drag_get_source_widget(context);

  Gtk::ToolPalette* drag_palette = dynamic_cast<Gtk::ToolPalette*>(widget);
  while(widget && !drag_palette)
  {
    widget = widget->get_parent();
    drag_palette = dynamic_cast<Gtk::ToolPalette*>(widget);
  }

  Gtk::ToolItem* drag_item = 0;
  if(drag_palette)
    drag_item = drag_palette->get_drag_item(selection_data);

  // Create a drop indicator when a tool button was found:
  Gtk::ToolButton* button = dynamic_cast<Gtk::ToolButton*>(drag_item);
  if(!button)
    return;

  delete m_drop_item;
  m_drop_item = 0;

  try
  {
    CanvasItem* item = new CanvasItem(this, button, x, y);

    if(m_drag_data_requested_for_drop)
    {
      m_canvas_items.push_back(item);

      // Signal that the item was accepted and then redraw.
      context->drag_finish(true /* success */, false /* del */, time);
    }
    else
    {
      m_drop_item = item;

      // We are getting this data due to a request in drag_motion,
      // rather than due to a request in drag_drop, so we are just
      // supposed to call gdk_drag_status (), not actually paste in
      // the data.
      context->drag_status(Gdk::ACTION_COPY, time);
    }

    queue_draw();
  }
  catch (const Gtk::IconThemeError& ex)
  {
    std::cerr << "IconThemeError: " << ex.what() << std::endl;
  }

  Gtk::DrawingArea::on_drag_data_received(context, x, y, selection_data, info, time);
}


bool Canvas::on_drag_drop(const Glib::RefPtr<Gdk::DragContext>& context, int /* x */, int /* y */, guint time)
{
  // Request DnD data for creating a dopped item.
  // This will cause on_drag_data_received() to be called.
  const Glib::ustring target = drag_dest_find_target(context);

  if (target.empty())
    return false;

  m_drag_data_requested_for_drop = true;
  drag_get_data(context, target, time);

  return true;
}

void Canvas::on_drag_leave(const Glib::RefPtr<Gdk::DragContext>& context, guint time)
{
  //This signal is emitted to clean up the item used for drag-motion,
  //either when the cursor moves out of the widget or when we drop.

  if(!m_drop_item)
    return;

  delete m_drop_item;
  m_drop_item = 0;

  queue_draw();

  Gtk::DrawingArea::on_drag_leave(context, time);
}

File: main.cc (For use with gtkmm 3, not gtkmm 2)

#include "examplewindow.h"
#include <gtkmm/application.h>

int main(int argc, char *argv[])
{
  Glib::RefPtr<Gtk::Application> app = Gtk::Application::create(argc, argv, "org.gtkmm.example");

  ExampleWindow window;

  //Shows the window and returns when it is closed.
  return app->run(window);
}