AutoLISP で MsgBox や InputBox もどきを使う

# AutoCAD の LISP でダイアログの UI を作るのは面倒
AutoCAD で簡単なツールを作るのに便利なのが AutoLISP ですが、ユーザとの入出力は、作図領域かコマンドライン領域で行う事が殆どです。
一応、ファイル選択ダイアログは用意されていますし、簡単なメッセージなら、`(alert "出来た！")` とかアラートダイアログで表示できます。
しかし、
+ メッセージを表示して、ユーザーに「OK」か「Cancel」を選択させる (VBA で言う MsgBox)
+ メッセージを表示して、ユーザーに文字を入力させる (VBA で言う InputBox)

という、ごく単純なダイアログが欲しい時があります。
## LISP でダイアログを表示するには、DCL を使う
LISP でダイアログを表示するには、DCL (ダイアログ・コントロール・ランゲージ) で記述されたファイルを用意し、それを LISP からロードして使います。
しかし、MsgBox や InputBox を使うためだけに、都度 DCL ファイルを用意するのは馬鹿らしいわけです。
## 別途 DCL ファイルを用意せずに、MsgBox や InputBox を使えるようにする
というわけで、関数内で DCL を生成し、目的を果たしたら DCL を消すような関数を作ればよいわけです。
### MsgBox もどき
```LISP:MsgBox もどき
;;;***************************************************
;;;
;;;  MsgBox関数
;;;
;;;***************************************************
(defun msg_box(
  str_label
  /
  dcl_file
  dcl_fnum
  ret_val
  ret_num
  )

  ;dclファイル名生成
  (setq ret_val "" dcl_file (vl-filename-mktemp "input_box.dcl"))
  ;dclファイル作成
  (setq dcl_fnum (open dcl_file "w"))
  (foreach str_tmp
    (list
      "inputbox : dialog"
      "{"
      "  label = \"InputBox\";"
      "  : text {"
      (strcat "    label = \"" str_label "\";")
      "  }"
      "  ok_cancel;"
      "}"
    );list
    (write-line str_tmp dcl_fnum)
  );foreach
  (close dcl_fnum)
  ;ダイアログをロードする
  (setq dcl_fnum (load_dialog dcl_file))
  (if (new_dialog "inputbox" dcl_fnum)
    (progn
      (action_tile "accept" "(done_dialog 1)")
      (action_tile "cancel" "(done_dialog 0)")
      (setq ret_num (start_dialog))
      (unload_dialog dcl_fnum)
      (if (zerop ret_num)
        (setq ret_val nil)
        (setq ret_val T)
      );if
    );progn
  );if
  ;dclファイルの削除
  (vl-file-delete dcl_file)
  (progn ret_val)

);defun
```
MsgBox 表示の用の DCL ファイルを作成・ロードし、引数で渡したメッセージを表示します。
「OK」が押されたら真、「Cancel」が押されたら偽が返ります。
注意点は、**引数の文字列には`\n`の改行は使えません**。
### InputBox もどき
```LISP:InputBox もどき
;;;***************************************************
;;;
;;;  InputBox関数
;;;
;;;***************************************************
(defun input_box(
  str_label
  str_edit
  /
  dcl_file
  dcl_fnum
  str_tmp
  ret_val
  ret_num
  )

  ;dclファイル名生成
  (setq ret_val "" dcl_file (vl-filename-mktemp "input_box.dcl"))
  ;dclファイル作成
  (setq dcl_fnum (open dcl_file "w"))
  (foreach str_tmp
    (list
      "inputbox : dialog"
      "{"
      "  label = \"InputBox\";"
      "  : text {"
      (strcat "    label = \"" str_label "\";")
      "  }"
      "  : edit_box {"
      "    key = \"input_text\";"
      "    edit_width = 50;"
      "  }"
      "  ok_cancel;"
      "}"
    );list
    (write-line str_tmp dcl_fnum)
  );foreach
  (close dcl_fnum)
  ;ダイアログをロードする
  (setq dcl_fnum (load_dialog dcl_file))
  (if (new_dialog "inputbox" dcl_fnum)
    (progn
      (action_tile "accept" "(setq ret_val (get_tile \"input_text\"))(done_dialog 1)")
      (action_tile "cancel" "(done_dialog 0)")
      (set_tile "input_text" str_edit)
      (setq ret_num (start_dialog))
      (unload_dialog dcl_fnum)
      (if (zerop (* ret_num (strlen ret_val)))
        (setq ret_val nil)
      );if
    );progn
  );if
  ;dclファイルの削除
  (vl-file-delete dcl_file)
  (progn ret_val)

);defun
```
InputBox 表示の用の DCL ファイルを作成・ロードし、ダイアログを表示します。
引数の内容は下記の通り。
+ 第一引数は、TextBox の上部に表示されるメッセージ(`\n`不可)
+ 第二引数は、TexyBox 内に記入される文字(`\n`不可)

「OK」を押下時に TextBox 内に文字が記載されていれば、その文字が返ります。
「Cancel」が押されるか、「OK」押下時に TextBox の内容が空なら、偽が返ります。
コマンドラインへの入力に対して、この InputBox を用いる利点は、`"`や`\`を直接入力できることです。
コマンドラインに記載するときは、`\"`や`\\`と記載しなければならないのですが、それを考慮しなくてよくなります。
## マルチテキスト(MTEXT) の書式コード付き文字を直接編集する
AutoCAD のマルチテキストオブジェクトは、一つの文字オブジェクト内で改行したり、文字の大きさを変えたり、文字に色を付けたりと、文字の書式を変更できるのが便利なのですが、プロパティを見ると分かる通り、文字に付与された書式情報は書式コード文字列として付与される形になっています。
モノとして近いのは、***Rich Text Format*** でしょうか？ `{ }` で囲う所とか、仕様も近いと思われます。
マルチテキストの標準エディタ「マルチテキストエディタ」で編集する際には、書式コードが見えない状態での編集になりますが、マルチテキストのエディタを外部エディタに変更すると、この書式コードを直接編集できます。
しかし、書式コードは可読性は低いため、普段は標準の「マルチテキストエディタ」を使用したいけど、細かい調整時に直接書式コードを編集したい場合は、その都度エディタの切り替えを行うのも面倒です。
そこで、先刻の InputBox ルーチンを使用して、MTEXT の書式コード付き文字列を直接編集するコマンドを作成してみましょう。
```LISP:MTEXT の書式コード付き文字を直接編集するコマンド
;マルチテキストの装飾文字付文字列を、そのまま充てるコマンド(要 input_box)
(defun C:EDIT_MTEXT_VALUE (
  /
  ename
  elist
  str_text
  ret_val
  )

  (if
    (and
      (setq ename (entsel "MTEXTオブジェクトを選択"))
      (setq ename (car ename))
      (setq elist (entget ename))
      (equal "MTEXT" (cdr (assoc 0 elist)))
    );and
    (progn
      (setq str_text (cdr (assoc 1 elist)))
      (setq ret_val (input_box "MTEXTの文字を編集" str_text))
      (if ret_val
        (entmod (subst (cons 1 ret_val) (assoc 1 elist) elist))
      );if
    );progn
  );if
  (princ)

);defun
```
## マルチテキストの「文字内容」は、何故プロパティから編集できない？
Autodesk は、何故プロパティの「文字内容」を直接編集できないようにしたんでしょうか？
矛盾した書式コードを記載する事で、データが破損する可能性があるからでしょうか？
しかし、下記の Autodesk のヘルプの様に、外部エディタで書式コードを直接記入する事を、拒んでいるわけではなさそうです。

https://help.autodesk.com/view/ACDLT/2023/JPN/?guid=GUID-6F59DA4A-A790-4316-A79C-2CCE723A30CA

データ破損の件で言えば、TrustedDWG の話を思い出します。
Autodesk 社以外のアプリが作成したDWG ファイルには、TrustedDWG のフラグが立っていない為、Autodesk 社のアプリで開くと警告が出るようになっています。
Autodesk 曰く非TrustedDWG はファイルとしての互換性が保証されないので、最悪破損してしまう可能性もあるとの事ですが、TrustedDWG のフラグが出来る前のバージョンの AutoCAD アプリケーション(確か 2004) で、LISP 等のツールを使用せずに、通常オペレーションだけで作成した DWG ファイルが、AutoCAD から開けなくなり、ダメもとで互換 CAD で開いて保存し直したら、再び AutoCAD から開けるようになった経験がある私としては、Autodesk 社のアプリが作る DWG だから大丈夫とは、とても言えないんじゃないかなぁ、と思ってしまう訳です。
しらんけど。