ドラッグアンドドロップ
概要
ドラッグアンドドロップについて説明する。あるWidgetをドラッグ可能にするには Gtk::Widget::drag_source_set()、 ドラッグ可能にするには Gtk::Widget::drag_dest_set() を用いる。 ここで、drag_source_set()とdrag_dest_set() の第一パラメータに送受信するデータのMIMEタイプを リストとして指定する必要があるが、ドラッグ開始側とドロップ側で共通のMIMEタイプを 指定しなければデータが送受信されないので気をつけること。
次にシグナル処理について説明する。ドラッグアンドドロップはドラッグ側とドロップ側の2つの Widget間でデータをやりとりするためシグナル処理がかなり複雑になる。ここでドラッグ側とドロップ側 でコネクトしなければならないシグナルを整理して示すと
・ドラッグ開始側
Gtk::Widget::signal_drag_begin() Gtk::Widget::signal_drag_data_get() Gtk::Widget::signal_drag_data_delete() Gtk::Widget::signal_drag_end()
・ドロップ側
Gtk::Widget::signal_drag_motion() Gtk::Widget::signal_drag_data_received() Gtk::Widget::signal_drag_drop()
となり、これらのシグナルがemitされる順番を時系列で書くと次のようになる。
ドラッグ開始
→ ドラッグ開始側がsignal_drag_begin()をemit
→ ドラッグするとドロップ側がsignal_drag_motion()を連続でemit
→ マウスボタンを放す
→ ドラッグ開始側がsignal_drag_data_get()をemitするので
slot関数の中でデータをセットする
→ ドロップ側がsignal_drag_data_received()をemitするので
slot関数の中でデータ受信し、終わったらGdk::DragContext::drag_finish()を呼ぶ。
→ もしドロップ側がGdk::DragContext::drag_finish()でデータ削除を指定したら
ドラッグ側はsignal_drag_data_delete()をemit
→ ドロップ側がsignal_drag_drop()をemit
→ ドラッグ側がsiglal__drag_end()をemit
→ 終了
その他、詳しくは下のサンプルソース内のコメントを参考にすること。
ソース
sig3.cpp
#include <gtkmm.h>
#include <list>
#include <iostream>
class MainWin : public Gtk::Window
{
Gtk::HBox m_hbox;
Gtk::Button m_label_src;
Gtk::Button m_label_dest1;
Gtk::Button m_label_dest2;
public:
MainWin();
protected:
// drag開始側のWidgetにコネクトするslot
void slot_drag_begin( const Glib::RefPtr< Gdk::DragContext >& context );
void slot_drag_data_get( const Glib::RefPtr< Gdk::DragContext >& context,
Gtk::SelectionData& selection, guint info, guint time );
void slot_drag_data_delete( const Glib::RefPtr< Gdk::DragContext >& context );
void slot_drag_end( const Glib::RefPtr< Gdk::DragContext >& context );
// drop側のWidgetにコネクトするslot
bool slot_drag_motion( const Glib::RefPtr< Gdk::DragContext >&, int, int, guint );
void slot_drag_data_received( const Glib::RefPtr<Gdk::DragContext>& context, int x, int y,
const Gtk::SelectionData& selection, guint info, guint time );
bool slot_drag_drop( const Glib::RefPtr< Gdk::DragContext >& context, int x, int y, guint time );
};
MainWin::MainWin()
:m_label_src( "From" ), m_label_dest1( "Dest1" ), m_label_dest2( "Dest2" )
{
m_hbox.pack_start( m_label_dest1 );
m_hbox.pack_start( m_label_src );
m_hbox.pack_start( m_label_dest2 );
add( m_hbox );
//////////////////////////////////////
// drag可能にする
// 送信可能なデータの種類(ターゲット)のリストを作成してセット
// srcとdestのターゲットには同じ物が最低1つ含まれていること(全て異なるとDnD不可)
std::list< Gtk::TargetEntry > list_target_src;
list_target_src.push_back( Gtk::TargetEntry( "text/dest1" ) );
list_target_src.push_back( Gtk::TargetEntry( "text/dest2" ) );
m_label_src.drag_source_set( list_target_src, Gdk::BUTTON1_MASK, Gdk::ACTION_COPY );
// drag開始側のシグナル設定
m_label_src.signal_drag_begin().connect( sigc::mem_fun( *this, &MainWin::slot_drag_begin ) );
m_label_src.signal_drag_data_get().connect( sigc::mem_fun( *this, &MainWin::slot_drag_data_get ) );
m_label_src.signal_drag_data_delete().connect( sigc::mem_fun( *this,
&MainWin::slot_drag_data_delete ) );
m_label_src.signal_drag_end().connect( sigc::mem_fun( *this, &MainWin::slot_drag_end ) );
//////////////////////////////////////
// drop可能にする
// dest1側
// 受信可能なデータの種類(ターゲット)のリストを作成してセット
// srcとdestのターゲットには同じ物が最低1つ含まれていること(全て異なるとDnD不可)
std::list< Gtk::TargetEntry > list_target_dest1;
list_target_dest1.push_back( Gtk::TargetEntry( "text/dest1" ) );
m_label_dest1.drag_dest_set( list_target_dest1, Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_COPY );
// drop側のシグナル設定
m_label_dest1.signal_drag_motion().connect( sigc::mem_fun( *this, &MainWin::slot_drag_motion ) );
m_label_dest1.signal_drag_data_received().connect( sigc::mem_fun(*this,
&MainWin::slot_drag_data_received ) );
m_label_dest1.signal_drag_drop().connect( sigc::mem_fun( *this, &MainWin::slot_drag_drop ) );
// dest2側
// 受信可能なデータの種類(ターゲット)のリストを作成してセット
// srcとdestのターゲットには同じ物が最低1つ含まれていること(全て異なるとDnD不可)
std::list< Gtk::TargetEntry > list_target_dest2;
list_target_dest2.push_back( Gtk::TargetEntry( "text/dest2" ) );
m_label_dest2.drag_dest_set( list_target_dest2, Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_COPY );
// drop側のシグナル設定
m_label_dest2.signal_drag_motion().connect( sigc::mem_fun( *this, &MainWin::slot_drag_motion ) );
m_label_dest2.signal_drag_data_received().connect( sigc::mem_fun(*this,
&MainWin::slot_drag_data_received ) );
m_label_dest2.signal_drag_drop().connect( sigc::mem_fun( *this, &MainWin::slot_drag_drop ) );
//////////////////////////////////////
resize( 100, 100 );
show_all_children();
}
void MainWin::slot_drag_begin( const Glib::RefPtr< Gdk::DragContext >& context )
{
std::cout << "src : ドラッグ開始(slot_drag_begin)\n";
}
bool MainWin::slot_drag_motion( const Glib::RefPtr< Gdk::DragContext >& context,
int x, int y, guint time )
{
std::cout << "dst : ドラッグ中(slot_drag_motion) x = " << x << " y = " << y << std::endl;
return true;
}
void MainWin::slot_drag_data_get( const Glib::RefPtr< Gdk::DragContext >& context,
Gtk::SelectionData& selection, guint info, guint time )
{
std::cout << "src : データ送信(slot_drag_data_get)\n";
// drop先から要求されたデータの種類(ターゲット)
std::cout << " target = " << selection.get_target() << std::endl;
// ターゲット別に送信データをセット
std::string type;
std::string data;
if( selection.get_target() == "text/dest1" ){
type = "type/dest1";
data = "dest1にドロップ";
}
else if( selection.get_target() == "text/dest2" ){
type = "type/dest2";
data = "dest2にドロップ";
}
// 送信
selection.set( type, data );
}
void MainWin::slot_drag_data_received( const Glib::RefPtr< Gdk::DragContext >& context, int x, int y,
const Gtk::SelectionData& selection, guint info, guint time )
{
std::cout << "dst : データ受信(slot_drag_data_received)\n";
// drag開始側が送信してきたターゲット
std::cout << " target = " << selection.get_target() << std::endl;
// データのタイプ
std::cout << " type = " << selection.get_data_type() << std::endl;
// データ(文字列)を表示
std::cout << " data = " << selection.get_data_as_string() << std::endl;
// 処理が終わったことをdragを開始したWidgetに知らせる
context->drag_finish( true, // 受信成功
true, // データ削除 (drag開始側のwidgetはsignal_drag_data_delete()をemitする)
time );
}
void MainWin::slot_drag_data_delete( const Glib::RefPtr< Gdk::DragContext >& context )
{
std::cout << "src : 削除(slot_drag_data_delete)\n";
}
bool MainWin::slot_drag_drop( const Glib::RefPtr< Gdk::DragContext >& context, int x, int y, guint time )
{
std::cout << "dst : ドロップ完了(slot_drag_drop) x = " << x << " y = " << y << std::endl;
return true;
}
void MainWin::slot_drag_end( const Glib::RefPtr< Gdk::DragContext >& context )
{
std::cout << "src : ドラッグ完了(slot_drag_end)\n";
}
int main( int argc, char *argv[] )
{
Gtk::Main kit( argc, argv );
MainWin mainwin;
Gtk::Main::run( mainwin );
return 0;
}
コンパイル
必要なコンパイルオプションは pkg-config を使って取得する。g++ drag.cpp -o drag `pkg-config gtkmm-2.4 --cflags --libs`
結果
・dest1にドロップした結果src : ドラッグ開始(slot_drag_begin) dst : ドラッグ中(slot_drag_motion) x = 47 y = 28 dst : ドラッグ中(slot_drag_motion) x = 46 y = 28 dst : ドラッグ中(slot_drag_motion) x = 45 y = 28 src : データ送信(slot_drag_data_get) target = text/dest1 dst : データ受信(slot_drag_data_received) target = text/dest1 type = type/dest1 data = dest1にドロップ src : 削除(slot_drag_data_delete) dst : ドロップ完了(slot_drag_drop) x = 36 y = 26 src : ドラッグ完了(slot_drag_end)
・dest2にドロップした結果
src : ドラッグ開始(slot_drag_begin) dst : ドラッグ中(slot_drag_motion) x = 1 y = 64 dst : ドラッグ中(slot_drag_motion) x = 2 y = 64 dst : ドラッグ中(slot_drag_motion) x = 3 y = 64 src : データ送信(slot_drag_data_get) target = text/dest2 dst : データ受信(slot_drag_data_received) target = text/dest2 type = type/dest2 data = dest2にドロップ src : 削除(slot_drag_data_delete) dst : ドロップ完了(slot_drag_drop) x = 3 y = 64 src : ドラッグ完了(slot_drag_end)
dest1とdest2では扱うMIMEタイプが異なるので、ドラッグ開始側が 異なるデータを送っているのに注意すること。