wxFormBuilder を使って電卓を作ってみました。


自宅PCに wxHaskell をインストールする際に wxWidgetsコンパイルした wxPack というのがあることを知り、 wxPackを使って wxHaskell をインストールしました。
wxPackをインストールするとwxFormBuilder もインストールされました。
wxFormBuilderwxWidgetsGUIを設計するツールで、C++Pythonのコードを吐き出すことが出来ます。
また、C++Pythonのコード以外にもGUIの情報をxmlで表現した xrc を吐き出すことが出来、wxHaskellは xrcを読み込むことが出来ます。

  • XRC の作成

wxRubyでGUIプログラミング -- XRCを使うの手順で xrc ファイルを作ります。
Dialogウィンドウを使用していますがリソースの読み込みに失敗してしまうので、Form を使いました。(「Formsタブを選択してDialogをクリックします」の手順は「Formsタブを選択してFormをクリックします」)

Form が出来たらプロジェクトをセーブし、File -> generate Code を選択してクリックすると noname.xrc のファイルが出力されます。

  • wxFormBuilder が吐き出した xrc ファイル
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<resource xmlns="http://www.wxwindows.org/wxxrc" version="2.3.0.1">
	<object class="wxFrame" name="CalcFrame">
		<style>wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL</style>
		<size>500,300</size>
		<title>Calculator</title>
		<centered>1</centered>
		<aui_managed>0</aui_managed>
		<object class="wxBoxSizer">
			<orient>wxVERTICAL</orient>
			<object class="sizeritem">
				<option>0</option>
				<flag>wxALL|wxEXPAND</flag>
				<border>5</border>
				<object class="wxTextCtrl" name="display">
					<value></value>
					<maxlength>0</maxlength>
				</object>
			</object>
			<object class="sizeritem">
				<option>1</option>
				<flag>wxEXPAND</flag>
				<border>5</border>
				<object class="wxGridSizer">
					<rows>4</rows>
					<cols>4</cols>
					<vgap>5</vgap>
					<hgap>5</hgap>
					<object class="sizeritem">
						<option>0</option>
						<flag>wxALL</flag>
						<border>5</border>
						<object class="wxButton" name="button7">
							<label>7</label>
							<default>0</default>
						</object>
					</object>
					<object class="sizeritem">
						<option>0</option>
						<flag>wxALL</flag>
						<border>5</border>
						<object class="wxButton" name="button8">
							<label>8</label>
							<default>0</default>
						</object>
					</object>
					<object class="sizeritem">
						<option>0</option>
						<flag>wxALL</flag>
						<border>5</border>
						<object class="wxButton" name="button9">
							<label>9</label>
							<default>0</default>
						</object>
					</object>
					<object class="sizeritem">
						<option>0</option>
						<flag>wxALL</flag>
						<border>5</border>
						<object class="wxButton" name="button_ac">
							<label>AC</label>
							<default>0</default>
						</object>
					</object>
					<object class="sizeritem">
						<option>0</option>
						<flag>wxALL</flag>
						<border>5</border>
						<object class="wxButton" name="button4">
							<label>4</label>
							<default>0</default>
						</object>
					</object>
					<object class="sizeritem">
						<option>0</option>
						<flag>wxALL</flag>
						<border>5</border>
						<object class="wxButton" name="button5">
							<label>5</label>
							<default>0</default>
						</object>
					</object>
					<object class="sizeritem">
						<option>0</option>
						<flag>wxALL</flag>
						<border>5</border>
						<object class="wxButton" name="button6">
							<label>6</label>
							<default>0</default>
						</object>
					</object>
					<object class="sizeritem">
						<option>0</option>
						<flag>wxALL</flag>
						<border>5</border>
						<object class="wxButton" name="button_plus">
							<label>+</label>
							<default>0</default>
						</object>
					</object>
					<object class="sizeritem">
						<option>0</option>
						<flag>wxALL</flag>
						<border>5</border>
						<object class="wxButton" name="button1">
							<label>1</label>
							<default>0</default>
						</object>
					</object>
					<object class="sizeritem">
						<option>0</option>
						<flag>wxALL</flag>
						<border>5</border>
						<object class="wxButton" name="button2">
							<label>2</label>
							<default>0</default>
						</object>
					</object>
					<object class="sizeritem">
						<option>0</option>
						<flag>wxALL</flag>
						<border>5</border>
						<object class="wxButton" name="button3">
							<label>3</label>
							<default>0</default>
						</object>
					</object>
					<object class="sizeritem">
						<option>0</option>
						<flag>wxALL</flag>
						<border>5</border>
						<object class="wxButton" name="button_minus">
							<label>-</label>
							<default>0</default>
						</object>
					</object>
					<object class="sizeritem">
						<option>0</option>
						<flag>wxALL</flag>
						<border>5</border>
						<object class="wxButton" name="button0">
							<label>0</label>
							<default>0</default>
						</object>
					</object>
					<object class="spacer">
						<option>1</option>
						<flag>wxEXPAND</flag>
						<border>5</border>
						<size>0,0</size>
					</object>
					<object class="spacer">
						<option>1</option>
						<flag>wxEXPAND</flag>
						<border>5</border>
						<size>0,0</size>
					</object>
					<object class="sizeritem">
						<option>0</option>
						<flag>wxALL</flag>
						<border>5</border>
						<object class="wxButton" name="button_equal">
							<label>=</label>
							<default>0</default>
						</object>
					</object>
				</object>
			</object>
		</object>
	</object>
</resource>
import Graphics.UI.WX
import Graphics.UI.WXCore
import Data.IORef
import Data.Char

data State = EMPTY | REG1 | BRIGE | REG2 deriving (Eq,Show)


data Calc = Calc{register1, register2 :: Integer, operator :: Maybe Char, state::State} deriving Show
{-
状態   表示   入力    動作       次の状態
EMPTY   REG1   数値    REG1に追加表示 REG1
               演算子 何もしない      EMPTY 
               =      何もしない      EMPTY
REG1   REG1   数値    REG1に追加表示  REG1
              演算子 演算子を登録     BRIGE
              =      何もしない       REG1

BRIGE   REG1  数値   REG2に登録表示 REG2
              演算子 演算子を登録   BRIGE
              =      何もしない     BRIGE

REG2   REG2   数値    REG2に登録表示 REG2
             演算子  結果をREG1へ、演算子を登録 BRIGE
             =       結果をREG1へ、BRIGE
-}

initCalc :: Calc
initCalc = (Calc 0  0 Nothing EMPTY)

chNumBase :: Int
chNumBase = ord '0'

charToInteger :: Char -> Integer
charToInteger ch = fromIntegral $ (ord ch) - chNumBase

isNumChar :: Char -> Bool
isNumChar ch = ch >= '0' && ch <= '9'

isOpChar :: Char -> Bool
isOpChar ch = ch == '+' || ch == '-'

isEqual :: Char -> Bool
isEqual ch = ch == '='

main :: IO ()
main = start $ do
    calcState    <- newIORef initCalc
    res          <- xmlResourceCreateFromFile "wxcalc.xrc" wxXRC_USE_LOCALE
    form         <- xmlResourceLoadFrame res objectNull "CalcFrame"
    display      <- xmlResourceGetTextCtrl form "display"
    button0      <- xmlResourceGetButton   form "button0"
    button1      <- xmlResourceGetButton   form "button1"
    button2      <- xmlResourceGetButton   form "button2"
    button3      <- xmlResourceGetButton   form "button3"
    button4      <- xmlResourceGetButton   form "button4"
    button5      <- xmlResourceGetButton   form "button5"
    button6      <- xmlResourceGetButton   form "button6"
    button7      <- xmlResourceGetButton   form "button7"
    button8      <- xmlResourceGetButton   form "button8"
    button9      <- xmlResourceGetButton   form "button9"
    button_ac    <- xmlResourceGetButton   form "button_ac"
    button_plus  <- xmlResourceGetButton   form "button_plus"
    button_minus <- xmlResourceGetButton   form "button_minus"
    button_equal <- xmlResourceGetButton   form "button_equal"

    let numBtns = zip [button0,button1,button2, button3,button4, button5,button6,button7,button8,button9] ['0'..'9']
        opBtns  = [(button_plus,'+'),(button_minus,'-'),(button_equal,'=')]

    mapM_ (\(btn,ch) -> set btn [on command := pushButton display calcState ch]) (numBtns ++ opBtns)
    set button_ac [on command := pushACButton display calcState]

    windowShow form


showText :: Textual w => w -> Calc -> IO ()
showText display (Calc reg1 reg2 op state) =
    case state of
        REG2  -> set display [text := show reg2]
        _     -> set display [text := show reg1]

showAndWrite :: Textual w => w -> (IORef Calc) -> Calc -> IO ()
showAndWrite display ioState state = do
    showText   display state
    writeIORef ioState state


pushACButton  :: Textual w => w -> (IORef Calc) -> IO ()
pushACButton display ioState = showAndWrite display  ioState initCalc

pushButton :: Textual w => w -> (IORef Calc) -> Char -> IO ()
pushButton display ioState ch = 
      readIORef ioState >>= \state ->
      case state of
       (Calc r1 r2 op EMPTY) -> if isNumChar ch then showAndWrite display ioState (Calc (r1 * 10 + charToInteger ch)  r2 op REG1)
                                                else showAndWrite display ioState (Calc r1  r2 op  REG1)
       (Calc r1 r2 op REG1)  -> let reg1Next  | isNumChar ch = showAndWrite display ioState (Calc (r1 * 10 + charToInteger ch)  r2 op REG1)
                                              | isOpChar  ch = showAndWrite display ioState (Calc r1  r2 (Just ch) BRIGE)
                                              | otherwise    = showAndWrite display ioState (Calc r1  r2 op  REG1)
                                in  reg1Next

       (Calc r1 r2 op BRIGE) -> let brigeNext | isNumChar ch = showAndWrite display ioState (Calc r1 (r2 * 10 + charToInteger ch) op REG2)
                                              | isOpChar  ch = showAndWrite display ioState (Calc r1  r2 (Just ch) BRIGE)
                                              | otherwise    = showAndWrite display ioState (Calc r1  r2 op BRIGE)
                                in  brigeNext
       (Calc r1 r2 op REG2)  -> let reg2Next  | isNumChar ch = showAndWrite display ioState (Calc r1 (r2 * 10 + charToInteger ch) op REG2)
                                              | isOpChar  ch = showAndWrite display ioState (Calc (doCalc op r1 r2) 0 (Just ch) BRIGE)
                                              | isEqual   ch = showAndWrite display ioState (Calc (doCalc op r1 r2) 0 op BRIGE)
                                in  reg2Next

doCalc :: Maybe Char -> Integer -> Integer -> Integer
doCalc op reg1 reg2 | op == (Just '+') = reg1 + reg2
                    | op == (Just '-') = reg1 - reg2