ドラッグアンドドロップ

概要

ドラッグアンドドロップについて説明する。

ある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タイプが異なるので、ドラッグ開始側が 異なるデータを送っているのに注意すること。