[gtkmm] 「カット」、「コピー」、「ペースト」 | ぽんのブログ

ぽんのブログ

自分用の備忘録ブログです。書いてある内容、とくにソースは、後で自分で要点が分かるよう、かなり簡略化してます(というか、いい加減)。あまり信用しないように(汗

今回は、「カット」、「コピー」、「ペースト」といった機能を実装しましょう。

simple-editor.rb では、カット、コピー、ペーストのスロット関数で、textviewから

cut_clipboard
copy_clipboard
paste_clipboard

というシグナルを発行しているようです。
でも gtkmm ではそれに該当するシグナルは見当たらないようなのですが。。。これは Ruby-gtk での拡張なんでしょうか??

その代わり、 Gtk::TextBuffer に

    void Gtk::TextBuffer::cut_clipboard (const Glib::RefPtr <Gtk::Clipboard>& clipboard,
                                                          bool default_editable=true)

    void Gtk::TextBuffer::copy_clipboard (const Glib::RefPtr <Gtk::Clipboard>& clipboard,
                                                            bool default_editable=true)
    void Gtk::TextBuffer::paste_clipboard (const Glib::RefPtr <Gtk::Clipboard>& clipboard,
                                                             bool default_editable=true)

というメンバ関数が用意されていますのでこれらで代用できそうです。


ところで、クリップボードについてですが、チュートリアルを読むと Gtk::Clipboard はシングルトンだそうで、その唯一のインスタンスを Gtk::Clipboard::get () で取得できるそうです。

    Glib::RefPtr <Gtk::Clipboard> ref_clipboard = Gtk::Clipboard::get ();

ところで、例えばこちらで書かれているように(多分 X Window System に限ってだと思うのですが)クリップボードには

    * PRIMARY selection(選択/ドラッグ範囲・マウス中クリックで貼り付け)
    * CLIPBOARD(多くのGUIアプリケーションがコピペ作業を行うのに使用される)

など複数の種類があるようです。

    Glib::RefPtr <Gtk::Clipboard> ref_clipboard = Gtk::Clipboard::get (GDK_SELECTION_PRIMARY);

で前者、

    Glib::RefPtr <Gtk::Clipboard> ref_clipboard = Gtk::Clipboard::get (GDK_SELECTION_CLIPBOARD);

で後者のクリップボードのインスタンスが得られるようです。ちなみに

    Gtk::Clipboard::get (GdkAtom selection = GDK_SELECTION_CLIPBOARD)

なので、引数なしで get を呼ぶと後者が得られます。

Gtk::Clipboard のシグナルとしては、内容が書き換えられた時 signal_owner_change が発行されます。またそのスロットは

    void   on_owner_changed (GdkEventOwnerChange *event)

という書式を持つように決められています。

クリップボードには、テキストだけでなく画像やリッチテキスト、それらを組み合わせたものなど、結構自由なターゲットを記憶させることが出来るようです。ですが最初に挙げた TextBuffer の便利関数ではテキストしか扱えない & simple-editor.rb でもテキストしか扱っていなさそう(?)なので以下ではターゲットをテキストのみに限っています。

あと、前回はメニューを実装しましたが、めんどくさいので(汗)今回はボタンで。。。


#include <iostream>
#include <gtkmm.h>

class AppWin : public Gtk::Window {

    Gtk::TextView  *m_textview;
    Glib::RefPtr <Gtk::TextBuffer> ref_textbuf;
    Glib::RefPtr <Gtk::Clipboard>  ref_clipboard;

    public:
    AppWin ();
    ~AppWin () {}

    protected:
    void  on_cut ();
    void  on_copy ();
    void  on_paste ();

    void on_clipboard_owner_changed (GdkEventOwnerChange *event);
};

AppWin::AppWin ()
{
    Gtk::VBox  *vbox_main = Gtk::manage (new Gtk::VBox);
    add (*vbox_main);

    m_textview = Gtk::manage (new Gtk::TextView);
    vbox_main->pack_start (*m_textview, true, true, 0);

    ref_textbuf = m_textview->get_buffer ();

    Gtk::HBox  *hbox_bt = Gtk::manage (new Gtk::HBox);
    vbox_main->pack_start (*hbox_bt, false, false, 0);

    Gtk::Button    *bt_cut = Gtk::manage (new Gtk::Button (Gtk::Stock::CUT));
    hbox_bt->pack_start (*bt_cut, false, false, 0);
    bt_cut->signal_clicked ().connect (sigc::mem_fun (*this, &AppWin::on_cut));

    Gtk::Button    *bt_copy = Gtk::manage (new Gtk::Button (Gtk::Stock::COPY));
    hbox_bt->pack_start (*bt_copy, false, false, 0);
    bt_copy->signal_clicked ().connect (sigc::mem_fun (*this, &AppWin::on_copy));

    Gtk::Button    *bt_paste = Gtk::manage (new Gtk::Button (Gtk::Stock::PASTE));
    hbox_bt->pack_start (*bt_paste, false, false, 0);
    bt_paste->signal_clicked ().connect (sigc::mem_fun (*this, &AppWin::on_paste));

    ref_clipboard = Gtk::Clipboard::get (GDK_SELECTION_CLIPBOARD);
    ref_clipboard->signal_owner_change ().connect (
        sigc::mem_fun (*this, &AppWin::on_clipboard_owner_changed));

    show_all_children ();
}

void
AppWin::on_cut ()
{

}

void
AppWin::on_copy ()
{

}

void
AppWin::on_paste ()
{

}

void
AppWin::on_clipboard_owner_changed (GdkEventOwnerChange *event)
{
    std::cout << "owner changed : text is " << ref_clipboard->wait_for_text () << std::endl;
}


ここで

    Glib::ustring  Gtk::Clipboard::wait_for_text ()

はクリップボードに、クリップボード内のコンテンツをテキストにして返すよう要求します。

また、クリップボードにテキストを登録するには

    void    Gtk::Clipboard::set_text (const Glib::ustring &text)

で行います。


最初に述べたように、TextBuffer にはクリップボードを扱う便利な機能が用意されているわけですが、例えば

    Gtk::TextBuffer::cut_clipboard ()

では

  * TextViewの文書内に選択領域があれば、そのテキストをクリップボードにコピー
  * (cut_clipboard の2番目の引数 default_editable = true なら) TextBufferからその領域を削除

という事を一度にやってくれます。

// 切り取り(cut)
void
AppWin::on_cut ()
{
    ref_textbuf->cut_clipboard (ref_clipboard);
}

或いは、これを手動で行うなら

void
AppWin::on_cut ()
{
    Gtk::TextIter    start, end;
    // 選択範囲があるか調べる
    bool    text_selected = ref_textbuf->get_selection_bounds (start, end);
    if (text_selected) { // 選択範囲があるなら
        ref_clipboard->set_text (ref_textbuf->get_text (start, end)); // クリップボードにコピー
        ref_textbuf->erase_selection (); // 選択範囲を消去
    }
}

同様に

// コピー(copy)
void
AppWin::on_copy ()
{
    ref_textbuf->copy_clipboard (ref_clipboard);
}

これを手動で行うなら

void
AppWin::on_copy ()
{
    Gtk::TextIter    start, end;
    // 選択範囲があるか調べる
    bool    text_selected = ref_textbuf->get_selection_bounds (start, end);
    if (text_selected) ref_clipboard->set_text (ref_textbuf->get_text (start, end)); // クリップボードにコピー
}

// ペースト(paste)
void
AppWin::on_paste ()
{
    ref_textbuf->paste_clipboard (ref_clipboard);
}

これを手動で行うなら

void
AppWin::on_paste ()
{
    Glib::ustring  text = ref_clipboard->wait_for_text (); // クリップボードのテキスト取得
    if (!text.empty ()) ref_textbuf->insert_at_cursor (text); // カーソル位置にテキスト挿入
}

で出来るようです。

それから・・・
simple-editor.rb の編集メニューは、この他に 「クリア」、「全て選択」があるようですが、これらをgtkmmで書きなおせば

// 「クリア」 TextViewのクリア
void
AppWin::on_clear ()
{
    ref_textbuf->set_text (""); // TextBufferの中身を空にしてしまう。
}

// 「全て選択」
void
AppWin::select_all ()
{
    ref_textbuf->select_range (ref_textbuf->begin (), ref_textbuf->end ()); // 文頭~文末を選択
}

となるでしょう。また「削除」は無いようですが、作るとすれば

// 「削除」
void
AppWin::on_delete ()
{
    ref_textbuf->erase_selection ();  // 選択範囲があれば消去
}

でしょうか。