GtkTreeViewのGTK_SELECTION_MULTIPLEモード(その1)

GtkTreeView で選択された項目を取得するには、 gtk_tree_model_get () を使えば良いという記事を書きましたが、これは、 GtkTreeView の選択オブジェクト GtkTreeSelection のモードが GTK_SELECTION_SINGLE と GTK_SELECTION_BROWSE の時だけで、GTK_SELECTION_MULTIPLE の時には、違う処理が必要になります。

ということで、サンプルは以下の通り。最初は GtkBuilder 用の m_treeview.ui ファイル。

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <!-- interface-requires gtk+ 3.0 -->
  <object class="GtkListStore" id="liststore1">
    <columns>
      <!-- column-name column1 -->
      <column type="gchararray"/>
    </columns>
  </object>
  <object class="GtkWindow" id="window1">
    <property name="can_focus">False</property>
    <signal name="destroy" handler="cb_quit" swapped="no"/>
    <child>
      <object class="GtkScrolledWindow" id="scrolledwindow1">
        <property name="visible">True</property>
        <property name="can_focus">True</property>
        <property name="shadow_type">in</property>
        <child>
          <object class="GtkTreeView" id="treeview1">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="model">liststore1</property>
            <property name="reorderable">True</property>
            <child internal-child="selection">
              <object class="GtkTreeSelection" id="treeview-selection1">
                <property name="mode">multiple</property>
              </object>
            </child>
            <child>
              <object class="GtkTreeViewColumn" id="treeviewcolumn1">
                <property name="title" translatable="yes">fruit</property>
                <property name="clickable">True</property>
                <property name="reorderable">True</property>
                <signal name="clicked" handler="cb_click" swapped="no"/>
                <child>
                  <object class="GtkCellRendererText" id="renderer1"/>
                  <attributes>
                    <attribute name="text">0</attribute>
                  </attributes>
                </child>
              </object>
            </child>
          </object>
        </child>
      </object>
    </child>
  </object>
</interface>

注意するところは、

<child internal-child="selection">
  <object class="GtkTreeSelection" id="treeview-selection1">
    <property name="mode">multiple</property>
  </object>
</child>

のように、 GtkTreeSelection のプロパティ "mode" が multiple になっていることです。

それ以外では、 signal 処理として、 GtkWindow 型オブジェクト "window1" の "destroy" singal に cb_quit() を割り当て、GtkTreeViewColumn 型のオブジェクト "treeviewcolumn1" の、 "clicked" signale に "cb_click" を割り当てていることに注意してください。

プログラムは、m_treeview.cとします。

#include <gtk/gtk.h>

GtkBuilder *builder;

G_MODULE_EXPORT void
cb_quit (GtkAction *action)
{
	gtk_main_quit ();
}


void
cb_foreach (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data)
{
	gchar *fruit;

	gtk_tree_model_get (model, iter, 0, &fruit, -1);
	g_print ("%s\n", fruit);
	g_free (fruit);
}

G_MODULE_EXPORT void
cb_click (GtkAction *action)
{
	GtkTreeView *treeview;
	GtkTreeSelection *selection;

	treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, "treeview1"));
	selection = gtk_tree_view_get_selection (treeview);
	gtk_tree_selection_selected_foreach (selection, cb_foreach, NULL);
}

int
main (int argc, char* argv[])
{
	GtkWindow *window;
	GError *error = NULL;
	gchar *fruit[] = {"banana", "apple", "orange", "grape", 
		"strawberry", "peach", "melon", "marron", "papaya", 
		"pear", "mango", NULL};
	GtkTreeView *treeview;
	GtkTreeIter iter;
	GtkListStore *store;
	gint i;

	gtk_init (&argc, &argv);
	builder = gtk_builder_new ();
	gtk_builder_add_from_file (builder, "m_treeview.ui", &error);
	g_assert_no_error (error);

	gtk_builder_connect_signals (builder, NULL);

	treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, "treeview1"));
	store = GTK_LIST_STORE (gtk_tree_view_get_model (treeview));
	for (i = 0; fruit[i] != NULL; i++)
	{
		gtk_list_store_append (store, &iter);
		gtk_list_store_set (store, &iter, 0, fruit[i], -1);
	}

	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), 0, GTK_SORT_ASCENDING);

	window = GTK_WINDOW (gtk_builder_get_object (builder, "window1"));
	gtk_widget_show_all (GTK_WIDGET (window));
	gtk_main();
	return 0;
}

ということで、個別に見ていきます。

最初の関数 cb_quit() は、 window1 の X ボタンを押すと発信される signal "destroy" のコールバック関数で、単純に gtk_main_quit() を呼び出しています。

G_MODULE_EXPORT void
cb_quit (GtkAction *action)
{
	gtk_main_quit ();
}

次の関数 cb_foreach() は、更に次の関数 cb_click() 内の gtk_tree_selection_selected_foreach() で指定されている関数で。GtkTreeView で使用しているインターフェースモデル GtkTreeModel と選択されている個々の GtkTreeIter を用いて値を取得し、 g_print() で出力しています。

void
cb_foreach (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data)
{
	gchar *fruit;

	gtk_tree_model_get (model, iter, 0, &fruit, -1);
	g_print ("%s\n", fruit);
	g_free (fruit);
}

この関数は、以下のように宣言されています。

void
(*GtkTreeSelectionForeachFunc) (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data);

その次が、 GtkTreeView の項目名 "fruit" をクリックした時に実行されるコールバック関数です。
単純に GtkTreeView 型の treeview で複数選択されている行を gtk_tree_view_get_selection() で取得し、 gtk_tree_selection_selected_foreach() で、それぞれを第2引数で指定した関数 cb_foreach() で処理します。

G_MODULE_EXPORT void
cb_click (GtkAction *action)
{
	GtkTreeView *treeview;
	GtkTreeSelection *selection;

	treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, "treeview1"));
	selection = gtk_tree_view_get_selection (treeview);
	gtk_tree_selection_selected_foreach (selection, cb_foreach, NULL);
}

main() 関数では、 GTK+ の初期化、 GtkBuilder の初期化、signale の処理後、GtkTreeView の中身を登録します。次に、 gtk_tree_sortable_set_sort_column_id() でソートされるようにしています。
後は、GtkWindowを表示してメインループに入ります。

int
main (int argc, char* argv[])
{
	GtkWindow *window;
	GError *error = NULL;
	gchar *fruit[] = {"banana", "apple", "orange", "grape", 
		"strawberry", "peach", "melon", "marron", "papaya", 
		"pear", "mango", NULL};
	GtkTreeView *treeview;
	GtkTreeIter iter;
	GtkListStore *store;
	gint i;

	gtk_init (&argc, &argv);
	builder = gtk_builder_new ();
	gtk_builder_add_from_file (builder, "m_treeview.ui", &error);
	g_assert_no_error (error);

	gtk_builder_connect_signals (builder, NULL);

	treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, "treeview1"));
	store = GTK_LIST_STORE (gtk_tree_view_get_model (treeview));
	for (i = 0; fruit[i] != NULL; i++)
	{
		gtk_list_store_append (store, &iter);
		gtk_list_store_set (store, &iter, 0, fruit[i], -1);
	}

	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), 0, GTK_SORT_ASCENDING);

	window = GTK_WINDOW (gtk_builder_get_object (builder, "window1"));
	gtk_widget_show_all (GTK_WIDGET (window));
	gtk_main();
	return 0;
}

Makefile とビルド・実行結果は以下の通りです。apple grape melon を選んで、fruit と書かれたラベルをクリックした結果です。

$ cat Makefile
CFLAGS = -pthread -DGSEAL_ENABLE -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/gtk-3.0 -I/usr/include/atk-1.0 -I/usr/include/cairo -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/pango-1.0 -I/usr/include/pixman-1 -I/usr/include/freetype2 -I/usr/include/libpng12

LDFLAGS = -pthread -Wl,--export-dynamic -lgtk-3 -lgdk-3 -latk-1.0 -lgio-2.0 -lpangoft2-1.0 -lpangocairo-1.0 -lgdk_pixbuf-2.0 -lcairo-gobject -lcairo -lpango-1.0 -lfreetype -lfontconfig -lgobject-2.0 -lgmodule-2.0 -lgthread-2.0 -lrt -lglib-2.0

all : m_treeview
$ make
cc -pthread -DGSEAL_ENABLE -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/gtk-3.0 -I/usr/include/atk-1.0 -I/usr/include/cairo -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/pango-1.0 -I/usr/include/pixman-1 -I/usr/include/freetype2 -I/usr/include/libpng12  -pthread -Wl,--export-dynamic -lgtk-3 -lgdk-3 -latk-1.0 -lgio-2.0 -lpangoft2-1.0 -lpangocairo-1.0 -lgdk_pixbuf-2.0 -lcairo-gobject -lcairo -lpango-1.0 -lfreetype -lfontconfig -lgobject-2.0 -lgmodule-2.0 -lgthread-2.0 -lrt -lglib-2.0  m_treeview.c   -o m_treeview
$ ./m_treeview
apple
grape
melon

きちんと全て出力されました。

今回は、 gtk_tree_selection_selected_foreach() を使って作りましたが、これではうまく行かない場合もあるので、それについては別の機会で述べたいと思います。