月別アーカイブ: 2013年2月

ApplicationCommands for IronPython

久々に .NET ネタ。
なかなか面白いことをしている人を見つけたので。
C# + WPF + XAML コマンドのバインドとメニューバー – Symfoware
static の意味をイマイチ解っていないようだけど…
いやコレでも動くんだが。

とにかくおかげで解ったこと。
下記を利用すればあらかじめ用意されたメニューを勝手に入れてくれるようだ。
ApplicationCommands クラス (System.Windows.Input)

Windows にも全自動で国際化メニューにしてくれる便利なものがあったのね。
WPF のみみたいだけど。
GTK+ みたくリソースであるほうが理解しやすいんだけど。

で、リンク先は多々無駄があるのでもう少し単純なサンプルを書いてみる。

ただ IronPython で、コンパイル面倒クセェ!
一応 C# 屋が見てもなんとなく解るように。

# -*- coding: UTF-8 -*-

"""
    WPF Simple TextEditor
    Read and Write encoding is UTF-8 Non BOM Text
"""

import clr

clr.AddReferenceByPartialName("PresentationCore")
clr.AddReferenceByPartialName("PresentationFramework")
clr.AddReferenceByPartialName("WindowsBase")

from System import *
from System.IO import *
from System.Windows import *
from System.Windows.Controls import *
from System.Windows.Input import *

from System.Windows.Markup import XamlReader
from Microsoft.Win32 import OpenFileDialog, SaveFileDialog

menu_str = """<Menu DockPanel.Dock="Top"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <MenuItem Header="_File">
        <MenuItem Command="New" />
        <Separator />
        <MenuItem Command="Open" />
        <MenuItem Command="Save" />
        <MenuItem Command="SaveAs" />
        <Separator />
        <MenuItem Command="Close" />
    </MenuItem>
    <MenuItem Header="_Edit">
        <MenuItem Command="Undo" />
        <MenuItem Command="Redo" />
        <Separator />
        <MenuItem Command="Cut" />
        <MenuItem Command="Copy" />
        <MenuItem Command="Paste" />
        <Separator />
        <MenuItem Command="SelectAll" />
    </MenuItem>
</Menu>"""

class TextEditor(Window):
    """
        Auto Internationalization menu Sample
        no mnemonic...
    """
    def __init__(self):
        # Menu
        menu = XamlReader.Parse(menu_str)
        # MenuItem Binding
        cb = CommandBinding(ApplicationCommands.New, self.on_new)
        self.CommandBindings.Add(cb)
        cb = CommandBinding(ApplicationCommands.Open, self.on_open)
        self.CommandBindings.Add(cb)
        cb = CommandBinding(ApplicationCommands.Save, self.on_save, self.on_can_execute)
        self.CommandBindings.Add(cb)
        cb = CommandBinding(ApplicationCommands.SaveAs, self.on_save_as, self.on_can_execute)
        self.CommandBindings.Add(cb)
        cb = CommandBinding(ApplicationCommands.Close, self.on_close)
        self.CommandBindings.Add(cb)
        # TextBox
        self.textbox = TextBox()
        self.textbox.TextWrapping = TextWrapping.NoWrap
        self.textbox.AcceptsReturn = True
        self.textbox.AcceptsTab = True
        self.textbox.VerticalScrollBarVisibility = ScrollBarVisibility.Auto
        self.textbox.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto
        # Add
        docpanel = DockPanel()
        docpanel.Children.Add(menu)
        docpanel.Children.Add(self.textbox)
        self.Content = docpanel
        self.Width = 300
        self.Height = 300
        self.textbox.Focus()

    def on_new(self, sender, e):
        self.textbox.Text = ""
        self.Title = ""

    def on_open(self, sender, e):
        dlg = OpenFileDialog()
        if dlg.ShowDialog():
            sw = StreamReader(dlg.FileName) 
            try:
                self.textbox.Text = sw.ReadToEnd()
                self.Title = dlg.FileName
            except Exception, ex:
                MessageBox.Show(ex.Message)
            finally:
                sw.Close()

    def on_save(self, sender, e):
        if self.Title == "":
            self.on_save_as(sender, e)
        else:
            self.save_file(self.Title)

    def on_save_as(self, sender, e):
        dlg = SaveFileDialog()
        if dlg.ShowDialog():
            self.save_file(dlg.FileName)

    def on_close(self, sender, e):
        self.Close()

    def on_can_execute(self, sender, e):
        e.CanExecute = self.textbox.Text != ""

    def save_file(self, filename):
        sw = StreamWriter(filename) 
        try:
            sw.Write(self.textbox.Text)
            self.Title = filename
        except Exception, ex:
            MessageBox.Show(ex.Message)
        finally:
            sw.Close()

if __name__ == "__main__":
    w = TextEditor()
    Application().Run(w)

menu_win

何故メニューだけ XAML なんだ?と言わない。
Python with GTK+ ではコレが普通。
メニューとツールバー – L’Isola di Niente

とりあえずこのバインディングでやったことの解説を少し。

XAML は見ての通り ApplicationCommands を指定するだけ。
そしてコードで CommandBinding オブジェクトを作る。

CommandBinding クラス (System.Windows.Input)

第一引数に利用する ApplicationCommands
第二引数に結びつけたいイベントハンドラ名
第三引数は必要なら任意でメニューの有効無効を決めるハンドラ名

ハンドラってつまり既に有るオブジェクトですので別途で作成する必要は無い。
それを Window に CommandBindings.Add() すればいい。

ついでに TextBox のバッファが空だと保存できないように。
CommandBinding 第三引数でアッサリと実現できるんですね。

Ctrl+C でコピー等は TextBox が提供している機能なので別にメニューに入れなくても使えるんですけどね。

後今頃知ったけど StreamReader.ReadToEnd ってバイナリを読み込めるのね。
せっかく例外処理を入れたけど意味なかった。

var と new と中括弧とセミコロン追加で C# コードにもなるはずw

で、自動的に日本語メニューにはなったけどニーモニックが無いんですけど。
Alt+F, Alt+S みたいなことができないって少し困る。

以下駄文。

久々に IronPython を使ったけど面白い、DLR の初期化さえ早くなれば…
Microsoft は .NET がネイティブ、かつ local が UTF-8 の新規 OS を出してくれ。
ネイティブなら DLR 初期化なんて一瞬のはず、内部は UTF-16 ではなく UCS-4 なら更に嬉しい。
今更 Windows の local 変更なんて無理なのは解っているから新規で。

Cairo for Python

Cairo Tutorial for Python Programmers

のサンプルコードで値が異様に小さい理由が解らなかった。
単純にコピペすると 1px にしか描写しない、なので DrawingArea サイズ得て計算する方法を覚書ページに書いた。

なんてことない、cairo_scale() で cairo のほうをサイズ指定すればよかったのね。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

from gi.repository import Gtk
import cairo

class DrawTest(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        da = Gtk.DrawingArea()
        da.connect("draw", self.on_draw)
        #da.set_double_buffered(False)
        self.add(da)
        self.connect("delete-event", Gtk.main_quit)
        self.resize(300, 300)
        self.show_all()

    def on_draw(self, widget, cr):
        # Get DrawingArea Size
        width = widget.get_allocated_width()
        height = widget.get_allocated_height()
        # cairo Change Size
        cr.scale(width, height)
        #
        cr.set_source_rgb(0, 0, 0)
        cr.move_to(0, 0)
        cr.line_to(1, 1)
        cr.move_to(1, 0)
        cr.line_to(0, 1)
        cr.set_line_width(0.2)
        cr.stroke()

        cr.rectangle(0, 0, 0.5, 0.5)
        cr.set_source_rgba(1, 0, 0, 0.80)
        cr.fill()

        cr.rectangle(0, 0.5, 0.5, 0.5)
        cr.set_source_rgba(0, 1, 0, 0.60)
        cr.fill()

        cr.rectangle(0.5, 0, 0.5, 0.5)
        cr.set_source_rgba(0, 0, 1, 0.40)
        cr.fill()

if __name__ == '__main__':
    w = DrawTest()
    Gtk.main()

cairo_python

cr.scale(width, height)
だけでサンプルコードがそのまんまコピペできた。
とにかく覚書ページの書き換えか、あーあ。

Boxes 仮想マシンの Lubuntu 上でも問題なく動いた。
しかしこのサンプルは一旦領域を塗りつぶす処理が入っていないのでダブルバッファを無効ににしてリサイズすると悲惨だ。

clutter – A toolkit for creating fast, portable, compelling dynamic UIs

このサンプルを見て気がついた、Clutter での 2D 表示も cairo なのね。
しかし Ubuntu には Clutter がデフォルトで入らないので困る。
海外で GTK+ 等のコードを検索すると皆 Ubuntu ばかり使っていて正直驚く。

ということで PyGI で DrawingArea に表示に作り替えてみた。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

from gi.repository import Gtk, GLib
import cairo, math

class DrawTest(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        da = Gtk.DrawingArea()
        da.connect("draw", self.on_draw)
        self.add(da)
        self.connect("delete-event", Gtk.main_quit)
        self.resize(150, 300)
        self.show_all()
        GLib.timeout_add(1000, self.on_timer, da)

    def on_timer(self, da):
        da.queue_draw()
        return True

    def on_draw(self, widget, cr):
        # get the current time and compute the angles
        now = GLib.DateTime.new_now_local()
        seconds = GLib.DateTime.get_second(now) * GLib.PI / 30
        minutes = GLib.DateTime.get_minute(now) * GLib.PI / 30
        hours = GLib.DateTime.get_hour(now) * GLib.PI / 6
        # scale the modelview to the size of the surface
        width = widget.get_allocated_width()
        height = widget.get_allocated_height()
        cr.scale(width, height)
        cr.set_line_cap(cairo.LINE_CAP_ROUND)
        cr.set_line_width(0.1)
        # the black rail that holds the seconds indicator
        cr.set_source_rgb(0, 0, 0)
        cr.translate(0.5, 0.5)
        cr.arc(0, 0, 0.4, 0, GLib.PI * 2)
        cr.stroke()
        # the seconds hand
        cr.set_source_rgb(1, 1, 1)
        cr.move_to(0, 0)
        cr.arc(math.sin(seconds) * 0.4, - math.cos(seconds) * 0.4, 0.05, 0, GLib.PI * 2)
        cr.stroke()
        # the minutes hand
        cr.set_source_rgb(1, 1, 0)
        cr.move_to(0, 0)
        cr.line_to(math.sin(minutes) * 0.4, - math.cos(minutes) * 0.4)
        cr.stroke()
        # the hours hand
        cr.set_source_rgb(1, 0, 0)
        cr.move_to(0, 0)
        cr.line_to(math.sin(hours) * 0.2, - math.cos(hours) * 0.2)
        cr.stroke()

if __name__ == '__main__':
    w = DrawTest()
    Gtk.main()

cairo_timer

GLib に sinf 関数くらい有りそうなのに見つからなかったので math を利用。
GLib.Math.sinf ? glib-2.0
Vala なら有るんだが、C 言語に sinf 関数があるから不要ということか。
それから色はテキトーです。

うん、基本的に cairo で使う数値は 0.0〜1.0 でいいみたい。
スマートフォンのような拡縮を考えるとそういう方向になるわけで。
作り手としては画面サイズ計算をする必要が無いというのも嬉しいですね。

PyGI Notify

usb_notyfy

GNOME3 マシンに USB メモリを刺した時等に出る Nothfy も DBus なんだね。
Na zdraví PyGI! ? Martin Pitt

うーん、やっぱり DBus は敷居が高いぞ。
GDBusProxy

(susssasa{sv}i) というワケワカメな表記は %s みたいなバリアント型の型指定子のようだ、Nothfy を使う場合にはコレで固定みたい。
一番上みたく作れというなら発狂するけどたしかに下方は簡単。
ただ一番下の result は long だったので添字はいらなかった。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

from gi.repository import Gio, GLib

d = Gio.bus_get_sync(Gio.BusType.SESSION, None)
notify = Gio.DBusProxy.new_sync(d, 0, None, 'org.freedesktop.Notifications',
    '/org/freedesktop/Notifications', 'org.freedesktop.Notifications', None)

result = notify.Notify('(susssasa{sv}i)', 'test', 1, 'gtk-ok', 'Hello World!',
    'Subtext', [], {}, 10000)
# result type is long
print result #[0]

ただ Notify を使いたいだけなら Libnotify がある。

Libnotify Reference Manual

多分上記をラッピングしているだけだと思うけど。

同じものを Libnotify で作ろうと思ったけど引数の 1 が何か解らない。
NotifyUrgency 列挙体くらいしか相当するものが無いけど違ったし。
実際数値を何に変えても動作するので無視してもよさげだが。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

from gi.repository import Notify

Notify.init("test")
# new(summary, body, icon)
notify = Notify.Notification.new("Hello World", "Subtext", "gtk-ok")
notify.set_timeout(10000)
notify.show()
print notify.props.id

とりあえず同じものができた。
result は int だったけど notify.show() の戻り値は bool なので困った。
プロパティの id と一致するようだ、表示毎に繰り上がるだけだったりする。

他にアップデート通知のようにボタンを表示して処理したい場合がある。
これは add_action で簡単に作成できるようだ。

ただし notify.show() は表示したら即制御を戻すのでコマンドを抜けてしまう。
なのでボタンを押した後の処理を入れるにはメインループが必要。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

from gi.repository import Notify, Gtk

def on_callback(notifiaction, action, data=None):
    dlg = Gtk.MessageDialog(
            None,
            Gtk.DialogFlags.MODAL,
            Gtk.MessageType.INFO,
            Gtk.ButtonsType.OK,
            action)
    dlg.set_title("message")  
    dlg.run()  
    dlg.destroy()
    # quit
    Gtk.main_quit()

Notify.init("test")
notify = Notify.Notification.new("Hello World", "Subtext", "gtk-ok")

# Set Button
notify.add_action("Action Text", "Button Text", on_callback, None, None)
# Do not sink
notify.set_urgency(Notify.Urgency.CRITICAL)

notify.set_timeout(10000)
notify.show()

# main loop
Gtk.main()

nothfy_button

action 引数で振り分けもできるみたい。
これなら簡単だし結構使いみちがありそうです。

Fedora 18 IBus Setting for Japanese

Fedora 18 にて iBus の設定を
Fedora 18 64bit Install (HDD)
時のようにして利用していたのだが何とも使いにくい。

Fedora 17 からだけどフォーカスが変わっても入力状態を保持するようになったのよね。
Alt+F2 時に日本語入力状態であると切り替えできずアルファベットを打ち込めない。

いや上記の場合は Ctrl+J でイケると最近気がついたのだが。

何より切り替えの反応が悪い、日本語を打っているつもりがアルファベットのままとか。
どうも入力ソースを切り替えしているのでタイムラグがあるようだ。
Fedora 17 までは切り替えは快適だったのに完全に改悪だ。

しかしどうやらコレも Ctrl+J で解決できるようだ。
Ctrl+J は入力ソースの切り替えではなく Anthy の設定変更なので素早い。

ならば。

anthy1

と Anthy のみにして英語ソースを削除、ちなみにフリーズしたw
切り替えショートカットも無意味なので無しにする。
後は設定ボタンを押して

anthy2

と英語キーボードなら Ctrl+space を追加。
日本語キーボードなら Zenkaku_Hankaku が使えると思う。
Ctrl+J を残すかどうかはお好みで。

後は再ログインすれば適用される。
これは快適、私的に入力切り替えでイライラしない環境になった。

文字入力可能状態でないと切り替えできなくなってしまったけどね。
日本語以外も使わなければいけない環境な人はご愁傷様です。