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

Clipoli 0.0.1

Windows アプリ作りの為に新しいノートを買うか迷っている。
が、よく考えたら VirtualBox 仮想マシンの Vista があるじゃないか。
Aero が使えず遅いので全然使わなかったから存在を忘れる所だった。

これを使えばホストの Ubuntu で使っているマジェスタッチで打てる。
あぁキーボード入力が楽々!

しかもコイツには Visual Studio 2008 pro をインストールしてある。
Debug が楽々、コード保管で楽々、リファクタで楽々、何もかもが楽々!
流石はアップグレードに六万円も払っただけはある、これぞ統合開発環境。
テキストエディタだけで開発をしばらくやった後で触るとマジで神アプリ。

でも基本的にこんなものをインストールしないで作れるアプリにしたい。
ので bat の W クリックだけでビルドできるようにアプリ作成を続ける。

とにかく clipoli というタスクトレイ常駐するアプリだ。
当面はランチャとクリップボードにテキストを入れる機能を本格的に。
カスタマイズが簡単にできるように何かのファイルからデータを入れる。

よし、INI ファイルだ。

あぁ Opera 使いの性。

Opera 屋以外はアドオンと考えそう…

ま、仕様をアッサリ決められるかどうかがプロと凡人の違い。
なんだけど後悔することのほうが多いのは書くまでもない。

[Launcher]
メモ帳=C:\Windows\notepad.exe
端末=C:\Windows\System32\cmd.exe
MSDN=http://msdn.microsoft.com/ja-jp/library/

[Clipboard]
けいおん=あずにゃん
俺妹=きりの
えむえむ=みおたん
声優=全部同じ人だった...

みたく INI を作り読み込んでメニューの作成という流れでとりあえずいこう。
C# での INI 読み書きは以前作っているから簡単だ。

UTF-8 の ini 読み書き

と思ったけど今見ると書いていることが少々古い…
てか MessageBox をココで使うと WindowsForm だと using が足らない。
Python で作ったの同様に間違いは例外を投げるように変更して書き換えなきゃ。

そんなことより、このアプリの場合はこんな複雑なコードいらないヤン…

iniread.cs

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;

namespace IniFile
{
	public struct Ini
	{
		public string section {get; set;}
		public string key {get; set;}
		public string value {get; set;}
	}
	public static class IniReader
	{
		public static IEnumerable<Ini> ReadLines(string path)
		{
			if (File.Exists(path))
			{
				string section = "";
				using (var sr = new StreamReader(path, Encoding.UTF8))
				{
					string buf;
					while ((buf = sr.ReadLine()) != null)
					{
						if (buf == "") continue;
						if (buf[0] == ';') continue;
						int len = buf.Length;
						if (len > 2 && buf[0] == '[' && buf[len - 1] == ']')
						{
							section = buf.Substring(1, len - 2);
						}
						else if (section == "")
						{
							//何もしない
						}
						else
						{
							int lPos = buf.IndexOf('='); //見つからない場合は -1
							if (lPos > 0)
							{
								Ini ini = new Ini();
								ini.section = section;
								ini.key = buf.Substring(0, lPos);
								ini.value = buf.Substring(lPos + 1, len - lPos - 1);
								yield return ini;
							}
						}
					}
				}
			}
		}
	}
}

これで十分だった、struct を yield で戻せば必要な値は取れるし。
んでコレを展開するコードを。

mainsrc.cs

using System;
using System.IO;
using System.Windows;
using System.Windows.Forms;
using System.Drawing;
using System.Reflection; // MethodInfo
using IniFile;

namespace Clipoli
{
	class TrayIcon
	{
		private NotifyIcon _icon;
		private ContextMenuStrip _menu;
		public TrayIcon()
		{
			_menu = new ContextMenuStrip();
			int i = 0;
			foreach (var ini in IniReader.ReadLines("default.ini"))
			{
				// Load Launcher
				if (ini.section == "Launcher")
				{
					_menu.Items.Add(ini.key, null, on_launcher);
					_menu.Items[i].Name = ini.value;
					i++;
				}
				// Load Clipboard
				else if (ini.section == "Clipboard")
				{
					_menu.Items.Add(ini.key, null, on_clipboard);
					_menu.Items[i].Name = ini.value;
					i++;
				}
				else
				{
					MessageBox.Show("現在 Launcher と Clipboard 以外のセクションは無効です");
				}
			}
			_menu.Items.Add("-");
			_menu.Items.Add("バージョン情報", null, on_about);
			_menu.Items.Add("-");
			_menu.Items.Add("終了", null, on_exit);
			// create tray icon
			_icon = new NotifyIcon();
			_icon.ContextMenuStrip = _menu;
			_icon.Icon = Icon.ExtractAssociatedIcon("clipoli.exe");
			_icon.Text = "Description Text";
			_icon.Visible = true;
			_icon.MouseUp += on_click;
		}

		private void on_click(object sender, MouseEventArgs e)
		{
			if (e.Button == MouseButtons.Left)
			{
				MethodInfo mi = typeof(NotifyIcon).GetMethod("ShowContextMenu",
								BindingFlags.Instance | BindingFlags.NonPublic);
				mi.Invoke(_icon, null);
			}
		}
		private void on_launcher(object sender, EventArgs e)
		{
			try
			{
				string s = (sender as ToolStripMenuItem).Name;
				System.Diagnostics.Process.Start(s);
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.Message);
			}
		}
		private void on_clipboard(object sender, EventArgs e)
		{
			try
			{
				string s = (sender as ToolStripMenuItem).Name;
				Clipboard.SetText(s);
				System.Media.SystemSounds.Beep.Play();
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.Message);
			}
		}
		private void on_about(object sender, EventArgs e)
		{
			var about = new About(_icon.Icon);
			about.ShowDialog();
		}
		private void on_exit(object sender, EventArgs e)
		{
			_icon.Visible = false;
			Application.Exit();
		}
	}
	class __main__
	{
		[STAThread]
		public static void Main(string[] args)
		{
			var n = new TrayIcon();
			Application.Run();
		}
	}
}

ContextMenu は ContextMenuStrip に変更したほうが扱いやすいみたいだったので。

その ContextMenuStrip.Name プロパティに文字列を入れるというウソみたいなことをやった。
だってこれで動くもの、sender から簡単に文字列が得られるし。

トレイアイコンの左クリックでメニューを出す方法は下記をパクった。
NotifyIconの左クリックでコンテキストメニューを表示させる:Gushwell’s C# Dev Notes

見えない Form をアクティブにするなんて方法も見つけたけど上手くいかなかった。
というか、こんなに強引なのに一番簡単かつ短いコードで終わるという。

想像していたより遥かに短いコードでこれだけ実装できてしまった。
私的にはもうコレで十分、ini の書き方が解らない人なんていないだろうし。
後は何を付けよう、というわけでまとめて zip

clipoli001.zip

しかしマジで Windows マシンはどうしよう?
キーボードでノートを選ぶなら東芝 R730 か Lenovo X201s がよさそうだが…
11.6 型の今でも持ち運びする時に少々大きいし…
1366×768 は正直狭いのでせめて 1600×900 以上にしたいけど…
かといってデスクトップでは使用歩度から考えて邪魔だし…
ブルーレイの再生をしたくなったら動画再生支援機能に問題が無い Windows のほうが…
結局決まらないのでミニノート 7 と仮想マシンの XP, Vista のままになりそう。

Clipoli 0.0.0

IronPython を Windows 7 から削除の予定。
言語としては素晴らしいのだがなにせ遅い、我慢するのはもう限界だ。
さて困った、こいつは自分で利用しているからなんとかしなければ。

NotifyIcon to use from IronPython

C# で作り変えするしか無い、いや C++ でも作れるけど何故かやりたくない。
WindowsForm を使うので同じなんだが .NET で作りたいので。

せっかく作り変えるのだし exe にまとめるのだし、もう少し拘りたい。

まずはアイコンを埋め込みしたい、これは Python じゃ無理だったので。
しかし Form は使わないので this.Icon からトレイ用のアイコンを取得できない。

Icon.ExtractAssociatedIcon メソッド (System.Drawing)

なんだ、exe からアッサリ取得できるみたい。
ということは自分自身の exe 名を指定だけでイケそうだ。
アイコンの指定は csc.exe への引数で埋め込みできるもんね。

どうでもいいかもしれないけど exe 名の名前空間に収めたいな。
exe 名は、minipoli 同様なミニアプリだから clipoli にしよう。

そう、Windows 用の新規アプリで公開してしまおうと、三年ぶりか。
Cinema とかのもう自分が使っていないアプリの公開を終了するか悩む所。

そうそう、バージョン情報ダイアログも追加しなきゃ。
これは SeeMe v4 の時に作ったのを持ってきて、って SeeMe v4 は WPF だ。
流用すると WindowsForm と混在になるので名前空間がややこしくなる…

しかたがない、 Form でテキトーに作ってみるか。

about.cs

using System;
using System.Windows;
using System.Windows.Forms;
using System.Drawing;

namespace Clipoli
{
	class About : Form
	{
		public About(Icon icon)
		{
			this.Text = "About Clipoli";
			this.Icon = icon;
			this.Width = 300;
			this.Height = 200;
			var label = new Label();
			label.Location = new Point(10, 10);
			label.Text = "情報";
			this.Controls.Add(label);
			var button = new Button();
			button.Location = new Point(10, 100);
			button.Text = ("OK");
			button.Click += on_ok;
			this.Controls.Add(button);
		}
		void on_ok(object sender, EventArgs e)
		{
			Close();
		}
	}
}

WindowsForm が嫌いだからといえ我ながらテキトーすぎる。
それはそれとしてメインソース。

mainsrc.cs

using System;
using System.IO;
using System.Windows;
using System.Windows.Forms;
using System.Drawing;

namespace Clipoli
{
	class TrayIcon
	{
		private NotifyIcon _icon;
		public TrayIcon()
		{
			var menu = new ContextMenu();
			menu.MenuItems.Add("メモ帳", on_notepad);
			menu.MenuItems.Add("クリップボード", on_clipboard);
			menu.MenuItems.Add("-");
			menu.MenuItems.Add("バージョン情報", on_about);
			menu.MenuItems.Add("-");
			menu.MenuItems.Add("終了", on_exit);
			// create tray icon
			_icon = new NotifyIcon();
			_icon.ContextMenu = menu;
			_icon.Icon = Icon.ExtractAssociatedIcon("clipoli.exe");
			_icon.Text = "Description Text";
			_icon.Visible = true;
		}
		private void on_notepad(object sender, EventArgs e)
		{
			System.Diagnostics.Process.Start("notepad.exe");
		}
		private void on_clipboard(object sender, EventArgs e)
		{
			try
			{
				Clipboard.SetText("あずにゃん");
				System.Media.SystemSounds.Beep.Play();
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.Message);
			}
		}
		private void on_about(object sender, EventArgs e)
		{
			var about = new About(_icon.Icon);
			about.ShowDialog();
		}
		private void on_exit(object sender, EventArgs e)
		{
			_icon.Visible = false;
			Application.Exit();
		}
	}
	class __main__
	{
		[STAThread]
		public static void Main(string[] args)
		{
			var n = new TrayIcon();
			Application.Run();
		}
	}
}

アイコンは IronPython 用に作ったのを仮で置いてビルドバッチ。
WindowsForm で .net 4.0 は馬鹿馬鹿しいので 3.5 の csc exe で。

build.bat

C:\Windows\Microsoft.NET\Framework\v3.5\csc.exe ^
/reference:System.Windows.Forms.dll ^
/reference:System.Drawing.dll ^
/target:winexe ^
/out:clipoli.exe ^
/win32icon:icon.ico ^
/optimize+ ^
*.cs
PAUSE

まとめて ZIP
clipoli000.zip

まだテスト段階だしこんな感じでいいだろう。
動かしてみる、まあ IronPython 作の時とほぼ同じだし当然動くか。

問題はトレイアイコンの右クリックしか受け付けないことなんだよな。
自分で使うだけの分にはそれでいいのだが公開するとなると…

というか、ミニノートで作るのが少々辛くなってきた。
意地をはってもしかたがないし、もう少しマシな Windows を買おうかなと。

Fake System.IO

System.IO.File.ReadLines は .NET 3.5 や mono で使えない。
だったら自分で作ってしまえ、と思ったのでやってみた。

どうでもいいけど gmcs も csc.exe と同じオプションが使えるんだね。
/out:FILENAME は -out:FILENAME みたくハイフンにするだけ。

いや、yield を使ったことが無いのでやってみようかと。
こんな強引というよりアホなメソッド追加なんてできるのかな?

using System;
using System.Collections.Generic; //IEnumerable

namespace System.IO
{
    public static partial class File
    {
        public static IEnumerable ReadLines(string path)
        {
            using(var sr = new System.IO.StreamReader(path))
            {
                string buf;
                while ((buf = sr.ReadLine()) != null)
                {
                    yield return buf;
                }
            }
        }
    }
}

File クラス (System.IO)
File クラスは partial class では無いみたいだけど。

コンフリクトか、やっぱりダメだった。
Windows でもやってみたけど同じ、こんなことをやろうと考えるほうがおかしい。
しかたがない、名前空間には追加できるはずだから違うクラス名にしてみる。

fakeio.cs

using System;
using System.Collections.Generic; //IEnumerable

namespace System.IO
{
    public static class FakeFile
    {
        public static IEnumerable ReadLines(string path)
        {
            using(var sr = new System.IO.StreamReader(path))
            {
                string buf;
                while ((buf = sr.ReadLine()) != null)
                {
                    yield return buf;
                }
            }
        }
    }
}

readlines.cs

using System;
using System.IO;

class __main__
{
    [STAThread]
    public static void Main(string[] args)
    {
        if (args.Length == 0)
        {
            Console.WriteLine("no arg...");
            return;
        }
        int n = 0;
        foreach (var line in FakeFile.ReadLines(args[0]))
        {
            n++;
            Console.WriteLine("{0}: {1}", n.ToString("000"), line);
        }
    }
}

で、cat.exe という実行ファイルを作るコマンド。

gmcs -out:cat.exe *.cs

何故か exe という拡張子を付けないとエラーになる。
Linux でも強制するんかい、まぁ解りやすいと思えばいいし。
アスタリスクでディレクトリ内をまるごとコンパイルしてくれるのはありがたい。

なるほど、yield はこういう時に使うと確かに便利だ。
C#4.0 の ReadLines って上記を自作する以上のメリットがあるのかな?

Default argument and ReadLines

何を今頃なのだが C# 4.0 を調べてみた。
なんと 4.0 では関数にデフォルト引数を指定できるようになっていた。

てか、今まで無かったのかよ…

Default argument
で可能とかウソを書いてしまった、はずかしい。
引数に数の差だけのためにオーバーロードをする必要はコレで無くなるわけだ。

それと、System.IO.File.ReadLines() というイテレーション関数も追加らしい。
StreamReader.ReadLine() じゃダメなの?と思うんだが…

他は興味がわかないので今回はパス。

ということで 3.5 と 4.0 の csc.exe で試しコードを書く。

default_param.cs

using System;

class Test
{
    public static int Func(int x=5, int y=7)
    {
        return x+y;
    }
}

class __main__
{
    [STAThread]
    public static void Main(string[] args)
    {
        int n = Test.Func(y:20);
        Console.WriteLine(n);
    }
}

readlines.cs

using System;
using System.IO;

class __main__
{
    [STAThread]
    public static void Main(string[] args)
    {
        int n = 0;
        //foreach (var line in File.ReadAllLines(args[0]))
        foreach (var line in File.ReadLines(args[0]))
        {
            n++;
            Console.WriteLine("{0}: {1}", n.ToString("000"), line);
        }
    }
}

ちなみに私がパスを通しているのは 3.5 のみなのでこうなる。
うん、やっぱりどちらも 4.0 でしかビルドできないや。

これだけではツマラナイので Ubuntu でもやってみる。

あれ?デフォルト引数のほうだけ問題なくビルドできちゃった。
どっちなんだよ Ubuntu に最初から入っている mono は。

Readlines は「そんなメソッドありません」ですね、そりゃそうだ。
ReadAlllines とコメントアウトを変更すれば普通にビルドできた。
StreamReader で while ループしたほうが圧倒的効率だけどテストです。

つまり何が言いたいかというと。
コンパイルするのがメンドイけど実用無視で遊ぶなら C# は最強。
Linux で Python は単なる実用ツールでしかないもの、私はだが。

Using wget

結局 Windows に戻ったのは正月だけだった、あぁ Linux は快適。

Linux で不便なのは Opera 11 の検索バーで日本語変換を行うと死ぬことだけだ。
フォントは pre と monospace を VL ゴシックに変更しただけで綺麗になった。

一昔前では考えられないほどマトモ、新参の WebKit 軍団にボロ負けだったしね。
しかしデフォルトショートカットキーをコロコロ変更しないでよ。

ネットをウロウロしていると自作漫画配布サイトに。
最近は Blog 機能を利用しているせいか連番で落とせないトコばかり。
だったけど連番で一枚一枚配布している所もまだ存在するんだね。

これは久々に新規スクリプトを書かないと。
何のために wget コマンドが存在するというのか。
wget だけでリンクをたどるのではなく連番ファイル以外はいらないし。

連番最初は 0 か 1 だし確実に 100 ファイル以下だと決め付けでいいだろう。
パラメータ 2 つ指定を強制するけど自分で使うんだから問題無い。
一応ゼロ詰めしているかどうかで振り分けしている。

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

"""
    連番ファイルの一括ダウンロード
    
    コマンド
    $ download url num
    
    url 連番最初のファイルのフルパス
    num 最後のファイル番号の整数
"""

import sys
import os

if len(sys.argv) < 3:
    print "URL と 数を指定してください"
else:
    # ファイル名分離
    pathname, filename = os.path.split(sys.argv[1])
    try:
        n = filename.rindex(".")
    except ValueError:
        print "拡張子が見つかりません"
        sys.exit()
    # 連番最初の数値(0 or 1) と拡張子
    begin = int(filename[n-1])
    ext = filename[n:]
    # ゼロ詰めかどうかで振り分け
    if filename[n-2] == "0":
        name = "wget " + pathname + "/" + filename[:n-2] + "%02d" + ext
    else:
        name = "wget " + pathname + "/" + filename[:n-1] + "%d" + ext
    # 連番数のチェック
    try:
        end = int(sys.argv[2]) + 1
    except ValueError:
        print "二番目の引数は整数でお願いします"
        sys.exit()
    # 開始
    for i in range(begin, end):
        os.system(name % i)
        #print name % i

~/bin に download という名前で置いて chmod で +x して。
覚書ページに書いているけど ~/bin を作れば Ubuntu は勝手にパスが通る。

使ってみる、よしよし動く。
端末を普通に使うようになると「コレで十分」になって退化する私であった。
てゆーか、アッサリこんなのを作って使えるから Linux のほうが楽なんだよね。

実は Python での文字列のスライス方法を忘れていたので何か作ろうと…
こんなしょーもないスクリプトあたりをたまには作らないと忘れるねん。

関係無いけどこんなのを見つけた。

[ Dolphinで右クリックメニューを使う方法 Dolphin用Script] by ひねもすLinux

Dolphin でもスクリプト拡張ができるんだ、知らなかった。
しかし面倒くさいな、Nautilus がこうならないことを祈る。