自作エディターを作る! C言語でコンソールのカーソルを移動する
今回は予告通りキー移動について書いていきます。
前回までで矢印キーの入力検出はできているので、あとはコンソール上のカーソルを移動すればいいです。
あとはmessage_loop()
関数でイベント処理をする仕組みについても紹介します。
前回の記事はこちらです。 r-mutax.hateblo.jp
カーソル移動実演
実際にカーソルを動かしているgifを貼っておきます。 まだ上限チェックをしていないので、空白にもガンガン動いていってしまいます。
設計内容
まずはじめに今回のリビジョンを貼っておきます。 github.com
イベントメッセージの処理をする。
イベントを創出してメッセージキューに積む処理は以下の記事で出しているので、今回はそれを利用する側です。 r-mutax.hateblo.jp
メッセージループ
MMGetMessage()
関数でイベントメッセージを受け取りながらmessage_procedure()
でそれぞれのイベントハンドラに分配します。
ちなみにイベントメッセージを取得するMMGetMessage()
関数はメッセージキューが空の場合は、内部でスリープするので帰ってきません。あとGetMessage()
関数から名前を変えました。
void CEditControl::message_loop(){ MESSAGE msg; // msg_id が 0(= MM_QUIT)になったらループを抜けて終了する while(int msg_id = MMGetMessage(msg) != MM_QUIT){ if(msg_id == MM_ERROR){ // エラー処理を入れる } message_procedure(msg); } }
メッセージの分配
MESSAGE.id
で分岐してメッセージごとのイベントハンドラを呼び出します。イベントハンドラはOnxxxx()
関数にしました。
気に入ってないところとしては、MM_CHANGE_WINSIZE
のパラメータをここでfree()
しているところですかね。これはちゃんと動いていないのでまた後で直します。
void CEditControl::message_procedure(MESSAGE& msg){ switch(msg.id){ case MM_KEYPRESS: { char input_key = (char)msg.lParam; OnKeyPress(input_key); break; } case MM_CHANGE_WINSIZE: { MSize ms = *(MSize*)msg.lParam; free((void*)msg.lParam); OnChangeWindowSize(ms); break; } case MM_PAINT: OnPaint(); break; default: break; } }
OnKeypress()
キープレスイベントのハンドラでの処理は下図のような感じになっています。
編集モードによってキー処理を変えようと思っているので、まずkeygen()
で現在のモードによる振り分けを行って、keygen_command_mode()
などに処理を流します。
これらのモードで扱わない共通キー(方向キーなど)はkeygen_commonkey()
で処理します。
今回は方向キー押下によって、カーソルを動かしたいので、以下にkeygen_commonkey()
の処理を提示します。
やっていることは簡単で、引数で受け取った仮想キーによってswitchで処理を切り分けています。move_cursor()
は仮想キーを受け取って、指定された方向にカーソルを動かして再表示します。
void CEditControl::keygen_commonkey(char key){ switch (key) { case 'd': m_mode = DEBUG_MODE; break; case VK_UP: move_cursor(VK_UP); break; case VK_DOWN: move_cursor(VK_DOWN); break; case VK_RIGHT: move_cursor(VK_RIGHT); break; case VK_LEFT: move_cursor(VK_LEFT); break; case VK_ESCAPE: // ESC m_mode = COMMAND_MODE; break; case QUIT_CHAR: m_mode = QUIT_OPERATION; break; case 0x35: // PAGE_UP PageUp(); break; case 0x36: // PAGE_DOWM PageDown(); break; default: break; } }
move_cursor()
m_cursor
は行と列をメンバに持つMSize
型のCEditControl
のメンバです。
move_cursor()
ではm_cursor
を変えてdisp_cursor()
で表示しています。
これなんでデクリメント演算子使ってないんでしょうね。
disp_cursor()
でprintf()
している謎の文字列はターミナル制御文字で、カーソルをn行n列目に表示するためのものです。
詳細は以前紹介したこちらの記事が詳しいです。(参考させてもらいました。)
dev.grapecity.co.jp
disp_cursor()
で呼んでいるfflush()
ですが、描画性能改善のためにstdout
をフルバッファリングしているので、カーソル描画文字を送るためにコールしています。
描画性能改善については別途記事にまとめます。
void CEditControl::move_cursor(char dir){ switch(dir){ case VK_UP: // up m_cursor.iRow -= 1; disp_cursor(); break; case VK_DOWN: // down m_cursor.iRow++; disp_cursor(); break; case VK_RIGHT: // right m_cursor.iColumn++; disp_cursor(); break; case VK_LEFT: // left m_cursor.iColumn -= 1; disp_cursor(); break; default: break; } } void CEditControl::disp_cursor(){ printf("\033[%d;%dH", m_cursor.iRow, m_cursor.iColumn); fflush(stdout); }
次回予告
move_cursor()
の章でも触れましたが、現在作成しているテキストエディタは標準出力の頻度が多すぎて画面がちらつくという問題が発生していました。
次回は描画性能改善の取り組みについて語ります。