複数アイテムウィジット

複数アイテムウィジットはGtk::Containerを継承しています。これはGtk::Binと同じで、add()メソッドとremove()メソッドを使って内蔵するウィジットを加えたり除去したりできます。しかし、Gtk::Binとは違い、Gtk::Containerremove()メソッドは、引数をとってどのウィジットを除去するか指定できます。

パッキング

おそらくもうgtkmmウィンドウが"伸縮自在"であることにお気づきでしょう。ウィンドウは様々な方向に引き伸ばされることがあります。それがウィジット・パッキングシステムが必要な理由です。

多くのGUIツールキットではウィンドウにウィジットを正確に配置するのに、絶対座標を使ったり、GUIエディタを使ったりしています。これは様々な問題を引き起こします:

  • ウィジットはウィンドウの大きさが変更されても再配置されません。なかにはウィンドウが小さくなると見えなくなってしまったり、大きくなると不恰好な隙間ができてしまうものまであります。

  • しかし必要な隙間の量を予測することは、テキストが別の言語に翻訳されたり別のフォントで表示されたりすると全く不可能になります。UNIX上ではさらに、様々なテーマやウィンドウマネージャーの効果を予想することもできません。

  • ウィンドウのレイアウトを"オン・ザ・フライ"で変更すること、例えば、枠外のウィジットを表示させるようにするのは非常に込み入った問題です。そうするには全てのウィジットの位置のうんざりするような再計算が必要です。

gtkmmではパッキングシステムを使うことでこれらの問題を解決しています。ウィンドウ内のそれぞれのウィジットを位置を指定するのではなく、ウィジットを行あるいは列、またはテーブルに配置できます。gtkmmではウィンドウサイズを、内部にあるウィジットのサイズを基に自動で変更するようにできます。その内部ウィジットのサイズはそれが持つテキストの量、または設定したサイズの最大値・最小値、かつ/またはウィジット間で共有するように設定したスペースの量で決定されます。 レイアウトを完璧に設定するには、ウィジットごとに詰め物(padding)の距離とセンタリングの値を指定してください。ユーザーがウィンドウを操作すると、gtkmmはそれらの情報全てを使ってリサイズと再配置をスマートかつなめらかに行います。

gtkmmでは、コンテナを使ってウィジットを階層的に配置します。コンテナウィジットは他のウィジットを保持(contain)するものです。多くのgtkmmウィジットはコンテナです。ウィンドウ、ノートブックタブ、ボタン、これらは全てコンテナウィジットです。コンテナは二種類あります。単一ウィジットコンテナ(全てGtk::Binの派生)と、複数ウィジットコンテナ(Gtk::Containerの派生)です。gtkmm内のほとんどのウィジットはGtk::Binの派生であり、これにはGtk::Windowも含まれます。

そう、次のことは正しいです: ウィンドウは高々ひとつのウィジットしか含むことはできません。それでは、ウィンドウを「使える」ようにするにはどうすればいいのでしょう?それにはウィンドウ内に複数コンテナのウィジットを配置することです。この際、非常に有用なコンテナウィジットとして、Gtk::VBox, Gtk::HBox, Gtk::Tableがあります。

  • Gtk::VBoxGtk::HBoxは自身の子ウィジットをそれぞれ垂直と水平に配置します。子ウィジットを挿入するには、pack_start()pack_end()を使ってください。

  • Gtk::Tableは自身の子ウィジットをグリッド状に配置します。子ウィジットを挿入するにはattach()を使ってください。

他にもコンテナはいくつかあります。それらについては後々、説明していきます。

もしもツールキットによるパッキングを使った経験がないのなら、馴れるには少々時間がかかるかもしれません。しかし、その他のツールキットと同様、こうすることでビジュアルなフォームエディタに全く頼らずに済むようになる、ということに気がつくでしょう。

改良版Hello World

それでは、学んできたことを説明しながら少々改良したhelloworldを見ていきましょう。

Figure 7.6. Hello World 2

Hello World 2

Source Code

File: helloworld.h

#ifndef GTKMM_EXAMPLE_HELLOWORLD_H
#define GTKMM_EXAMPLE_HELLOWORLD_H

#include <gtkmm/button.h>
#include <gtkmm/box.h>
#include <gtkmm/window.h>

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

protected:

  // Signal handlers:
  // Our new improved on_button_clicked(). (see below)
  virtual void on_button_clicked(Glib::ustring data);

  // Child widgets:
  Gtk::HBox m_box1;
  Gtk::Button m_button1, m_button2;
};

#endif // GTKMM_EXAMPLE_HELLOWORLD_H

File: main.cc

#include <gtkmm/main.h>
#include "helloworld.h"

int main (int argc, char *argv[])
{
  Gtk::Main kit(argc, argv);

  HelloWorld helloworld;
  //Shows the window and returns when it is closed.
  Gtk::Main::run(helloworld);

  return 0;
}

File: helloworld.cc

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

HelloWorld::HelloWorld()
: m_button1("Button 1"),
  m_button2("Button 2")
{
  // This just sets the title of our new window.
  set_title("Hello Buttons!");

  // sets the border width of the window.
  set_border_width(10);

  // put the box into the main window.
  add(m_box1);

  // Now when the button is clicked, we call the "on_button_clicked" function
  // with a pointer to "button 1" as it's argument
  m_button1.signal_clicked().connect(sigc::bind<Glib::ustring>(
              sigc::mem_fun(*this, &HelloWorld::on_button_clicked), "button 1"));

  // instead of gtk_container_add, we pack this button into the invisible
  // box, which has been packed into the window.
  // note that the pack_start default arguments are Gtk::EXPAND | Gtk::FILL, 0
  m_box1.pack_start(m_button1);

  // always remember this step, this tells GTK that our preparation
  // for this button is complete, and it can be displayed now.
  m_button1.show();

  // call the same signal handler with a different argument,
  // passing a pointer to "button 2" instead.
  m_button2.signal_clicked().connect(sigc::bind<-1, Glib::ustring>(
              sigc::mem_fun(*this, &HelloWorld::on_button_clicked), "button 2"));

  m_box1.pack_start(m_button2);

  // Show the widgets.
  // They will not really be shown until this Window is shown.
  m_button2.show();
  m_box1.show();
}

HelloWorld::~HelloWorld()
{
}

// Our new improved signal handler.  The data passed to this method is
// printed to stdout.
void HelloWorld::on_button_clicked(Glib::ustring data)
{
  std::cout << "Hello World - " << data << " was pressed" << std::endl;
}

このプログラムをビルド、実行したら、ウィンドウをリサイズしてそのふるまいを観てください。また、ボックスのセクションを読みながらpack_start()のオプションで遊んでみてください。

STLスタイルのAPI

TODO: Use 'Standard Library' instead of STL. 熟練したC++プログラマーはこのことを聞いてよろこぶでしょう。gtkmmのほとんどのContainerウィジットは、Gtk::Box::children()Gtk::Notebook::pages()などのアクセッサメソッドを経由して、STLスタイルのAPIを利用できます。実際にはSTLコンテナを使っているわけではありません(これには理由があります)が、見た目も使用感も動作もSTLコンテナのクラスのようにふるまいます。

これらのAPIはSTLコンテナのAPIとほとんど同じなので、ここで詳細に説明するかわりに、STLのドキュメントを参照してもらいます。このことはgtkmmのポリシーのひとつ、既存のスタンダードを利用するというものからきています。

しかしながら、STLスタイルのAPIはある状況では醜くダラダラとしたものになってしまうことがあり、使用を好まない人もいます。一方で、ほとんど宗教的に使用する人もいます。ですから、私たちは強制はしません。多くのコンテナウィジットにはシンプルな非STLスタイルのAPIとして、append()prepend()があります。

少なくともどのgtkmmコンテナリストも、イテレータと普通の挿入・削除・追加メソッドをサポートしています。以下のSTLスタイルのAPIは利用可能であると考えてかまいません:

  • begin() returns a begin iterator

  • end() returns an end iterator

  • rbegin() returns a reverse begin iterator

  • rend() returns a reverse end iterator

  • size()

  • max_size()

  • empty()

  • insert()

  • push_front()

  • push_back()

  • pop_front()

  • pop_back()

  • clear()

  • erase()

  • remove()

  • find()

  • front()

  • back()

また、[]演算子もオーバーロードされます。しかし、これは普通O(N)オーダーになりますから、パフォーマンスが必要な場合やリストの要素数が非常に多い場合には、使用する前によく検討してください。

コンテナの要素オブジェクトとリストオブジェクトは、それぞれのコンテナに対して名前に_Helpersを付けた名前空間において定義されています。例えば、ノートブックウィジットの補助(helper)名前空間はGtk::Notebook_Helpersです。

アイテムの追加

gtkmmのSTLスタイルAPIと本物のSTLコンテナの間には大きな違いがひとつあります。通常std::vectorを使う場合、例えばどんなオブジェクトを入れても取り出すときに変更されてはいないはずです。std::vector<int>を作って、そこからdoubleを取り出すことはできないでしょう。しかし、gtkmmのSTLスタイルAPIが常にこのように働くとは限りません。ある種のオブジェクトを入れて、別種のオブジェクトを取り出す、ということがしばしばあります。どうしてこんな奇妙な振舞いをするのでしょう?

メニューウィジットを考えましょう。このウィジットはメニューとメニューアイテムの階層的なリストを管理しなくてはいけません。メニューが保持できるのは、ある種のオブジェクト、メニューアイテムや区切り、サブメニューなどだけです。一貫性を維持するためには間違ったオブジェクト型を締め出すような”フィルタ”が必要になります。また、数種のオブジェクト型だけが許容されているのですから、メニューを簡単に構築するのに便利なメソッドが提供できるはずです。

gtkmmはこのふたつの要求を特別な補助エレメントを使うことで解決しています。補助エレメントは一時的なものであり、通常は生成されてすぐ同じ呼び出しの中で挿入メソッドに渡されます。リストの挿入メソッドは、この補助エレメントにある情報を使って本当のオブジェクトを生成し、それからこれをコンテナに挿入します。

コード例としてNotebookウィジット(ノートブックのセクションで解説しています)を見てみましょう。Notebookウィジットはひとつながりの”ページ”を持っています。

ノートブック内のそれぞれのページは、少なくとも以下の情報を要求します:

  • 子ウィジット(無し、またはひとつ)。ページ内に配置される。

  • ページのタブに表示されるラベル

gtkmmのノートブックウィジットはこの他にもデータをそれぞれのページで保持しています。)

新しいページをノートブックに挿入するには、ノートブックの補助クラスが使えます。このように:

notebook->pages().push_back(
          Gtk::Notebook_Helpers::TabElem(*frame, bufferl));

さて、何をしているのか見てみましょう。Notebookウィジットのオブジェクトを指すポインター、notebookがあります。そこからpages()メンバメソッドを呼び出します。これはSTLスタイルのリストオブジェクトを返します。さらにその上でpush_back()メソッド(これはSTLを知ってる人には馴染ぶかいはずです)を呼びます。

pages()メソッドが返すオブジェクトはNotebook_Helpers::PageListと呼ばれるものです。これは先ほどから説明しつづけているSTLスタイルのコンテナのひとつです。さあ、クラスをちょっと見てみましょう(以下のものは明解さのために大幅に編集されています。実際のクラス定義は<gtkmm/notebook.h>を見てください)。

namespace Notebook_Helpers
{
    class PageList
    {
    public:
             . . .
        void push_back(const Element& e);
             . . .
        Page* operator[](size_type l);
    };
};

ここで注目すべき重要なことがふたつあります:

  • push_back()メソッドは引数に(補助)Elementのオブジェクトをひとつ取ること。

  • オーバーロードされた[]演算子はPageオブジェクトへのポインタを返すこと。

このような方法をとることには大きな利点がいくつかあります:

  • メニューのような複雑なウィジットを簡単に生成したいという要求に対して、様々な種類の補助オブジェクトが提供されます。

  • 実際のオブジェクトが生成されるのは適切な時が来るまで遅延されます。GTK+ではそれまでに十分な情報が得られない場合がときおりあるからです。

  • リストが持っているオブジェクトの定義は変わり得ます。オブジェクトのインターフェイスをプログラマが考慮する必要はありません。例えば、Pageオブジェクトが大幅に変更されたとしても、プログラマは気にかける必要はありません。Elementは変更する必要がなく、それらは期待されたとおりに動作するからです。

  • 新しい機能をサポートするために、新しいElementオブジェクトをいつでも既存のコードに追加できます。

全ての複数アイテムコンテナにはElementオブジェクトが補助クラスの名前空間にあります。また、追加のクラス(TabElemMenuElemなど)が使える場合もあります。これらはElementから派生しています。Elementはコンテナによって違いますから、それぞれ違った種類のオブジェクト型を持っています。

このことを覚えておくのはとても重要です。Elementは”本物の”オブジェクトではありません。これらは一時的なものとしてのみ存在し、決してコンテナに入ることはありません。これらが使われるのは一時的な”パラメータ保持”のためだけです。ですから、次のようなコードは許されません。:

MenuElem* m = new MenuElem("hello");
m->right_justify();
items().push_back(*m);

ここでは新しくMenuElem補助オブジェクトを生成し、それからメニューに加える前にright_justify()メソッドを呼び出そうとしています。ところが困ったことにMenuElemにはright_justify()メソッドはありません。これを行う正しいやり方は、次のようになるでしょう:

items().push_back(MenuElem("hello"));
items().back()->right_justify();

ここではMenuElemを生成し、それをメニューのなかにpush_back()で挿入しています。こうすると本当のメニューアイテムが生成されます。それからリストから取り出したオブジェクトでright_justify()を呼び出します。これは問題ありません。リストから取り出したオブジェクトはMenuElemではなく、本物のMenuItemオブジェクトです。ですから予期したとおり、right_justify()メソッドをサポートしています。

ボックス

上の例のように、ほとんどのパッキングはボックスを使っています。ボックスは目に見えないコンテナで、この中に使用するウィジットをパックできます。水平ボックスにウィジットをパッキングすると、そのオブジェクトは水平に詰められていきます。左から右、または右から左といった詰める方向はpack_start()pack_end()のどちらを使ったかによります。垂直ボックスではウィジットは垂直に詰められていきます。上から下、または下から上に詰める方向を決める方法は水平と同様です。望ましい効果が得られるように、ボックスを入れ子にしたり横付けしたり、どのように組み合わせて使ってもかまいません。

ウィジットを追加する

子ウィジットごとのパッキング設定

pack_start()pack_end()の2メソッドはウィジットをコンテナの内側に配置します。pack_start()メソッドでは頂上の配置から始め、ちょうどVBoxと同じように下に向かって降りていく、あるいはHBoxのように左から右に詰めていきます。pack_end()はその反対です。VBoxでは底から頂上に向かって詰めていき、HBoxでは右から左に向かいます。これらのメソッドを使えばウィジットを左詰め、あるいは右詰めにすることが出来ます。わたしたちのコード例ではほとんどpack_start()の方を使っています。

どのようにウィジットの配置を管理するかという問題にはいくつか選択肢があり、そのため最初のうちは混乱してしまうかもしれません。困難さを覚えるようでしたら、glade GUIデザイナで遊んでみてどういう配置が可能なのか見てみるのがいい場合があります。libglademm APIを使ってGUIを実行時にロードするように決断するかもしれません。

ウィジットの配置には基本的な5つのスタイルがあります。以下の図で説明します:

Figure 7.7. Box Packing 1

Box Packing 1

各行にはひとつずつ水平ボックス(HBox)があり、その中にいくつかボタンがあります。行ごとのそれぞれのボタンは同じ引数のpack_start()メソッドでHBoxへパックされています。

以下がpack_start()メソッドの宣言となっています:

void pack_start(Gtk::Widget& child, PackOptions options = PACK_EXPAND_WIDGET, guint padding = 0);

最初の引数はパッキングするウィジットです。このコード例では全てButtonオブジェクトです。

引数optionsは以下の3つのうちひとつを取ることができます:

  • PACK_SHRINK: 余白は子ウィジットのサイズに縮められます。ウィジットはちょうどいいサイズにまで広げられ、伸張されることはありません。

  • PACK_EXPAND_PADDING: 余白はパディング(詰め物)で満たされます。ウィジットは間隔を空けて配置され、サイズが変更されることもありません。つまり、ウィジットの間には空っぽの空間が空くことになります。

  • PACK_EXPAND_WIDGET: 余白は子ウィジットのサイズを広げることに使われます。しかし、ウィジット間のスペース量が変更されることはありません。

引数paddingは詰められたウィジットの外側にある、余分な境界の幅を指定します。

pack_start()pack_end()メソッドではなく、STLスタイルのAPIを使うのが好みに合うかもしれません。childrenメソッド経由で利用可能です。詳細はSTLスタイルのAPIセクションを見てください。

Reference

コンテナごとのパッキング設定

次がボックスウィジットのコンストラクタです:

Gtk::Box(bool homogeneous = false, int spacing = 0);

引数homogeneoustrueを渡すと、全ての内部ウィジットは同一サイズになります。引数spacingはウィジット間に残しておくピクセルの(最小)量です。

spacing(ボックスを生成するときに設定する)とpadding(子ウィジットをパックするときに設定する)の違いは何でしょうか? spacingはオブジェクトの間に加えられます。paddingはウィジット両隣りに加えられます。次の図を見るとこの違いはより明確になるでしょう:

Figure 7.8. Box Packing 2

Box Packing 2

以下のものは、先のスクリーンショットを生成するコード例のソースです。このコード例にコマンドラインで1から3までの数字を与えて実行すれば、使われているパッキング設定の違いがわかるでしょう。

Source Code

File: examplewindow.h

#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H

#include <gtkmm.h>
#include <packbox.h>

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

protected:
  //Signal handlers:
  virtual void on_button_quit_clicked();

  //Child widgets:
  Gtk::Button m_button;
  Gtk::VBox m_box1;
  Gtk::HBox m_boxQuit;
  Gtk::Button m_buttonQuit;

  Gtk::Label m_Label1, m_Label2;

  Gtk::HSeparator m_seperator1, m_seperator2, m_seperator3, m_seperator4, m_seperator5;
};

#endif //GTKMM_EXAMPLEWINDOW_H

File: packbox.h

#ifndef GTKMM_EXAMPLE_PACKBOX_H
#define GTKMM_EXAMPLE_PACKBOX_H

#include <gtkmm.h>

class PackBox : public Gtk::HBox
{
public:
  PackBox(bool homogeneous, int spacing, Gtk::PackOptions, int padding = 0);
  virtual ~PackBox();

protected:
  Gtk::Button m_button1, m_button2, m_button3;
  Gtk::Button* m_pbutton4;

  char padstr[80];
};

#endif //GTKMM_EXAMPLE_PACKBOX_H

File: main.cc

#include <iostream>
#include <gtkmm/main.h>
#include "examplewindow.h"

int main(int argc, char *argv[])
{
  Gtk::Main main_instance(argc, argv);

  if(argc != 2)
  {
    std::cerr << "usage: packbox num, where num is 1, 2, or 3." << std::endl;
    // this just does cleanup in GTK, and exits with an exit status of 1.
    gtk_exit (1);
  }

  ExampleWindow window( atoi(argv[1]) );
  Gtk::Main::run(window); //Shows the window and returns when it is closed.

  return 0;
}

File: packbox.cc

#include "packbox.h"
#include <cstdio> //For sprintf().

PackBox::PackBox(bool homogeneous, int spacing, Gtk::PackOptions options,
        int padding) :
  Gtk::HBox(homogeneous, spacing),
  m_button1("box.pack_start("),
  m_button2("button,"),
  m_button3((options == Gtk::PACK_SHRINK) ? "Gtk::PACK_SHRINK" :
            ((options == Gtk::PACK_EXPAND_PADDING) ?
             "Gtk::PACK_EXPAND_PADDING" : "Gtk::PACK_EXPAND_WIDGET"))
{
  pack_start(m_button1, options, padding);
  pack_start(m_button2, options, padding);
  pack_start(m_button3, options, padding);

  sprintf(padstr, "%d);", padding);

  m_pbutton4 = new Gtk::Button(padstr);
  pack_start(*m_pbutton4, options, padding);
}

PackBox::~PackBox()
{
  delete m_pbutton4;
}

File: examplewindow.cc

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

ExampleWindow::ExampleWindow(int which)
: m_buttonQuit("Quit")
{
  set_title("Gtk::Box example");

  PackBox *pPackBox1, *pPackBox2, *pPackBox3, *pPackBox4, *pPackBox5;

  switch(which)
  {
    case 1:
    {
      m_Label1.set_text("Gtk::HBox(false, 0);");

      // Align the label to the left side.  We'll discuss this function and
      // others in the section on Widget Attributes.
      m_Label1.set_alignment(Gtk::ALIGN_LEFT, Gtk::ALIGN_TOP);

      // Pack the label into the vertical box (vbox box1).  Remember that
      // widgets added to a vbox will be packed one on top of the other in
      // order.
      m_box1.pack_start(m_Label1, Gtk::PACK_SHRINK);

      // Create a PackBox - homogeneous = false, spacing = 0,
      // options = Gtk::PACK_SHRINK, padding = 0
      pPackBox1 = Gtk::manage(new PackBox(false, 0, Gtk::PACK_SHRINK));
      m_box1.pack_start(*pPackBox1, Gtk::PACK_SHRINK);

      // Create a PackBox - homogeneous = false, spacing = 0,
      // options = Gtk::PACK_EXPAND_PADDING, padding = 0
      pPackBox2 = Gtk::manage(new PackBox(false, 0, Gtk::PACK_EXPAND_PADDING));
      m_box1.pack_start(*pPackBox2, Gtk::PACK_SHRINK);

      // Create a PackBox - homogeneous = false, spacing = 0,
      // options = Gtk::PACK_EXPAND_WIDGET, padding = 0
      pPackBox3 = Gtk::manage(new PackBox(false, 0, Gtk::PACK_EXPAND_WIDGET));
      m_box1.pack_start(*pPackBox3, Gtk::PACK_SHRINK);

      // pack the separator into the vbox.  Remember each of these
      // widgets are being packed into a vbox, so they'll be stacked
      // vertically.
      m_box1.pack_start(m_seperator1, Gtk::PACK_SHRINK, 5);

      // create another new label, and show it.
      m_Label2.set_text("Gtk::HBox(true, 0);");
      m_Label2.set_alignment(Gtk::ALIGN_LEFT, Gtk::ALIGN_TOP);
      m_box1.pack_start(m_Label2, Gtk::PACK_SHRINK);

      // Args are: homogeneous, spacing, options, padding
      pPackBox4 = Gtk::manage(new PackBox(true, 0, Gtk::PACK_EXPAND_PADDING));
      m_box1.pack_start(*pPackBox4, Gtk::PACK_SHRINK);

      // Args are: homogeneous, spacing, options, padding
      pPackBox5 = Gtk::manage(new PackBox(true, 0, Gtk::PACK_EXPAND_WIDGET));
      m_box1.pack_start(*pPackBox5, Gtk::PACK_SHRINK);

      m_box1.pack_start(m_seperator2, Gtk::PACK_SHRINK, 5);

      break;
    }

    case 2:
    {

      m_Label1.set_text("Gtk::HBox(false, 10);");
      m_Label1.set_alignment(Gtk::ALIGN_LEFT, Gtk::ALIGN_TOP);
      m_box1.pack_start(m_Label1, Gtk::PACK_SHRINK);

      pPackBox1 = Gtk::manage(new PackBox(false, 10, Gtk::PACK_EXPAND_PADDING));
      m_box1.pack_start(*pPackBox1, Gtk::PACK_SHRINK);

      pPackBox2 = Gtk::manage(new PackBox(false, 10, Gtk::PACK_EXPAND_WIDGET));
      m_box1.pack_start(*pPackBox2, Gtk::PACK_SHRINK);

      m_box1.pack_start(m_seperator1, Gtk::PACK_SHRINK, 5);


      m_Label2.set_text("Gtk::HBox(false, 0);");
      m_Label2.set_alignment(Gtk::ALIGN_LEFT, Gtk::ALIGN_TOP);
      m_box1.pack_start(m_Label2, Gtk::PACK_SHRINK);

      pPackBox3 = Gtk::manage(new PackBox(false, 0, Gtk::PACK_SHRINK, 10));
      m_box1.pack_start(*pPackBox3, Gtk::PACK_SHRINK);

      pPackBox4 = Gtk::manage(new PackBox(false, 0, Gtk::PACK_EXPAND_WIDGET, 10));
      m_box1.pack_start(*pPackBox4, Gtk::PACK_SHRINK);

      m_box1.pack_start(m_seperator2, Gtk::PACK_SHRINK, 5);

      break;
    }

    case 3:
    {
      // This demonstrates the ability to use Gtk::Box::pack_end() to
      // right justify widgets.  First, we create a new box as before.
      pPackBox1 = Gtk::manage(new PackBox(false, 0, Gtk::PACK_SHRINK));

      // create the label that will be put at the end.
      m_Label1.set_text("end");

      // pack it using pack_end(), so it is put on the right side
      // of the PackBox.
      pPackBox1->pack_end(m_Label1, Gtk::PACK_SHRINK);

      m_box1.pack_start(*pPackBox1, Gtk::PACK_SHRINK);

      // this explicitly sets the separator to 400 pixels wide by 5 pixels
      // high.  This is so the hbox we created will also be 400 pixels wide,
      // and the "end" label will be separated from the other labels in the
      // hbox.  Otherwise, all the widgets in the hbox would be packed as
      // close together as possible.
      m_seperator1.set_size_request(400, 5);

      // pack the separator into ourselves
      m_box1.pack_start(m_seperator1, Gtk::PACK_SHRINK, 5);

      break;
    }

    default:
    {
      std::cerr << "Unexpected command-line option." << std::endl;
      break;
    }
  }

  // Connect the signal to hide the window:
  m_buttonQuit.signal_clicked().connect( sigc::mem_fun(*this,
              &ExampleWindow::on_button_quit_clicked) );

  // pack the button into the quitbox.
  // The last 2 arguments to Box::pack_start are: options, padding.
  m_boxQuit.pack_start(m_buttonQuit, Gtk::PACK_EXPAND_PADDING);
  m_box1.pack_start(m_boxQuit, Gtk::PACK_SHRINK);

  // pack the vbox (box1) which now contains all our widgets, into the
  // main window.
  add(m_box1);

  show_all_children();
}

ExampleWindow::~ExampleWindow()
{
}

void ExampleWindow::on_button_quit_clicked()
{
  hide();
}

ボタンボックス

ボタンボックスはボタンのグループを素早く配置するのに便利な方法のひとつです。これには水平のもの(Gtk::HButtonBox)と垂直のもの(Gtk::VButtonBox)の2種類があります。両方は完全に同一です。名前と方向をのぞけば。

ButtonBoxはアプリケーションの見かけを一貫したものにする手助けになります。ButtonBoxは内部のボタンの余白やパッキング設定に、標準的な設定を使うからです。

ボタンはButtonBoxadd()メソッドで追加されます。

ボタンボックスはレイアウトのスタイルをいくつかサポートしています。スタイルは取得、あるいは変更できます。それぞれget_layout()set_layout()を使ってください。

Reference

Figure 7.9. ButtonBox

ButtonBox

Source Code

File: examplebuttonbox.h

#ifndef GTKMM_EXAMPLE_BUTTONBOX_H
#define GTKMM_EXAMPLE_BUTTONBOX_H

#include <gtkmm.h>

class ExampleButtonBox : public Gtk::Frame
{
public:
  ExampleButtonBox(bool horizontal,
       const Glib::ustring& title,
       gint spacing,
       Gtk::ButtonBoxStyle layout);

protected:
  Gtk::Button m_Button_OK, m_Button_Cancel, m_Button_Help;
};

#endif //GTKMM_EXAMPLE_BUTTONBOX_H

File: examplewindow.h

#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H

#include <gtkmm.h>

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

protected:
  //Signal handlers:
  virtual void on_button_clicked();

  //Child widgets:
  Gtk::VBox m_VBox_Main, m_VBox;
  Gtk::HBox m_HBox;
  Gtk::Frame m_Frame_Horizontal, m_Frame_Vertical;
};

#endif //GTKMM_EXAMPLEWINDOW_H

File: main.cc

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

int main(int argc, char *argv[])
{
  Gtk::Main kit(argc, argv);

  ExampleWindow window;
  //Shows the window and returns when it is closed.
  Gtk::Main::run(window);

  return 0;
}

File: examplebuttonbox.cc

#include "examplebuttonbox.h"

ExampleButtonBox::ExampleButtonBox(bool horizontal,
       const Glib::ustring& title,
       gint spacing,
       Gtk::ButtonBoxStyle layout)
: Gtk::Frame(title),
  m_Button_OK("OK"),
  m_Button_Cancel("Cancel"),
  m_Button_Help("Help")
{
  Gtk::ButtonBox* bbox = 0;

  if(horizontal)
    bbox = Gtk::manage( new Gtk::HButtonBox() );
  else
    bbox = Gtk::manage( new Gtk::VButtonBox() );

  bbox->set_border_width(5);

  add(*bbox);

  /* Set the appearance of the Button Box */
  bbox->set_layout(layout);
  bbox->set_spacing(spacing);

  bbox->add(m_Button_OK);
  bbox->add(m_Button_Cancel);
  bbox->add(m_Button_Help);
}

File: examplewindow.cc

#include "examplewindow.h"
#include "examplebuttonbox.h"

ExampleWindow::ExampleWindow()
: m_Frame_Horizontal("Horizontal Button Boxes"),
  m_Frame_Vertical("Vertical Button Boxes")
{
  set_title("Gtk::ButtonBox");
  add(m_VBox_Main);

  m_VBox_Main.pack_start(m_Frame_Horizontal, Gtk::PACK_EXPAND_WIDGET, 10);

  //The horizontal ButtonBoxes:
  m_VBox.set_border_width(10);
  m_Frame_Horizontal.add(m_VBox);

  m_VBox.pack_start(*Gtk::manage(
              new ExampleButtonBox(true, "Spread (spacing 40)", 40,
                  Gtk::BUTTONBOX_SPREAD)),
          Gtk::PACK_EXPAND_WIDGET, 0);

  m_VBox.pack_start(*Gtk::manage(
              new ExampleButtonBox(true, "Edge (spacing 30)", 30,
                  Gtk::BUTTONBOX_EDGE)),
          Gtk::PACK_EXPAND_WIDGET, 5);

  m_VBox.pack_start(*Gtk::manage(
              new ExampleButtonBox(true, "Start (spacing 20)", 20,
                  Gtk::BUTTONBOX_START)),
          Gtk::PACK_EXPAND_WIDGET, 5);

  m_VBox.pack_start(*Gtk::manage(
              new ExampleButtonBox(true, "end (spacing 10)", 10,
                  Gtk::BUTTONBOX_END)),
          Gtk::PACK_EXPAND_WIDGET, 5);


  //The vertical ButtonBoxes:
  m_VBox_Main.pack_start(m_Frame_Vertical, Gtk::PACK_EXPAND_WIDGET, 10);

  m_HBox.set_border_width(10);
  m_Frame_Vertical.add(m_HBox);

  m_HBox.pack_start(*Gtk::manage(
              new ExampleButtonBox(false, "Spread (spacing 5)", 5,
                  Gtk::BUTTONBOX_SPREAD)),
          Gtk::PACK_EXPAND_WIDGET, 0);

  m_HBox.pack_start(*Gtk::manage(
              new ExampleButtonBox(false, "Edge (spacing 30)", 30,
                  Gtk::BUTTONBOX_EDGE)),
          Gtk::PACK_EXPAND_WIDGET, 5);

  m_HBox.pack_start(*Gtk::manage(
              new ExampleButtonBox(false, "Start (spacing 20)", 20,
                  Gtk::BUTTONBOX_START)),
          Gtk::PACK_EXPAND_WIDGET, 5);

  m_HBox.pack_start(*Gtk::manage(new ExampleButtonBox(false, "End (spacing 10)",
                  10, Gtk::BUTTONBOX_END)),
          Gtk::PACK_EXPAND_WIDGET, 5);

  show_all_children();
}

ExampleWindow::~ExampleWindow()
{
}

void ExampleWindow::on_button_clicked()
{
  hide();
}

テーブル

テーブルを使えばウィジットを格子上に配置できます。

コンストラクタ

使用するグリッドの次元はコンストラクタで指定する必要があります:

Gtk::Table(int rows = 1, int columns = 1, bool homogeneous = false);

最初の引数はテーブルに作成する行の数、第二引数はもちろん列の数です。homogeneoustrueにすると、テーブルのセルは全て同一サイズとなります(一番大きなウィジットのサイズに揃えられます)。

行と列のインデックスは0からスタートします。rows = 2, columns = 2と設定したとすると、レイアウトは以下のようになるでしょう:

 0          1          2
0+----------+----------+
 |          |          |
1+----------+----------+
 |          |          |
2+----------+----------+

座標系は左上から始まることに注意してください。

ウィジットを追加する

ウィジットをセル内に配置するには次のメソッドを使います:

void Gtk::Table::attach(Gtk::Widget& child,
                        guint left_attach, guint right_attach,
                        guint top_attach, guint bottom_attach,
                        guint xoptions = Gtk::FILL | Gtk::EXPAND,
                        guint yoptions = Gtk::FILL | Gtk::EXPAND,
                        guint xpadding = 0, guint ypadding = 0);

最初の引数はテーブル内に配置したいウィジットです。

left_attachright_attach引数にはウィジットをどこに配置するか指定します。例えば、2x2テーブルにおいてボタンを右下のセルに、そのセルを占有するように配置する場合、left_attach = 1、right_attach = 2, top_attach = 1, bottom_attach = 2となります。一方、ウィジットをその2x2テーブルの上列全てに渡って配置する場合、パラメータの設定はleft_attach = 0, right_attach = 2, top_attach = 0, bottom_attach = 1となります。

xoptionsyoptionsはパッキングオプションであり、OR演算によって複数指定できます。オプションには以下のものがあります:

Gtk::FILL

テーブルがウィジットよりも大きい場合、このGtk::FILLが指定されているならばウィジットは利用可能な領域全てに展開されます。

Gtk::SHRINK

テーブルに割り当てられたスペースが要求されるものよりも小さい場合(例えば、ユーザーがウィンドウをリサイズした場合など)、ウィジットは通常、ウィンドウの底の方から見えなくなるだけです。しかし、Gtk::SHRINKが指定されているならば、ウィジットはテーブルと共に縮小されます。

Gtk::EXPAND

これはテーブルがウィンドウ内に残っているスペース全てを使い切るようにします。

パディング引数はpack_start()での働きと同じです。

その他のメソッド

set_row_spacing()set_col_spacing()メソッドは行と列の間のスペースを設定します。注意して欲しいのは、スペースは列ではそれぞれの列の右に、行ではそれぞれの行の下に取られることです。

また、全ての行、列に対して一貫したスペース設定を設定することもできます。set_row_spacings()set_col_spacings()を使ってください。これらの呼び出しでは最後の行と最後の列にはスペースが取られないことに気を付けてください。

Reference

以下の例では、2x2のテーブルに3つのボタンを配置したウィンドウを生成しています。最初のふたつのボタンは上の列に置かれます。3つめのボタンは下の列に、ふたつの列に渡って配置されます。

Figure 7.10. Table

Table

Source Code

File: examplewindow.h

#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H

#include <gtkmm.h>

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

protected:
  //Signal handlers:
  virtual void on_button_quit();
  virtual void on_button_numbered(Glib::ustring data);

  //Child widgets:
  Gtk::Table m_Table;
  Gtk::Button m_Button_1, m_Button_2, m_Button_Quit;

};

#endif //GTKMM_EXAMPLEWINDOW_H

File: main.cc

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

int main(int argc, char *argv[])
{
  Gtk::Main kit(argc, argv);

  ExampleWindow window;
  //Shows the window and returns when it is closed.
  Gtk::Main::run(window);

  return 0;
}

File: examplewindow.cc

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

ExampleWindow::ExampleWindow()
: m_Table(2, 2, true),
  m_Button_1("button 1"),
  m_Button_2("button 2"),
  m_Button_Quit("Quit")
{
  set_title("Gtk::Table");
  set_border_width(20);

  add(m_Table);

  m_Table.attach(m_Button_1, 0, 1, 0, 1);
  m_Table.attach(m_Button_2, 1, 2, 0, 1);
  m_Table.attach(m_Button_Quit, 0, 2, 1, 2);

  m_Button_1.signal_clicked().connect(
          sigc::bind<Glib::ustring>( sigc::mem_fun(*this,
                  &ExampleWindow::on_button_numbered), "button 1") );
  m_Button_2.signal_clicked().connect(
          sigc::bind<Glib::ustring>( sigc::mem_fun(*this,
                  &ExampleWindow::on_button_numbered), "button 2") );

  m_Button_Quit.signal_clicked().connect(sigc::mem_fun(*this,
              &ExampleWindow::on_button_quit) );

  show_all_children();
}

ExampleWindow::~ExampleWindow()
{
}

void ExampleWindow::on_button_quit()
{
  hide();
}

void
ExampleWindow::on_button_numbered(Glib::ustring data)
{
  std::cout << "Hello again - " << data << " was pressed" << std::endl;
}

ノートブック

Notebookは積み重なったページ(page)を持ち、ここにウィジットを保持できます。ページを選択するにはラベルのついたタブ(tab)を使います。Notebookを使えばいくつかウィジットの集まりを小さな領域に、一度に1ページだけ見せるように配置できます。これは例えば、設定ダイアログによく使われます。

append_page(), prepend_page() , insert_page()メソッドを使って、タブのついたページをNotebookオブジェクトに追加してください。引数として子ウィジットとタブの名前を指定します。

現在見えているページを取得するには、get_current_page()メソッドを使います。これはページ数を返しますから、get_nth_page()にその数を与えて呼ぶことで、実際の子ウィジットへのポインタが得られます。

プログラムから選択しているページを変更するには、set_page()メソッドを使います。

NotebookにもSTLスタイルのAPIがあります。こちらを使った方が明快に感じるかもしれません。

Reference

Figure 7.11. Notebook

Notebook

Source Code

File: examplewindow.h

#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H

#include <gtkmm.h>

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

protected:
  //Signal handlers:
  virtual void on_button_quit();
  virtual void on_notebook_switch_page(GtkNotebookPage* page, guint page_num);

  //Child widgets:
  Gtk::VBox m_VBox;
  Gtk::Notebook m_Notebook;
  Gtk::Label m_Label1, m_Label2;

  Gtk::HButtonBox m_ButtonBox;
  Gtk::Button m_Button_Quit;
};

#endif //GTKMM_EXAMPLEWINDOW_H

File: main.cc

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

int main(int argc, char *argv[])
{
  Gtk::Main kit(argc, argv);

  ExampleWindow window;
  //Shows the window and returns when it is closed.
  Gtk::Main::run(window);

  return 0;
}

File: examplewindow.cc

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

ExampleWindow::ExampleWindow()
: m_Label1("Contents of tab 1"),
  m_Label2("Contents of tab 2"),
  m_Button_Quit("Quit")
{
  set_title("Gtk::Notebook example");
  set_border_width(10);
  set_default_size(400, 200);


  add(m_VBox);

  //Add the Notebook, with the button underneath:
  m_Notebook.set_border_width(10);
  m_VBox.pack_start(m_Notebook);
  m_VBox.pack_start(m_ButtonBox, Gtk::PACK_SHRINK);

  m_ButtonBox.pack_start(m_Button_Quit, Gtk::PACK_SHRINK);
  m_Button_Quit.signal_clicked().connect(sigc::mem_fun(*this,
              &ExampleWindow::on_button_quit) );

  //Add the Notebook pages:
  m_Notebook.append_page(m_Label1, "First");
  m_Notebook.append_page(m_Label2, "Second");

  m_Notebook.signal_switch_page().connect(sigc::mem_fun(*this,
              &ExampleWindow::on_notebook_switch_page) );

  show_all_children();
}

ExampleWindow::~ExampleWindow()
{
}

void ExampleWindow::on_button_quit()
{
  hide();
}

void ExampleWindow::on_notebook_switch_page(GtkNotebookPage* /* page */, guint page_num)
{
  std::cout << "Switched to tab with index " << page_num << std::endl;

  //You can also use m_Notebook.get_current_page() to get this index.
}

STLスタイルのAPI

Gtk::NotebookウィジットはSTLスタイルのAPIを持っています。これはpages()メソッド経由で利用できます。こちらのほうを使ってページを追加したりアクセスしたいと思うかもしれません。一般的な情報についてはSTLスタイルのAPIセクションを見てください。

PageList Reference

ノートブックにページを挿入するには、TabElem補助クラスをこのように使います:

m_Notebook.pages().push_back( Gtk::Notebook_Helpers::TabElem(m_ChildWidget, "tab 1") );

TabElem Reference. TODO: Correct URL.

既存の子ウィジットにアクセスするには、PageListの要素であるPageオブジェクトでget_child()メソッドを呼んでください:

Gtk::Widget* pWidget = m_Notebook.pages()[2].get_child();