Ubuntu Linux サーバーをルーターにする

ある日突然Fedora LinuxTomcatサーブレットが動かなくなってしまった。本来であれば原因を調べて復旧すべきなのだが、一刻も早くQMACloneを復旧させたかったため、Ubuntuに乗り換えることにした。以下はその時の備忘録である。

PPPoEを用いてネットに繋ぐ

Ubuntu Serverにはデフォルトで"pppoeconf"というコマンドがインストールされるので、これを引数なしで実行してPPPoEの設定を行う。このとき接続IDはドメイン付きで指定する。ODNの場合は"@odn.ne.jp"。

pppoeconf

NICを立ち上げる

LAN側のNICのせっていを行う。

auto eth1
iface eth1 inet static
	address 192.168.0.1
	netmask 255.255.255.0
	broadcast 192.168.0.255

DHCPサーバーを立ち上げる

"dhcp3-server"をインストール後、以下のファイルを書き換える。

/etc/dhcp/dhcpd.conf
subnet 192.168.0.0 netmask 255.255.255.0 {
       range 192.168.0.128 192.168.0.191;
       option routers 192.168.0.1;
       option subnet-mask 255.255.255.0;
       option broadcast-address 192.168.0.255;
       option domain-name-servers 192.168.0.1;
       default-lease-time 6000;
       max-lease-time 7200;
}

/etc/default/dhcp3-server

INTERFACES="eth1"

LANの内側からWANに繋がるようにする

iptablesIPマスカレードの設定をする。設定はwebmin経由が楽。これ以外に以下の設定を行う。

/etc/sysctl.conf
net.ipv4.ip_forward=1

Fedora Linuxサーバーをルーターにする

QMACloneが夜間に重くなる原因を調査しているのだが、ネットワークに詳しい人に聞いたところ、Corega製の安物ルーターのせいではないかとアドバイスを頂いた。そこで、YAMAHANTT-ME製のルーターに買い換えようと思ったのだが、近くのPCショップには置いていなかった。仕方なく、LinuxサーバーにNICを2枚差してルーターにすることにした。設定方法を備忘録として書いておく。

Linuxサーバーに追加した設定は以下の通り。

  • PPPoEを用いてネットに繋ぐ
  • 新しく差したNICにstaticなIPを割り当てる
  • DHCPサーバーを立ち上げる
  • LANの内側からWANに繋がるようにする

PPPoEを用いてネットに繋ぐ

"rp-pppoe"というパッケージをインストールすると"pppoe-*"というコマンドが使えるようになる。このうち"pppoe-setup"というコマンドで、PPPoEの設定ができる。"pppoe-start"でPPPoE接続を開始、"pppoe-stop"で切断。
WAN側のNICの設定ファイル"/etc/sysconfig/network-scripts/ifcfg-*"は以下のようにしておくと、起動時にDHCPクライアントがタイムアウトするのを待たずに済むようになるらしい。

DEVICE="(インターフェース名)"
ONBOOT="yes"

新しく差したNICを立ち上げる

Fedora15からはNICのインターフェース名の命名規則が変わったため、若干分かりにくい。"ifconfig -a"でインタフェース名が確認できるのでメモ。"/etc/sysconfig/network-scripts/ifcfg-*"を以下のように設定する。

DEVICE="(インターフェース名)"
ONBOOT="yes"
BOOTPROTO="static"
TYPE="Ethernet"
IPADDR="192.168.0.1"
NETMASK="255.255.255.0"
NETADDR="192.168.0.0"
BROADCAST="192.168.0.255"
PEERDNS="no"

"sudo /etc/init.d/network restart"して設定を反映させる。

DHCPサーバーを立ち上げる

DHCPサーバーをインストールする。

$ sudo yum install dhcp

設定ファイル"sudoedit /etc/dhcp/dhcpd.conf"の内容は以下の通り。

subnet 192.168.0.0 netmask 255.255.255.0 {
       option routers      192.168.0.1;
       option subnet-mask  255.255.255.0;
       option domain-name-servers       143.90.130.39, 143.90.130.165, 8.8.8.8, 8.8.4.4;
       range  dynamic-bootp     192.168.0.128   192.168.0.191;
       default-lease-time       21600;
       max-lease-time           43200;
}

DNSサーバーを立ち上げるのが面倒だったため、ISPDNSサーバーを直書きしてしまった。後半二つはGoogleが提供しているDNS
起動時に起動するようにする。

$ sudo systemctl enable dhcpd.service

LANの内側からWANに繋がるようにする

iptablesIPマスカレードを行う。設定はwebmin経由が楽。以下のコマンドを打っておく。

echo "1" > /proc/sys/net/ipv4/ip_forward

127.0.0.1にアクセスできなくなった場合はiptablesを再起動してみる。

参考リンク

MySQLをバージョンアップした際にプロシージャーが動かなくなる問題

MySQLのメジャーバージョンを変更すると、プロシージャーが動かなくなることがある。これはバージョンアップによりプロシージャー関連のテーブルの構造が変更されたのが原因だそうだ。以下のコマンドを使うと一応動くようになる。

mysql_upgrade -uroot -p(管理者パスワード) --force

リンク

COBOLを全く知らない自分がCOBOLを想像してみた

自分の周りにはCOBOLを嫌う(性格にはCOBOLerを嫌う)人が多いように思う。自分はCOBOを知らないので、なぜそんなにCOBOLが嫌われているのか良く分からない。ということで、ネットで見つかる断片的な情報を元にCOBOLがどんな言語なのか想像してみた。

Wikipediaを中心にCOBOLに関する断片的な情報を集めてみると、こんな感じになった。

  • 自然言語である英語に近い記述になるようなコマンド語彙や構文
    • 全て現在形、語尾変化なし
    • ピリオド(厳密にはピリオド及びその後に続くスペース)を記述してコードの行端を示す
  • オブジェクト指向にも対応
  • カラムの位置が重要
  • 金額計算など事務処理(商用計算)の用途に広く使われている

まずは想像してみる

金額計算や事務処理ができるので、数値や文字列を扱うことが出来るはず。数値は多分整数と浮動小数だと思う。文字列はよく分からないが内部的にはUnicodeなんじゃないかな。ということで、数値や文字の表現はこんな感じ。

12,345,678,901,234,567,890
1,357,924,680.135792468
"コボル"

金額を計算しなければならないので、多分JavaのBigIntegerやBigDecimalみたいなやつになるとおもう。あと、人間が見やすいように数値の途中に桁区切りの","が入っていると思う。

多分変数という概念はあるので、変数への代入はきっとこんな感じ。

ASSIGN 1 TO A.

四則演算もあるはずで、きっとこんな感じ

ADD 2 TO B.
SUBTRACT 3 FROM C.
MULTIPLY D BY 4.
DIVIDE E BY 5.

でもこれだけだと複雑な式の計算がものすごく面倒になるので、texの数式表現も使えると思う。

ASSIGN 6.0 TO X.
ASSIGN $\exp^{X}+\exp^{-X}$ TO F.

画面に表示するときは、きっと帳簿とか領収書みたいなやつしか表示しないはずなので、宣言部分でHTMLの雛形を定義して、そこのにテンプレートエンジン的な感じで当てはめるのだと思う。あと、オブジェクト指向だから、何とかのオブジェクトの中の何とかという表現が使えるはず。Pythonの"""とかも使えるんじゃないかな。

ASSIGN """<html>
<header><title>タイトル</title></heaer>
<table><tr><td>$Y$</td><td>$Z$</td></tr></table>
</html>""" TO GAMEN AS TEMPLATE.
ASSIGN "左" TO GAMEN'S Y.
ASSIGN "右" TO GAMEN'S Z.

標準出力くらい使えるよね。

WRITE GAMEN TO STANDARD OUTPUT.

条件分岐も必要のはず。カラムの位置が重要と書いてあったので、多分Pythonみたいにインデントでブロックを分ける方式になっているんじゃないかな。インデントはタブだと視覚的に見づらいので、先頭の"-"のかずで行うことにする。使われている予約後はBASICのやつっぽい気がするので、

IF G IS 7 THEN
-ASSIGN 10 TO H.
-MULTIPLY G BY 8.
ELSE
-ASSIGN 11 TO H.
-MULTIPLY G BY 9.
-IF G IS 42 THEN
--CALCULATE $\log(X)$ AND ASSIGN TO H.

ループもあると思う。きっとネスト出来る

ASSIGN 1 TO I.
REPEAT UNTIL I IS LESS THAN 10
-ASSIGN 1 TO J.
-REPEAT UNTIL J IS LESS THAN 10
--WRITE I TO STANDARD OUTPUT.
--WRITE "×" TO STANDARD OUTPUT.
--WRITE J TO STANDARD OUTPUT.
--WRITE "=" TO STANDARD OUTPUT.
--CALCULATE $I*J$ AND ASSIGN TO K.
--WRITE K TO STANDARD OUTPUT.
--WRITE END OF LINE TO STANDARD OUTPUT.
--ADD 1 TO J.
-ADD 1 TO I.

きっとMaximaみたいに数式処理もお手のものなんじゃないかな。方程式解いたりするのとか。

ASSIGN 10.0 TO A.
ASSIGN 20.0 TO B.
ASSIGN 30.0 TO C.
SOLVE $AX^2+BX+C=0$ ABOUT X.
WRITE X TO STANDARD OUTPUT.

データベースも扱えないとまずい。多分こんな感じ?SQLとか使うのかなぁ?

DEFINE DATABASE CONNECTION AS CON WHERE ID IS "hoge" AND PASSWORD IS "fuga".
DEFINE DATABASE TABLE AS TBL WHERE TABLE NAME IS "hage" USING CON.
ASSIGN TBL'S "012345"'S "name" TO NAME.
WRITE NAME TO STANDARD OUTPUT.

多分こんな感じ。正直使いたくない。

注意

上記の架空言語はもちろん架空言語です。あまり面白くないですがエイプリルフールのネタとしてお赦しください。

Trac 0.12以降でsubversionからチケットを更新する方法

Trac 0.12以降を導入してsubversionのコミットからチケットをcloseできなくなってしまった。Trac 0.11まではsubversionのレポジトリのhookディレクトリにpost-commitを配置して、trac-post-commit-hookを呼び出せばよかったのだが、Trac 0.12からはやり方が変わったようだ。

Trac 0.12以降でsubversion経由でチケットをclose出来るようにするには以下ので順を踏めば良い。以下ではMTracで複数のプロジェクトを動かしている場合を想定している。ディレクトリ名等は適宜読み替えること。

手順

  • subversionのレポジトリのhookディレクトリに以下をpost-commitという名前で実行可能属性をつけて配置。
#!/bin/sh
export TRAC_ADMIN="/opt/local/bin/trac-admin"
export TRAC_ENV_PARENT_DIR="/Volumes/Data/trac"
export PYTHON_EGG_CACHE="${TRAC_ENV_PARENT_DIR}/.egg_cache"
export TRAC_ENV="${TRAC_ENV_PARENT_DIR}/hoge"
${TRAC_ADMIN} ${TRAC_ENV} changeset added "$1" "$2"
  • 同様にpost-revprop-changeを配置
#!/bin/sh
export TRAC_ADMIN="/opt/local/bin/trac-admin"
export TRAC_ENV_PARENT_DIR="/Volumes/Data/trac"
export PYTHON_EGG_CACHE="${TRAC_ENV_PARENT_DIR}/.egg_cache"
export TRAC_ENV="${TRAC_ENV_PARENT_DIR}/hoge"
${TRAC_ADMIN} ${TRAC_ENV} changeset modified "$1" "$2"
  • trac-adminを使用してユーザーに管理画面の使用権限を付加
$ trac-admin hoge permission add fuga TRAC_ADMIN
  • tracのページの右上にある管理->プラグインからCommitTicketUpdaterを有効にする。

以上で0.11以前と同様に"close #123"や"fix #234"と書くだけでチケットがcloseされるようになった。

前の方法とこの方法とどちらがエレガントなのだろう・・・。

リンク

Javaからプリンターへ印刷する

とあるイベントでWebフォームで入力したテキストをプリンターで印刷するプログラムを書いた。その時の備忘録。

フォーム

フォームはGoogle Web Toolkitでサクッと作った。以下はクライアント側のソースコードの一部。テキストボックス二つと送信ボタンというシンプルな物。

public class JagPrint implements EntryPoint, ClickHandler {
	private static final int MAX_PREVIEW = 100;
	final TextBox textBoxTeamName = new TextBox();
	final TextArea textAreaSourceCode = new TextArea();
	final Button buttonSend = new Button("send");

	public void onModuleLoad() {
		final Grid grid = new Grid(2, 2);
		grid.setWidget(0, 0, new Label("team name"));
		grid.setWidget(1, 0, new Label("source code"));
		grid.setWidget(0, 1, textBoxTeamName);
		grid.setWidget(1, 1, textAreaSourceCode);
		textBoxTeamName.setWidth("640px");
		textAreaSourceCode.setSize("640px", "480px");
		RootPanel.get().add(grid);
		RootPanel.get().add(buttonSend);
		buttonSend.addClickHandler(this);
	}

	@Override
	public void onClick(ClickEvent event) {
		if (event.getSource() == buttonSend) {
			final String teamName = textBoxTeamName.getText();
			final String sourceCode = textAreaSourceCode.getText();

			final StringBuilder sb = new StringBuilder();
			sb.append("team name: ").append(teamName).append('\n');
			sb.append("source code:\n");
			sb.append(sourceCode.length() > MAX_PREVIEW ? sourceCode.substring(0, MAX_PREVIEW) + "..." : sourceCode);
			if (!Window.confirm(sb.toString())) {
				return;
			}

			Rpc.Util.get().send(teamName, sourceCode, callbackSend);
			setEnabled(false);
		}
	}

	private final AsyncCallback<Void> callbackSend = new AsyncCallback<Void>() {
		@Override
		public void onSuccess(Void result) {
			Window.alert("done.");
			setEnabled(true);
		}

		@Override
		public void onFailure(Throwable caught) {
			Window.alert(caught.getLocalizedMessage());
		}
	};

	public void setEnabled(boolean b) {
		final FocusWidget[] widgets = { textBoxTeamName, textAreaSourceCode, buttonSend, };
		for (FocusWidget widget : widgets) {
			widget.setEnabled(b);
		}
	}
}

RPC

続いてRPC部分。こちらも定形通り。

@RemoteServiceRelativePath("rpc")
public interface Rpc extends RemoteService {
	public static class Util {
		public static RpcAsync get() {
			return GWT.create(Rpc.class);
		}
	}

	public void send(String teamName, String sourceCode) throws Exception;
}
public interface RpcAsync {
	void send(String teamName, String sourceCode, AsyncCallback<Void> callback);
}

Servlet

最後にServlet部分。swingのJEditorPaneには簡易印刷機能が付いているそうで、これを利用している。以下の例では印刷設定ダイアログを表示しないように設定して、デフォルトプリンタに出力している。Servletの中でswingを使うというのも妙な話だと思う。

@SuppressWarnings("serial")
public class RpcImpl extends RemoteServiceServlet implements Rpc {
	@Override
	public void send(String teamName, String sourceCode) throws Exception {
		sourceCode = sourceCode.replaceAll("\t", "    ");

		final StringBuilder sb = new StringBuilder();
		sb.append("team name: ").append(teamName).append('\n');
		sb.append("source code:\n");
		sb.append(sourceCode);

		final JEditorPane pane = new JEditorPane();
		pane.setText(sb.toString());
		pane.print(null, null, false, null, null, true);
	}
}

いざDeploy

上記のコードをGoogle Web ToolkitでコンパイルしてWindows上でサービスから起動したTomcatで動かそうとすると、以下のエラーメッセージがログに出力され正しく印刷されない。これはTomcatがローカルシステムアカウントで動いており、プリンタへのアクセス権限がないからだそうだ。(詳しい話は分からない。)

com.google.gwt.user.client.rpc.SerializationException: Type 'java.awt.print.PrinterException' was not included in the set of types which can be serialized by this SerializationPolicy or its Class object could not be loaded. For security purposes, this type will not be serialized.: instance = java.awt.print.PrinterException: No print service found.
	at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serialize(ServerSerializationStreamWriter.java:614)
	at com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamWriter.writeObject(AbstractSerializationStreamWriter.java:126)
	at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter$ValueWriter$8.write(ServerSerializationStreamWriter.java:152)
	at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serializeValue(ServerSerializationStreamWriter.java:534)
	at com.google.gwt.user.server.rpc.RPC.encodeResponse(RPC.java:616)
	at com.google.gwt.user.server.rpc.RPC.encodeResponseForFailure(RPC.java:390)
	at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:588)
	at com.google.gwt.user.server.rpc.RemoteServiceServlet.processCall(RemoteServiceServlet.java:208)
	at com.google.gwt.user.server.rpc.RemoteServiceServlet.processPost(RemoteServiceServlet.java:248)
	at com.google.gwt.user.server.rpc.AbstractRemoteServiceServlet.doPost(AbstractRemoteServiceServlet.java:62)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:306)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:240)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:161)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
	at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:541)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:383)
	at org.apache.coyote.http11.Http11AprProcessor.process(Http11AprProcessor.java:284)
	at org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:322)
	at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:1684)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at java.lang.Thread.run(Unknown Source)

これを解決するには、「コントロールパネル」->「管理ツール」->「サービス」->「Apache Tomcat 7」をダブルクリックして、ログオンダブから現在ログオン中のアカウント情報を入力すれば良い。これでTomcatサービスがプリンタにアクセスすることが出来るようになる。ただし、権限を変更するのでセキュリティ上好ましくはないように思う。

GWTを使用したPukiwikiプラグインを作る

Pukiwikiにタスクの進捗を管理するページがある。最初の頃は表を手動で書き換えるようになっていて、めちゃめちゃ面倒だった。最近、listboxプラグインを導入して簡単に書き換えられるようにしたのだが、今度は見栄えが悪くなってしまった。そこで、見栄えと操作性を両立することができないものかと思い、GWTを使用したAjax的なlistboxプラグインを作ることにした。

調査

listboxプラグイン及びcheckbox2プラグインがどのような動きをしているのか調べてみた。

plugin_*_inline()

plugin_*_inline()では引数に対応したHTMLを生成している。このときページ内で何番目のプラグインかを覚えておくようにして、そのインデックスをJavaScriptに渡している。また、0番目のプラグインを表示させる前にJavaScriptを生成している。JavaScriptでは与えられたパラメータに応じてpluginを呼び出している。呼び出すときにはformを動的に生成してsubmitを呼び出すなどしている。受け渡されるパラメータはプラグインの種類、何番目のプラグインか、値、ページ名等。

plugin_*_action()

plugin_*_action()では渡されたパラメータを元にページの書き換えを行っている。ページの書き換えは現在のページの先頭から順に調べていって、該当するプラグインの位置まで来たら文字列を書き換えるという物。

設計

インターフェースとしては、文字orマークをクリックするとポップアップウィンドウが表示され、ポップアップウィンドウ内の選択肢をクリックするとページが書き換わるような感じにしたい。これを内部の動作と対応させると、plugin_*_inline()では、クリックするとJavaScriptを呼び出すようなHTMLを生成し、JavaScriptでは渡されたパラメータに応じてプラグインを呼び出すような感じになる。

JavaScriptの直書きは慣れていないので、Google Web Toolkit(GWT)を使用して楽をしたい。その為にはnativeなJavaScriptからGWTの生成したJavaScriptを呼び出す必要がある。これにはJNSIを使う。JNSIはGWTの内部で直接JavaScriptを扱いたい場合に使用する機能だ。通常はJavaからJavaScriptを呼び出すときに使うのだが、ちょっとしたtrickを使うとnativeなJavaScriptからGWTが生成したJavaScriptを呼び出すことができるらしい。

以上より実装すべきものは以下の通りとなる。

  • plugin_listbox3_action()
  • plugin_listbox3_inline()
  • ポップアップウィンドウ

実装

plugin_listbox3_action()

listboxプラグインを元に引数の変更などを行った。ほとんど元のソースと同じ。

function plugin_listbox3_action() {
	global $vars;
	$listBoxIndex = 0;
	$pagedata = '';
	$pagedata_old  = get_source($vars['refer']);
	foreach($pagedata_old as $line) {
		if(!preg_match('/^(?:\/\/| )/', $line)) {
			if (preg_match_all('/(?:&listbox3\(([^\)]*)\));/', $line, $matches, PREG_SET_ORDER)) {
				$paddata = preg_split('/&listbox3\([^\)]*\);/', $line);
				$line = $paddata[0];
				foreach($matches as $i => $match) {
					$opt = $match[1];
					if($vars['listBoxIndex'] == $listBoxIndex++) {
						//ターゲットのプラグイン部分
						$opt = preg_replace('/[^,]*/', $vars['optionIndex'], $opt, 1);
					}
					$line .= "&listbox3($opt);" . $paddata[$i+1];
				}
			}
		}
		$pagedata .= $line;
	}
	page_write($vars['refer'], $pagedata);
	return array('msg' => '', 'body' => '');
}
plugin_listbox3_inline()
function plugin_listbox3_inline()
{
	$listBoxIndex = plugin_listbox3_getNumber();
	if(func_num_args() == 2)
	{
		$options = func_get_args();
		$optionIndex = $options[0];
		return plugin_listbox3_getBody($listBoxIndex, $optionIndex);
	}
	return FALSE;
}

function plugin_listbox3_getNumber() {
	global $vars;
	static $listBoxIndexs = array();
	if (!array_key_exists($vars['page'],$listBoxIndexs))
	{
		$listBoxIndexs[$vars['page']] = 0;
	}
	return $listBoxIndexs[$vars['page']]++;
}

function plugin_listbox3_getBody($listBoxIndex, $optionIndex) {
	$options_html = plugin_listbox3_getOptions($listBoxIndex, $optionIndex);
	$body = ($listBoxIndex == 0) ? plugin_listbox3_getScript() : '';
	$body .= $options_html;
	return $body;
}

function plugin_listbox3_getOptions($listBoxIndex, $optionIndex) {
	global $vars;
	$page_enc = htmlspecialchars($vars['page']);
	$options = array(
<<<EOD
<button style="background-color:#ccffcc;font-size:x-large;border:none;cursor:pointer;" onclick="showOptions(this,$listBoxIndex,'$page_enc')"></button>
EOD
	,
<<<EOD
<button style="background-color:#ffffcc;font-size:x-large;border:none;cursor:pointer;" onclick="showOptions(this,$listBoxIndex,'$page_enc')"></button>
EOD
	,
<<<EOD
<button style="background-color:#ffcccc;font-size:x-large;border:none;cursor:pointer;" onclick="showOptions(this,$listBoxIndex,'$page_enc')">×</button>
EOD
	,
<<<EOD
<button style="background-color:#cccccc;font-size:x-large;border:none;cursor:pointer;" onclick="showOptions(this,$listBoxIndex,'$page_enc')"></button>
EOD
	);
	return $options[$optionIndex];
}

function plugin_listbox3_getScript() {
	return <<<EOD
<script type="text/javascript" language="javascript" src="listbox3/listbox3.nocache.js"></script>
EOD;
}
ポップアップウィンドウ

初めにnativeなJavaScriptからGWTが生成したJavaScriptを呼ぶためのトリック部分。initialize()ではポップアップウィンドウを表示させるメソッドをshowOptionsという関数に代入している。こうすることでイベントハンドラからshowOptions(this,listIndex,pageName);といった感じで呼び出すことができるようになる。GWT Exporterというライブラリを使うともっと簡単に書けるようになるらしいが、今回はこれで十分。

package hoge.client;

import com.google.gwt.core.client.EntryPoint;

public class Listbox3 implements EntryPoint {
	@Override
	public void onModuleLoad() {
		initialize();
	}

	private native void initialize() /*-{
		$wnd.showOptions = @hoge.client.Selector::showOptions(Lcom/google/gwt/dom/client/Element;ILjava/lang/String;);
	}-*/;
}

ポップアップウィンドウを表示させる部分。第一引数でイベントハンドラを渡してもらって、表示位置を取得している。イベントハンドラはElementで渡せるらしい。

package hoge.client;

import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DecoratedPopupPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;

public class Selector {
	private static void showOptions(Element element, int listBoxId, String refer) {
		final DecoratedPopupPanel decoratedPopupPanel = new DecoratedPopupPanel(true);

		final HorizontalPanel horizontalPanel = new HorizontalPanel();
		final String[] options = { "○", "△", "?", "‐" };
		for (int optionIndex = 0; optionIndex < options.length; ++optionIndex) {
			final String option = options[optionIndex];
			final Button button = new Button(option);
			button.addClickHandler(new Poster(listBoxId, optionIndex, refer));
			horizontalPanel.add(button);
		}

		decoratedPopupPanel.setWidget(horizontalPanel);
		final int x = element.getAbsoluteLeft();
		final int y = element.getAbsoluteTop();
		decoratedPopupPanel.setPopupPosition(x, y);
		decoratedPopupPanel.show();
	}
}

プラグインを呼び出す部分。必要な引数をパラメーターに入れてGETをしている。

package hoge.client;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Window;

public class Poster implements ClickHandler {
	private final int listBoxIndex;
	private final int optionIndex;
	private final String refer;

	public Poster(int listBoxIndex, int optionIndex, String refer) {
		this.listBoxIndex = listBoxIndex;
		this.optionIndex = optionIndex;
		this.refer = refer;
	}

	@Override
	public void onClick(ClickEvent event) {
		Window.open("?listBoxIndex=" + listBoxIndex + "&optionIndex=" + optionIndex + "&plugin=listbox3&refer=" + refer, "_self", "");
	}
}

利用方法

GWTで作ったJavaScriptpukiwikiディレクトリ内に配置する。あとはWiki内で普通にプラグインを使うだけ。

問題点

  • 選択肢をハードコーディングしてしまっているが非常に汚い。しかし、これをWikiの書式側に持ってくるとパースが面倒になる。
  • 表の中で使用すると表がすかすかになってしまい見栄えが良くない。
  • スタイルシートが上書きされてしまった。

どうしよう・・・。