1. ホーム
  2. スクリプト・コラム
  3. ルビートピックス

RubyGnome2 ライブラリを用いた GTK 環境での Ruby GUI プログラミングの基本的な考え方

2022-02-01 11:07:12

前書き
RubyGnome2ライブラリがどんどん良くなり、ruby1.9の性能向上もあって、RubyでGUIプログラムを書くことは、趣味から仕事の主要な部分へと徐々に移行しています。
Rubyでプログラムを書くのは本当に楽しくて、自分のアイデアを素早く、エレガントに実装することができます。この記事で紹介するある逸品は、RubyのGUIプログラムを簡単に楽しく書けるようにする、非常に興味深い機能をわずかなコードで実現した例です。
RubyGnome2入門

RubyGnome2はこれまで何度も紹介してきましたが、改めてお勧めします。RubyでGUIプログラムを書くには、本当に最初の選択肢です。
RubyGnome2 は GTK+ ライブラリを Ruby で拡張したものです。GTK+のオブジェクトモデルをRubyで注意深くラップし、GTK+のAPIの命名法と意味を保持します。したがって、GTK+のドキュメントはRubyGnome2にも適用可能です。
GTK自体はCで書かれていますが、慎重に設計されたオブジェクトのシステム全体を持っているため、そのGUIコンポーネントは非常に柔軟で、自由に組み合わせて複雑で強力なインターフェースを実装することができます。
GTKはオブジェクトシステムの柔軟性に重点を置いているため、GTKを使ったプログラミングは、最初は簡単ではありません。表面的には単純に見える機能でも、GTKで実装するためには何度か回り道をしなければならないことが多々あります。例えば、ラベルのフォントを設定したい場合、GTKのフォントレンダリングのトランザクションをすべて引き継ぐPangoを介してそれを行う必要があります・・・。このような余分な回り道をするケースはたくさんあり、GTKのプログラムを書くのが簡単ではなくなります。それはGTKのプログラムを書くことを簡単にはしませんが、その代わりにGTKシステム全体が少ない費用で非常に柔軟で強力になり、クロスプラットフォーム、スキニング、国際化において非常に優れています。
RubyGnome2は、GTKの長所と短所を含むすべての機能を継承しています。
GUIレイアウト
GTKプログラムのレイアウトは柔軟で、多くの種類のコンテナから選択することができます。レイアウトに関しては、GUIプログラムが異なる解像度のスクリーンに適応し、特定のスタイルのUIを微調整しやすくするために、絶対位置よりも相対位置を推奨するものがほとんどです。
最も一般的なコンテナはボックスで、水平ボックスと垂直ボックスがあります。Visual UI コンポーネントは "boxes" に配置され、異なる "boxes" を互いに積み重ねて目的のレイアウトを構築することができます。
理論的には、どんな相対位置のレイアウトもボックスで構築できますが、利便性のために、GTKはテーブルのようなより高度なコンテナも提供しています。
ボックスモデルは、GTKプログラミングを初めて行う多くの人にとって馴染みにくく、Gladeのようなビジュアルレイアウトツールでも、初心者はボックスモデルに悩まされることが多いようです。ビジュアルレイアウトツールは、固定位置のレイアウトに最適です。相対位置レイアウトの場合は、代わりにGladeを使うよりも、コードでインターフェイスを構築した方が早くて簡単な場合が多いのです。
shoose.netの以下の例のように、UIレイアウトをコードで視覚化できるように、ビルダー形式のコードでUIを記述するShoseなどのGUIライブラリがあります。

Shoes.app { 
 stack(:margin => 4) { 
  button "Mice" 
  button "Eagles" 
  button "Quail" 
 } 
} 


こうすることで、コードからUIレイアウトを一度に確認することができるのです。
ビルダースタイルのコードはHTMLに非常に似ており、HTMLに慣れたWebインターフェース設計者にとっては、ビジュアルエディタはあまり必要ありません。
RubyGnome2にはレイアウトのためのビルダー的なアプローチは用意されていないので、UIコードは以下のように記述します。

class MyWin < Gtk::Window 
 def initialize 
  super 
  vbox = Gtk::VBox.new 
  btn_mice = Gtk::Button.new 'Mice' 
  vbox.pack_start btn_mice 
  btn_eagles = Gtk::Button.new 'Eagles' 
  vbox.pack_start btn_eagles 
  btn_quail = Gtk::Button.new 'Quail' 
  vbox.pack_start btn_quail 
  add vbox 
 end 
end 



上のコードからでは、UIレイアウトを一度に確認することは難しいです。
RubyGnome2 用のビルダー風レイヤーも構築すると、以下のようなコードになります。

class MyWin < Gtk::Window 
 
 def initialize 
  super 
  add my_layout 
 end 
 
 def my_layout 
  vbox do 
   button 'Mice' 
   button 'Eagles' 
   button 'Quail' 
  end 
 end 
 
end 



さて、このコードはShoseとほぼ同じで、コードからUIレイアウトが一目瞭然です。
今回紹介する GtkSimpleLayout の特徴の一つは、RubyGnome2 用にビルダースタイルのレイアウトを提供することです。
GtkSimpleLayout レイアウト
このシンプルなレイアウトは元々200行に満たないコードで、プロジェクトにそのままコピーして使うことが多かったです。その後、徐々に機能を追加していき、より便利になってきたので、githubにリリースしてgemを生成し、興味のある方に使っていただいています。
ソース: git://github.com/rickyzheng/GtkSimpleLayout.git
または
gem source -a http://gems.github.com && gem install rickyzheng-GtkSimpleLayout
以下、主な機能と簡単な例を説明します。
ビルダースタイルのレイアウトを提供する
上の例で説明したように、GtkSimpleLayout は RubyGnome2 にビルダースタイルのレイアウト機能をもたらします。レイアウトのクラスは GtkSimpleLayout::Base を拡張するだけで、完全な例として

require 'gtk2' 
require 'simple_layout' 
 
class MyWin < Gtk::Window 
 include SimpleLayout::Base 
 def initialize 
  super 
  add my_layout 
  signal_connect('destroy') do 
   Gtk.main_quit 
  end 
 end 
 
 def my_layout 
  hbox do 
    label 'Hello, ' 
    button 'World ! 
   end 
 end 
end 



MyWin.new.show_all 
Gtk.main  

上記の例からわかるように、GtkSimpleLayout は RubyGnome2 アプリケーションのメインフレームワークを変更するものではなく、単なる拡張機能でしかありません。
属性の設定
UI コンポーネントを配置する際、初期プロパティを設定したり、レイアウトパラメータを指定する必要がある場合があります。

vbox do 
 button 'sensitive = false', :sensitive => false # Initially disable 
 button 'expand space', :layout => [true, true] # Specify this button to fill the remaining space 
end 


上記の例では、最初のボタンの初期状態はdisableです。 ":sensitive => false" このパラメータは、最終的にはプロパティ設定に変換されます。Gtk::Button#sensitive=false に設定できるプロパティについては、RubyGnome2 API ドキュメントまたは GTK のドキュメントを参照してください。GtkSimpleLayoutは、ここでは単純なパラメータ変換にとどめています。
2つ目のボタンの ":layout => [true, true]" は、ちょっと特殊です。この ":layout" パラメータは GtkSimpleLayout の予約パラメータで、この UI がコンテナに入れられると、その内容に変換されるものです。この例では、コンテナは vbox(Gtk::VBox) で、デフォルトの結合メソッドは Gtk::VBox#pack_start です。この例の [true, true] は最終的に pack_start に渡されるため、このボタンが vbox に追加されたときに呼び出されるメソッドとパラメータは、 "Gtk ::VBox#pack_start( button, true, true)" になっています。
したがって、GtkSimpleLayout を使用するためには、まず RubyGnome2 の各種コンポーネント、コンテナの使い方、パラメータについて熟知しておく必要があります。一旦 RubyGnome2 に慣れると、GtkSimpleLayout を使うのは非常に簡単になります。
バッチプロパティの設定
UIレイアウトの場合、以下のように、複数のUIコンポーネントに同じプロパティを設定することはよくあることです。

hbox do 
  button 'C', :layout => [false, false, 5] 
  button 'D', :layout => [false, false, 5] 
  button 'E', :layout => [false, false, 5] 
end 



今回は、"with_attr"を使って簡略化することができます。

hbox do 
 with_attr :layout => [false, false, 5] do 
  button 'C' 
  button 'D' 
  button 'E' 
 end 
end  



特殊コンテナ
例えば Gtk::HPaned の場合、左側のサブウィンドウは Gtk::HPaned#add1() で、右側のサブウィンドウは Gtk::HPaned#add2() で追加する必要があります。このようなコンテナに対しては、GtkSimpleLayoutは特別な扱いをしなければなりませんが、これはhpanedを例にとって説明します。

hpaned do 
 area_first do 
  frame 'first area' 
 end 
 area_second do 
  frame 'second area' 
 end 
end


特別な扱いが必要な容器は
hpaned/vpaned : area_first と area_second を使って、子ウィンドウを追加します。
table : グリッドを使用して入力します。
nodebook : サブページを追加するためのページを使用します。
UIコンポーネントのマーク付け
GtkSimpleLayout は ":id => ? " このパラメータで UI コンポーネントを識別します。

hbox do 
 button 'first', :id => :btn_first 
 button 'second', :id => :btn_second 
end 



その後、このUIコンポーネントはcomponent()関数で取得することができます。

my_first_button = component(:btn_first) 
my_second_button = component(:btn_second) 
 
... 
my_first_button.signal_connect('clicked') do 
 puts "first button clicked" 
end 
 
my_second_button.signal_connect('clicked') do 
 puts "second button clicked" 
end 


もしそれが面倒なら、GtkSimpleLayout は expose_components() も提供し、識別されたすべてのコンポーネントをインスタンス読み取り可能なプロパティ (ゲッター) として自動的に追加してくれます。

expose_components() # は、btn_first と btn_second の読み取り属性(ゲッター)を自動的に追加します。  

... 
btn_first.signal_connect('clicked') do 
 puts "first button clicked" 
end 
 
btn_second.signal_connect('clicked') do 
 puts "second button clicked" 
end 



イベントレスポンスの自動マッピング
イベントを登録するために明示的に signal_connect を呼び出すのにうんざりしている場合、GtkSimpleLayout は以下の方法でイベント・レスポンスのマッピングを自動化する機能を提供します。

require 'gtk2' 
require 'simple_layout' 
 
class MyWin < Gtk::Window 
 include SimpleLayout::Base 
 def initialize 
  super 
  add my_layout 
  register_auto_events() # Register automatic event response mapping 
 end 
 
 def my_layout 
  hbox do 
   button "First', :btn_first 
   button "Second", :btn_second 
  end 
 end 
 
 # Event response functions 
 def btn_first_on_clicked(*_) 
  puts "First button clicked" 
 end 
 
 # Event response function 
 def btn_second_on_clicked(*_) 
  puts "Second button clicked" 
 end 
 
 # Exit event response function 
 def self_on_destroy(*_) 
  Gtk.main_quit 
 end 
end 



最後の「self」は、ホストコンテナを指しています。
UIグループ化
GtkSimpleLayoutでは、レイアウト時にUIグループを指定することができます。
GtkSimpleLayoutのUIグループ化ルールは以下の通りです。
デフォルトでは、名前付きコンテナ(つまり :id パラメータが渡されたもの)は、所属する子コンポーネントのグループを自動的に作成し、グループ名はコンテナの Ming になります。
コンテナに :gid=>? パラメータが渡されると、そのコンテナが属するサブコンポーネントに対して、その名前でグループが作成されます。
複数のコンテナが同じ :gid 名を持つことができ、その場合、所属するサブコンポーネントは同じグループに分類されます。
UI は "group" で明示的にグループ化することができ、これは仮想コンテナと見なすことができます。
component_children(group_name)を使って、UIグループを取得します。
UIグループ化の例は長くなるので、ここには掲載しませんが、ソースコードのexamples/group.rbファイルをご覧ください。
UIとロジックのコードの分離
GtkSimpleLayout は潜在的にインターフェイスのコードと論理処理(またはイベント応答)のコードを分離することを強制するので、プログラム全体の階層がより明確になります。レイアウトの結果は依然としてコンテナであり、そのコンテナを他のコンテナに入れて、より複雑なインタフェースに組み合わせることができるため、より多くのインタフェース部品を持つプログラムでは、レイアウトを分割することが容易になります。
レイアウト時に変数を使用して、UIを動的にレイアウトし、動的に生成することができます。
GtkSimpleLayout の実装はわずか 300 行で、これが Ruby の素晴らしさです。
最後に、電卓のインターフェース部分のコード例を投稿してください。コードの中にUIレイアウトが見えるでしょうか?

require 'gtk2' 
require 'simple_layout' 
 
class MyWin < Gtk::Window 
 include SimpleLayout::Base 
 def initialize 
  super 
  add my_layout 
  signal_connect('destroy') do 
   Gtk.main_quit 
  end 
 end 
 
 def my_layout 
  vbox do 
   with_attr :border_width => 3 do 
    hbox do 
     entry :id => :ent_input, :layout => [true, true, 5] 
    end 
    hbox do 
     frame do 
      label 'M', :set_size_request => [20, 20] 
     end 
     hbutton_box do 
      button 'Backspace' 
      button 'CE' 
      button 'C' 
     end 
    end 
    hbox do 
     vbutton_box do 
      button 'MC' 
      button 'MR' 
      button 'MS' 
      button 'M+' 
     end 
     with_attr :layout => [true, true] do 
      number_and_operators_layout 
     end 
    end 
   end 
  end 
 end 
 
 def number_and_operators_layout 
  vbox do 
   [ ['7', '8', '9', '/', 'sqt'], 
    ['4', '5', '6', '*', '%'], 
    ['1', '2', '3', '-', '1/x'], 
    ['0', '+/=', '.' , '+', '=']].each do |cols| 
    hbox :layout => [true, true] do 
     cols.each do |txt| 
      button txt, :set_size_request => [20, 20], :layout => [true, true] 
     end 
    end 
   end 
  end 
 end 
 
end 



MyWin.new.show_all 
Gtk.main 

お楽しみに :-)