mikutterをいじりたおす 第3回

ワードミュートを実装します。第1回はこちら

ワードミュート実装(後半)

前半はこちら

ダイアログの表示

ミュート追加ボタンを押したらダイアログが表示されるようにしたい。

何事も「習うより慣れろ」。extract.rbの中身を見てみよう。抽出タブでも、追加ボタンを押すと単語を聞いてくるので、その実装に合わせる。追加ボタンをおした時のコールバック先、on_extract_tab_open_create_dialogの定義を見てみよう。

on_extract_tab_open_create_dialog do
  dialog = Gtk::Dialog.new(_("抽出タブを作成 - %{mikutter}") % {mikutter: Environment::NAME}, nil, nil,
                           [Gtk::Stock::OK, Gtk::Dialog::RESPONSE_ACCEPT],
                           [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_REJECT])
  prompt = Gtk::Entry.new
  dialog.vbox.
    add(Gtk::HBox.new(false, 8).
        closeup(Gtk::Label.new(_("名前"))).
        add(prompt).show_all)
  dialog.run{ |response|
    if Gtk::Dialog::RESPONSE_ACCEPT == response
      Plugin.call :extract_tab_create, name: prompt.text end
    dialog.destroy
    prompt = dialog = nil } end

Gtk::Dialog.newでダイアログのインスタンスを作成、そこにGtk::Label(ラベル)とGtk::Entry(テキストボックス)のインスタンスを含んだGtk::HBoxをぶちこんであって、run関数を呼び出すことでダイアログが表示される。そして「OK」を押すとresponseがGtk::Dialog::RESPONSE_ACCEPTとなってif文の中身が実行される。最後にdestroyとnil代入をしてやってインスタンスを消してあげる。ということがこのコードから読み取れるだろう。読み取ってくれ。頼む。

まあなんでもいい。前回実装したon_wordmute_open_create_dialogの中でこれをもとに実装しよう。さきほどのコードと比べて、文字列と、if文の中の処理を出力に変えただけだ。prompt.textは上のコードに書いてあったのでそれを出力してみただけだが、これにテキストボックスのテキストが入っていることはリファレンスをみなくても容易に推測できるだろう。

on_wordmute_open_create_dialog do |liststore|
  dialog = Gtk::Dialog.new(_("ミュートワードを指定 - %{mikutter}") % {mikutter: Environment::NAME}, nil, nil,
                           [Gtk::Stock::OK, Gtk::Dialog::RESPONSE_ACCEPT],
                           [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_REJECT])
  prompt = Gtk::Entry.new
  dialog.vbox.
    add(Gtk::HBox.new(false, 8).
        closeup(Gtk::Label.new(_("ワード"))).
        add(prompt).show_all)
  dialog.run{ |response|
    if Gtk::Dialog::RESPONSE_ACCEPT == response
      puts prompt.text
    end
    dialog.destroy
    prompt = dialog = nil }
end

これでダイアログが表示できた。

ダイアログからミュートワードの登録へ

さて、extract.rbではif文のなかで別の関数をコールすることで実装をわけていた。ここでも同様に、wordmute_createをコールバックしてあげよう。定義の方でon_をつけるのを忘れないようにしよう。引数は、liststoreと決定した文字列が必要なのでこの2つを渡すことにする。

まずは呼び出しの方。当然if文の中に書けばよい。

Plugin.call(:wordmute_create, liststore, prompt.text)

次に関数の定義。ここで、前回学んだliststoreにitemをappendして、textを設定してあげるような実装をしておこう。

on_wordmute_create do |liststore, text|
  item = liststore.append
  item.set_value(0, text)
end

これで実行してみよう。追加ボタンを押すとダイアログが出てきて、文字を入力してOKを押すとリストに文字が追加される。カンペキだ。

さて、この登録したワードが配列に追加されるようにしよう。文字列の配列で存在すれば扱いやすいだろう。

とりあえず配列を定義しておく。mutewordsなどの名前にしておこう。Plugin.createの直後の行に配列を定義する。Plugin.createの中は実行時に呼び出されるからだ。あとはon_wordmute_createの中でmutewordsにtextを追加しておくような実装をしておこう。あとはプリント文でもいれておけば配列に文字列が追加されることがわかるだろう。

配列からの読み込み

一度設定画面を閉じて、もう一度設定画面を開くと、さきほど追加されたワードが消えているはずだ。そう、設定画面を開くたびにsettingsブロックが呼び出されてインスタンスが作られなおすために、先ほど追加したアイテムが消えているのだ。これを直すには、当然settingsブロックの中で配列mutewordsの中身をぶち込んでしまえば良い。今後のためにこれを今実装しておこう。

といってもmutewords.eachだのなんだのすればRubyの文法をご存知の皆さんなら290Lvの半減期くらいで実装できるだろう。一応Rubyの文法が不安な人のためにコードを示しておく。

mutewords.each do |m|
  item = liststore.append
  item.set_value(0, m)
end

こうすることで、設定画面を一度閉じてもちゃんとリストが復活していることを確認できるはずだ。

削除の実装

さて、追加したワードが削除できるようにしよう。配列も定義してしまったので、リストと配列両方から削除できるようにしておかなければならない。

抽出タブの場合は削除時に一度確認画面が出るが、ワードミュートごとき一旦削除してしまってもサクっと追加できるし別に確認なんてしなくていいだろう。時間短縮も兼ねてそういうことにする。

リファレンスによればListStoreから削除をするにはremove関数を使えばよいらしい。ここで渡すのはTreeIterであるから、選択したアイテムに対応するTreeIterのインスタンスが欲しい。

TreeViewにはselectionプロパティが定義されていて、それはGtk::TreeSelectionのインスタンスになっている。さらにGtk::TreeSelectionにはselectedプロパティが定義されていて、これがTreeIterのインスタンスだ。だから、これを渡してあげれば削除する対象がわかりそうだ。

これを考えると、削除ボタンでは以下の様な実装をすればよいだろう。

Gtk::Button.new(Gtk::Stock::DELETE).tap{ |button|
  button.ssc(:clicked) {
    selected = treeview.selection.selected
    if selected
      Plugin.call(:wordmute_delete, liststore, selected)
    end
    true
  }
}

さて、Gtk::TreeIterにはget_value関数が定義されているので、これを使えば何の文字列に対するTreeIterが選択されているかわかりそうだ。とりあえず、selected.get_value(0)をputsしてみよう。そうすると、削除ボタンを押した時に選択されていたアイテムの文字列が表示されるはずだ。

これを考えると、削除の実装は以下のようにすればよいはずだ。配列から文字列を削除して、liststoreからデータを削除する。一応関数の各所に文字列配列を出力するような実装をしておくとデバッグしやすいだろう(下では省略している)。

on_wordmute_delete do |liststore, selected|
  mutewords.delete(selected.get_value(0))
  liststore.remove(selected)
end

これで追加、削除まで実装できたことになる。

フィルターの実装

あとはこの配列に対してフィルターを実装すればよい。つまり、先ほど定義した配列mutewordsに含まれている単語を含むツイートをツイートから削除すれば良い。これは公式に書いてあるので、これを参考にすることにしよう。

めんどくさいのでそのまま下のように実装する。

filter_update do |service, msgs|
  msgs = msgs.select{|m| not mutewords.any?{|word| m.to_s.include?(word)}}
  [service, msgs]
end

もちろんfilter_show_filterだの他の関数に対して使っても良いが、とりあえずこれで実装した。これで使ってみると、ちゃんとそのミュートリストに追加した単語がミュートされているのがわかるだろう。

ここまででミュートを実装することができた。

ユーザーデータに記録する

さて、終了してしまうと当然配列は削除されてしまうので、次の起動時までデータが保存されない。これでは使い物にならない。mikutterは設定を間違いなく保存しているので、これを探そう。

といってもどうせ多少探せばわかるので答えから言うと、これはUserConfigである。UserConfig[:wordmute_list]というものを定義して、これをミュートのリストとして使おう。簡単な話で、mutewordsをすべてUserConfig[:wordmute_list]に置換してしまえばよい。

最初の定義だけは以下のようにしておこう。これは各所で見られる方法で、定義されていなければ[]で初期化する、というだけである。

UserConfig[:wordmute_list] ||= []

ただ、UserConfigはあまり使ってほしくない、というのがどうやら本音のようで、extract.rbでも変更した時にだけUserConfigとやり取りしている。特にフィルターをかける場所では、ツイートを取得するたびにUserConfigからデータを取ってくるので不安がある。単純にUserConfigから呼び出し続けるのよりは、変更時だけUserConfigとやりとりするのが良いだろう。

全体をまとめると、wordmute.rbは以下のようになる。

# -*- coding: utf-8 -*-

Plugin.create :wordmute do
  UserConfig[:wordmute_list] ||= []
  mutewords = UserConfig[:wordmute_list].dup
  
  settings _("ワードミュート") do
    liststore = Gtk::ListStore.new(String)
    treeview = Gtk::TreeView.new(liststore)
    treeview.append_column(Gtk::TreeViewColumn.new("ワード", Gtk::CellRendererText.new, :text => 0))

    mutewords.each do |m|
      item = liststore.append
      item.set_value(0, m)
    end
    
    pack_start(Gtk::HBox.new.
               add(treeview).
               closeup(Gtk::VBox.new(false, 4).
                       closeup(Gtk::Button.new(Gtk::Stock::ADD).tap{ |button|
                                 button.ssc(:clicked) {
                                   Plugin.call(:wordmute_open_create_dialog, liststore)
                                               true } }).
                       closeup(Gtk::Button.new(Gtk::Stock::DELETE).tap{ |button|
                                 button.ssc(:clicked) {
                                   selected = treeview.selection.selected
                                   if selected
                                     Plugin.call(:wordmute_delete, liststore, selected) end
                                   true }})))
             end

  on_wordmute_open_create_dialog do |liststore|
    dialog = Gtk::Dialog.new(_("ミュートワードを指定 - %{mikutter}") % {mikutter: Environment::NAME}, nil, nil,
                             [Gtk::Stock::OK, Gtk::Dialog::RESPONSE_ACCEPT],
                             [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_REJECT])
    prompt = Gtk::Entry.new
    dialog.vbox.
      add(Gtk::HBox.new(false, 8).
          closeup(Gtk::Label.new(_("ワード"))).
          add(prompt).show_all)
    dialog.run{ |response|
      if Gtk::Dialog::RESPONSE_ACCEPT == response
        Plugin.call(:wordmute_create, liststore, prompt.text)
      end
      dialog.destroy
      prompt = dialog = nil }
  end

  on_wordmute_create do |liststore, text|
    unless text.length == 0 || mutewords.include?(text)
      item = liststore.append
      item.set_value(0, text)
      mutewords << text
      Plugin.call :wordmute_modify_mutewords
    end
  end

  on_wordmute_delete do |liststore, selected|
    mutewords.delete(selected.get_value(0))
    liststore.remove(selected)
    Plugin.call :wordmute_modify_mutewords
  end

  filter_update do |service, msgs|
    msgs = msgs.select{|m| not mutewords.any?{|word| m.to_s.include?(word)}}
    [service, msgs]
  end

  on_wordmute_modify_mutewords do
    UserConfig[:wordmute_list] = mutewords.dup
  end

end

編集ボタンは実装がめんどくさいので消した。

一部実装は実験で実装したものと異なるが、本質的な違いはないので了承していただきたい。

次回予告

次回はいよいよ画像投稿に入ります。


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です