Παράδειγμα παλέτας εργαλείου

Αυτό το παράδειγμα προσθέτει μια ToolPalette και μια DrawingArea σε ένα παράθυρο και επιτρέπει στον χρήστη να μεταφέρει εικονίδια από την παλέτα εργαλείων στην περιοχή σχεδίασης. Η παλέτα εργαλείων περιέχει αρκετές ομάδες στοιχείων. Τα σύνθετα πλαίσια επιτρέπουν στον χρήστη να αλλάξει την τεχνοτροπία και τον προσανατολισμό της παλέτας εργαλείων.

Φιγούρα 13-1Παλέτα εργαλείου

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();

      auto 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->m_pixbuf = icon_theme->load_icon(icon_name, width, Gtk::ICON_LOOKUP_GENERIC_FALLBACK);
      this->m_x = x;
      this->m_y = y;
    }

    Glib::RefPtr<Gdk::Pixbuf> m_pixbuf;
    double m_x, m_y;
  };

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

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

  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()
{
  auto 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_for_context = icon_theme->list_icons(context_name);
    std::sort(icon_names_for_context.begin(), icon_names_for_context.end());
    guint icons_count = 0;
    for (type_stringvec::const_iterator iconiter = icon_names_for_context.begin(); iconiter != icon_names_for_context.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;

      auto image = Gtk::manage(new Gtk::Image(pixbuf));
      auto 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()
{
  auto 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);
    auto 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)
{
  auto entry = Gtk::manage(new Gtk::Entry());
  entry->set_text(text);
  entry->set_width_chars(5);

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

  return item;
}

void ExampleWindow::load_special_items()
{
  auto 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);

  auto 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())
  {
    auto iter = m_canvas_items.begin();
    auto 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->m_pixbuf)
    return;

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

  Gdk::Cairo::set_source_pixbuf(cr,
    item->m_pixbuf,
    item->m_x - cx * 0.5, item->m_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->m_x = x;
    m_drop_item->m_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.
  auto widget = drag_get_source_widget(context);

  auto 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 = nullptr;
  if(drag_palette)
    drag_item = drag_palette->get_drag_item(selection_data);

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

  delete m_drop_item;
  m_drop_item = nullptr;

  try
  {
    auto 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 = nullptr;

  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[])
{
  auto app = Gtk::Application::create(argc, argv, "org.gtkmm.example");

  ExampleWindow window;

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