title: Apollo チュートリアル
Author: NISHIO Mizuho
mailto: mzh@portnet.ne.jp


1. メモ帳

  本来は Phi::Memo を使うべきなのですが、今回は %href: http://member.nifty.ne.jp/~katsuhiko/index.html  本田勝彦さん % が作られている TEditor( Apolloでは Phi::Editor ) を使ってメモ帳を作成します。

1.1. その1

  %href: ./rb/memo.rb memo.rb%

--ruby
001  #!ruby -Ks
002  
003  require 'phi'
004  require 'dialogs'
005  require 'editor'
006  
007  DEFAULT_TITLE = 'メモ帳'
008  
009  class AboutDialog < Phi::Form
010    
011    def initialize
012      super
013      self.border_style = Phi::BS_DIALOG
014      self.width = 300
015      self.height = 180
016      self.caption = 'このソフトについて'
017  
018      margin = 15
019      w = self.width - margin * 2
020  
021      button = Phi::Button.new(self, :button1, 'OK')
022      button.modal_result = Phi::MR_OK
023      button.width = w
024      button.left = margin
025      button.top = self.height - margin * 3 - button.height
026  
027      label = Phi::Label.new(self, :label1)
028      label.caption = "このスクリプトはフリーソフトウェアです。\r\nご自由にお使い下さい。"
029      label.width = w
030      label.height = button.top - margin * 2
031      label.left = margin
032      label.top = margin
033    end
034  
035  end
036  
037  def error_dlg(msg)
038    result = Phi::message_dlg(msg, Phi::MT_ERROR, [Phi::MB_YES, Phi::MB_NO, Phi::MB_CANCEL], 0)
039    return result
040  end
041  
042  def confirm_save
043    result = error_dlg('ファイルが変更されています。保存しますか?')
044    case result
045    when Phi::MR_YES
046      save_file
047      return true
048    when Phi::MR_NO
049      return true
050    when Phi::MR_CANCEL
051      return false
052    end
053  end
054  
055  def save_file
056    if $filename.nil?
057      dlg = Phi::SaveDialog.new
058      dlg.filter = 'すべてのファイル(*.*)|*|テキスト文書(*.txt)|*.txt|'
059      if dlg.execute
060        $filename = dlg.file_name
061      end 
062    end
063  
064    if $filename and Phi::SCREEN.form1.editor1.modified
065      Phi::SCREEN.form1.editor1.lines.save($filename)
066      Phi::SCREEN.form1.editor1.modified = false    
067      Phi::SCREEN.form1.caption = DEFAULT_TITLE + ' ' + $filename
068    end
069  end
070  
071  def before_exit
072    if Phi::SCREEN.form1.editor1.modified
073      result = confirm_save
074      return result
075    end
076    return true
077  end
078  
079  
080  
081  form = Phi::Form.new(:form1, DEFAULT_TITLE)
082  editor = Phi::Editor.new(form, :editor1, '')
083  editor.align = Phi::AL_CLIENT
084  editor.lines.clear
085  editor.modified = false
086  $filename = nil
087  form.width = 500
088  form.height = 400
089  
090  Phi.new_menu(form, :menu1, [
091    menu_file = Phi.new_item('ファイル(&F)', '', :mi_file).add(
092      menu_open = Phi.new_item('開く(&O)', 'CTRL+O', :mi_open),
093      menu_save = Phi.new_item('保存(&S)', 'CTRL+S', :mi_save),
094      Phi.new_line,
095      menu_exit = Phi.new_item('終了(&X)', '', :mi_exit)
096    ),
097    menu_edit = Phi.new_item('編集(&E)', '', :mi_edit).add(
098      menu_undo = Phi.new_item('元に戻す(&U)', 'Ctrl+Z', :mi_undo),
099      menu_redo = Phi.new_item('やり直し(&R)', 'Ctrl+A', :mi_redo),
100      Phi.new_line,
101      menu_cut = Phi.new_item('切り取り(&T)', 'Ctrl+X', :mi_cut),
102      menu_copy = Phi.new_item('コピー(&C)', 'Ctrl+C', :mi_copy),
103      menu_paste = Phi.new_item('貼り付け(&P)', 'Ctrl+V', :mi_paste),
104      menu_delete = Phi.new_item('削除(&D)', '', :mi_delete),
105      Phi.new_line,
106      menu_select_all = Phi.new_item('すべて選択(&A)', '', :mi_select_all)
107    ),
108    menu_help = Phi.new_item('ヘルプ(&H)', '', :mi_help).add(
109        menu_about = Phi.new_item('このソフトについて', '', :mi_about)
110    )
111  ])
112  
113  # menu
114  menu_open.on_click = proc do
115    dlg = Phi::OpenDialog.new
116    dlg.filter = 'テキスト文書(*.txt)|*.txt|すべてのファイル(*.*)|*|'
117    if dlg.execute
118      editor.lines.load(dlg.file_name)
119      editor.modified = false
120    end
121  end
122  
123  menu_save.on_click = proc do
124    if editor.modified
125      save_file
126    end
127  end
128  
129  menu_exit.on_click = proc do
130    form.close
131  end
132  
133  menu_undo.on_click = proc do
134    if editor.can_undo 
135      editor.undo
136    end
137  end
138  
139  menu_redo.on_click = proc do
140    if editor.can_redo 
141      editor.redo
142    end
143  end
144  
145  menu_cut.on_click = proc do
146    editor.cut_to_clipboard
147  end
148  
149  menu_copy.on_click = proc do 
150    editor.copy_to_clipboard
151  end
152  
153  menu_paste.on_click = proc do 
154    editor.paste_from_clipboard
155  end
156  
157  menu_delete.on_click = proc do
158    editor.clear_selection
159  end
160  
161  menu_select_all.on_click = proc do 
162    editor.select_all
163  end
164  
165  menu_about.on_click = proc do
166    dlg = AboutDialog.new
167    dlg.show_modal
168  end
169  
170  # other control
171  form.on_close_query = proc do
172    before_exit
173  end
174  
175  menu_file.on_click = proc do
176    menu_save.enabled = editor.modified
177  end
178  
179  menu_edit.on_click = proc do 
180    menu_undo.enabled = editor.can_undo
181    menu_redo.enabled = editor.can_redo
182    menu_cut.enabled = editor.selected
183    menu_copy.enabled  = editor.selected 
184    menu_delete.enabled = editor.selected 
185    menu_paste.enabled = Phi::CLIPBOARD.has_format?(Win::CF_TEXT)
186  end
187  
188  form.show
189  Phi::mainloop
--

  このスクリプトを実行すると、%href: img/memo.png このような% ウィンドウがあらわれます。

1.1.1. 解説

1.1.1.1. 最初の部分(1〜35行)

  1行目はこのスクリプトで使用する文字コードを指定していて、 -Ks がその部分にあたります。今回の場合はスクリプトの文字コードがSJISなので、-Ks になっていますが、-Ks以外にも

  * -Ke
  * -Ku
  * -Kn

  
  のようなオプションを使うことができます。詳細についてはRubyのマニュアルを読んでください。-Ks より前の %c:「#!ruby」% については以前も述べたようにおまじないだと思って構わないです。

  5行目が Phi::Editor を使うためのおまじないです。
  
  7行目はメモ帳のタイトルに使用する文字列を定数として設定しています。

  9〜35行はメニューの「このソフトについて」が押されたときに表示するダイアログのクラスを定義しています。

1.1.1.2. メソッドの定義(37〜77行)

  37〜77行まではメソッド定義です。ここでは

    * error_dlg
    * confirm_save
    * save_file
    * before_exit

 
  の4つのメソッドが定義されています。詳細についてはそれぞれのメソッドが呼ばれるときに行います。

1.1.1.3. GUI部分の構成(81〜111行)
   
  82行が Phi::Editor のオブジェクトを生成している部分です。

  84行の editor.lines ( Phi::Editor#lines ) はエディターで編集している文章を表します。その実体は Phi::Editor::Strings のオブジェクトです。 Phi::Editor::Strings は Phi::Strings# を継承しているので、 Phi::ComboBox で使用した Phi::ComboBox#lines と 同じ使い方をすることができます。
  
  editor.lines.clear( Phi::Editor::Strings#clear )は編集している文章をすべて消去します。

  85行の editor.modified( Phi::Editor#modified )は編集している文章が修正されている時に true を、修正されていない時に false を返す属性です。 Phi::Editor は文章が変更されると自動的に Phi::Editor#modified を false から true へ変更してくれますが、false から true へ変更してくれません。そのため85行のように自分で false にしなければなりません。 

  86行は現在編集しているファイル名を表す大域変数です。何度も言いますが、大域変数を使うのは問題があるので、このスクリプトはあまり良くありません。クラスを定義すれば大域変数を使わないで済むので、自信のある方はクラスを定義してみてください。

1.1.1.4. メニューのイベントハンドラ(114〜168行)

  114〜121行は「開く」メニューを押したときのイベントハンドラです。 115〜117行はダイアログの章で出てきたので、そちらを読んでください。118行の editor.lines.load( Phi::Ediotor::Strings#load )は引数のファイルを読みこんで、エディター部分に表示させます。 

  123〜127行は「保存」メニューを押した時です。125行は55〜69行で定義されている save_file のメソッド呼び出しです。これによって編集中の文章が保存されます。

  save_fileの56〜62行は現在編集している文章にファイル名がない時の処理です。 Phi::SaveDialog を使っていますが、使い方は Phi::OpenDialog とほとんど変わりません。

  64〜68行が実際に保存を行う部分です。保存するファイルの名前は $filename ですので、65行の Phi::SCREEN.form1.editor1.lines.save( Phi::Editor::Strings#save )を使って $filename に文章を保存しています。ここで Phi::SCREEN を使っているのは以前も説明したようにRubyの変数のスコープ(変数を参照することができる範囲)には制限があるからです。

  メニューのイベントハンドラのうち残った部分については機械的にメニューに機能を割り当てているだけなので、すぐに理解できると思います。ここでは Phi::Editor のメソッドについて記述するにとどめます。

  : Phi::Editor#can_udno
     Phi::Editor#undo を行うことができるかどうか表します。

  : Phi::Editor#undo
     編集した文字を元に戻します。

  : Phi::Editor#can_redo
     Phi::Editor#redo を行うことができるかどうか表します。

  : Phi::Editor#redo
     Phi::Editor#undo で元に戻した編集処理をやり直します。
  
  : Phi::Editor#cut_to_clipboard
     選択された文字列をクリップボードに転送し、その選択された文字列は編集している文章から消します。
 
  : Phi::Editor#copy_to_clipboard
     選択された文字列をクリップボードに転送します。

  : Phi::Editor#paste_from_clipboard
     クリップボードの文字列を編集中の文章に転送します。

  : Phi::Editor#clear_selection
     選択している文字列を消します。

  : Phi::Editor#select_all
     編集している文章すべてを選択した状態にします。

1.1.1.5. メニュー以外のコントロールのイベントハンドラ(171〜186行)

  171〜173行の説明は最後に回します。

  175〜177行は「ファイル」のメニューが押された時のイベントハンドラです。「保存」メニューは編集中のときだけ行うことができるようにしたいので、 editor.modified( Phi::Editor#modified )の値に応じて menu_save.enabled を変更しています。 

  179〜186行は「編集」メニューが押された時のイベントハンドラです。それぞれのメニューは使用することが出来る条件がきまっているので、条件を満たす時だけメニューを使用することができるようにしています。

  「元に戻す」メニューは editor.can_undo( Phi::Editor#can_undo )が、「やり直し」メニューは editor.can_redo( Phi::Editor#can_redo )が true を返す時に使うことができるようにします。「切り取り」「コピー」「削除」の三つは領域選択をしている時に使うことができるので、editor.selected( Phi::Editor#selected )の値に応じてメニューの使用の可・不可を決定しています。「貼り付け」のメニューはクリップボードに文字列データが入っているときに使用することができるようにしなければなりませんが、これには Phi::CLIPBORAD.has_format? を使います。 Phi::CLIPBORAD.has_format? はメソッドの引数が表すデータがクリップボードに存在する時に true を返し、存在しないときに false を返します。今のところ Phi::CLIPBOARD.has_format? の引数に使うことができるのは下のような定数です。

  * Win::CF_BITMAP
  * Win::CF_COMPONENT
  * Win::CF_METAFILEPICT
  * Win::CF_PICTURE
  * Win::CF_TEXT


  それでは元に戻って171〜173行の説明をします。ここはフォームが閉じる前に呼ばれるイベントハンドラです。エディターでは文章の編集中にいきなりフォームを閉じてしまうと、編集された文章が失われることになるので、このようなイベントハンドラを用います。 Phi::Form#on_close_query は一番最後の行(この場合は172行)を評価し、その値が true であればフォームを閉じる処理を続け、false の時は フォームを閉じる処理を中断します。172行の before_exit は71行で定義されているメソッドの呼び出しです。このメソッドではダイアログを表示させて、編集中の文章を保存するかどうかユーザーに問い合わせます。ユーザーが押したダイアログのボタンに応じて before_exit の返り値が true もしくは false になり、フォームを閉じる処理を中断することが出来ます。

  before_exit の72行目は編集中であるかどうかを確かめて、もし、編集中であれば 42行で定義されている confirm_save のメソッド呼び出しを行い、その返り値を result に代入します。 confirm_save はユーザーに問い合わせるダイアログを表示したり、データを保存したりするメソッドです。次の行の return result は before_exit の返り値を設定しています。 return を使った時点でメソッドの処理はすべて終了することになるので、74行が実行されると、76行は実行されないことになるので、注意してください。76行は編集中でない時に実行される部分です。ここでは メソッドの返り値として true を返すことで上の form.on_close_query でフォームの処理が中断されないようにします。

  confirm_save では最初に error_dlg を呼び出して、ダイアログを表示させます。error_dlg の返り値は result に代入され、その値に応じて処理が変わります。この辺りはダイアログの章の説明で理解できると思います。 error_dlg の返り値は before_exit の返り値に関係しますし、form.on_close_query の処理にも関わってきます。


1.1.1.6. クラスやメソッド
: Phi::Editor
* Phi::Editor.new
* Phi::Editor#can_udno
* Phi::Editor#undo
* Phi::Editor#can_redo
* Phi::Editor#redo
* Phi::Editor#cut_to_clipboard
* Phi::Editor#copy_to_clipboard
* Phi::Editor#paste_from_clipboard
* Phi::Editor#clear_selection
* Phi::Editor#select_all
* Phi::Editor#modified
* Phi::Editor#lines


: Phi::Editor::Strings
* Phi::Editor::Strings#load ( < Phi::Strings#load )
* Phi::Editor::Strings#save ( < Phi::Strings#save )
* Phi::Editor::Strings#clear ( < Phi::Strings#clear )

: Phi::SaveDialog
* Phi::SaveDialog.new
* Phi::SaveDialog#filter
* Phi::SaveDialog#file_name
* Phi::SaveDialog#execute

: Phi::Form
* Phi::Form#on_close_query

: Phi::CLIPBOARD.format?

