ruby-****@sourc*****
ruby-****@sourc*****
2008年 10月 26日 (日) 12:31:17 JST
------------------------- REMOTE_ADDR = 222.225.51.171 REMOTE_HOST = URL = http://ruby-gnome2.sourceforge.jp/ja/hiki.cgi?libglade2-tut-create-src ------------------------- @@ -73,7 +73,7 @@ PROG_NAMEは地域化用データのファイル名(拡張子を除く)として扱われます。ここではプログラム名を代入しています。 -=== シグナルハンドラの実装 +== シグナルハンドラの実装 さて、後は気の向くままにシグナルハンドラたちを実装しましょう。@glade["textview1"]というような形でそれぞれウィジェットのインスタンスを呼び出すことができます。"textview1"はGlade-2上で設定した(あるいはデフォルトのままではウィジェット + 番号という形になる)ウィジェット名です。 ただ、いちいちそのように書くのも手間なので良く使うウィジェットはインスタンス変数に代入しておくと便利です。 @@ -83,15 +83,18 @@ super(path_or_data, root, domain, localedir, flag) bindtextdomain(domain, localedir, nil, "") + @window = @glade['main_window'] @editor = @glade['textview1'] @filedlg = @glade['filechooser'] @aboutdlg = @glade['aboutdialog'] end -{{br}} +以下、シグナルハンドラの実装例です。 -以下、シグナルハンドラの実装例です。まずは簡単な所で、"Edit"メニューの"Cut(切り取り)"、"Copy(コピー)"、"Paste(貼り付け)"コマンドを実装してみます。 +=== "Edit"メニュー +まずは簡単な所で、"Cut(切り取り)"、"Copy(コピー)"、"Paste(貼り付け)"コマンドを実装してみます。 + def on_paste1_activate(widget) puts "on_paste1_activate" @editor.paste_clipboard @@ -109,9 +112,12 @@ 以上(笑)。Gtk::TextViewのおかげです。ちなみにTextViewウィジェットの右クリック(コンテキスト)メニュー内のコマンドは実装する必要すらありません。デフォルトで動作します。 -{{br}} +=== "Help"メニュー -次は、"Help"メニューの"About"コマンドを選択した時に、Gladeで作っておいたアバウトダイアログを表示する例です。 +次は、"About"コマンドを選択した時に、Gladeで作っておいたアバウトダイアログを表示する例です。 def on_about1_activate(widget) puts "on_about1_activate" @@ -142,28 +145,219 @@ {{br}} -もう一つ、"File"メニューの"Open(開く)"コマンドを選択した時に、Gladeで作っておいたGtk::FileChooserDialogを使って、TextViewにテキストファイルを読み込む例です。 +以後、"File"メニュー内のコマンドを実装していきます。 +=== "Quit(終了)"コマンド + + def on_quit1_activate(widget) + puts "on_quit1_activate" + unles****@windo*****_emit('delete_event', nil) + @window.signal_emit('destroy') + end + end + +"Quit(終了)"コマンドが選択された場合、ウィンドウのクローズボタンが押された時に自動で行われる処理を明示的に実行します。まず、"delete_event"シグナルを自分で発生させ、その戻り値がfalseの場合さらに"destroy"シグナルを発生させます。 + +ウィンドウを閉じようとした時やプログラムの終了時に行いたいことは、"on_main_window_delete_event"メソッドや"on_main_window_destroy"メソッド内で実行します。 + +=== "New(新規)"コマンド + require 'hwedit_glade' - require 'kconv' # この行を追加 + require 'kconv' # 追加 -まず文字コード変換処理に必要なKconvモジュールを読み込むコードを冒頭に追加します。プログラムからTextViewにテキストを入力する場合、文字コードがUTF-8になっている必要があります。 + class Hwedit < HweditGlade + DEFAULT_FILECHARSET = Kconv::SJIS # 追加 +{{br}} + def on_new1_activate(widget) + puts "on_new1_activate" + @filename = nil # 編集中テキストのファイルパス情報をクリア + @filecharset = DEFAULT_FILECHARSET # 編集中テキストの文字コード情報をデフォルトに戻す + @editor.buffer.text = "" # TextViewのデータをクリア + @window.title = 'Hello World Editor - ' + 'untitled' # ウィンドウタイトル更新 + end + +"New(新規)"コマンドでは、読み込み/保存ファイルに関する情報とTextViewの初期化、およびウィンドウタイトルの更新を行います。 + +"DEFAULT_FILECHARSET"は新規文書を保存するときのデフォルト文字コードです。値には、ファイル入出力の時などに利用する(予定の)Kconvモジュールの定数を使っています。チュートリアル用プログラムの動作確認をMS Windowsで行っているのでShift-JISを選択しました。プログラムのユーザが自分の環境に合わせて適宜変更するという想定です(起動時のコマンドラインオプションで指定できるようにすると便利かも)。スクリプト冒頭にKconvモジュールを読み込むコードも追加します。 + +"@filecharset"はファイル保存時の文字コードです。TextViewはUTF-8のテキストしか受け付けないので、このような変数を用意して新規作成時やファイル読み込み時に記録しておく必要があります。 + +このサンプルでは、直前に編集していたテキストを保存するかどうか、ユーザに尋ねる処理は省略しています。(保存しません) + +=== "New(新規)"コマンド(改) + +処理としては上記の通りでいいのですが、"on_new1_activate"メソッド内のコードは、プログラムの起動時にも実行するものなので、別メソッドとして切り出すことにします。またウィンドウタイトルの更新は上記2つの場所以外でも実行されることが容易に想像できるので、独立したメソッドにします。そのように書き換えたコードが以下です。 + + def initialize(path_or_data, root = nil, domain = nil, localedir = nil, flag = GladeXML::FILE) + super(path_or_data, root, domain, localedir, flag) + bindtextdomain(domain, localedir, nil, "") + + @window = @glade['main_window'] + @editor = @glade['textview1'] + @filedlg = @glade['filechooser'] + @aboutdlg = @glade['aboutdialog'] + + initialize_editor # 追加 + end + + def on_new1_activate(widget) + puts "on_new1_activate" + initialize_editor # 置き換え + end + + def initialize_editor # 新規作成 + @filename = nil + @filecharset = DEFAULT_FILECHARSET + @editor.buffer.text = "" + update_window_title('untitled') + end + + def update_window_title(filename) # 新規作成 + @window.title = 'Hello World Editor - ' + File.basename(filename) + end + +ウィンドウタイトルにはファイルパスではなく、ファイル名のみ表示するようにしてみました。 + +=== "Open(開く)"コマンド + +処理の流れとしては、Gladeで作っておいたGtk::FileChooserDialogを表示してユーザにファイルを選択させる、そのファイルをまるごと読み込んでTextViewに表示、ファイル名を使ってウィンドウタイトルを更新、ダイアログを隠す、という形になります。 + def on_open1_activate(widget) puts "on_open1_activate" + show_opendlg # 追加 + end + + def show_opendlg # 新規作成 + @filedlg.action = Gtk::FileChooser::ACTION_OPEN # ダイアログをオープン用に設定 + @filedlg.title = 'Open Dialog' if****@filed***** == Gtk::Dialog::RESPONSE_OK - @editor.buffer.text = "" # バッファをクリア - File.open(@filedlg.filename).each do |line| # 1行ずつ処理 - @editor.insert_at_cursor(Kconv.toutf8(line)) - # 文字コードを変換してカーソル位置(バッファ末尾)に挿入 + if File.exist?(get_platform_filename(@filedlg.filename)) + @filename =****@filed***** # ファイルパスを記録 + read_file(@filename) # 選択されたファイルを読み込んでTextViewに表示 + update_window_title(@filename) end - @editor.move_cursor(Gtk::MOVEMENT_BUFFER_ENDS, -1, false) # カーソルを先頭に移動 end @filedlg.hide end -処理の内容はコメントの通りです。サンプルのため、以前に表示されていたテキストを保存するかどうかの確認や、読み込んだテキストの文字コードの記憶などはしていません。 + def get_platform_filename(filename) # 新規作成 + if RUBY_PLATFORM.include?('mswin32') + return Kconv.tosjis(filename) + else + return Kconv.toutf8(filename) + end + end -アバウトダイアログと同じようにrunメソッドを使っていますが、ブロックは付けずに戻り値から"Open(開く)"ボタンが押されたかどうかを判定しています。 +このチュートリアルのプログラムでは、一つのFileChooserDialogをオープン時、保存時両方で使い回しますので、まず"show_opendlg"メソッドの冒頭でオープン用の設定をしています。その後ダイアログを表示して、"OK"ボタンで閉じられ、かつ取得したファイルパスが存在する時だけファイルの読み込みと表示を行います。 + +アバウトダイアログの例と同じようにrunメソッドを使っていますが、ブロックは付けずに戻り値から"OK"ボタンが押されたかどうかを判定しています。 + +選択されたファイルの存在チェックでは"get_platform_filename"メソッドを経由したパスを指定しています。これはダイアログで取得できるパスの文字コード(UTF-8)と"File.exist"メソッドが受け付けるパスの文字コードが異なる場合があるためです。例えば、MS Windowsでは日本語などを含むパスはShift-JISでなければなりません。"get_platform_filename"メソッドでプラットフォームに応じた文字コードに変換しています。このメソッドはMS Windows以外の場合の処理は適当です。ご注意ください。 + +"update_window_title"メソッドは"New(新規)"コマンドの実装時に作ったものです。 + +下に"read_file"メソッドのコードを挙げます。 + + def read_file(filename) # 新規作成 + text = "" + File.open(get_platform_filename(filename)) do |f| + text = f.readlines.join # まるごと読み込む + end + @filecharset = Kconv.guess(text) # ファイル保存時の文字コードを記録 + if @filecharset == Kconv::UNKNOWN + @filecharset = DEFAULT_FILECHARSET + end + @editor.buffer.text = Kconv.kconv(text, Kconv::UTF8, @filecharset) # TextViewに表示 + @editor.move_cursor(Gtk::MOVEMENT_BUFFER_ENDS, -1, false) # カーソルを先頭に移動 + end + +"File.open"メソッドでも文字コードを変換したパスを指定しています。またファイルから読み込んだデータについても必要な処理をしています。上でも書きましたが、Gtk::TextViewがUTF-8のテキストしか正常に表示できないためです。 + +Gtk::TextView#move_cursorは、移動単位と移動量を指定してカーソルを移動するメソッドです。移動量はマイナス値も指定できます。3番目の引数は、移動前の位置から移動後の位置までのテキストを選択状態にするかどうかを指定します。 + +=== "Save(保存)"コマンド + +保存ファイルパスが既に決まっている場合(既存ファイルを読み込んだ、もしくは保存済み)、そのまま保存し、そうでなければダイアログを表示してユーザに指定してもらいます。"save_file"メソッドは"Save As(別名で保存)"コマンドでも使います。"show_savedlg"メソッドは"Save As(別名で保存)"コマンドで説明します。 + + def on_save1_activate(widget) + puts "on_save1_activate" + if @filename + save_file(@filename) + else + show_savedlg + end + end + + def save_file(filename) # 新規作成 + File.open(get_platform_filename(filename), 'w') do |f| + f.write(Kconv.kconv(@editor.buffer.text, @filecharset, Kconv::UTF8)) + end + end + +=== "Save As(別名で保存)"コマンド + + def on_save_as1_activate(widget) + puts "on_save_as1_activate" + show_savedlg + end + +常にダイアログを表示してユーザにファイル名を入力してもらいます。 + + def show_savedlg # 新規作成 + @filedlg.action = Gtk::FileChooser::ACTION_SAVE # ダイアログを保存用に設定 + @filedlg.title = 'Save Dialog' + loop do + if****@filed***** == Gtk::Dialog::RESPONSE_OK + next unles****@filed***** # ファイル名が空 + if File.exist?(get_platform_filename(@filedlg.filename)) + next unless overwrite_file?(@filedlg.filename) # 上書き拒否 + else + next unless filename_valid?(@filedlg.filename) # ファイル名が不正 + end + save_file(@filedlg.filename) + @filename =****@filed***** + update_window_title(@filename) + end + break + end + @filedlg.hide + end + +"show_savedlg"メソッドでは、"Open(開く)"コマンドでも使ったGtk::FileChooserDialogを保存用に使い、入力されたファイル名が不正でなければ保存します。Gtk::Dialog#runメソッドでレスポンスを捕捉してその値をチェックするのは"show_opendlg"メソッドと同じですが、そのブロックを無限ループで挟んで、"OK"ボタンが押され、かつ上書きが拒否されたり入力されたファイル名が不正である場合、そのままファイル保存ダイアログが表示され続けるようにしています。 + + def overwrite_file?(filename) # 新規作成 + dlg = Gtk::MessageDialog.new( + @filedlg, Gtk::Dialog::MODAL, + Gtk::MessageDialog::QUESTION, + Gtk::MessageDialog::BUTTONS_OK_CANCEL, + filename + "\n already exists. Do you overwrite it?") + result = dlg.run + dlg.destroy + result == Gtk::Dialog::RESPONSE_OK + end + +"overwrite_file?"メソッドでは、呼び出される度に、ファイルを上書きするかどうか確認するダイアログ(Gtk::MessageDialog)を新規に作成して表示します。"OK"ボタンが押されたかどうかを戻り値として返します。 + + def filename_valid?(filename) # 新規作成 + begin + File.open(get_platform_filename(filename), 'w') do |f| end + rescue Errno::EINVAL => err + p err + basename = File.basename(filename) + dlg = Gtk::MessageDialog.new( + @filedlg, Gtk::Dialog::MODAL, + Gtk::MessageDialog::ERROR, + Gtk::MessageDialog::BUTTONS_CLOSE, + basename + " is a invalid file name.") + dlg.run + dlg.destroy + return false + end + true + end -Gtk::TextView#move_cursorは、移動単位と移動量を指定してカーソルを移動するメソッドです。移動量はマイナス値を指定できます。3番目の引数は、移動前の位置から移動後の位置までのテキストを選択状態にするかどうかを指定します。 +"filename_valid?"メソッドでは、ファイルパスをチェックして不都合があればダイアログで表示してfalseを返します。このメソッドはファイル保存処理専用に作ったので、試しにファイルを作ってみてパスの有効性を確認しています(ファイルができてしまっても問題がない)。パスに問題がなければtrueを返します。