• 追加された行はこの色です。
  • 削除された行はこの色です。
TITLE:JRuby
#keywords(JRuby Swing)
#description(JRubyでのSwingの使用方法や文字コードに関するメモなど)

RIGHT:Posted by &author(aterai); at 2012-07-19
* JRuby で Swing などのサンプル [#ved1e336]
- [[JRuby+Swing - てんぷらメモ@はてな>http://d.hatena.ne.jp/aterai/searchdiary?word=%2a%5bJRuby%5d]]から移動中。

#contents(big)

* 概要 [#h838d289]
環境変数``JAVA_HOME``, ``JRUBY_HOME``を設定し、パスを通しておく。
環境変数`JAVA_HOME`, `JRUBY_HOME`を設定し、パスを通しておく。
 > set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_04
 > set JRUBY_HOME=C:\jruby-1.7.0
 > set Path=%JAVA_HOME%\bin;%JRUBY_HOME%\bin;%Path%
 > jruby -version
 jruby 1.7.0 (1.9.3p203) 2012-10-22 ff1ebbe on Java HotSpot(TM) 64-Bit Server VM 1.7.0_09-b05 [Windows 7-amd64]

* Swing + JRuby サンプル [#f4745b09]
** EDT で起動(JFrameを表示) [#y2f124bd]
- ``Swing``のコンポーネントは``Event Dispatch Thread``上で扱う必要があるので、以下のサンプルでは、``self``が``java.lang.Runnable``の``run``メソッドを実装し、``java.awt.EventQueue.invokeLater``で実行している
- ``require "java"``より``include Java``が良い?
- %%``import``より``java_import javax.swing.JLabel``が良い?%%
- ``jruby EDTSwingTest.rb``と実行
- `Swing`のコンポーネントは`Event Dispatch Thread`上で扱う必要があるので、以下のサンプルでは、`self`が`java.lang.Runnable`の`run`メソッドを実装し、`java.awt.EventQueue.invokeLater`で実行している
- `require "java"`より`include Java`が良い?
- %%`import`より`java_import javax.swing.JLabel`が良い?%%
- `jruby EDTSwingTest.rb`と実行

#gist(6579373)

** 例外処理の記述方法(SystemLookAndFeelの設定) [#ef47fd0c]
- ``SystemLookAndFeel``を設定する際の例外処理を、``begin rescue``で記述する
- `SystemLookAndFeel`を設定する際の例外処理を、`begin rescue`で記述する

#gist(6579399)

** クラスの継承(JPanelを継承) [#ga5f4479]
- ``class MainPanel < JPanel``
- `class MainPanel < JPanel`

#gist(6579429)

** 無名インナークラス(JButtonにActionListenerを追加) [#vc5b32ac]
- リスナーを``JButton``に設定し、クリックすると``JTextField``に文字を追加
- リスナーを`JButton`に設定し、クリックすると`JTextField`に文字を追加
-- [http://jira.codehaus.org/browse/JRUBY-903 #JRUBY-903 Java interface modules - jira.codehaus.org]
-- [http://jira.codehaus.org/browse/JRUBY-991 #JRUBY-991 Auto java interface coercion for vanilla ruby objects - jira.codehaus.org]
--- ``implicit closure conversion``
--- `implicit closure conversion`
-- [http://blog.nicksieger.com/articles/2006/12/01/rspec-jruby-mocking-and-multiple-interfaces RSpec, JRuby, Mocking, and Multiple Interfaces]
-- %%[http://www.bloglines.com/blog/ThomasEEnebo?id=40 Exploring JRuby Syntax Trees in JRuby]%%
-- [https://blogs.oracle.com/nishigaya/entry/tips_for_accessing_java_objects2 Javaインタフェースの実装にクロージャを使用する - Tips for accessing Java objects from JRuby (Part 3) (Nishigaya's Weblog)]

#gist(6579457)

** インタフェースの実装(MouseListenerを追加) [#sbceeeb6]
- ``java_implements``はもう使用できない?
-- 代わりに: ``class MainPanel < JPanel; include MouseListener``
-- カンマ区切りで複数指定しても可: ``class MainPanel < JPanel; include MouseListener, ActionListener``
- ``instanceof``
-- ``self.java_kind_of?(MouseListener)``
- `java_implements`はもう使用できない?
-- 代わりに: `class MainPanel < JPanel; include MouseListener`
-- カンマ区切りで複数指定しても可: `class MainPanel < JPanel; include MouseListener, ActionListener`
- `instanceof`
-- `self.java_kind_of?(MouseListener)`

#gist(6579526)

** オーバーライドしたメソッドでsuperクラスのメソッドを呼び出すと例外が発生する [#l5cc4e22]
 Exception in thread "AWT-EventQueue-0" org.jruby.exceptions.RaiseException: (NoMethodError) undefined method `paintComponent' for nil:NilClass

- 参考: [http://markmail.org/message/oxpqjwpfo74fdr3j Re jruby-user Error when calling protected method paintComponent in Java Swing superclass - Bill Dortch - org.codehaus.jruby.user - MarkMail]

 SuperPaint = JComponent.java_class.declared_method 'paintComponent', 'java.awt.Graphics'

などとして、関数ポインタ?を作成して実行する。

#gist(6579618)

** 配列(JTableに行追加) [#x41f0fcd]
- ``["No.", "Name", "Path"].to_java``で``ruby``の配列を``java``の配列に変換して、``JTable``の行を追加
- `["No.", "Name", "Path"].to_java`で`ruby`の配列を`java`の配列に変換して、`JTable`の行を追加

#gist(6579639)

** protected なメソッドをオーバーライドして使用 [#ye59d71e]
- protected なメソッドをオーバーライドして使用できなかった
-- JRuby 1.2.0で修正?
- `protected`なメソッドをオーバーライドして使用できなかった
-- `JRuby 1.2.0`で修正?
--- [http://jira.codehaus.org/browse/JRUBY-2861 JRUBY-2861 Cannot call super inside a method that overrides a protected method on Java base class]
- SwingWorker#process がオーバーライドして使用(firePropertyChange を使用しなくて済む)
-- http://terai.xrea.jp/data/jruby/swing-worker3.rb
- `SwingWorker#process`がオーバーライドして使用(`firePropertyChange`を使用しなくて済む)
//-- http://terai.xrea.jp/data/jruby/swing-worker3.rb
- `JDK 1.6.0_18`で、`TableRowSorter`でのソートが不正な件は、`JDK 1.6.0_2x`で修正済
-- `Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: java.lang.Byte cannot be cast to java.lang.Long`

- JDK 1.6.0_18で、TableRowSorterでのソートがうまくいかなくなってい件は、JDK 1.6.0_2x で修正済
-- Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: java.lang.Byte cannot be cast to java.lang.Long
#gist(6590330)

#code{{
# -*- encoding: utf-8 -*-
include Java
java_import java.awt.BorderLayout
java_import java.awt.Color
java_import java.awt.Dimension
java_import java.util.Random
java_import java.util.Vector
java_import java.util.concurrent.Executors
java_import javax.swing.BorderFactory
java_import javax.swing.JButton
java_import javax.swing.JPanel
java_import javax.swing.JProgressBar
java_import javax.swing.JScrollPane
java_import javax.swing.JTable
java_import javax.swing.SwingWorker
java_import javax.swing.table.DefaultTableCellRenderer
java_import javax.swing.table.DefaultTableModel
java_import javax.swing.table.TableCellRenderer

PROGRESS_IDX = 2

class TestModel < DefaultTableModel
  @@number  = 0
  @@columns = {
    :name=>["No.", "Name", "Progress"],
    :editable=>[false, false, false],
    :class=>[java.lang.Integer, java.lang.String, java.lang.Integer],
  }
  def add_test(test)
    self.add_row Vector.new([@@number, test.tname, test.progress])
    #self.add_row [@@number, test.tname, test.progress].to_java
    @@number = @@number+1
  end
  def getColumnCount
    return @@columns[:name].size
  end
  def getColumnName(modelIndex)
    return @@columns[:name][modelIndex]
  end
  def isCellEditable(row, col)
    return @@columns[:editable][col]
  end
  def getColumnClass(modelIndex)
    return @@columns[:class][modelIndex]
  end
end

class Test
  attr_accessor :tname
  attr_accessor :progress
  def initialize(tname = nil, progress = 0)
    self.tname = tname
    self.progress = java.lang.Integer.new(progress)
  end
end

class MainPanel < JPanel
  def initialize
    super BorderLayout.new
    model  = TestModel.new
    model.add_test Test.new("aaaa", 100)

    table  = JTable.new model
    table.auto_create_row_sorter = true
    table.fills_viewport_height  = true
    table.show_horizontal_lines  = false
    table.show_vertical_lines    = false
    table.intercell_spacing      = Dimension.new
    table.put_client_property "terminateEditOnFocusLost", true

    column = table.column_model.column 0
    column.max_width = 60
    column.min_width = 60
    column.resizable = false

    column = table.column_model.column PROGRESS_IDX
    bar = JProgressBar.new 0, 100
    bar.border = BorderFactory.createEmptyBorder 1,1,1,1
    renderer = table.get_default_renderer java.lang.Object
    column.set_cell_renderer do |table, obj, isSelected, hasFocus, row, column|
      text = "Done"
      if obj<0
        text = "Canceled"
      elsif obj<100
        bar.value = obj
        return bar
      end
      return renderer.getTableCellRendererComponent(
        table, text, isSelected, hasFocus, row, column)
    end
    executor = Executors.new_cached_thread_pool;
    button = JButton.new "add dummy task"
    button.add_action_listener do
      worker = ProgressWorker.new model
      #worker.addPropertyChangeListener ProgressListener.new(model)
      model.add_test Test.new("example", 0)
      worker.execute
      #executor.execute worker #JDK 1.6.0_18
    end
    self.add button, BorderLayout::SOUTH
    self.add JScrollPane.new(table)
    self.preferred_size = Dimension.new 320, 240
  end
end

class ProgressWorker < SwingWorker
  attr_accessor :model
  def initialize(model)
    super()
    @model = model
    @key = model.row_count
    @sleepDummy = 0.1
    @lengthOfTask = Random.new.nextInt(60) + 1
  end
  def doInBackground
    prev = 0
    current = 0
    while current<@lengthOfTask && !self.cancelled?
      current = current + 1
      begin
        sleep @sleepDummy
      rescue InterruptedException => e
        proxied_e = JavaUtilities.wrap e.cause
        proxied_e.print_stack_trace
        self.publish [-1].to_java(:Integer)
        break
      end
      #puts "#{@key}: #{i}"
      i = 100*current/@lengthOfTask
      self.publish [i].to_java :Integer
      #self.firePropertyChange("progress", prev, i)
      ##self.publish i
      prev = i
    end
    return @sleepDummy*@lengthOfTask
  end
  def process(chunks)
    chunks.each {|i|
      #@model.set_value_at i, @key, PROGRESS_IDX
      #Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException:
      # java.lang.Byte cannot be cast to java.lang.Long
      @model.set_value_at java.lang.Integer.new(i), @key, PROGRESS_IDX
    }
  end
  def done
    text = nil
    i = -1
    if self.cancelled?
      text = "Canceled"
    else
      begin
        i = self.get
        text = "Done"
      rescue Exception => e
        text = "InterruptedException"
        proxied_e = JavaUtilities.wrap e.cause
        proxied_e.print_stack_trace
      end
    end
    puts "#{@key}: #{text} : #{i}s"
  end
end

import javax.swing.UIManager
import javax.swing.WindowConstants
def create_and_show_GUI
  begin
    UIManager.look_and_feel = UIManager.system_look_and_feel_class_name
  rescue Exception => e
    proxied_e = JavaUtilities.wrap e.cause
    proxied_e.print_stack_trace
  end
  frame = javax.swing.JFrame.new "JRuby Swing JTable"
  frame.default_close_operation = WindowConstants::EXIT_ON_CLOSE
  frame.content_pane.add MainPanel.new
  frame.pack
  frame.location_relative_to = nil
  frame.visible = true
end
def run; create_and_show_GUI; end
java.awt.EventQueue.invokeLater self
}}

** JRubyでリソース(URL)のエントリを取得してSwingで使用する [#cd7f9169]
- [[JRubyでリソース(URL)のエントリを取得してSwingで使用する>JRuby/Resource]]

** ソースコードの文字コード(エンコーディング) [#k09b3d13]
- 1.7.0以前?は、*.rbの文字コードはUTF-8固定だった?
- 1.7.0から?は、ソースコードの先頭にマジックコメント
-- [http://d.hatena.ne.jp/Artisan/20110409/1302326813 Ruby1.9にしたら invalid multibyte char (US-ASCII) - 屑プログラマの憂鬱]

#code{{
# -*- encoding: utf-8 -*-
include Java
java_import javax.swing.UIManager
java_import javax.swing.WindowConstants
java_import javax.swing.table.DefaultTableModel
def make_ui
  p = javax.swing.JPanel.new java.awt.BorderLayout.new
  p.add javax.swing.JLabel.new("日本語")
  return p
end
def run #Override
  begin
    UIManager.look_and_feel = UIManager.system_look_and_feel_class_name
  rescue Exception => e
    proxied_e = JavaUtilities.wrap e.cause
    proxied_e.print_stack_trace
  end
  f = javax.swing.JFrame.new "タイトル"
  f.default_close_operation = WindowConstants::EXIT_ON_CLOSE
  f.content_pane.add make_ui
  f.set_size(320, 240)
  f.location_relative_to = nil
  f.visible = true
end
java.awt.EventQueue.invoke_later self
}}

* その他、JRuby + One-Liner などのサンプル [#j67b4b82]

** JRuby One-Liner で、UIManagerのDefaults.keySetを一覧表示する [#zdaf3499]
 > jruby -rjava -e "javax.swing.UIManager.lookAndFeelDefaults.keySet.each{|o| puts o}"

** One-Liner での文字コード [#ze146211]
 > chcp
 現在のコード ページ: 932

 > jruby -J-Dfile.encoding=UTF-8 -rjava -e "puts java.net.URLEncoder.encode('日本語','UTF-8')"
 > jruby -J-Dfile.encoding=UTF-8 -rjava -e 'puts java.net.URLEncoder.encode(""日本語"",""UTF-8"")'
 > jruby --1.9 -U -rjava -e "puts java.net.URLEncoder.encode('日本語','UTF-8')"
 > jruby -J-Dfile.encoding=UTF-8 -ruri -e "puts URI.encode('日本語')"
 > jruby -ruri -e "puts URI.encode('日本語'.encode('UTF-8'))"
 %E6%97%A5%E6%9C%AC%E8%AA%9E

 > jruby --1.9 -rjava -e "puts java.net.URLEncoder.encode('日本語','MS932')"
 > jruby --1.9 -ruri -e "puts URI.encode_www_form_component('日本語')"
 > jruby -ruri -e "puts URI.encode('日本語')"
 %93%FA%96%7B%8C%EA

- 参考: [http://jp.rubyist.net/magazine/?0025-Ruby19_m17n Rubyist Magazine - Ruby M17N の設計と実装]

** JRuby 1.7.0.preview1 でArgumentErrorが発生する場合 [#n2494e41]
- JRuby 1.7.0.preview1 で以下のようなArgumentErrorが発生する場合、-J-Dfile.encoding=UTF-8 でOK?
-- set LANG=ja_JP.UTF-8 は、JRubyでは効果がない?
-- %%「-J-Dfile.encoding=UTF-8」は「-E UTF-8」と同じ?%%

 >jruby -v
 jruby 1.7.0 (1.9.3p203) 2012-10-22 ff1ebbe on Java HotSpot(TM) 64-Bit Server VM
 1.7.0_09-b05 [Windows 7-amd64]
 
 >chcp
 現在のコード ページ: 932
 
 >jruby -J-Dfile.encoding=UTF-8 -ruri -e "puts URI.encode('日本語')"
 %E6%97%A5%E6%9C%AC%E8%AA%9E
 
 >jruby -E UTF-8 -ruri -e "puts URI.encode('日本語')"
 %93%FA%96%7B%8C%EA

- JRuby 1.7.0.preview2 では、「-E UTF-8」の方が良さそう

#code{{
lines = src.rstrip.split(/\r?\n/).map {|line| line.chomp }
}}

 C:\pukiwiki2markdown>jruby p2m.rb wiki
 ArgumentError: invalid byte sequence in Windows-31J
    split at org/jruby/RubyString.java:4643
  to_html at p2m.rb:45
     main at p2m.rb:219
     each at org/jruby/RubyArray.java:1611
     main at p2m.rb:214
   (root) at p2m.rb:234

 C:\pukiwiki2markdown>jruby -J-Dfile.encoding=UTF-8 p2m.rb wiki
 wiki/2010-04-12-swing-leaftreecelleditor.markdown
 wiki/2007-06-18-swing-leftclippedcombobox.markdown
 ...
 C:\pukiwiki2markdown>jruby -E UTF-8 p2m.rb wiki
 wiki/2011-07-18-swing-listmouseselection.markdown
 ...

** JRuby で PukiWiki のファイル名をデコード [#v3f63eba]
- 参考: [http://homepage1.nifty.com/~tetsu/ruby/cmd/ll.html Ruby: ll (ls -l) command (Japanese)]

#code{{
if ARGV[0] == nil then
  while line = STDIN.gets
    STDOUT.puts "#{line.gsub!(/[A-F\d]{2}/) {|x| [x.hex].pack('C*')} }"
  end
else
  Dir::glob("*.#{ARGV[0]}").each {|f|
    puts "#{f.gsub!(/[A-F\d]{2}/) {|x| [x.hex].pack('C*')} }"
  }
end
}}

 > ls *.txt | jruby decode.rb
 > ls *.txt | jruby -pe "$_.gsub!(/[A-F\d]{2}/) {|x| [x.hex].pack('C*')}"

** PukiWiki形式のテキストをMarkdown形式のファイルに変換する [#ce127298]
[[PukiWiki形式のテキストをMarkdown形式のファイルに変換する>JRuby/PukiWiki2Markdown]]に移動

** PukiWiki形式のテキストをWiki形式(Google Project Hosting)のファイルに変換する [#xc0d624b]
- [[PukiWiki形式のテキストをMarkdown形式のファイルに変換する>JRuby/PukiWiki2Markdown]]を改変して、PukiWiki形式のテキストファイルを、Google Project Hosting の Wiki形式ファイルに変換

 jruby -E UTF-8 p2w.rb ./wiki ../java-swing-tips-wiki

- [http://terai.xrea.jp/data/jruby/p2w.rb  p2w.rb]
-- https://code.google.com/p/java-swing-tips/w/list
-- [http://code.google.com/p/support/wiki/WikiSyntax WikiSyntax - support - The reference to the wiki syntax for Google Code projects - User support for Google Project Hosting - Google Project Hosting]
-- どちらもWiki なので、変換は簡単
-- ArrowButtonm, BorderFactory などが自動リンクになる

* リンク [#f74d094d]
- [http://www.rubyinside.com/jruby-swt-future-cross-platform-ruby-desktop-app-development-298.html JRuby + SWT = Future Cross Platform Ruby Desktop App Development?]
-- JRuby + SWT サンプル
-- via [http://www.javalobby.org/java/forums/t84113.html:title=Frankengui? Using SWT and Ruby for your GUIs]

* コメント [#id3843ca]
#comment