mikutterをいじりたおす 第2回

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

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

見たくない単語をミュートする。ミュート単語が増えるたびにプラグインを書き換えるのは億劫なので、設定画面を作ることを覚えよう。

実装フロウ

  1. 設定画面の作り方を他の設定画面から学ぶ
  2. オリジナルの設定画面を作成し、ワードの登録・解除をできるようにする
  3. 登録単語をユーザーデータとして保存する
  4. 保存したユーザーデータをもとにフィルターをかけてミュートを実装する

設定画面を考察する

今回はミュートワードをリスト表示できるようにしたい、という目標をもとに進めていく。リスト表示している設定といえば、「抽出タブ」がある。これが一番シンプルそうなので、この定義を実際に見てみよう。

「抽出タブ」で全grepをかけると、core/plugin/extract/extract.rbにありそうだとわかる。

settings _("抽出タブ") do
  tablist = Plugin::Extract::ExtractTabList.new(Plugin[:extract])
  pack_start(Gtk::HBox.new.
             add(tablist).
             closeup(Gtk::VBox.new(false, 4).
                     closeup(Gtk::Button.new(Gtk::Stock::ADD).tap{ |button|
                               button.ssc(:clicked) {
                                 Plugin.call :extract_tab_open_create_dialog
                                 true } }).
                     closeup(Gtk::Button.new(Gtk::Stock::EDIT).tap{ |button|
                               button.ssc(:clicked) {
                                 id = tablist.selected_id
                                 if id
                                   Plugin.call(:extract_open_edit_dialog, id) end
                                 true } }).
                     closeup(Gtk::Button.new(Gtk::Stock::DELETE).tap{ |button|
                               button.ssc(:clicked) {
                                 id = tablist.selected_id
                                 if id
                                   Plugin.call(:extract_tab_delete_with_confirm, id) end
                                 true } })))
  Plugin.create :extract do
    add_tab_observer = on_extract_tab_create(&tablist.method(:add_record))
    delete_tab_observer = on_extract_tab_delete(&tablist.method(:remove_record))
    tablist.ssc(:destroy) do
      detach add_tab_observer
      detach delete_tab_observer end end
end

該当部分を見ると、どうやらsettings _(‘hoge’) do ~ endで設定画面を登録して、その中で設定画面の中身を追加しているようだ。特にpack_startは設定画面に引数のウィジェットを追加している。

実際、pack_startはこのリファレンスにあるとおり、ボックスの最初に要素を追加するという関数である。この引数にあるのがHBoxで、そのHBoxにはtablistやらVBoxやらが追加されているようだ。

また、リストの本体であろうExtractTabListとやらは同じディレクトリのextract_tab_list.rbにあり、見てみるとTreeViewを継承したウィジェットであることがわかる。

補足

現在、こういったリストを用いる実装においては、アカウント情報やリスト設定で用いられている「Gtk::CRUD」を用いるのが丸い(参考: CRUDとは)。実験中では気づかなかったので本記事でも扱わないが、こちらで実装するほうがいろいろな面でラクだろう。

新しいプラグインを作る

まずはディレクトリを作る。core/pluginよりはplugin/のほうが良い気がするがどっちゃでもいいのでcore/pluginに作った。ディレクトリはwordmuteなどとしよう。

めんどくさいのでとりあえずwordmute/にextract/の中身を全部コピーする。.から始まるファイルも忘れずに。ローカライズはするつもりがないので、po/はコピーしなくて良い。

まずは.mikutter.ymlをいじる。これにプラグインの情報が入っているので、これをいじろう。slug、author、name、descriptionは適当に書き換えればよい。問題なのはdepends、すなわち依存関係だ。ちょっと微妙だがよくわからないのでそのままにしておこう。

さて、どうでもよさそうなのでedit_window.rbとextract_tab_list.rbは削除してしまう。これは元のファイルを参照しつつ実装すれば良いからだ。またextract.rbはwordmute.rbにリネームしておこう。そうしないと動かないっぽい。

補足

プラグインの正しい作り方はWriting mikutter pluginに書かれている。こちらは~/.mikutter/plugin/に作るようになっている。どちらにしろやることは同じなのでなんでもよいだろう。

新しいプラグインの実装

新しく作ったwordmute.rbを大改築しよう。まずは削除だ。上のrequireからPlugin.createの直前まではすべて他の処理用になっているようなので、これをすべて削除してしまう。もし万が一必要だったらコピペすればいい。

settings _(“抽出タブ”)から始まるブロックは残しておいたほうがいいだろう。ここからendまでは残しておく。次のcommandはメニューであることが明らかなので削除(前回扱った)。

さて、さらに下のon_hogehogeという奴らをどうするかだが、いろいろと実装してあるようである。ここでちょっと立ち止まって、settings _(‘hoge’)ブロックの中で定義されているボタンのコールバックを見ると、たとえば:extract_tab_open_create_dialogなどとなっている。下の方を見ると、on_extract_tab_open_create_dialogがある。すなわち、この書き方によってプラグイン内の関数が呼び出されるっぽいということを覚えておこう。

ここまで来たらだいたい把握したので削除しよう。settingsブロックの外はすべて削除でかまわない。settingsの中の最後にオブザーバーのような定義がしてあるが、めんどくさいのでこれも削除しよう。必要になったらコピペしよう。最初の変数定義も削除。正直新しいファイルを作ったほうが早いんじゃないかというレベル。

全体としてはこんな感じまで減らした。

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

Plugin.create :extract do
  settings _("抽出タブ") do
    tablist = Plugin::Extract::ExtractTabList.new(Plugin[:extract])
    pack_start(Gtk::HBox.new.
               add(tablist).
               closeup(Gtk::VBox.new(false, 4).
                       closeup(Gtk::Button.new(Gtk::Stock::ADD).tap{ |button|
                                 button.ssc(:clicked) {
                                   Plugin.call :extract_tab_open_create_dialog
                                   true } }).
                       closeup(Gtk::Button.new(Gtk::Stock::EDIT).tap{ |button|
                                 button.ssc(:clicked) {
                                   id = tablist.selected_id
                                   if id
                                     Plugin.call(:extract_open_edit_dialog, id) end
                                   true } }).
                       closeup(Gtk::Button.new(Gtk::Stock::DELETE).tap{ |button|
                                 button.ssc(:clicked) {
                                   id = tablist.selected_id
                                   if id
                                     Plugin.call(:extract_tab_delete_with_confirm, id) end
                                   true } })))
  end
end

さて、extractから始まっているのはおそらくslug、すなわちそれがそれであること(哲学)を示している単語である可能性が高いので、全部これをwordmuteに置換する。ついでに「抽出タブ」の文字列も「ワードミュート」に書き換えておこう。またtablistはExtractTabListとかいうよくわからないものを使われてもちょっと困るので、もともとExtractTabListの継承元のGtk::TreeViewにしておこう。書き直すとこんな感じ。

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

Plugin.create :wordmute do
  settings _("ワードミュート") do
    tablist = Gtk::TreeView.new
    pack_start(Gtk::HBox.new.
               add(tablist).
               closeup(Gtk::VBox.new(false, 4).
                       closeup(Gtk::Button.new(Gtk::Stock::ADD).tap{ |button|
                                 button.ssc(:clicked) {
                                   Plugin.call :wordmute_open_create_dialog
                                   true } }).
                       closeup(Gtk::Button.new(Gtk::Stock::EDIT).tap{ |button|
                                 button.ssc(:clicked) {
                                   id = tablist.selected_id
                                   if id
                                     Plugin.call(:wordmute_open_edit_dialog, id) end
                                   true } }).
                       closeup(Gtk::Button.new(Gtk::Stock::DELETE).tap{ |button|
                                 button.ssc(:clicked) {
                                   id = tablist.selected_id
                                   if id
                                     Plugin.call(:wordmute_delete_with_confirm, id) end
                                   true } })))
  end
end

とりあえず動かしてみよう。設定画面を開くと下のような画面をできているはずだ。

クリップボード01

なおボタンを押すと落ちる。関数を定義していないので当然だ。

さて、関数を定義しよう。さきほどon_hogehogeという何かをつくるとそのブロックの中が動作されるっぽいと説明した。なので、

on_wordmute_open_create_dialog do
  puts 'hoge'
end

とかを、settingsブロックの後に追加してみよう。そして設定画面を開いて追加ボタンを押すと、hogeとターミナルに出力される。完璧だ。Hello Wolrd最強説。

GUIの実装

GUIの実装は、上のTreeViewというものをヒントに作ろう。もともとExtractTabListというクラスであったが、この中身を見てみると、ListStoreやTextViewColumnやCellRendererTextなどの文字列が見れる。適当にググッてみると、このサイトなどがわかりやすそうだ。

これを見ると、リストがListStoreクラス、それの列表示を設定するのがCellRendererText、カラムの実体がTreeViewColumn、更に最後にそれをTreeViewに追加する、という手順であることがわかる。

さて、とりあえず追加ボタンを押したらリストにhemiが追加されるようにしよう。

まずはTreeViewの定義。settingsブロックの最初に追加する。上の節で載せたコードではtablistとなっているが、ここではtreeviewに名前を直したので、HBoxにaddするときの変数名もtreeviewにすることに注意しよう。

liststore = Gtk::ListStore.new(String)
treeview = Gtk::TreeView.new(liststore)
treeview.append_column(Gtk::TreeViewColumn.new("ワード", Gtk::CellRendererText.new, :text => 0))

次はボタンをおした時の挙動だ。その前に、liststore.appendをしないといけないので、on_wordmute_open_create_dialogのなかでその変数が使えるようにしなければいけない。

liststoreをグローバル化するわけにもいかない(設定画面を開くたびにsettingsブロックが呼ばれてインスタンスを作りなおさなければならない、逆に設定画面が閉じた時にインスタンスが残っているのは気持ち悪い)ので、この関数的な物体に引数を渡す。これはまあextract.rbをサッと見ればわかるように、on_hoge do |args| 〜 endとかで定義して、呼び出すときはPlugin.call(:hoge, args)とすればよい。これを用いて、

Plugin.call(:wordmute_open_create_dialog, liststore)

と呼び出すことにして、定義の方を

on_wordmute_open_create_dialog do |liststore|
  item = liststore.append
  item.set_value(0, 'hemi')
end

としよう。いきなりappendの部分まで書いてしまったが、前述のWebサイトを見れば余裕だろう。世界は優しい人によって支えられている。

さて、ここまで実装したら実行してみよう。確かに追加ボタンを押すたびにhemiが追加されるだろう。

次回予告

次回はダイアログ表示、削除の実装、そして実際にフィルターを適用します。


コメントを残す

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