2012/01/10


WXやCocoaもあるみたいなんですが、Gtk慣れてるのもあるのでGtkで。
以前から、memcachedに対して簡単なコマンドをやり取りできるGUIクライアントを各言語でやってみようという一人プロジェクトをやっているのだけど、haskellって触った事が殆ど無かったのでやってみた。
module Main (Main.main) where

import Text.Regex
import Graphics.UI.Gtk as Gtk
import Network.Memcache
import Network.Memcache.Protocol
 
main :: IO ()
main = do
  server <- Network.Memcache.Protocol.connect "localhost" 11211

  Gtk.initGUI
  
  window <- Gtk.windowNew
  Gtk.onDestroy window Gtk.mainQuit
  Gtk.set window [ containerBorderWidth := 10, windowTitle := "memcachedclient"]

  vbox <- Gtk.vBoxNew False 0

  swin <- scrolledWindowNew Nothing Nothing
  Gtk.scrolledWindowSetPolicy swin Gtk.PolicyAutomatic Gtk.PolicyAutomatic
  Gtk.containerAdd vbox swin

  tview <- textViewNew 
  Gtk.set tview [ containerBorderWidth := 1 ]
  buf <- Gtk.textViewGetBuffer tview
  Gtk.textViewSetEditable tview False
  Gtk.containerAdd swin tview

  entry <- Gtk.entryNew
  Gtk.onEntryActivate entry $ do
    end <- textBufferGetEndIter buf
    t <- Gtk.get entry entryText
    let tt = splitRegex (mkRegex " ") t
    if (length tt == 2 && (head tt == "get" || head tt == "delete")) ||
       (length tt == 3 && head tt == "set")
    then do
      case head tt of
        "get" -> do
          let
            key = (tt !! 1)
          r <- Network.Memcache.get server key
          case r of
            Nothing -> textBufferInsert buf end ((show key) ++ " not found")
            Just v -> textBufferInsert buf end ((v::String) ++ "\n")
        "set" -> do
          let key = (tt !! 1)
          let val = (tt !! 2)
          r <- Network.Memcache.set server key val
          case r of
            True -> textBufferInsert buf end "OK\n"
            False -> textBufferInsert buf end "ERROR\n"
        "delete" -> do
          let
            key = (tt !! 1)
          r <- Network.Memcache.delete server key 0
          case r of
            True -> textBufferInsert buf end "OK\n"
            False -> textBufferInsert buf end "ERROR\n"
    else
      textBufferInsert buf end "Unknown command\n"
    Gtk.entrySetText entry ""

  Gtk.boxPackEnd vbox entry Gtk.PackNatural 0
  Gtk.set window [ containerChild := vbox ]
  Gtk.windowSetDefaultSize window 400 300
  Gtk.widgetShowAll window
  Gtk.widgetGrabFocus entry

  Gtk.mainGUI

-- vim: set et ts=2:
むむむー。haskell難しい。
なぜif-thenにelseが必須なのか、何故他の言語の変数というものに近い物が存在しないのか、Justってなんだよコノヤロ、gtk2hsのAPIがGTKっぽくないよ!とかいろいろ...
Posted at by



2011/12/13


node.js の実装を司る libuv を使ってC言語のアプリケーションを書いていると、コールバック関数が増えていく。まぁこれは致し方ない事なんだけど、これで面倒なのがループを止める方法。libuv は作ったハンドルが全て閉じられると勝手に uv_run が終了する仕組みになっているんだけど、例えば処理内に2つハンドルがあって、かつ閉じようとする箇所が別のコールバック処理内にあるとハンドルをグローバル変数にするか uv_timer_t timer1;
uv_timer_t timer2;

void exit_cb(void* data) {
  uv_close(timer1);
  uv_close(timer2);
}
構造体に格納して引きずり回さなければならない。
typedef struct {
  uv_timer_t timer1;
  uv_timer_t timer2;
} THE_TIMERS;

void exit_cb(void* data) {
  THE_TIMERS* timers = (THE_TIMERS*) data;
  uv_close(timers->timer1);
  uv_close(timers->timer2);
}
どんな時に起きるかというと、libuv を GUI アプリと併用する場合。libuv はメインループを uv_run() で実行する為、GUI のメインループを回す為にアイドルを作る必要がある。

アイドルを作るだってぇ!!!

と思ったあなた、ぜひクリスマスは libuv とお過ごし下さい。
例えば、GTK だと以下の様になります。
#include <uv/uv.h>
#include <gtk/gtk.h>

void
idle_cb(uv_idle_t* idle, int status) {
  gtk_main_iteration_do(FALSE);
}

int
main(int argc, char* argv[]) {
  gtk_init(&argc, &argv);

  GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_widget_show_all(window);

  uv_idle_t idle;
  uv_idle_init(uv_default_loop(), &idle);
  uv_idle_start(&idle, idle_cb);

  uv_run(uv_default_loop());
}
この uv_run() のループを止めるには idle を uv_close() する必要があるのですが、ループ自身もハンドルで、かつ uv_run() はそのハンドルのリファレンスがある内しか回らないという仕様なので、こう書く事が出来ます。
#include <uv/uv.h>
#include <gtk/gtk.h>

void
timer_cb(uv_timer_t* timer, int status) {
  GtkWidget* label = (GtkWidget*) timer->data;
  char buf[64];
  time_t t;
  time(&t);
  strftime(buf, sizeof(buf), "%c", localtime(&t));
  gtk_label_set_text(GTK_LABEL(label), buf);
}

void
idle_cb(uv_idle_t* idle, int status) {
  gtk_main_iteration_do(FALSE);
}

void
destroy_cb(GtkWidget* w, gpointer data) {
  uv_loop_t* loop = (uv_loop_t*) data;
  uv_unref(loop); // stop idle
  uv_unref(loop); // stop timer
}

int
main(int argc, char* argv[]) {
  gtk_init(&argc, &argv);

  GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "clock");

  GtkWidget* vbox = gtk_vbox_new(FALSE, 1);
  GtkWidget* label = gtk_label_new("");
  gtk_container_add(GTK_CONTAINER(vbox), label);
  gtk_container_add(GTK_CONTAINER(window), vbox);
  gtk_window_set_default_size(GTK_WINDOW(window), 30020);
  gtk_widget_show_all(window);

  uv_timer_t timer;
  uv_timer_init(uv_default_loop(), &timer);
  timer.data = (void*) label;
  uv_timer_start(&timer, timer_cb, 10001000);

  uv_idle_t idle;
  uv_idle_init(uv_default_loop(), &idle);
  uv_idle_start(&idle, idle_cb);

  g_signal_connect(G_OBJECT(window), "destroy",
          G_CALLBACK(destroy_cb), uv_default_loop());

  uv_run(uv_default_loop());
}
もちろん、uv_close() する回数が少なければループは止まらないので、あまりちゃんとした方法では無い気もしますが。

GTKの様に iteration 動作出来るツールキットならばいいのですが、iteration 動作出来ないライブラリと併用する場合、libuv 側が回される事になる訳です。つまり libuv に1回だけループを回す手続きが必要な訳で、探しても無かったのでさっきパッチ書いて投げた。取り込まれるかどうかは知らない。
ちなみに、上記の例は libuv と gtk で作る時計なんだけど、これを perl で書くとこうなる
use strict;
use warnings;

use Encode;
use Encode::Locale;
use UV;
use Glib;
use Gtk2 -init;
use Time::Piece;

my $w = Gtk2::Window->new('toplevel');
$w->set_title("timer");
my $v = Gtk2::VBox->new(01);
my $l = Gtk2::Label->new();
$v->add($l);
$w->add($v);
$w->set_default_size(30020);
$w->show_all;

my $t = UV::timer_init();
UV::timer_start($t10001000sub {
  $l->set_label(decode(locale => localtime->strftime));
});

my $i = UV::idle_init();
UV::idle_start($isub {
  Gtk2->main_iteration_do(0);
});

$w->signal_connect(destroy => sub {
  UV::close($t);
  UV::close($i);
});

UV::run;
UV は typester さんが書いた奴。
typester/p5-UV - GitHub
https://github.com/typester/p5-UV
あと、最近書いた go言語の libuv バインディング go-uv で書くとこうなる
package main

import (
    "fmt"
    "github.com/mattn/go-uv"
    "github.com/mattn/go-gtk/gtk"
    "time"
)

func main() {
    gtk.Init(nil)
    window := gtk.Window(gtk.GTK_WINDOW_TOPLEVEL)
    window.SetTitle("Clock")
    vbox := gtk.VBox(false1)
    label := gtk.Label("")
    vbox.Add(label)
    window.Add(vbox)
    window.SetDefaultSize(30020)
    window.ShowAll()

    timer, _ := uv.TimerInit(nil)
    timer.Start(func(h *uv.Handle, status int10001000) {
        label.SetLabel(fmt.Sprintf("%v", time.Now()))
    })

    idle, _ := uv.IdleInit(nil)
    idle.Start(func(h *uv.Handle, status int) {
        gtk.MainIterationDo(false)
    })

    window.Connect("destroy"func() {
        timer.Close(nil)
        idle.Close(nil)
    })

    uv.DefaultLoop().Run()
}
mattn/go-uv - GitHub

Go binding for libuv

https://github.com/mattn/go-uv
Posted at by



2011/12/07


この記事は Oppai Advent Calendar 2011 の7日目の記事です。
C++11のユーザ定義リテラルを使うと、ユーザ指定のサフィックスに従って型変換や任意の処理を行う事が出来ます。
今日はこれを使って、おっぱいを判定するコードを書いてみました。
#include <iostream>
#include <string>

#define X(x) #x ")"_oppai
#define _(x) "(" #x ")(" X

char constoperator "" _oppai(char const* str, std::size_t len) {
  std::string x(str, len);
  return
    x == "(・)(・)" ? "ハリのあるおっぱい" :
    x == "(.)(.)"   ? "垂れ気味のおっぱい" :
    x == "(◎)(◎)" ? "立体的なおっぱい" :
    x == "(○)(○)" ? "乳輪が大きいおっぱい" :
    x == "(●)(●)" ? "乳輪が大きく、黒いおっぱい" :
    x == "(。)(。)" ? "色が薄く、左向きのおっぱい" :
    "もはやおっぱいではない";
}

int
main(int argc, char* argv[]) {
  std::cout << _(・)(・) << std::endl; // ハリのあるおっぱい
  std::cout << _(.)(.)   << std::endl; // 垂れ気味のおっぱい
  std::cout << _(◎)(◎) << std::endl; // 立派なおっぱい
  std::cout << _(○)(○) << std::endl; // 乳輪が大きいおっぱい
  std::cout << _(●)(●) << std::endl; // 乳輪が大きく、黒いおっぱい
  std::cout << _(。)(。) << std::endl; // 色が薄く、左向きのおっぱい
  return 0;
}
ユーザ定義リテラル便利ですね!
Posted at by