オライリーの Erlangプログラミングの wxErlang のチュートリアルを動かしてみたり

やぁっと環境ができましたので、ちょっと作ってみようかと。お手軽なチュートリアルとして手元にあった

Erlangプログラミング

Erlangプログラミング

の 14章をちょちょいと実装。

%% -*- coding: utf-8 -*-
%% オライリーの Erlang プログラミングの 14.4 章のプログラム
%%
%% microblog (14.3) に以下の機能が追加されます
%%
%% |New         | 新しく空のミニブログを生成します
%% |Open        | BLOGファイルに保存されたブログを開きます
%% |Save        | 現状のブログをBLOGファイルに保存します。コンテンツがすでに存在する場合、上書きします
%% |Add entry   | ブログの末尾にエントリを追加します。 エントリには自動で日付が付与されます
%% |Undo latest | 最新のAdd entry を取り消します。この操作は再帰的に行うことができます

-module(miniblog).
-compile(export_all).

-include_lib("/usr/local/lib/erlang/lib/wx-1.6/include/wx.hrl").
% code:lib_dir(wx) ++ "/include/wx.hrl" になります

-define(APPEND, 131).
-define(UNDO  , 132).
-define(OPEN  , 133).
-define(SAVE  , 134).
-define(NEW   , 135).
-define(ABOUT , ?wxID_ABOUT).
-define(EXIT  , ?wxID_EXIT).

start() ->
    wx:new(),
    Frame = wxFrame:new(wx:null(), ?wxID_ANY, "MiniBlog"),
    Text  = wxTextCtrl:new(Frame, ?wxID_ANY,
                           [{value, "MiniBlog"},
                            {style, ?wxTE_MULTILINE}]),
    setup(Frame, Text),
    wxFrame:show(Frame),
    loop(Frame, Text),
    wx:destroy().
    
setup(Frame, Text) ->
    %% elaborate menu bar
    MenuBar = wxMenuBar:new(),
    File    = wxMenu:new(),
    Edit    = wxMenu:new(),
    Help    = wxMenu:new(),

    wxMenu:append(File, ?NEW   , "New\tCtrl-N"),
    wxMenu:append(File, ?OPEN  , "Open saved\tCtrl-O"),
    wxMenu:appendSeparator(File),
    wxMenu:append(File, ?SAVE  , "Save\tCtrl-S"),
    wxMenu:append(File, ?EXIT  , "Quit"),

    wxMenu:append(Edit, ?APPEND, "Add en&try\tCtrl-T"),
    wxMenu:append(Edit, ?UNDO  , "Undo latest\tCtrl-U"),

    wxMenu:append(Help, ?ABOUT , "About MicroBlog"),

    wxMenuBar:append(MenuBar, File, "&File"),
    wxMenuBar:append(MenuBar, Edit, "&Edit"),
    wxMenuBar:append(MenuBar, Help, "&Help"),

    wxFrame:setMenuBar(Frame,MenuBar),

    %% elaborate TextEdit
    wxTextCtrl:setEditable(Text, false),

    %% elaborate status bar
    wxFrame:createStatusBar(Frame),
    wxFrame:setStatusText(Frame, "Welcome to wxErlang"),

    wxFrame:connect(Frame, command_menu_selected),
    wxFrame:connect(Frame, close_window).

loop(Frame, Text) ->
    receive
        #wx{id=?APPEND, event=#wxCommand{type=command_menu_selected}} ->
            Prompt = "Please enter text here.",
            MsgDlg = wxTextEntryDialog:new(Frame, Prompt,
                                           [{caption, "New blog entry"}]),
            case wxTextEntryDialog:showModal(MsgDlg) of
                ?wxID_OK ->
                    Str = wxTextEntryDialog:getValue(MsgDlg),
                    wxTextCtrl:appendText(Text, [10] ++ dateNow() ++ " " ++ Str); %10 as LF(0x0a)
                _ -> ok
            end,
            wxDialog:destroy(MsgDlg),
            loop(Frame, Text);

        #wx{id=?UNDO  , event=#wxCommand{type=command_menu_selected}} ->
            {StartPos, EndPos} = lastLineRange(Text),
            wxTextCtrl:remove(Text, StartPos-2, EndPos+1),
            loop(Frame, Text);

        #wx{id=?OPEN  , event=#wxCommand{type=command_menu_selected}} ->
            wxTextCtrl:loadFile(Text, "BLOG"),
            loop(Frame, Text);

        #wx{id=?SAVE  , event=#wxCommand{type=command_menu_selected}} ->
            wxTextCtrl:saveFile(Text, [{file,"BLOG"}]),
            loop(Frame, Text);

        #wx{id=?NEW   , event=#wxCommand{type=command_menu_selected}} ->
            {_, EndPos} = lastLineRange(Text),
            StartPos    = wxTextCtrl:xYToPosition(Text, 0, 0),
            wxTextCtrl:replace(Text, StartPos, EndPos, "MiniBlog"),
            loop(Frame, Text);

        #wx{id=?ABOUT , event=#wxCommand{}} ->
            Str = "MiniBlog is minimal WxErlang example.",
            MsgDlg = wxMessageDialog:new(Frame, Str,
                                         [{style, ?wxOK bor ?wxICON_INFORMATION},
                                          {caption, "About MiniBlog"}]),
            wxDialog:showModal(MsgDlg),
            wxDialog:destroy(MsgDlg),
            loop(Frame, Text);

        #wx{id=?EXIT  , event=#wxCommand{type=command_menu_selected}} ->
            wxWindow:close(Frame,[])
    end.

dateNow() ->
    {{Yea,Mon,Day},{Hou,Min,Sec}} = erlang:localtime(),
    io_lib:format("~4..0B-~2..0B-~2..0B ~2..0B:~2..0B:~2..0B", [Yea,Mon,Day,Hou,Min,Sec]).

lastLineRange(Text) ->
    EndPos = wxTextCtrl:getLastPosition(Text),
    LastLineNum = wxTextCtrl:getNumberOfLines(Text) -1,
    StartPos = EndPos - wxTextCtrl:getLineLength(Text, LastLineNum) +1,
    {StartPos, EndPos}.
1> c(miniblog).     
{ok,miniblog}
2> miniblog:start().

まぁ、こんな感じかな。

wxErlang で面白いのは、wxWidgets のイベントを Erlang のメッセージとして プロセスのメールボックスから取得できるところですね。wx:new/0 で wxサーバーを立てて、そのままのプロセスでloopでメッセージを待ち、抜けたらwx:destroy/0 wxサーバーにお亡くなりいただく..というのが大まかな構成になるわけです。

ここらへんは、普通のGUIフレームワークがUIスレッド上での処理は簡単だけれども、UIが固まるのが嫌だからと、ちょっと非同期にしようとすると途端にプログラミングが面倒になるのとは逆をいっているのかな?と思います。多分。

この14章のチュートリアル自信は about とquitしかできない microblog からスタートして、ここに載せた miniblog の作成までと成長させるという、学びやすい構成になっています。一部関数がしれっと定義しないまま使っていて、それぐらい自分で作れるだろう?的なところも、学習にはいい感じでした。


* * *


そんなこんなで、まずはお手つきと思ったのですが、環境の構築のほうに労力を取られてしまいました。wxErlang にもうすこし慣れれば日常のGUIツールをErlangでさくっとつくる、なんてできるかな、とか思わなくもないです。

が、激しく目的外使用というか、手段のために目的を選んでいるような...(^^;