Category Archives: 未分類

2021-05-01

2021年春の新番組と視聴環境

さて、今期はどうでしょうね。

03/27(土) 17:30~ 日本テレビ 僕のヒーローアカデミア 第5期
03/28(土) 24:40~ NHK総合 赤ちゃん本部長
03/31(水) 24:00~ ニコニコ動画 ヘタリア World★Stars
04/01(木) 17:55~ テレビ東京 シャーマンキング 新作
04/01(木) 22:30~ TOKYO MX ゴジラ シンギュラポイント(Godzilla Singular Point)
04/01(木) 24:00~   天地創造デザイン部【特別編】
04/01(木) 27:00~ BS-TBS 歌うサッカーパンダ ミファンダ
04/02(金) 18:55~ NHK Eテレ もっと!まじめにふまじめ かいけつゾロリ 第2シリーズ
04/02(金) 22:00~ TOKYO MX SSSS.DYNAZENON(ダイナゼノン)
04/02(金) 25:23~ テレビ東京 灼熱カバディ
04/02(金) 26:25~ TBS ましろのおと
04/03(土) 08:00~ テレビ東京 カードファイト!! ヴァンガード overDress
04/03(土) 09:00~ NHK Eテレ おしりたんてい 新シリーズ
04/03(土) 12:00~   どすこい すしずもう
04/03(土) 22:30~ TOKYO MX Thunderbolt Fantasy 東離劍遊紀3
04/03(土) 23:30~ TOKYO MX Vivy -Fluorite Eye’s Song-
× 04/03(土) 24:57~ TOKYO MX ダイナ荘びより
04/04(日) 09:30~ テレビ東京 マジカパーティ
04/04(日) 22:00~ TOKYO MX ドラゴン、家を買う。
04/04(日) 22:30~ TOKYO MX 憂国のモリアーティ 2クール目
04/04(日) 23:00~ TOKYO MX NOMAD メガロボクス2
04/04(日) 23:30~ TOKYO MX さよなら私のクラマー
04/04(日) 24:00~ TOKYO MX セブンナイツレボリューション -英雄の継承者-
04/04(日) 24:10~ NHK総合 キングダム 第3シリーズ ※第1話から放送再開
04/04(日) 24:30~ TOKYO MX 戦闘員、派遣します!
04/04(日) 25:00~ TOKYO MX 黒ギャルになったから親友とヤってみた。
04/05(月) 22:30~ TOKYO MX やくならマグカップも
04/05(月) 23:00~ TOKYO MX 恋と呼ぶには気持ち悪い
04/05(月) 24:00~ TOKYO MX ひげを剃る。そして女子高生を拾う。
04/05(月) 25:30~ テレビ東京 フルーツバスケット The Final
04/05(月) 26:00~ テレビ東京 オッドタクシー
04/06(火) 07:30~ テレビ東京 iiiあいすくりん 
04/06(火) 18:45~ NHK Eテレ ふしぎ駄菓子屋 銭天堂 新シリーズ
04/06(火) 23:00~ TOKYO MX 転スラ日記
04/06(火) 24:30~ TOKYO MX 聖女の魔力は万能です
× 04/06(火) 25:29~ 日本テレビ 擾乱 THE PRINCESS OF SNOW AND BLOOD
× 04/07(水) 18:45~ NHK Eテレ 宇宙なんちゃら こてつくん
04/07(水) 22:00~ TOKYO MX MARS RED
04/07(水) 23:30~ TOKYO MX 究極進化したフルダイブRPGが現実よりもクソゲ―だったら
04/07(水) 25:35~ TOKYO MX スーパーカブ
× 04/08(木) 23:30~ TOKYO MX Fairy蘭丸~あなたの心お助けします~
04/08(木) 24:00~ TOKYO MX ゾンビランドサガリベンジ
× 04/08(木) 24:55~ フジテレビ バクテン!!
04/08(木) 25:28~ TBS 異世界魔王と召喚少女の奴隷魔術Ω (第2期)
04/08(木) ~ Netflix 極主夫道
04/09(金) 18:25~ テレビ東京 妖怪ウォッチ♪(新シリーズ)
04/09(金) 19:25~ テレビ東京 新幹線変形ロボ シンカリオンZ
× 04/09(金) 25:25~ TBS すばらしきこのせかい The Animation
04/09(金) 25:50頃~ TBS 結城友奈は勇者である ちゅるっと! (ショートアニメ)
× 04/09(金) 25:55~ TBS BLUE REFLECTION RAY/澪
04/10(土) 22:00~ TOKYO MX スライム倒して300年、知らないうちにレベルMAXになってました
04/10(土) 24:00~ TOKYO MX 86―エイティシックス―
04/10(土) 24:30~ TOKYO MX シャドーハウス
× 04/10(土) 24:55~ 日本テレビ EDENS ZERO
× 04/10(土) 25:00~ TOKYO MX イジらないで、長瀞さん
× 04/10(土) 25:30~ テレビ朝日 バトルアスリーテス 大運動会ReSTART!
04/10(土) 26:00~ テレビ朝日 美少年探偵団
  04/11(日) 10:30~ テレビ東京 ミュークルドリーミー みっくす!
× 04/11(日) 25:35~ テレビ東京 東京リベンジャーズ
04/12(月) 22:50~ NHK Eテレ 不滅のあなたへ
× 04/12(月) 23:57~ TOKYO MX いたずらぐまのグル~ミ~
04/13(火) 22:29~ TOKYO MX SDガンダムワールド ヒーローズ
× 04/14(水) 24:55~ フジテレビ セスタス -The Roman Fighter-
× 04/14(水) 25:05~ TOKYO MX 幼なじみが絶対に負けないラブコメ
04/17(土) U-NEXT 新テニスの王子様 氷帝vs立海 Game of Future 後篇
04/17(土) 17:35~ NHK Eテレ 魔入りました!入間くん 第2シリーズ
04/28(水) ~ Netflix YASUKE -ヤスケ-

今期一押しはやっぱりスーパーカブ。とても丁寧な作りで大変好感が持てますね。私好みの話でもあります。ただ、退屈に思う人もいるでしょうね。幸薄そうな女の子が鼻につく人もいるかもしれません。個人的にはこの手の現実への適応、ちょっとした心の成長を描く話はもう少し男の子が主人公のものがあっても良いのではと常々。

他に数話見た段階で気になるのは:

他にもいくつか視聴を継続しているものはあります。

ところで今期から視聴の大半をネット配信に移行しました。というのもほとんどテレビが映らなくなってしまったからです。チューナーを変えてもダメなのでおそらくアンテナから部屋までのどこかに問題があるのではないかと思います。最近はほぼ全ての作品がネット配信されているのでほとんど困ることはありません。Abemaやニコニコの無料視聴もありますし、Prime Videoは元々見られますし、dアニメストアにも入ったのでそれでほぼ全て網羅できます。良い時代になったものです。

dアニメストアの月額440円は安すぎです。見逃してしまったりまた見たいと思っていた過去の作品もいくらでも見られます。以前カノープスのチューナーカードMTV2000が壊れたときに途中から見られなかった(続きが気になっていた)ファンタジックチルドレンと巌窟王をようやく見ることが出来ました。また見たいなと思っていた作品も色々見ています。真っ先に見たのはスケッチブック~full color’s~でした。最終回付近で面白さに気がついて録画を残していなかったパターンです。まぁ、私好みですよね。

現状でいくつか見られないものもあるのですが、一番大きいのはNetflix独占ものです。正直1~2本の作品を見るためだけに入るのはコスパが悪い気がします。今期ならゴジラはMXで3話くらいまでかろうじて見られたので続きが気になっています。前期のBEASTARSも続きがみたいのですが。短い期間だけ入って後でまとめて見た方が良いかもしれません。

2021-02-01

2021年冬の新番組

さて、今期も一通り見終わりました。

× 01/01(金) ネット配信 幼女社長
01/03(日) 25:00~ TOKYO MX じみへんっ!!~地味子を変えちゃう純異性交遊~
01/04(月) 21:00~ TOKYO MX ゲキドル ACTIDOL SCHOOL 新春スペシャル
01/04(月) 23:00~ TOKYO MX たとえばラストダンジョン前の村の少年が序盤の街で暮らすような物語
01/04(月) 22:30~ TOKYO MX 7SEEDS 第2期
01/04(月) 24:30~ TOKYO MX 裏世界ピクニック
01/04(月) 24:00~ TOKYO MX ウマ娘 プリティーダービー Season2
01/05(水) 07:30~ テレビ東京系 PUI PUI モルカー
01/05(火) 21:00~ AT-X ゲキドル ACTIDOL SCHOOL
01/05(火) 23:00~ TOKYO MX 転生したらスライムだった件 特別話数「閑話:ヒナタ・サカグチ」
01/06(水) 17:55~ テレビ東京系 七つの大罪 憤怒の審判
01/06(水) 23:30~ TOKYO MX Re:ゼロから始める異世界生活(第2期 後半クール)
× 01/06(水) 23:00~ TOKYO MX アイ★チュウ
× 01/06(水) 24:00~ TOKYO MX 装甲娘戦機
× 01/06(水) 24:30~ TOKYO MX オルタンシア・サーガ
01/06(水) 24:55~ フジテレビ BEASTARS 第2期
01/06(水) 25:57~ ABCテレビ エビシー修業日記
01/07(木) 22:00~ TOKYO MX SHOW BY ROCK!!STARS!!
01/07(木) 23:30~ TOKYO MX ゆるキャン△ SEASON2
01/07(木) 24:00~ TOKYO MX 天地創造デザイン部
01/07(木) 24:30~ TOKYO MX ひぐらしのなく頃に 業(新作) 14話以降
01/07(木) 24:55~ フジテレビ 2.43 清陰高校男子バレー部
01/07(木) 26:10~ フジテレビ 約束のネバーランド 第2期
01/07(木) 25:28~ TBS 五等分の花嫁∬
× 01/08(金) 22:00~ TOKYO MX 弱キャラ友崎くん
× 01/08(金) 22:30~ TOKYO MX 蜘蛛ですが、なにか?
01/08(金) 23:00~ BS朝日 アイドールズ!-IDOL Survival-
01/08(金) 24:00~ TOKYO MX バック・アロウ
01/08(金) 24:30~ TOKYO MX WIXOSS DIVA(A)LIVE
01/08(金) 25:00~ TOKYO MX おとなの防具屋さん 第2期
× 01/08(金) 25:55~ TBS プレイタの傷 PROJECT SCARD
01/08(金) 26:55~ TBS 俺だけ入れる隠しダンジョン
× 01/09(土) 23:30~ TOKYO MX はたらく細胞!!
× 01/09(土) 24:00~ TOKYO MX はたらく細胞 BLACK
× 01/09(土) 24:30~ TOKYO MX ホリミヤ
01/09(土) 25:00~ TOKYO MX Levius レビウス
01/09(土) 25:30~ TOKYO MX 怪病医ラムネ
01/09(土) 25:30~ テレビ朝日系 ワールドトリガー 2ndシーズン
01/09(土) 26:00~ テレビ朝日系 SK∞ エスケーエイト
× 01/10(日) 07:00~ テレビ東京系 アイカツプラネット!
01/10(日) 21:30~ TOKYO MX 魔道祖師(日本語吹替版)
01/10(日) 21:54~ テレビ愛知 八十亀ちゃんかんさつにっき 3さつめ
01/10(日) 22:00~ TOKYO MX 怪物事変
× 01/10(日) 22:30~ TOKYO MX スケートリーディング☆スターズ
× 01/10(日) 23:30~ TOKYO MX IDOLY PRIDE
01/10(日) 24:00~ TOKYO MX 無職転生-異世界行ったら本気だす-
01/10(日) 25:05~ TOKYO MX EX-ARMエクスアーム
01/10(日) 25:50~ テレビ東京 のんのんびより のんすとっぷ
01/10(日) 27:20~ テレビ東京 闇芝居 第8期
01/11(月) 25:00~ TOKYO MX アズールレーン びそくぜんしんっ!
01/11(月) 25:10~ TOKYO MX 真・中華一番! 第二期
01/11(月) 26:00~ テレビ東京 WAVE!!~サーフィンやっぺ!!~
01/12(火) 23:00~ TOKYO MX 転生したらスライムだった件 第2期 第1部
01/12(火) 24:30~ TOKYO MX 文豪ストレイドッグス わん!
× 01/12(火) 24:45~ TOKYO MX ワールドウィッチーズ発進しますっ!
01/12(火) 25:35~ 日本テレビ ワンダーエッグ・プライオリティ
01/13(水) 19:25~ NHK Eテレ ログ・ホライズン -円卓崩壊-
01/13(水) 25:05~ TOKYO MX 回復術士のやり直し
01/13(水) 28:00~ AT-X 回復術士のやり直し(完全《回復》ver.)
01/14(木) 22:30~ TOKYO MX Dr.STONE(ドクターストーン) 第2期
01/14(木) ~ ABCテレビ 開運 キンタマーニドッグ
01/20(水) 22:00~ TOKYO MX 魔術士オーフェンはぐれ旅 キムラック編

一番よく出来ていると思ったのは無職転生でしょうか。なろうのくせに、異世界転生俺ツエーもののくせに面白いぞと心中複雑な感じです。何がいいんでしょうね。丁寧に作られている感はありますね。話運びもスムーズだし、美術面も綺麗ですし。

次に気になるのがエスケーエイト。スケボーレースに興じる青少年たちの話。何でもありのレースと癖のあるキャラクターたちでこれまでのところなかなか楽しめる作品になっています。

前々から期待されていた作品と言えばゆるキャン△とのんのんびよりでしょうか。

ゆるキャン△は前期に比べるとだいぶ見劣りがするスタート。特に1話は疑問に思うところが多かったのですが、後で聞いたところによるとそういった点はアニメオリジナル要素だと聞いて納得。へやキャン△もイマイチでしたからね。現在4話まで進んで少しずつ良くなっている気もするので今後に期待します。

のんのんびよりは、まぁ、元々常に面白いわけではないので、時々面白い話があればOKです。

2021-01-31

Emacs Lispから音を鳴らす

ふとEmacs Lispマニュアルの Sound Output のところが目に入ったので試してみました。サウンドまわりは大昔に試してあまり良くなかった記憶がありますがその後どれだけ改善されたかな。

マニュアルを読んでみると (play-sound '(sound :file "ファイルへのパス"))(play-sound '(sound :data "サウンドデータ")) で鳴るみたいです。

試しにサイン波でも生成して鳴らしてみましょうか。

対応形式は.wav(RIFF)または.auと書いてあります。.auの方がヘッダーがシンプルなのでそれにしましょう。

44.1KHzモノラルの16-bit PCMのヘッダーは文字列にすると ".snd\x00\x00\x00\x18\xff\xff\xff\xff\x00\x00\x00\x03\x00\x00\xac\x44\x00\x00\x00\x01" の24バイトですね(長さは不明ffffffffとしてあります)。各要素はビッグエンディアンな32-bit整数です。

16-bit PCMの各要素は負号付き整数みたいですね。-32767~0~32768。

うーん、どうしましょう。文字列バッファを先に確保して、そこにバイト列を埋めていく方が速度的に良さそうでしょうか? (適当に文字列結合しまくる方が作るのは楽そうですが)

ひとまず16-bit, 32-bit整数を文字列化する関数を作り……

(defun my-snd-aset-i32 (bytearray index x)
  (aset bytearray index (logand 255 (ash x -24)))
  (aset bytearray (+ index 1) (logand 255 (ash x -16)))
  (aset bytearray (+ index 2) (logand 255 (ash x -8)))
  (aset bytearray (+ index 3) (logand 255 x)))

(defun my-snd-aset-i16 (bytearray index x)
  (aset bytearray index (logand 255 (ash x -8)))
  (aset bytearray (+ index 1) (logand 255 x)))

サイン波を生成する関数を作り……

(defconst my-snd-byte-size-i16 2)
(defconst my-snd-amplitude-i16 32767)

(defun my-snd-aset-sin-wave (dst-buffer dst-index num-samples wavelength)
  (let ((2pi/wavelength (/ (* 2 pi) wavelength))
        (i 0))
    (while (< i num-samples)
      (my-snd-aset-i16 dst-buffer dst-index
                       (truncate (* my-snd-amplitude-i16
                                    ;;周期の誤差をどこまで許容するかよく分からないよね
                                    (sin (* i 2pi/wavelength)))))
      (setq dst-index (+ dst-index my-snd-byte-size-i16))
      (setq i (1+ i))))
  dst-buffer)

試しに440Hzのサイン波を含むauデータを生成する関数を作る。

(defun my-snd-generate-au-sin-wave-440hz-1sec ()
  (let* ((samples-per-sec 44100)
         (seconds 1)
         (num-samples (* seconds samples-per-sec))
         (data-size (* my-snd-byte-size-i16 num-samples))
         (data (make-string (+ 24 data-size) 0)))
    ;; header
    (my-snd-aset-i32 data 0 #x2e736e64)
    (my-snd-aset-i32 data 4 24) ;;Data offset
    (my-snd-aset-i32 data 8 data-size)
    (my-snd-aset-i32 data 12 3) ;;16-bit linear PCM
    (my-snd-aset-i32 data 16 samples-per-sec)
    (my-snd-aset-i32 data 20 1) ;;Channels
    ;; data
    (my-snd-aset-sin-wave data 24 num-samples (/ samples-per-sec 440.0))

    data))

鳴らしてみましょぅ。

(play-sound (list 'sound :data (string-as-unibyte ;;string-as-unibyte必要?
                                (my-snd-generate-au-sin-wave-440hz-1sec))))

あれ、結果は (error "Invalid sound specification") だって。

おかしいなデータが壊れているのかな。試しにファイルに書き出してみる。

(with-temp-file "~/tmp/sin440.au"
  (set-buffer-file-coding-system 'no-conversion)
  (insert (string-as-unibyte (my-snd-generate-au-sin-wave-440hz-1sec))))

プレイヤーで鳴らしたら「ポー」っとちゃんと鳴ります。

それならこのファイルをplay-soundで鳴らしてみる。

(play-sound '(sound :file "~/tmp/sin440.au"))

あれ鳴らない、と思ったらプレイヤーで開いていたからでした。プレイヤーを閉じたらEmacsからちゃんと鳴りました。

play-sound関数はsubr.elにあって、Emacsのサウンドサポートを確認してからplay-sound-internal関数を呼び出しています。play-sound-internal関数はsound.cにあって、最初の方でparse_sound関数を呼び出しています。parse_sound関数を見ると……

#else /* WINDOWSNT */
  /*
    Data is not supported in Windows.  Therefore a
    File name MUST be supplied.
  */
  if (!STRINGP (attrs[SOUND_FILE]))
    {
      return 0;
    }
#endif /* WINDOWSNT */

Windowsでは :data をサポートしていないんだって!


そういえば昔Emacsから音を鳴らしたくて↓こういうのを作ったのでした。

これは何万個もある音声ファイルをテキストファイルと付き合わせて確認する作業で活躍したのでした。今でも動くのかは不明。

2021-01-02 , ,

org-mode文書の中に囲碁の碁盤を埋め込む

Emacsの中で囲碁の棋譜(SGF)を編集するツールを作りました。

2020-01-02-el-igo-screenshot.gif

この通り、org-mode文書の中にSGF形式の棋譜を埋め込んで編集できます。エクスポートも可能。この文書自体org-modeで書いているのでほらこの通り(隅の七目)。

作ったのは碁の勉強をするにあたって普段使っているorg-modeでノートが取れたら便利そうだなと思ったことがきっかけです。

ちょうど先日EmacsでのSVG使用例を見かけてもっと自由に使っていいんだと感銘を受けたところでした。

SVGといえば少し前にJavaScriptでSVGを使った碁盤を作ったところだったので、SVGでの碁盤の表現方法はすでに分かっていました。なので基本的な考え方はこれを踏襲してEmacs Lisp上で実装し直すことにしました。

最近書いていたのはそのための要素技術なのでした。

思いのほかよく出来て、機能的にも内部構造的にもJavaScript版より良くなった気がします。

私は以前からEmacsやorg-modeにはもっとビジュアルで埋め込まれた要素が沢山あって良いと思っていたので迷わずorg-mode文書の中に碁盤を埋め込んで表示してビジュアルに編集できる仕様を思いつきました。

最初はソースブロック(#+begin_src sgf ~ #+end_src)を盤面のオーバーレイに置き換えてみたのですがソースブロックは評価するとかしないとか結果が何だとか色々としっくり来ないところが多かったので(棋譜を評価した結果って何!?)最終的に専用の特殊ブロック(#+begin_igo ~ #+end_igo)を盤面にすることにしました。

みんなもっと自分の好きなものを埋め込みましょう。私が音楽が好きだったら譜面入力インタフェースを作成することでしょう。簡単な作図くらいorg-modeの中で完結しても罰は当たらないと思いますよ。XWidgets? 日和ってんじゃねーよ! とはいえ現状のSVG一本槍では複雑な用途には厳しいものがありますね。更新速度と当たり判定的に。

2020-12-19 ,

個別のorg-modeファイルにエクスポート時のフィルターを設定する

Defining filters for individual files(個別のファイルにフィルタを定義する)に書かれている例を試してみる。

#+BIND: org-export-filter-timestamp-functions (tmp-f-timestamp)
#+BIND: org-export-filter-strike-through-functions (tmp-f-strike-through)
#+BEGIN_SRC emacs-lisp :exports results :results none
  (defun tmp-f-timestamp (s backend info)
    (replace-regexp-in-string "&[lg]t;\\|[][]" "" s))
  (defun tmp-f-strike-through (s backend info) "")
#+END_SRC

time stamp [2020-12-19 Sat 14:06]

abc +def+ ghi

この例では二つのフィルタを設定している。一つはタイムスタンプのブラケットを消去する(tmp-f-timestamp関数)。もう一つは取り消し線のテキストをまるまる消去する(tmp-f-strike-through関数)。フィルタ関数は引数のsの文字列を加工して返せばいいらしい。

あれれ、HTMLでエクスポートしてみたけどフィルタが適用されていない。

ソースブロック(#+BEGIN_SRC#+END_SRC)部分はyes/no確認の後評価されているので tmp-f-timestamptmp-f-strike-through はちゃんと定義されている(エクスポートが終わっても関数が残っているのは不愉快だけど)。

変数 org-export-filter-timestamp-functionsorg-export-filter-strike-through-functionsnil 。となるとBIND( #+BIND: )の部分が機能していない? しかし調べたところ #+BIND: はエクスポートの間だけバッファローカル変数になるらしい。なら今nilでも当然だけど……ははぁ、これはセキュリティがらみだな。BIND部分を無条件で評価してしまうと他人から貰ったorg文書をエクスポートしたときにイタズラされる可能性がある。

探してみると org-export-allow-bind-keywords という変数があって nil になっていた。試しにバッファ内で M-: (setq-local org-export-allow-bind-keywords t) してみたらちゃんとフィルタが機能した。

うーん org-export-allow-bind-keywords を常に t にするのは少々不安だし、どうするべきなんだろう。

試しに (setq-local org-export-allow-bind-keywords t) をソースブロックの中に入れてみた。

#+BIND: org-export-filter-timestamp-functions (tmp-f-timestamp)
#+BIND: org-export-filter-strike-through-functions (tmp-f-strike-through)
#+BEGIN_SRC emacs-lisp :exports results :results none

  ;;; 追加!!!
  (setq-local org-export-allow-bind-keywords t)

  (defun tmp-f-timestamp (s backend info)
    (replace-regexp-in-string "&[lg]t;\\|[][]" "" s))
  (defun tmp-f-strike-through (s backend info) "")
#+END_SRC

time stamp [2020-12-19 Sat 14:06]

abc +def+ ghi

ファイルを開き直してエクスポート(ソースブロックの評価はyes/no確認あり)してみるとちゃんとフィルタが機能してブラケットや取り消し線部分が消去されていた。つまりBINDとソースブロックを評価するセキュリティリスクがソースブロックに一本化されたわけだ。どうなんだろうこれ。

最初からBINDなんか使わなければいいんじゃないだろうか。

#+BEGIN_SRC emacs-lisp :exports results :results none
  (setq-local org-export-filter-timestamp-functions '(tmp-f-timestamp))
  (setq-local org-export-filter-strike-through-functions '(tmp-f-strike-through))

  (defun tmp-f-timestamp (s backend info)
    (replace-regexp-in-string "&[lg]t;\\|[][]" "" s))
  (defun tmp-f-strike-through (s backend info) "")
#+END_SRC

time stamp [2020-12-19 Sat 14:06]

abc +def+ ghi

うん、これでもちゃんとフィルタされる。

逆にBINDだけにするなら次のようにすれば良い。

#+BIND: org-export-filter-timestamp-functions ((lambda (s backend info) (replace-regexp-in-string "&[lg]t;\\|[][]" "" s)))
#+BIND: org-export-filter-strike-through-functions ((lambda (s backend info) ""))

time stamp [2020-12-19 Sat 14:06]

abc +def+ ghi

この場合 org-export-allow-bind-keywordst ならフィルタされる。nilならされない。 org-confirm-babel-evaluate 設定のようにBINDを評価するときにyes/no確認が出せれば良いのかもしれないが、そういう設定は見当たらない。まぁ、そこまでするならソースブロックで良いだろう。

ちなみに tmp-* 関数が残ってしまうのが気に入らなければ次のようにすれば良いのだろうきっと。

#+BEGIN_SRC emacs-lisp :exports results :results none
(setq-local org-export-filter-timestamp-functions
            (list (lambda (s backend info)
                    (replace-regexp-in-string "&[lg]t;\\|[][]" "" s))))
(setq-local org-export-filter-strike-through-functions
            (list (lambda (s backend info)
                    "")))
#+END_SRC

time stamp [2020-12-19 Sat 14:06]

abc +def+ ghi

いちいち評価するのか確認するのが嫌ならば、あらかじめ何らかの条件で自動的にフィルタが設定されるようにどこかに登録しておくことになるのだろう。

2020-12-13

Emacs Lisp でマウスの動きを追跡する

(Emacs27.1, Windows 10で確認)

まず普段マウスに関してどんなイベントが来るのか調べる。

(while t
  (message "%s" (read-event)))
(down-mouse-1 (#<window 26 on *scratch*> 186 (400 . 236) 979458359 nil 186 (50 . 6) nil (400 . 104) (8 . 22)))
(mouse-1 (#<window 26 on *scratch*> 186 (400 . 236) 979458625 nil 186 (50 . 6) nil (400 . 104) (8 . 22)))
(down-mouse-1 (#<window 26 on *scratch*> 186 (213 . 271) 979474031 nil 186 (26 . 6) nil (213 . 139) (8 . 22)))
(drag-mouse-1 (#<window 26 on *scratch*> 186 (213 . 271) 979474031 nil 186 (26 . 6) nil (213 . 139) (8 . 22)) (#<window 26 on *scratch*> 186 (271 . 232) 979475453 nil 186 (33 . 6) nil (271 . 100) (8 . 22)))
(down-mouse-1 (#<window 26 on *scratch*> 186 (113 . 250) 979478593 nil 186 (14 . 6) nil (113 . 118) (8 . 22)))
(mouse-1 (#<window 26 on *scratch*> 186 (113 . 250) 979478687 nil 186 (14 . 6) nil (113 . 118) (8 . 22)))
(down-mouse-1 (#<window 26 on *scratch*> 186 (102 . 250) 979479796 nil 186 (12 . 6) nil (102 . 118) (8 . 22)))
(drag-mouse-1 (#<window 26 on *scratch*> 186 (102 . 250) 979479796 nil 186 (12 . 6) nil (102 . 118) (8 . 22)) (#<window 26 on *scratch*> 186 (161 . 244) 979480796 nil 186 (20 . 6) nil (161 . 112) (8 . 22)))
(down-mouse-3 (#<window 26 on *scratch*> 186 (252 . 245) 979484453 nil 186 (31 . 6) nil (252 . 113) (8 . 22)))
(mouse-3 (#<window 26 on *scratch*> 186 (252 . 245) 979484578 nil 186 (31 . 6) nil (252 . 113) (8 . 22)))

ボタンの押し離しについてはイベントが来るが動きについてはイベントが来ない。

マウスの動きを得るには track-mouse 変数をtにするか、 変数をtにして任意の式を評価する (track-mouse body ) マクロを使う。あるコマンドで track-mouse 変数を t にして別のコマンドでnilにするやり方もあり得るのだろうけれど、一つのコマンドの中で (track-mouse body ) マクロを使う方が無難。 (29.15 Mouse Tracking – Emacs Lisp Reference Manual)

実際にtrack-mouseを使ったときにどんなイベントが来るのか調べる。

(track-mouse
  (while t
    (let ((ev (read-event)))
      (message "basic type=%s modifiers=%s event=%s" (event-basic-type ev) (event-modifiers ev) ev))))

だいたい次のような感じになる。

  • クリック時に来るイベントの流れ:

    1. down-mouse-1
    2. mouse-movement (0回以上)
    3. mouse-1

    ほとんどの場合mouse-movementは来ないが、わずかに動いた場合来ることもある。何らかの閾値以下の動きならクリックと判定される。

  • ドラッグ時に来るイベントの流れ:

    1. down-mouse-1
    2. mouse-movement (0回以上)
    3. drag-mouse-1

    ほとんどの場合mouse-movementは来るが、指し示す位置がスクロールによってのみ変わった場合来ないこともある。mouse downの後画面がスクロールしてマウスが動かずmouse upした場合でもきちんとdragと判定される。

  • up-mouse-1というのは無い。click(mouse-1)かdrag(drag-mouse-1)のどちらかのみが来る。
  • シフトキーを押したままクリックした時に来るイベントの流れ:

    1. S-down-mouse-1
    2. mouse-movement (0回以上)
    3. S-mouse-1

    シンボルにS-といった部分が付く。(21.7.4 Click Events – Emacs Lisp Reference Manual)

  • クリックの途中でシフトキーを押した場合(mouse down, shift down, mouse up):

    1. down-mouse-1
    2. mouse-movement (0回以上)
    3. S-mouse-1

    つまり、down-mouse-1に対応するmouse upがmouse-1やdrag-mouse-1とは限らない。S-mouse-1とかC-S-drag-mouse-1とかになることもある。逆にS-down-mouse-1(シフトを押しながらマウスボタン押し下げ)に対してmouse upが(S-が付かない)mouse-1になることもある(途中でシフトキーが離された場合)。

    判別にはevent-basic-typeとevent-modifiersを使うと良い。

    • (event-basic-type event) で ‘mouse-1 や ‘mouse-3 等の(S-等が付かない)ボタンの種類が返ってくる。
    • (event-modifiers event) で ‘(click) や ‘(drag) が返ってくる。

    (21.7.12 Classifying Events – Emacs Lisp Reference Manual)

  • mouse-movement中にボタンの状態を取得する方法は見当たらない。イベントの中にも無いし、位置における (mouse-position) のような現在の状態を取得する関数も見当たらない。
  • フレームの外に出た場合の挙動はOSによって違うかもしれないが、とりあえずWindows 10での動作:
    • フレーム外に出ても特に何かイベントは来ない。出たことを知る確実な方法は見当たらない。
    • ドラッグ中にフレームの外に出た場合、フレームがアクティブな間はマウスイベントはモーションも含めて来続ける。フレームの外でボタンを離したときもdrag-mouse-1が発生する。
    • ドラッグ中にフレームの外に出て、ドラッグしたままで(Alt-Tab等で)フレームが非アクティブになったとき、マウスイベントは来なくなる。この後マウスボタンが離されてもイベントは発生せず、離されたことを検出する方法は見当たらない。マウスカーソルがフレーム内に戻ってきたときにモーションイベントは発生するが、ボタンがすでに離されていることに気がつけない。
    • フレームが非アクティブの時にフレームの上をマウスが通過した場合、モーションイベントが来る。

以上の点に注意しながらプログラムを組んでいくことになる。

ひとまずtrack-mouseマクロとread-eventを使うとして、マウスイベント以外はどう処理したら良いのか。read-eventを使ってイベントを読み込むと当然マウス以外のイベントも来る。マウス入力中の不正入力として破棄しても問題ないのだろうけれど、一応デフォルトの処理をさせてみたい。イベントをデフォルト処理にdispatchする関数は見当たらない。 unread-command-events 変数を使うと、まだ処理していないイベントを先読みしたり、戻ししたり出来る。使わないイベントをread-eventで読んでしまった場合、 (push (cons t event) unread-command-events) で戻せる。実質的にこの操作がdispatchになる。余談だけど全く関係ないイベントを生成してポストすることも出来る。 (push (cons t ?a) unread-command-events) とすれば現在のポイントでaを押したときのキーバインドが呼び出される。その気になればこの変数だけからpeek-eventとかpost-eventとか作れそう。(21.8.6 Miscellaneous Event Input Features – Emacs Lisp Reference Manual)

ちなみにイベントを待つにはread-event等を使っても良いが、 (sit-for seconds) を使っても良い。時間またはイベントの到着を待つ。再描画(redisplay)もしてくれる。(21.10 Waiting for Elapsed Time or Input – Emacs Lisp Reference Manual)

以上の調査を元に、試しに画像(オブジェクト)に対してマウスの押し下げから移動、解放までを追跡してコールバックを呼び出す関数を作ってみた。

(defun track-dragging-on (down-event on-move &optional on-up on-leave)
  "マウスdownイベント(down-mouse-1やdown-mouse-3等)が発生したときにこの関数を呼び出すと、押したところにある画像上で発生するモーションイベントをON-MOVEにコールバックし続ける。ボタンが離されたり画像の外に出たり何かマウスイベント以外が発生したとき関数は終了する。"
  (if (not (memq 'down (event-modifiers down-event)))
      (error "down-event is not a down event. %s" (event-modifiers down-event)))
  (let* ((down-basic-type (event-basic-type down-event))
         (down-position (event-start down-event))
         (target-window (posn-window down-position))
         (target-point (posn-point down-position))
         (target-object (posn-object down-position)))

    (track-mouse
      (let (result)
        (while (null result)
          (let ((event (read-event)))
            (cond
             ;; mouse move
             ((mouse-movement-p event)
              ;; クリックしたときと同じオブジェクト上であることを確認する。
              ;; 単純にobjectをeqで判定できない。
              ;; displayプロパティの値(リスト)は毎回コピーされているようなので。
              ;; SVGを含んだ結構大きなリストになるのでequalを使うのもためらわれるし、一致したところで同じオブジェクトとは限らない。
              ;; 指しているウィンドウとポイントが同じでobjectの最初の要素(おそらく'image)が一致していることで確認している。
              (if (and (eq (posn-window (event-start event))
                           target-window)
                       (= (posn-point (event-start event))
                          target-point)
                       (eq (car (posn-object (event-start event))) ;;ex: 'image
                           (car target-object))) ;;ex: 'image
                  (if on-move (funcall on-move event))
                ;; out of target
                (if on-up (funcall on-leave event))
                (setq result 'leave)))
             ;; mouse up
             ((and (eq (event-basic-type event) down-basic-type)
                   (or (memq 'click (event-modifiers event))
                       (memq 'drag (event-modifiers event))))
              (if on-up (funcall on-up event))
              (setq result 'up))
             ;; otherwise
             (t
              (if on-up (funcall on-up event))
              (setq result 'unknown-event)
              (push (cons t event) unread-command-events)))))
        result))))

この関数を使って試しにSVG画像を作ってその中でドラッグしたときのイベントを表示させてみる。(画像の表示方法については先日の記事を参照のこと)

(let (;;適当な文字列("A")を追加しその範囲をstart,endとする。
      (start (point))
      (end (progn (insert "A") (point))))

  ;; start~endのdisplayプロパティにSVG画像を設定する。
  (put-text-property start end 'display
                     (let ((svg (svg-create 400 300)))
                       (svg-rectangle svg 0 0 400 300 :fill "#333")
                       (svg-image svg)))

  ;; start~endのkeymapプロパティに down-mouse-1 に反応するキーマップを設定する。
  (put-text-property
   start end 'keymap
   (let ((km (make-sparse-keymap)))
     (define-key km [down-mouse-1]
       (lambda (down-event)
         (interactive "e")
         (message "mouse down %s" down-event)

         (track-dragging-on
          down-event
          (lambda (motion-event)
            (message "mouse move %s" motion-event))
          (lambda (up-event)
            (message "mouse up %s" up-event))
          (lambda (leave-event)
            (message "mouse leave %s" leave-event)))

         (message "finish")))
     km))
  )
2020-12-12 ,

org-modeで文字をエスケープする方法

org-modeで文字が意図しない解釈をされてしまったことはないでしょうか。_が下付きになっちゃった! テーブルの中に|を書いたらフィールドが分かれてしまった! 等々。

起きるたびに調べて場当たり的に対処してきたのですが、今日はorg-modeにおける文字のエスケープ、意図したとおりに解釈させる方法についてまとめてみました。(Org9.3.7時点)

Org entity

最も筋の良い方法はentityを使うことです。

entityというのはLaTeX風の記法で記号を文書中に挿入できる仕組みです。HTMLで言うところの実体参照のようなものです。

参考: Special Symbols (The Org Manual)

例えば_の場合、 \under{} と書くことで _ に置換されます。 {} の部分は無くても良いのですが、あった方が区切りが明確になって助かる場合があります。例えば a_b が下付きで表示されて困る場合は a\under{}b と書きます。org-modeではこのような書き方が出来る文字が多数定義されています。

M-x org-entities-help を実行するとその書き方の一覧が見られます。実際にやってみると多数の記号が表示されます。

これだとよく引っかかる文字が分かりづらいので、org-modeで実際に特殊な意味で使われている記号をピックアップし、それに対応するentityを表にしてみました。

記号 entity 使われているところ(マニュアルの該当箇所)
[   timestamp, link, image, foot note, priority, subtask fraction, checkbox
< lt timestamp, link target, column group, column width
> gt column group
| vert table
#   comment, block, meta, table row
\   latex, line break, entity
.   list
$ dollar latex, table row
: colon drawers, property, fixed-width area, tag, list
  list, horizontal line
^ asciicirc superscript, table row
@   export snippets
{   macro
* star bold, headline, list, table row
/ slash italic, table row
_ under underline, subscript, table row
+ plus strike through, list, table.el
= equal verbatim, table formula
~ tilde code
!   table row
%   agenda
,   block

こうしてみると対応する書き方がない記号が目立ちます。特に \ (バックスラッシュ。円記号に見えていますか?)に対応する書き方がないのが致命的ですね。将来的に増えてくれれば良いのですが。

ユーザー定義のentityを加えることも出来るようですが文書の交換に支障が出るケースもあります。例えばGithubにREADME.orgとして上げる場合は org-entities-user に何かを加えたところで解決にはならないでしょう。

HTMLの数値文字参照のように任意のコードポイントを指定できるような機能があれば良いのですが。

何かで包む

~(code) や =(verbatim) で囲むと文字の解釈を抑制できる場合があります。

~\under{}~ と書くことで最初の\はそのまま表示されます。

codeやverbatimの意味的に使いたくないケースはあるでしょうし、中に入る文字次第では抑制できません。

他にもソースブロックやexampleブロックで文字の解釈を抑制できる場合があります。

#+begin_example
exampleの中に \under と書くと最初の\はそのまま表示されます。
#+end_example

しかし代わりに別の文字(行頭の# , *)が特殊な解釈をされることになります。

ちなみにこれらのブロックの中では、行頭に,を入れることで行頭の#や*をエスケープできます。

#+begin_src org
,#+begin_example
exampleの中に \under と書くと最初の\はそのまま表示されます。
,#+end_example
#+end_src

fixed-widthエリアは行指向なので比較的問題が起きにくい気がします。

: \underと書けるし
: :コロンも先頭に書ける

設定で回避する

一部の文字は設定で解釈を変更できます。

オプション指定 意味
#+OPTIONS: ^:nil a^b a_b を変換しない
#+OPTIONS: ^:{} a^{b} a_{b} の書き方のみ許容
#+OPTIONS: *:nil *word* _word_ /word/ +word+ を変換しない
#+OPTIONS: -:nil \- -- --- ... を変換しない
#+OPTIONS: tex:verbatim \begin $ 等のTeX表記を変換しない

参考: Export Settings (The Org Manual)

Export snippetsを使う

Export snippets は #+begin_export のインライン版です。 @@NAME:VALUE@@ という書き方で、NAMEで指定したバックエンドでエクスポートする内容をVALUEで指定できます。

参考:

文字をエスケープする良い方法はないかと探していたところ、Stack Exchangeにこの仕組みを使ったやり方が載っていました。

参考:

例えば次のように書くことで A[[B]]C になります(LaTeX, HTML両方でそうなるはずですがLaTeXは未確認)。

A@@latex:\char91\char91@@@@html:&#91;&#91;@@B@@latex:\char93\char93@@@@html:&#93;&#93;@@C

これだとさすがにつらいので、マクロを併用して次のようにします。

#+MACRO: BO @@latex:\char91@@@@html:&#91;@@
#+MACRO: BC @@latex:\char93@@@@html:&#93;@@

A{{{BO}}}{{{BO}}}B{{{BC}}}{{{BC}}}C

もちろんリンクの中でも使えます。

[​[https://google.com/search?q=%5d][Search result of {{{BC}}}]]

zero width spaceを挟む

見えない空白を入れることで無理矢理解釈を変更できます。

参考: Escape Character (The Org Manual)

見えない空白を挿入するには、 C-x 8 RET zero width space と入力します。

例えば[と[の間に見えない空白を入れることでリンクと解釈されることを防げます。

検索に支障を来すこともあり得ますし正直あまり良い方法とは思えませんが、強力な方法ではあると思います。

最後に

軽量マークアップ言語では限られた記号を何に使用するかが設計上の最大のポイントになります。そして何かの意味に使用した記号を元の意味で使うための逃げ道をどう用意するかも工夫のしどころとなります。

org-modeの文法はこの辺りがうまく設計されていないなと前々から感じていました。今回調べてみてもまだ釈然としない部分が残っています。文脈によってエスケープする必要のある文字やエスケープする方法は変わってくるので、全てを網羅するにはもっとちゃんとした調べ方をしないとダメそうです。

2020-12-08

Emacs Lispでポップアップメニューを出す方法(x-popup-menuの使用例)

EmacsでGUIプログラミングをしていると何かをクリックしたときにポップアップメニューを出したくなることがあります。メニューボタンを押したとき、対象を右クリックしてコンテキストメニューを出したいとき、といった具合です。

そんなときに使えるのが x-popup-menu という関数です。

x-popup-menu関数の概要

29.17 Pop-Up Menus – Emacs Lisp Manual

Function: (x-popup-menu position menu )

position にはメニューの表示位置を指定します。具体的な位置を指定するリスト ((XOFFSET YOFFSET) WINDOW-OR-FRAME) の他、マウスイベントやtを指定出来ます。tの場合現在のマウス位置に表示します。nilの時は実際には表示せず何かを事前計算するらしいのですがよく分かりません(やってみると単にnilが返ってきます)。クリックに応じてポップメニューを出したい場合は、last-input-event(interactive “e”)で取得したイベント、または t を指定すると良さそうです。プルダウンメニューのように位置が決まっているものは具体的な位置を計算した上指定する必要があるのでしょう。

menu にはキーマップ(またはキーマップのリスト)か、独自の形式のリスト(ペインのリスト)を指定できます。

x-popup-menuにペインのリストを指定する

menu にペインのリストを指定するのが一番簡単ですが出来ることは限られます。

例えば次のコードを評価すると……

(x-popup-menu t '("Menu Title"
                  ("Pane Title1"
                   ("Item1-1" . 11)
                   ("Item1-2" . 12))
                  ("Pane Title2"
                   ("Item2-1" . 21)
                   ("Item2-2" . 22))))

次のようなポップアップメニューが表示されます。

x-popup-menuに複数のペインを含むリストを指定した場合の表示
図1: x-popup-menuに複数のペインを含むリストを指定した場合の表示

項目を選択すると項目の値がそのまま返ってきます。上の例では「Item1-1」を選択すると11が返ってきます。選択しなかった場合はC-gを押したときのようにQuitシグナルが発生します。

複数のペインを持ちたくない場合はペインを一つだけ指定することになりますが、その場合ペインのタイトルは表示されません。次の例では「Pane Title1」は表示されません。

(x-popup-menu t '("Menu Title"
                  ("Pane Title1"
                   ("Item1-1" . 11)
                   ("Item1-2" . 12))))
x-popup-menuに一つのペインを含むリストを指定した場合の表示
図2: x-popup-menuに一つのペインを含むリストを指定した場合の表示

x-popup-menuにキーマップを指定する

x-popup-menuの機能を完全に利用するにはキーマップを指定する必要があります。

例えば次のコードを評価すると……

(defun test-item-1 () (interactive) (message "Menu Item 1"))
(defun test-item-2 () (interactive) (message "Menu Item 2"))
(setq test-item-toggle-selected t)
(defun test-item-toggle () (interactive) (setq test-item-toggle-selected (not test-item-toggle-selected)))
(setq test-radio-choice 1)
(defun test-item-radio-1 () (interactive) (setq test-radio-choice 1))
(defun test-item-radio-2 () (interactive) (setq test-radio-choice 2))
(defun test-submenu-1-2 () (interactive) (message "hello"))

(let* ((menu '(keymap "Menu Title"
                      ;;イベント型 拡張メニュー 項目名 コマンド プロパティ...
                      (test-item-1 menu-item "Item1" test-item-1)
                      (test-item-2 menu-item "Item2" test-item-2 :keys "HogeHogeKey")
                      (3 menu-item "Item3" test-item-3 :enable nil)
                      (4 menu-item "Item4" test-item-4 :visible nil)
                      (5 menu-item (concat "Item" "5") test-item-3 :enable nil)
                      (test-item-toggle menu-item "ItemToggle" test-item-5 :button (:toggle . test-item-toggle-selected))
                      (separator-1 menu-item "--")
                      (test-item-radio-1 menu-item "ItemRadio1" test-item-radio-1 :button (:radio . (= test-radio-choice 1)))
                      (test-item-radio-2 menu-item "ItemRadio2" test-item-radio-2 :button (:radio . (= test-radio-choice 2)))
                      (separator-2 menu-item "--")
                      (text-1 menu-item "選択できない項目")
                      (separator-3 menu-item "--")
                      (10 menu-item "About Emacs" about-emacs) ;; :keysを書かなくてもglobal-mapから割り出してくれる
                      (submenu-1 menu-item "Submenu" (keymap "Submenu Title"
                                                             (submenu-1-1 menu-item "SubItem1" test-subitem-1)
                                                             (test-submenu-1-2 menu-item "SubItem2" test-subitem-2)))))
       (result (x-popup-menu t menu)))
  ;; 結果のcarが関数なら呼び出す
  (if (and (symbolp (car (last result))) (fboundp (car (last result))))
      (funcall (car (last result)))
    ;; 関数でなければそのまま表示してみる
    (message "result=%s" result)))

次のようなポップアップメニューが表示されます。

x-popup-menuにkeymapを指定した場合の表示
図3: x-popup-menuにkeymapを指定した場合の表示

このように様々な事が出来るようになっています。

詳しくはマニュアルを参照のこと。

少しだけ補足。

:keys は通常書く必要はありません。上の例では HogeHogeKey なんて存在しないキーを表示させてましたが、あくまで何でも書けるか試しただけです。特筆すべきは About Emacs のところ。何も指定していなくても C-h C-a と表示されています。コマンド名からキーシーケンスを自動的に割り出して表示してくれます。グローバルマップやローカルマップだけでなく、(ポイントがそこにあれば)テキストプロパティやオーバーレイのkeymapプロパティまで考慮してくれます。

x-popup-menuは選択結果を返すだけでコマンドの呼び出し等は行いません。呼び出すことを考えると、項目を表すリストの一番最初の要素は関数のシンボルにしておくのが最も楽そうです。

easymenu.elというのが標準で入っていてもう少し簡単にメニュー用のキーマップが定義できるのですが、easy-menu-defineでキーマップを作ると項目の一番最初の要素は項目名の文字列をシンボルにしたものになるのでx-popup-menuで選択するとその人間向けな感じのシンボルが返ってきてしまいます。そこから実際にコマンドを呼び出す方法が今ひとつ分かりません。おそらく返ってきたシンボルを使ってキーマップから関数を割り出して呼び出せということなんだと思いますが、それなら最初から関数シンボルが返ってきた方が楽そうですよね……。そうでもないのかな? キーマップの意味論的に最初の要素はキー、というかイベント(シンボルはその名前のファンクションキーを押したというイベントという意味)なのだから、ルックアップを挟むべきってことなのでしょうか。それならば、こんな感じ?

(easy-menu-define test-menu nil
  "This is a Test Menu."
  '("Test Menu Title"
     ["Forward word desu!" forward-word] ;;x-popup-menuの結果は '(Forward\ word\ desu!)
     ["Backward word desu!" backward-word] ;;結果は '(Backward\ word\ desu!)
     ["Fun" (lambda () (interactive) (message-box "Fun!"))] ;;結果は '(Fun)
     ("Sub"
      ["FunFun" (lambda () (interactive) (message-box "FunFun!"))] ;;結果は'(Sub FunFun)
     )))

(let ((events (x-popup-menu t test-menu))
      (km test-menu))
  (while (and events km)
    (setq km (cadddr (seq-find (lambda (item) (and (listp item) (equal (car item) (car events)))) (cdr km))))
    (setq events (cdr events)))
  (call-interactively km))

うーん、動くには動くけど……。キーマップをイベントのリストでルックアップする関数が見つからなかったので自分で探しています。lookup-keyはイベントのリストを受け付けてくれなかったので。

どうなんでしょうね。結局関数シンボルを設定してしまった方が手っ取り早いような。

なんて書いてたら popup-menu なんていう関数がmenu-bar.elにあるんですね。内部でx-popup-menuを使っているようですが、ちゃんとコマンド呼び出しまでやってくれます。Emacs Lispのマニュアルに書いてない関数だけどこれでいいっぽい?

(popup-menu test-menu)
2020-11-28

Emacs Lispで画像を表示する方法(Emacs 27.1で確認)

参考

画像の表示方法については、Emacs LispマニュアルのImagesの所で説明されている。

手順

Emacs Lispで画像を表示するには次の二つのステップを踏む。

  1. Image Descriptorを作成する
  2. Image Descriptorをオーバーレイまたはテキストプロパティに適用する

まず第一にImage Descriptorを作成する。Image Descriptorは画像をどのように表示するかを指定するリストで39.17.2 Image Descriptorsに書かれている形式で記述する。自分で組み立てても良いが39.17.8 Defining Imagesに書かれている create-image 等の関数を使うこともできる。SVG画像を作る場合は39.17.6 SVG Imagesに書かれている関数を使うこともできる。

次に作成したImage Descriptorをオーバーレイまたはテキストプロパティdisplayプロパティに設定する。それによってはじめて実際に画像が表示される。自分でオーバーレイやテキストプロパティを設定しても良いが39.17.9 Showing Imagesに書かれている insert-image 等の関数を使うこともできる。この関数はバッファ内に適当な(” “等の)文字列を挿入してそのテキストプロパティのdisplayプロパティにImage Descriptorを設定する。

実例

それではやってみよう。

まず適当な画像ファイルを用意してそれを読み込むようなImage Descriptorを作成する。次の式を評価するとImage Descriptorが返ってくる。

(create-image "~/tmp/karasawa.jpg")
(image :type jpeg :file "~/tmp/karasawa.jpg" :scale 1)

この時点ではまだ画像は読み込まれない。

次のようにすると実際に画像が表示される。

(insert-image (create-image "~/tmp/karasawa.jpg"))
画像ファイルを表示
図1: 画像ファイルを表示

関数insert-imageは適当な文字列(” “)をバッファに挿入し、その文字列がある範囲のテキストプロパティ(‘display)にImage Descriptorを設定する。

なので、次のようにしても同じ結果が得られる。

(progn
  (insert " ")
  (put-text-property (1- (point)) (point) 'display (create-image "~/tmp/karasawa.jpg"))))

適当な画像を用意するのが面倒ならばSVG画像を作る方法もある。次のコードを評価するとSVGデータを含んだImage Descriptorが返ってくる。

(require 'svg)
(let ((svg (svg-create 400 300)))
  (svg-rectangle svg 0 0 400 300 :fill "#69f")
  (svg-circle svg 200 150 100 :fill "#eee")
  (svg-image svg))
(image :type svg :data "<svg width=\"400\" height=\"300\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"> <rect width=\"400\" height=\"300\" x=\"0\" y=\"0\" fill=\"#69f\"></rect> <circle cx=\"200\" cy=\"150\" r=\"100\" fill=\"#eee\"></circle></svg>" :scale 1)

実際に表示させてみる。

(require 'svg)
(insert-image
 (let ((svg (svg-create 400 300)))
   (svg-rectangle svg 0 0 400 300 :fill "#69f")
   (svg-circle svg 200 150 100 :fill "#eee")
   (svg-image svg)))
SVG画像を表示した結果
図2: SVG画像を表示した結果

画像の変換

表示する際に簡単な画像処理も可能。

(insert-image (create-image "~/tmp/karasawa.jpg" 'jpeg nil :width 300 :rotation 90 :relief -10))
縮小、回転、レリーフ
図3: 縮小、回転、レリーフ
(insert-image (create-image "~/tmp/karasawa.jpg" 'jpeg nil :width 400 :conversion 'disabled))
disabled効果
図4: disabled効果

これらの変形・効果はSVG画像にも適用できる。おそらくレンダリング後に適用している。

この他にも39.17.2 Image Descriptorsにはいくつかのプロパティが説明されている。また、最終的な表示のされ方はsliceなど他のdisplayプロパティの影響も受ける。

GIFアニメーションの表示も可能。(see:39.17.10 Multi-Frame Images)

画像のクリック

画像がクリックされたときに何か処理を行うこともできる。

(require 'svg)

(let (;;適当な文字列("A")を追加しその範囲をstart,endとする。
      (start (point))
      (end (progn (insert "A") (point))))

  ;; displayプロパティにSVG画像を設定する。
  (put-text-property
   start end 'display
   (let ((svg (svg-create 200 40)))
     (svg-rectangle svg 0 0 200 40 :rx 10 :ry 10 :stroke "#888" :fill "#fff")
     (svg-text svg "Button" :x 100 :y 30 :font-size 35 :text-anchor "middle")
     (svg-image svg)))

  ;; keymapプロパティにマウスクリックの際に呼ばれる関数を設定する。
  (put-text-property
   start end 'keymap
   (let ((km (make-sparse-keymap)))
     (define-key km [mouse-1]
       (lambda (event)
         (interactive "e")
         ;;(message "(x . y)=%s" (posn-object-x-y (event-start event)))
         (message "clicked event=%s" event)))
     km))

  ;; pointerプロパティをhandにすることでマウスカーソルの形を変える。
  (put-text-property
   start end 'pointer 'hand))
画像を表示してクリックした結果
図5: 画像を表示してクリックした結果

マウスの左クリックは[mouse-1]をキーマップに設定することで検出できる。テキストプロパティのkeymapプロパティを設定することで、画像がある場所にだけ効果があるキーマップを設定できる。

クリックした位置などイベントの詳細情報はInteractive Codeの”e”か、 last-input-event で受け取れる。

イベントの詳細情報はリストの形で記録されている。(21.7.4 Click Events)

そこから画像内でのクリックされた位置を取り出すには次のようにする。(21.7.13 Accessing Mouse Events)

(let ((xy (posn-object-x-y (event-start event))))
  (format "x=%s y=%s" (car xy) (cdr xy)))

画像の一部をクリック

画像内の一部がクリックされたことを判定したい場合イベントの座標から割り出しても良いがImage Descriptor:mapプロパティを使うこともできる。

(require 'svg)

(let (;;適当な文字列("A")を追加しその範囲をstart,endとする。
      (start (point))
      (end (progn (insert "A") (point))))

  ;; displayプロパティにSVG画像を設定する。
  (put-text-property
   start end 'display
   (let ((svg (svg-create 400 300)))
     (svg-circle svg 100 150 30 :fill "red")
     (svg-circle svg 200 150 30 :fill "blue")
     (svg-circle svg 300 150 30 :fill "green")
     ;; SVG画像を作る。同時にImage Descriptorに:mapプロパティを付加する。
     (svg-image svg :map '(((circle . ((100 . 150) . 30)) red-area (pointer hand help-echo "red"))
                           ((circle . ((200 . 150) . 30)) blue-area (pointer hand help-echo "blue"))
                           ((circle . ((300 . 150) . 30)) green-area (pointer hand help-echo "green"))))))

  ;; keymapプロパティにマウスクリックの際に呼ばれる関数を設定する。
  (put-text-property
   start end 'keymap
   (let ((km (make-sparse-keymap)))
     (define-key km [red-area mouse-1] (lambda () (interactive) (message "red!")))
     (define-key km [blue-area mouse-1] (lambda () (interactive) (message "blue!")))
     (define-key km [green-area mouse-1] (lambda () (interactive) (message "green!")))
     km)))
mapプロパティを伴う画像をクリックした結果
図6: mapプロパティを伴う画像をクリックした結果

矩形、円、任意の多角形で当たり判定の領域を設定できる。

画像の自動スケーリング

関数 create-image には高解像度モニターに備えて画像を自動的に拡大する機能がある。

拡大率は変数 image-scaling-factor で設定できる。デフォルトは ‘auto になっており、ウィンドウサイズとフォントサイズの比率によって計算する。

具体的な計算は関数 image-compute-scaling-factor で行われてする。まず、ウィンドウのピクセル幅を横方向の文字数(桁数)で割った値を計算する(つまり、平均文字幅ピクセル数)。その値が10以上の時、10からどのくらい大きくなったかの比率が画像の拡大率となる。そうして計算された値がcreate-imageが返すImage Descriptorの:scaleプロパティに付加される。

create-imageのPROPS引数に :scale プロパティが指定されている場合はこの処理は行われない。

注意すべき点は、この拡大率はマウスイベントの座標には一切影響を及ぼさないということ。イベントから得られる座標はこの拡大率の影響を受けないフレーム上の純粋な座標となる。また、:mapプロパティもこの拡大率の影響を受けて調整されることは ない 。従ってこの自動スケーリングが機能してしまうと、座標や領域の位置が画像と食い違ってしまう。これが問題になる場合、 (image-compute-scaling-factor image-scaling-factor) の計算値を使ってマウス座標や:mapプロパティを変換する必要がある。

2020-10-27

2020年秋の新番組

ウィルス騒ぎの影響から少し戻ってきた感じがありますね。

09/25(金) 17:20~ NHK Eテレ かいじゅうステップ ワンダバダ 第2シリーズ
09/25(金) 19:00~ TOKYO MX 魔神英雄伝ワタル 七魂の龍神丸 1~4話イッキ見SP
10/–(-) ~ YouTube おねがいっパトロンさま!
10/01(木) 23:30~ TOKYO MX ひぐらしのなく頃に 新アニメ
10/01(木) 25:28~ TBS アサルトリリィ Bouquet
10/02(金) 21:54~ TOKYO MX 兄に付ける薬はない! 第4期
10/02(金) 22:00~ TOKYO MX 100万の命の上に俺は立っている
10/02(金) 22:30~ TOKYO MX 魔女の旅々
× 10/02(金) 24:00~ TOKYO MX ヒプノシスマイク -Division Rap Battle- Rhyme Anima
10/02(金) 24:30~ TOKYO MX ダンジョンに出会いを求めるのは間違っているだろうかIII
10/02(金) 25:00~ TOKYO MX レヱル・ロマネスク
10/02(金) 25:05~ TOKYO MX トニカクカワイイ
10/02(金) 25:23~ テレビ東京 キングスレイド-意志を継ぐものたち-
10/02(金) 25:25~ TBS系 呪術廻戦
10/02(金) 26:25~ TBS ハイキュー!! TO THE TOP(第4期)
10/02(金) ~ Amazon PV BURN THE WITCH
× 10/03(土) 05:30~ TBS 犬と猫どっちも飼ってると毎日たのしい
10/03(土) 09:30~ テレビ東京系 ドラゴンクエスト ダイの大冒険
10/03(土) 17:30~ 日本テレビ系 半妖の夜叉姫
10/03(土) 22:30~ TOKYO MX ラブライブ!虹ヶ咲学園スクールアイドル同好会
× 10/03(土) 23:30~ TOKYO MX 戦翼のシグルドリーヴァ
10/03(土) 24:30~ TOKYO MX 魔法科高校の劣等生 -来訪者編-(第2期)
10/03(土) 25:30~ TOKYO MX ギャルと恐竜
10/03(土) 26:00~ テレビ朝日系 いわかける!- Sport Climbing Girls –
10/04(日) 21:55~ BS12 かえるのピクルス
10/04(日) 22:00~ TOKYO MX 無能なナナ
10/04(日) 23:00~ TOKYO MX アイドリッシュセブン Second BEAT!
10/04(日) 23:30~ TOKYO MX 神達に拾われた男
10/04(日) 24:30~ TOKYO MX まえせつ!
× 10/04(日) 25:00~ TOKYO MX 大人にゃ恋の仕方がわからねぇ!
× 10/04(日) 25:05~ TOKYO MX エタニティ ~深夜の濡恋ちゃんねる~
× 10/04(日) 25:20~ TOKYO MX 秘密結社 鷹の爪 ~ゴールデン・スペル~
10/05(月) 23:00~ TOKYO MX ゴールデンカムイ 第三期
10/05(月) 25:05~ TOKYO MX One Room サードシーズン
10/05(月) 26:00~ テレビ東京 魔王城でおやすみ
10/06(火) 24:30~ TOKYO MX 池袋ウエストゲートパーク
10/07(水) 22:30~ TOKYO MX ツキウタ。 THE ANIMATION2
10/07(水) 23:30~ TOKYO MX くま クマ 熊 ベアー
10/07(水) 23:54~ BS11 せいぜいがんばれ!魔法少女くるみ 第3期
10/07(水) 24:00~ TOKYO MX NOBLESSE -ノブレス-
10/07(水) 25:05~ TOKYO MX 第501統合戦闘航空団 ストライクウィッチーズ ROAD to BERLIN
× 10/07(水) 25:35~ TOKYO MX キミと僕の最後の戦場、あるいは世界が始まる聖戦
10/08(木) 21:54~ TOKYO MX ぐらぶるっ!
10/08(木) 24:30~ TOKYO MX アクダマドライブ
10/08(木) 25:58~ TBS 安達としまむら
10/09(金) 07:30~ テレビ東京系 カピバラさん
× 10/09(金) 19:30~ TOKYO MX 最響カミズモード!
10/10(土) 22:00~ TOKYO MX ご注文はうさぎですか? BLOOM (第3期)
10/10(土) 24:00~ TOKYO MX 神様になった日
10/10(土) 25:30~ テレビ朝日系 体操ザムライ
10/11(日) 22:30~ TOKYO MX 憂国のモリアーティ
10/11(日) 24:30~ TOKYO MX まえせつ!
× 10/12(月) 22:30~ TOKYO MX おちこぼれフルーツタルト
10/12(月) 24:00~ TOKYO MX A3!(エースリー) -AUTUMN & WINTER-
10/12(月) 25:30~ テレビ東京 おそ松さん 第3期
10/12(月) 26:30~ テレビ東京 それだけがネック
× 10/13(火) 23:00~ TOKYO MX 禍つヴァールハイト
10/25(日) 20:30~ AT-X 刀使ノ巫女 刻みし一閃の燈火(前後編)