Programming」カテゴリーアーカイブ

pygtk3 pack_start

自作 PyGtk アプリを Gtk3 に切り替えしたい。
よし、まずは SeeMe for Linux だ。
現行 Opera の仕様と合わないまま放置もどうかと思うし。

名前を SeeMe for Linux という無駄に長い名前から seemex に変更。
単純に打つのが面倒なだけです、バージョンは 2.0 でいいな。

まず gtk 名前空間の変更、面倒だから変名しちゃえ。
というか大文字の名前空間ってやっぱり変だよね。

#import pygtk
#pygtk.require("2.0")
#import gtk
from gi.repository import Gtk as gtk

定数の大半が列挙体名のアトリビュートに変わっているのは以前書いた。
gtk.FILL が Gtk.AttachOptions.FILL になったのを見つけるのに苦労したぞ。

pack_start の引数は expand, fill, padding を C 同様に全部指定する必要あり。
デフォルト引数は現行方式では用意されていない、バインディング方法が違うし。

# old pack_start(child, expand=True, fill=True, padding=0)
v = Gtk.VBox()
v.pack_start(widget, True, True, 0)
#v.pack_start(widget) Error

アイコンは GdkPixbuf を作らなくてもパスで良かったのね、前から…

path = os.path.join(os.path.dirname( __file__ ), "seeme.xpm")
#self.icon = gtk.gdk.pixbuf_new_from_file(path)
#self.set_icon(self.icon)
self.set_icon_from_file(path)

でも GdkPixbuf をファイルから作らないと Y901x のボタンが利用できない。
んー cairo を使うみたいなんだけど今はよく解らない。
seeme からやりはじめて助かった…

レンダラのコールバック引数とかに user_data が入って飛んでくる。
とにかく何をやるのも C 言語で書くのと同じようにしなきゃいけなくいなった。

GtkDialog に vbox アトリビュートなんかも当然無い。
C 言語同様に get_content_area で GtkBox を得る必要あり。
PyGtk だからこそ便利だった拡張が丸ごと利用できなくなった。

他 PyGtk2 との細かい差はここから辿って見つけて。
GTK+ 3 Reference Manual

GtkAboutDialog は set_url_hook せずともリンクボタンになる。

VERSION = "1.9.0"

class SeeMe4(gtk.Window):
    #...
    def on_about(self, widget, event=None):
        url = "http://palepoli.skr.jp/"
        w = gtk.AboutDialog()
        w.set_transient_for(self)
        w.set_program_name("seemex")
        w.set_version(VERSION)
        w.set_copyright("Copyright(C) 2009-2011 sasakima-nao")
        w.set_license("GPL")
        w.set_logo(self.get_icon())
        v = "GTK+ Version {0}.{1}.{2}".format(
                gtk.get_major_version(),
                gtk.get_minor_version(),
                gtk.get_micro_version())
        w.set_comments(v)
        w.set_website(url)
        w.set_website_label(url)
        w.run()
        w.destroy()

よし、とりあえず GTK+ 3.0 なアプリになったぞ。
Ubuntu 11.04 で動かしていないけど多分 Ubuntu では GTK2 になるはずだが。
後は現行 Opera で無意味になったセパレータ指定とかを消したり色々。

バックアップ
seeme-1.9.0.tar.gz

コイツが一段落したら Windows 版をどうしよう…

v1 とある理由から Delphi でチャチャッと作成
v2 動画プレイヤー作りが嫌になった反動で無意味にコレの製作を続ける
v3 WPF で何か作りたいという理由だけで C# 化
v4 インストーラを作ってみたいという理由だけでインストーラ化
v5 IronPython で何かを作りたいという理由だけで IronPython 化
v6 自分でビルドしろ第二段にしたい理由だけで C# に戻す、かも…

Windows 版は完全に作者のおもちゃ。
いやコレを作ったから私は十年も生き残っているのは間違いないんだが。

Linux shift-jis zip file

とある ZIP を展開しようと思ったら Shift-JIS ファイル名を使っていて展開できない。
Ubuntu 日本語 Remix 付属の /usr/bin/zip なら展開できるのだけど Fedora だし。
仮想マシンの Ubuntu から頂いて、と思ったけど我が Fedora は 64bit だった…

仮想マシンな Windows XP から共有を利用して展開。
とソレだけの理由で仮想 Windows を立ち上げるのも面倒だ。
そういえば Python には zipfile モジュールがある、作ってみよう。

とりあえず日本語のドキュメントを探してみた。
13.4. zipfile ? ZIP アーカイブの処理 ? Python v2.6.2 documentation
extractall なんて便利なメソッドがあるじゃん、これは簡単そうだ。
ということで Nautilus スクリプトにしてみた。

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

import zipfile
import os

filenames = os.environ["NAUTILUS_SCRIPT_SELECTED_FILE_PATHS"].split("\n")

for f in filenames:
    z = zipfile.ZipFile(f)
    z.extractall()
    z.close()

こんなに単純なコードでいいみたい。
ファイル名は見事に化けてしまいますが展開だけは成功する。

そういえばパスワード付きの場合もある。
GTK+ でダイアログでも出して入力すればいいかな。

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

import zipfile
import os
import gtk

filenames = os.environ["NAUTILUS_SCRIPT_SELECTED_FILE_PATHS"].split("\n")

for f in filenames:
    z = zipfile.ZipFile(f)
    dlg = gtk.Dialog("password", None, 0, (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
    def dlg_ok(self):
        dlg.response(gtk.RESPONSE_ACCEPT)
    entry = gtk.Entry()
    entry.connect("activate", dlg_ok)
    dlg.vbox.pack_start(entry)
    dlg.show_all()
    dlg.run()
    p = entry.get_text()
    dlg.destroy()
    z.extractall(pwd=p)

展開が終了するまでダイアログが閉じないのは何故だろう…
それと展開は死ぬほど遅い…

せっかく作ったけど素直に仮想 Windows から展開するとかのほうが良さそう。
まあ使いたくなる場合もあるだろうからスクリプト登録だけはしておくか。

ところで今頃気がついた。
Nautilus スクリプト位置にディレクトリを作れば入れ子にできるんだね。

まだまだ増やしても大丈夫そうだ。

gi.repository GTK+ Version

Fedora 15 と Ubuntu 11.04 の PyGtk について。

その前に VirtualBox ose 上で Ubuntu を使っているけど \ や | が打てない。
Ubuntu日本語フォーラム / Virtualbox 4.0 で一部のキーが使えない?
VirtualBox ose の Update を待とう…
今回はホストの fedora からクリップボードコピペでなんとかした。

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

from gi.repository import Gtk

message = "「はい」を押すと終了"

while 1:
    dlg = Gtk.MessageDialog(
            None,  
            Gtk.DialogFlags.MODAL,  
            Gtk.MessageType.WARNING, 
            Gtk.ButtonsType.YES_NO,  
            message)  
    r = dlg.run()  
    dlg.destroy()
    if r == Gtk.ResponseType.YES:
        break
    elif r == Gtk.ResponseType.NO:
        message = "それは「いいえ」だろ!"
    elif r == Gtk.ResponseType.DELETE_EVENT:
        message = "閉じるボタンじゃネェ!"
    else:
        message = "不明な動作..."

このコードで Fedora 15, Ubuntu 11.04 のどちらでも動く。
Ubuntu 10.10 以前では例外になる、つか gi から無いわけで。
Fedora のダイアログは「閉じる」ボタンが出ないので右クリックで「閉じる」とやる。

import が変更になり gtk を Gtk に全置換。
以前は定数だったものの大半が enum 名のアトリビュートに変わっている。
それ以外はほとんど同様と思っていいみたい。

つまり Ubuntu 11.04 も GTK3 なのか。
と思ったけどインタラクティブシェル上でコレをやってみる。

from gi.repository import Gtk
Gtk._version

Ubuntu 11.04 の場合はこの形式で書いても GTK2 になるってことなのか。
/usr/lib64/python2.7/site-packages/gi/overrides/Gtk.py
がバージョン違いを振り分けているみたい、とにかくそういうこと。
ソースコードを見てもなんだかよくワカンネェ!

なんにしても早めにこの形式なコードへ書き換えしたほうがいいみたい。
API ドキュメントは既に GTK+ 3.0 になっているのだし。
PyGtk ドキュメントが遅いのはいつものこと。
GNOME 開発センター

ついでに

EyeOfGnome/Plugins – GNOME Live!

このとうりにやっても全然 eog が自作プラグインを認識しない…
~/.local/share/eog/plugins や ~/.local/lib64/eog/plugins
にも試しに置いてみたけど無意味だった。
/usr/lib64/eog/plugins に置くしか無いのだろうか。
ついでに IAge 指定が 2 のままじゃん、Gedit と違ってヤル気が無い感じ。
おかげで自作 eog プラグインの更新ができないよ、まいった。

Fedora 15 006

Fedora 15 (GNOME 3) 64bit 生活六日目。

※ Gedit Plugin

Gedit/PythonPluginHowTo – GNOME Live!

Gedit 3 のプラグインは妙に複雑になった感じだけどやってみると簡単。
Gedit プラグインの作り方 – L’Isola di Niente
私の公開しているこのページを Gedit 3 用に改造してみる。

*.gedit-plugin 拡張子であったファイルは *.plugin と拡張子を変更。
セクション名を [Plugin] に、IAge を 3 に変更、v2 との違いはコレだけだ。

肝心のコード

#-*- coding:utf-8 -*-

import gedit
import gtk

class TestPligin(gedit.Plugin):
    def __init__(self):
        gedit.Plugin.__init__(self)

    def activate(self, window):
        self.window = window

    def deactivate(self, window):
        pass

    def update_ui(self, window):
        pass

#-*- coding:utf-8 -*-

from gi.repository import GObject, Gedit, Gtk

class TestPligin(GObject.Object, Gedit.WindowActivatable):
    __gtype_name__ = "TestPligin"
    window = GObject.property(type=Gedit.Window)
    def __init__(self):
        GObject.Object.__init__(self)

    def do_activate(self):
        pass
        
    def do_deactivate(self):
        pass

    def do_update_state(self):
        pass

ということみたい、コールバックに引数は無くなったんだね。
__gtype_name__ は class 名と同じにしなければいけないみたい。

多重継承の敬称元を Gedit.AppActivatable とか3つから選べになっている。
普通なら Gedit.WindowActivatable だけでいいだろう。
下のほうにあるサンプルコードは継承元を書き忘れているじゃん…

後は self.window のメソッドを辿って弄くっていく。
v2 のコードから gtk → Gtk に全置換する必要がある。
それと activate のコールバックは引数を3つにしておく必要があった。
user_data を指定しなくても None が送られてくるみたい、注意ね。

ついでに GtkMessageDialog の引数は enum 形式に変更されていた。
指定がよく解らない人は dir() で辿って(私はそうやった)

testtest.plugin

[Plugin]
Loader=python
Module=testtest
IAge=3
Name=testtest
Name[ja]=テストテスト
Description=plugin test
Description[ja]=プラグインのテスト
Authors=sasakima-nao 
Copyright=Copyright © 2011 sasakima-nao 
Website=http://palepoli.skr.jp/

testtest.py

#-*- coding:utf-8 -*-

from gi.repository import GObject, Gedit, Gtk

ui_str = """<ui>
  <menubar name="MenuBar">
    <menu name="EditMenu" action="Edit">
      <placeholder name="EditOps_3">
        <menuitem name="testtest" action="testtest"/>
      </placeholder>
    </menu>
  </menubar>
</ui>
"""

class TestTest(GObject.Object, Gedit.WindowActivatable):
    __gtype_name__ = "TestTest"
    window = GObject.property(type=Gedit.Window)
    def __init__(self):
        GObject.Object.__init__(self)

    def do_activate(self):
        # GtkUIManager を得る
        manager = self.window.get_ui_manager()
        # GtkActionGroup を新規で作成
        self._action_group = Gtk.ActionGroup("TestTestActions")
        # GtkActionEntry を作成
        # name, stock_id, label, accelerator, tooltip, callback
        actions = [("testtest", None, "引用に変換", None, "すてーたすばー", self.on_testtest_activate)]
        # GtkActionGroup に挿入
        self._action_group.add_actions(actions)
        # GtkUIManager に追加
        manager.insert_action_group(self._action_group, -1)
        self._ui_id = manager.add_ui_from_string(ui_str)
        
    def do_deactivate(self):
        manager = self.window.get_ui_manager()
        manager.remove_ui(self._ui_id)
        manager.remove_action_group(self._action_group)
        manager.ensure_update()
        

    def do_update_state(self):
        pass
    
    def on_testtest_activate(self, action, data=None):
        view = self.window.get_active_view()
        buf = view.get_buffer()
        try:
            begin, end = buf.get_selection_bounds()
        except:
            self.messagebox("変換したいテキストを選択してください")
            return
        text = begin.get_text(end)
        lines = text.split("\n")
        # list の join に変更
        result = []
        for line in lines:
            result.append("> {0}".format(line))
        buf.delete_selection(True, True)
        buf.insert_at_cursor("\n".join(result))
            
        
    def messagebox(self, text):
        dlg = Gtk.MessageDialog(
                self.window,  
                Gtk.DialogFlags.MODAL,  
                Gtk.MessageType.WARNING, 
                Gtk.ButtonsType.OK,  
                text)  
        r = dlg.run()  
        dlg.destroy()

と2つのファイルを作成して ~/.local/share/gedit/plugins に置く。
Gedit を再起動して設定からこのプラグインを選択。

GtkUIManager のメソッドや XML UI 指定は v2 と変わっていないようだ。
これで Gedit 3 のプラグイン作りはなんとかなりそう。

ついでに、外部ツールで実行前にファイルを保存するように指定すると動作しない。
gedit って更新毎に必ず一ヶ所はおかしな所があるよね。

gdiplus save

Keyleigh をそろそろなんとかしたいと考えた。
拾ったデルヒャァコンポーネントを使っているだけのクソソフトだが。
せっかくなので画像コンバータ機能とかでも追加すれば面白くなるかも。

GDI+ を使うのが一番だよなぁ。
C# で使えば死ぬほど簡単だ。

using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

namespace iconpoli2
{
	class Program
	{
		static void Main(string[] args)
		{
			foreach (var s in args)
			{
				string t = Path.ChangeExtension(s, "jpg");
				Bitmap b = new Bitmap(s);
				b.Save(t, ImageFormat.Jpeg);
			}
		}
	}
}

コンバートならマジでこれだけだ。
いや、せっかくなので C++ でやりたいぞ。
と思ったけど

Gdiplus::Bitmap * img = new Gdiplus::Bitmap(__wargv[i]);
img->Save(outname, &Gdiplus::ImageFormatJPEG);

とやっても全然上手くいかない。

Transforming a JPEG Image Without Loss of Information (Windows)
Retrieving the Class Identifier for an Encoder (Windows)

どうやら自力で CLSID を取ってくるしかないようで。
つか ImageCodecInfo って class なのにサイズを得て malloc なの?
試しに普通な new で作ったら見事にヒープ領域不足、どういう設計だよ。

とにかく MSDN の方法で上記と同じになるよう C++ で作ってみる。
C++ じゃ ChangeExtension なんて便利なものは無いし GDI+ 初期化も必須。

stdafx.h

#pragma once

// 面倒なのでココで参照追加
#pragma comment(lib, "gdiplus.lib")

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0600 // Vista
#endif

#include <stdio.h>
//#include <tchar.h>
#include <wchar.h>
#include <windows.h>
#include <gdiplus.h>

凄く無駄だと解っているけど tchar を利用しないで UNICODE 関数をまんま使う。
てか今となっては Ansi 関数を併用する必要は無いと思うんだが。
WCHAR や UINT のマクロも利用したくない。
だって Visual Studio は大文字マクロの型名では色が付かないんだよね。
というかなるべく小文字にしたいと思うのは多分 Python のやりすぎ。

iconpoli.cpp

#include "stdafx.h"

using namespace Gdiplus;

int GetEncoderClsid(const wchar_t* format, CLSID* pClsid)
{
	unsigned int  num = 0;
	unsigned int  size = 0;

	ImageCodecInfo * pImageCodecInfo = NULL;

	GetImageEncodersSize(&num, &size);
	if(size == 0)
		return -1;  // Failure

	// コレだとヒープ領域不足になる
	//ImageCodecInfo * pImageCodecInfo = new ImageCodecInfo();
	pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
	if(pImageCodecInfo == NULL)
		return -1;  // Failure

	GetImageEncoders(num, size, pImageCodecInfo);

	for(unsigned int j = 0; j < num; ++j)
	{
		if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
		{
			*pClsid = pImageCodecInfo[j].Clsid;
			free(pImageCodecInfo);
			return j;  // Success
		}    
	}

	free(pImageCodecInfo);
	return -1;  // Failure
}

bool ChangeExtension(const wchar_t* src, wchar_t* dst, const wchar_t* ext)
{
	wchar_t path[4][256];
	_wsplitpath_s(src, path[0], 256, path[1], 256, path[2], 256, path[3], 256);
	_wmakepath_s(dst, 1024, path[0], path[1], path[2], ext);
	return TRUE;
}

int wmain(int argc, wchar_t* argv[])
{
	// GDI+ 初期化
	GdiplusStartupInput gdiplusStartupInput;
	ULONG_PTR gdiplusToken;
	GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

	for (int i=1; i < __argc; i++)
	{
		// makepath
		wchar_t outname[1024];
		ChangeExtension(__wargv[i], outname, L".jpg");

		Gdiplus::Bitmap * img = new Gdiplus::Bitmap(__wargv[i]);

		CLSID encoderClsid;
		int r = GetEncoderClsid(L"image/jpeg", &encoderClsid);
		if (r == -1)
		{
			wprintf(L"no CLSID");
			return 0;
		}
		// コレは上手くいかない
		//int result = img->Save(outname, &Gdiplus::ImageFormatJPEG);
		int result = img->Save(outname, &encoderClsid);
		if (result == Gdiplus::Ok)
			wprintf(L"Encode Success");
		else
			wprintf(L"Encode Failure");
		delete img;
	}
	// 後片付け
	Gdiplus::GdiplusShutdown(gdiplusToken);
	return 0;
}

長い。。。。。

たったこれだけのモンにえらく時間が掛かった、やっぱり C# のほうがいいかなと。
それと Python や C# ばかりやっていると開放処理をつい忘れそうになるよね。