プログラム講座 中級編20

- 簡易マルチウィンドウエディタの作成(その3) -

 中級編20です。今回は「ウィンドウメニューの作成」「状況に応じたメニュー処理」「HTML形式への書き出し」を行います。だんだんリストがぼろぼろ(笑)になってきてしまいましたので、一旦エディタを作成する講座は今回で終了です。




◆前回からの変更点
 今回のプログラムは以前と比べて、かなり追加削除が行われています。追加箇所については後で解説します。今回削除された部分は「ファイルの読み込み」「ファイルの書き出し」部分です。

「えっ、そんな所を?」

 今までは「標準テキスト形式」(Plain Textとも呼びます)でなくFuture BASIC独自の「ZTXT形式」で読み込み、書き出しを行っていました。書き出しに関してはテキスト形式として書き出す事はできましたが、テキストを読み込むことが出来ませんでした。ZTXT形式は手軽にスタイル情報も扱えるので便利ですが、今回はHTML書き出しや整合性の都合で(笑)「標準テキスト」のみ扱うことにしました。

 それでは「標準テキスト形式」をエディットフィールドに読み込む部分について解説します。以下が抜粋したプログラム部分です。
'--------------------------------------------------------
' "テキストを開く(TEXT形式のみ)"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN openWindow
  filename$ = FILES$(_fOpen,"TEXT",,volRefNum%)
  LONG IF filename$<>""
    flag = FN newWindows(filename$):              ' "ウィンドウが、開けるかどうか調べる"
    LONG IF flag
      OPEN "I",#1,filename$,,volRefNum%
      size& = LOF(1,1):                           ' "ファイルサイズを求める"
      efHandle& = FN NEWHANDLE(size&+2):          ' "ファイルサイズ分メモリを確保"
      err% = FN HLOCK(efHandle&)
      LONG IF efHandle&
        READ FILE #1,[efHandle&]+2,size&:           ' "サイズ分読み込む"
        POKE WORD [efHandle&],size&
        EDIT FIELD #1,&efHandle&:                   ' "エディットフィールドに設定"
        err% = FN DISPOSHANDLE(efHandle&):          ' "ハンドルを破棄"
      END IF
      CLOSE #1
      
      SETSELECT 0,32767:                          ' "全てを選択"
      EDIT TEXT _sysFont,10:                      ' "システムフォント(大阪)、10ポイントに設定"
      SETSELECT 0,0:                              ' "選択を解除"
    END IF
  END IF
END FN
 太字の部分がテキストファイルを読み込む部分です。まずFN NEWHANDLEを使ってファイルサイズ分+2バイトメモリを確保します。2バイト多く確保するのは先頭部分にテキストデータのサイズを格納するためです。
 ファイルからサイズ分だけ読み込みます。先頭にテキストサイズを書き込み、エディットフィールドにハンドルを設定します。EDIT FIELD #1,&efHandle&でハンドルが「コピー」されます。コピーするだけですので、efHandle& = FN NEWHANDLE(size&+2)で確保したメモリを破棄しなければ、どんどんメモリにゴミが残ってしまいます。

 このままテキストを読み込むと、日本語のテキストの場合文字化けして表示されることがあります。これはフォントやサイズが設定されていないためです。今回は非常に手抜きな方法を使ってフォント等を指定しています。SETSELECT命令を使ってテキストを全部選択し、EDIT TEXTで選択されたテキストに対してフォント等を設定します。最後にSETSELECT 0,0で選択を解除しています。結構インチキな手法ですので真似してはいけませんf(^^;



◆ウィンドウメニューの作成
 マルチウィンドウを扱っているソフトには「ウィンドウのリスト」を表示するメニューがあります。今あるウィンドウがどれだけあるかメニューで把握でき、選択すると選択されたウィンドウが手前になるというものです。コマンドキー+数字キーを割り当てるソフトもありますが、今回は中級編17で使用したものを、そっくりコピー(流用)して使用してしまいます。
 グローバル変数やアプリにべったり依存する書き方をしなければ、このようにコピー/ペーストするだけで使えるようになります。ということで(?)ウィンドウリストの作り方については中級編17を参照して下さい。やっている事はまったく同じですので(手抜きですね(^^;)



◆状況に応じたメニュー処理
 ウィンドウが一枚も開いていない場合、ファイルメニューの保存ができてしまうのは変です。そこで条件をチェックしてメニューの状態を変化させる事になります。メニューを変化させる時は「メニュー処理が行われた後」になります。メニュー状態を変更するには、

MENU メニューバー番号,メニュー番号,状態,表示文字列

 の「状態」の部分を変更します。この部分は
 といったパラメーターを設定します。注意しなければならないのは、すでに構築されているメニューの場合、表示文字列にメタ文字である/(スラッシュ:コマンドキー割り当て)を使用した場合、そのままメニューに表示されてしまうことです。さらに面倒なことに表示文字列を省略すると変な文字列がメニューバーに表示されてしまいます。「B>表示文字列には/を含まず省略してはならない」という事を念頭においておいてください。

 以下に状況に応じて変化するファイルメニューの部分のプログラムを載せておきます。
'--------------------------------------------------------
' "ファイルメニューを状況に応じて設定する"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN setFileMenu
  LONG IF windowTotal = 0
    MENU _fileMenu,_closeText,_disable,"閉じる"
    MENU _fileMenu,_saveText,_disable,"保存..."
    MENU _fileMenu,_saveHTML,_disable,"HTML形式で保存..."
  XELSE
    MENU _fileMenu,_closeText,_enable,"閉じる"
    MENU _fileMenu,_saveText,_enable,"保存..."
    MENU _fileMenu,_saveHTML,_enable,"HTML形式で保存..."
    LONG IF windowTotal = _maxWindows
      MENU _fileMenu,_newText,_disable,"新規"
      MENU _fileMenu,_openText,_disable,"開く..."
    XELSE
      MENU _fileMenu,_newText,_enable,"新規"
      MENU _fileMenu,_openText,_enable,"開く..."
    END IF
  END IF
END FN



◆HTML形式への書き出し
 最後におまけ機能でHTML形式でも書き出せるようにしたいと思います。テキストからHTMLへの変換については中級編1で行いました。という事で中級編1を参照して下さい。実は変換部分もコピーしてきただけです(^^;
 このように、何かプログラムを作ろうと考えた場合、以前作成したプログラムから流用できるようになれば、すごく効率が上がります。本当はこのようにしてプログラムを作成していく事が中級者から上級者への第一歩になります。
 あとはノウハウを積むことでしょうか。頑張りましょう(^o^)/



◆終わりに
 エディタは簡単だ〜と思ってましたが、ZTXTを使わない、標準外の事をしようとすると結構面倒というのを実感してしまいました。特にテキストが読み込めずに対策に時間を費やしてしまった、というのもありました。やはり、こういうのはProgram Generator(PG)向きだなあと思います。PGについては矢野さんのホームページに解説がありますので参考にしてください。中級編18〜20で行った以上の事が、ちょっとした事でできてしまいます。
 あと、やはり最初にきとんと設計をした方が良いというのも実感しました。結構追加変更には耐えられるプログラミングをしていますが、標準外の事をしようとした場合、きちんと設計しておかないと面倒です。

 なんか自爆状態で終わってしまいましたが、次回のネタは不明です。久々に上級編かもf(^^;



◆今回のプログラムリスト
' ' "簡易テキストエディタ" ' OUTPUT FILE "fbEdit" ' "----------------------- 定数の定義 ----------------------" _maxWindows = 33: ' "32枚までウィンドウを開くことが出来ます(33-1)" _fileMenu = 1: ' "ファイルメニュー" _editMenu = 2: ' "編集メニュー" _windowMenu = 3: ' "ウィンドウメニュー" _newText = 1: ' "新規テキスト" _openText = 2: ' "テキストオープン" _closeText = 3: ' "テキストクローズ" _saveText = 5: ' "TEXT形式で保存" _saveHTML = 6: ' "HTML形式で保存" _quit = 8: ' "終了メニュー" _selectAll = 8: ' "「全てを選択」のメニュー項目番号" gQuit_flag = _false: ' "プログラム終了フラグ" ' "------------------ グローバル変数の定義 -----------------" DIM theWindow(_maxWindows): ' "ウィンドウの有無を示す配列(_trueであり、_falseでなし)" DIM theName$(_maxWindows): ' "ウィンドウ名" DIM menuList(_maxWindows): ' "メニューバーに登録されているウィンドウの番号" windowTotal = 0: ' "開いているウィンドウの総数" END GLOBALS '-------------------------------------------------------- ' "空いているウィンドウを探して空いている場合は、" ' "新しくウィンドウをオープンし登録する" '-------------------------------------------------------- CLEAR LOCAL LOCAL FN newWindows(wnd$) flag = _false FOR i=1 TO _maxWindows LONG IF theWindow(i) = _false flag = _true theWindow(i) = _true: ' "ウィンドウがあることを示すフラグをオンにする" theName$(i) = wnd$: ' "ウィンドウリストに名前を追加" WINDOW #i,wnd$,(0,0)-(480,360),_doc EDIT FIELD #-1,"",(0,0)-(480-15,360-15),_noFramed SCROLL BUTTON #-1,1,1,1,32,,_scrollVert windowTotal = windowTotal + 1 i = 999 END IF NEXT END FN = flag '-------------------------------------------------------- ' "カレントウィンドウをクローズする。自動的にエディットフィールドもクローズされます" '-------------------------------------------------------- CLEAR LOCAL LOCAL FN closeWindow wNum = WINDOW(_activeWnd): ' "アクティブウィンドウの番号を調べる" LONG IF wNum <> 0 WINDOW CLOSE wNum: ' "ウィンドウをクローズします" theWindow(wNum) = _false: ' "ウィンドウの存在を消します" theName$(wNum) = "": ' "ウィンドウ名を空にします" END IF END FN '-------------------------------------------------------- ' "テキストを開く(TEXT形式のみ)" '-------------------------------------------------------- CLEAR LOCAL LOCAL FN openWindow filename$ = FILES$(_fOpen,"TEXT",,volRefNum%) LONG IF filename$<>"" flag = FN newWindows(filename$): ' "ウィンドウが、開けるかどうか調べる" LONG IF flag OPEN "I",#1,filename$,,volRefNum% size& = LOF(1,1): ' "ファイルサイズを求める" efHandle& = FN NEWHANDLE(size&+2): ' "ファイルサイズ分メモリを確保" err% = FN HLOCK(efHandle&) LONG IF efHandle& READ FILE #1,[efHandle&]+2,size&: ' "サイズ分読み込む" POKE WORD [efHandle&],size& EDIT FIELD #1,&efHandle&: ' "エディットフィールドに設定" err% = FN DISPOSHANDLE(efHandle&): ' "ハンドルを破棄" END IF CLOSE #1 SETSELECT 0,32767: ' "全てを選択" EDIT TEXT _sysFont,10: ' "システムフォンt(大阪)、10ポイントに設定" SETSELECT 0,0: ' "選択を解除" END IF END IF END FN '-------------------------------------------------------- ' "テキスト形式で保存する" '-------------------------------------------------------- CLEAR LOCAL LOCAL FN saveText filename$ = FILES$(_fSave,"保存ファイル名:",".txt",volRefNum%) LONG IF filename$<>"" DEF OPEN "TEXTttxt" OPEN "O",#1,filename$,,volRefNum% GET FIELD efHandle&,#1 err% = FN HLOCK(efHandle&): ' "ハンドルをロック" textSize& = PEEK WORD([efHandle&]): ' "先頭2バイトがテキストサイズ" WRITE FILE #1,[efHandle&]+2,textSize&: ' "データ保存" CLOSE #1 KILL FIELD efHandle&: ' "ハンドルを破棄" wNum = WINDOW(_activeWnd): ' "アクティブウィンドウの番号を調べる" WINDOW wNum,filename$ END IF END FN '-------------------------------------------------------- ' "HTML形式で保存する" '-------------------------------------------------------- CLEAR LOCAL LOCAL FN saveHTML filename$ = FILES$(_fSave,"保存ファイル名:",".html",volRefNum%) LONG IF filename$<>"" DEF OPEN "TEXTttxt" OPEN "O",#1,filename$,,volRefNum% GET FIELD efHandle&,#1 err% = FN HLOCK(efHandle&): ' "ハンドルをロック" textSize& = PEEK WORD([efHandle&]): ' "先頭2バイトがテキストサイズ" PRINT #1,"<HTML><HEAD><TITLE>";filename$;"</TITLE></HEAD>" PRINT #1,"<BODY bgColor='#FFFFFF'>" br$="<BR>": ' "HTML 改行タグ" amp$ = "&amp": ' "&マーク" lt$ = "&lt": ' "<記号" gt$ = "&gt": ' ">記号" FOR i&= 0 TO textSize&-1 a$=CHR$(PEEK([efHandle&]+2+i&)): ' "文字列に変換" IF a$=CHR$(13) THEN WRITE #1,br$;4 IF a$="&" THEN WRITE #1,amp$;4:a$=";" IF a$="<" THEN WRITE #1,lt$;3:a$=";" IF a$=">" THEN WRITE #1,gt$;3:a$=";" WRITE #1,a$;1 NEXT PRINT #1,br$: ' "改行コード" PRINT #1,"</BODY></HTML>" CLOSE #1 KILL FIELD efHandle&: ' "ハンドルを破棄" wNum = WINDOW(_activeWnd): ' "アクティブウィンドウの番号を調べる" WINDOW wNum,filename$ END IF END FN '-------------------------------------------------------- ' "テキストを選択する" '-------------------------------------------------------- CLEAR LOCAL LOCAL FN selectText SETSELECT 0,32767 END FN '-------------------------------------------------------- ' "選択されたウィンドウを前面にする" '-------------------------------------------------------- CLEAR LOCAL LOCAL FN toFront(count) No = menuList(count): ' "メニューリストからウィンドウ番号を求める" WINDOW No: ' "ウィンドウを一番手前にする" END FN '-------------------------------------------------------- ' "ウィンドウメニューを構築する" '-------------------------------------------------------- CLEAR LOCAL LOCAL FN makeMenu CALL DELETEMENU(_windowMenu): ' "ウィンドウメニュー自体を消去" MENU _windowMenu,0,_enable,"ウィンドウ": ' "新しくウィンドウメニューを作成" menuNo = 1: ' "登録するメニュー項目は1から" FOR i=1 TO _maxWindows: ' "最大ウィンドウ分繰り返す" LONG IF theWindow(i) = _true: ' "ウィンドウが存在する場合はメニューリストと項目を追加する" MENU _windowMenu,menuNo,_enable,theName$(i):' "項目を追加" menuList(menuNo) = i: ' "メニューリストにウィンドウ番号を格納" menuNo = menuNo + 1 END IF NEXT END FN '-------------------------------------------------------- ' "ファイルメニューを状況に応じて設定する" '-------------------------------------------------------- CLEAR LOCAL LOCAL FN setFileMenu LONG IF windowTotal = 0 MENU _fileMenu,_closeText,_disable,"閉じる" MENU _fileMenu,_saveText,_disable,"保存..." MENU _fileMenu,_saveHTML,_disable,"HTML形式で保存..." XELSE MENU _fileMenu,_closeText,_enable,"閉じる" MENU _fileMenu,_saveText,_enable,"保存..." MENU _fileMenu,_saveHTML,_enable,"HTML形式で保存..." LONG IF windowTotal = _maxWindows MENU _fileMenu,_newText,_disable,"新規" MENU _fileMenu,_openText,_disable,"開く..." XELSE MENU _fileMenu,_newText,_enable,"新規" MENU _fileMenu,_openText,_enable,"開く..." END IF END IF END FN '-------------------------------------------------------- ' "メニューを構築する" '-------------------------------------------------------- CLEAR LOCAL LOCAL FN initMenu '"ファイルメニュー" MENU _fileMenu,0,_enable,"ファイル" MENU _fileMenu,_newText,_enable,"/N新規" MENU _fileMenu,_openText,_enable,"/O開く..." MENU _fileMenu,_closeText,_enable,"/W閉じる" MENU _fileMenu,_closeText+1,_enable,";" MENU _fileMenu,_saveText,_enable,"/S保存..." MENU _fileMenu,_saveHTML,_enable,"/THTML形式で保存..." MENU _fileMenu,_quit-1,_enable,";" MENU _fileMenu,_quit,_enable,"/Q終了" FN setFileMenu ' "編集メニュー" EDIT MENU _editMenu MENU _editMenu,_selectAll-1,_disable,";" MENU _editMenu,_selectAll,_enable,"/A全てを選択" ' "ウィンドウメニュー" FN makeMenu END FN '--------------------------------------------- ' "メニューの選択" '--------------------------------------------- CLEAR LOCAL LOCAL FN doMenus menuID = MENU(_menuID): '"選択されたメニューバー項目の番号" itemID = MENU(_itemID): '"プルダウンメニューで選択された項目番号" SELECT menuID CASE _fileMenu : ' "ファイルメニュー" SELECT itemID CASE _newText: ' "新規" FN newWindows("新規テキスト") CASE _openText: ' "開く" FN openWindow CASE _closeText: ' "閉じる" FN closeWindow CASE _saveText: ' "保存" FN saveText CASE _saveHTML: ' "TEXT形式で保存" FN saveHTML CASE _quit: ' "終了が選択された" gQuit_flag = _true END SELECT CASE _editMenu: ' "編集メニュー" SELECT itemID CASE _selectAll: ' "全てを選択" FN selectText END SELECT CASE _windowMenu: ' "ウィンドウメニュー" IF itemID > 0 THEN FN toFront(itemID): ' "選択されたウィンドウを手前にします" END SELECT MENU: ' "これがないとメニューバーの項目が強調表示されたままになってしまいます" FN makeMenu FN setFileMenu END FN '-------------------------------------------------------- ' "イベントの処理(ウィンドウリサイズの時だけ処理します)" '-------------------------------------------------------- LOCAL FN doDialog events = DIALOG(0): ' "イベントの種類を取得" eType = DIALOG(events): ' "イベントIDを取得" IF eType = _wndSized THEN EDIT FIELD #-1,,(0,0)-(WINDOW(_width)-15,WINDOW(_height)-15) IF events = _wndClick THEN WINDOW(eType): ' "ウィンドウをアクティブにする" IF events = _wndClose THEN WINDOW(eType):FN closeWindow:' "ウィンドウを閉じる" CURSOR _arrowCursor IF events = _cursOver THEN CURSOR _iBeamCursor END FN WINDOW OFF ON MENU FN doMenus: '"メニューが選択された時の飛び先" ON DIALOG FN doDialog FN initMenu: '"メニューの初期化" DO HANDLEEVENTS: ' "イベント処理は自動で行ってくれます" UNTIL gQuit_flag