Groovy + SWT + WebKit + jQuery Mobile でデスクトップアプリケーションをつくる

最近のいわゆる OS 上で直接動作するデスクトップアプリも、iPhone の影響かぴょこぴょこ動くのが多くなってきた気がします。 Linux のアプリでもこの手のがありますが、だいたいは WebKit + Python でやっている模様。 じゃー、Groovy でもやってやろうじゃないかというのがこの企画でありまして、でもって結果 SWT + WebKit はなかなか強力な感じな気がします。 🙂

どういう風にするかというと、まずウインドを作成して WebKit のコンポーネントをぺたっと張ります。 でもって、Groovy 側から処理したいタイミングがあれば、JavaScript 経由で処理結果を WebKit の HTML に描画。 WebKit 側から Groovy を呼びたければ、これまた JavaScript を経由して処理をして値をもらう、みたいな感じです。

Groovy 側では JavaScript では処理できないような、ローカル資源を扱う処理をかいてあげて、WebKit 側にはビューを担当させます。 ここでは HDD にある画像ファイル一覧を読み出して画面に表示するようにしています。 まぁ HTML5 とか使うとこれはできるのかもですが、なんちゃってサンプルということで。  Java のライブラリで EXIF でも読めばそれっぽかったですね。

さて、HTML + JavaScript をビューに使うメリットは多くの派手な画面を描けるライブラリが使えることで、ここでは jQuery + jQuery Mobile を使って iPhone ちっくな画面とアニメにしてみました。 🙂

画像サイズが不揃いでかっこわるくてすいません、すいません。。

webkitgtk02

デバッグ用にツールバーとか、タブとかつけていますが、本来的にはこれはなくなるもので、ミニブラウザと言うべきものがアプリの体裁となります。

webkitgtk01

上の画面から下の拡大画面にアニメで遷移、、ということでこれは動画にて。 😀

 

Groovy – JavaScript インターフェースは、このスクラップコードでいいますと「HTML ソース表示」の部分が Groovy –> JavaScript 呼び出し。 ローカルファイル表示の部分が JavaScript –> Groovy 呼び出しとしています。

Groovy –> JavaScript は簡単で SWT の browser コンポーネントにメソッドが用意されています。

Groovy 側。(タブ切り替えたときのイベント)

tabFolder.addSelectionListener([
  widgetSelected : { SelectionEvent e ->
    if(e.item == tabSource) {
      source.setText(screen.evaluate('return getContentHtml()'))
    }
  },
  widgetDefaultSelected : {}] as SelectionListener)

 

JavaScript 側。

// タブ切替時にソース表示を行う。
function getContentHtml() {
    return $('html').html(); 

}

 

抜粋コードなのであれですが、Groovy から evaluate を発行して JavaScript から返値を string でもらっています。 evaluate の中身に return をかくのを忘れてしばらくはまっていたのはここだけの秘密です(笑)

今度は逆パターン。 (たとえば) jQuery の ready を契機に Groovy のメソッドを呼び出すパターン。

JavaScript 側。 getFileList() がそれです。 値もらって HTML くみたてています。

$(document).ready(function() {
  var fileList = eval("(" + getFileList() + ")");
  for (var name in fileList) {
    $("#filelist").prepend(
      "<li class='ui-li-has-thumb ui-li ui-li-static ui-body-b'>"
      + "<a href='#bar' class='ui-link-inherit'"
      + "onclick='imageChange(\"" + fileList[name] + "\")'>"
      + "<img src='" + fileList[name] + "' class='ui-li-thumb'/>"
      + "<h3 class='ui-li-heading'>" + name + "</h3></li>\n")
      + "</a>";
  }
  $('ul').listview('refresh');
});

 

Groovy 側、 JavaScript とのインターフェースをとるために SWT には BrowserFunction というクラスが用意されています。 これがエロい。

event = new BrowserFunction(screen, "getFileList") {
  public Object function(Object[] arguments) {
    return getFileList(arguments);
  }
}
 
def getFileList(args) {
  def files = [:]
  new File("${imagePath}").eachFileMatch(~/.*\.jpg/) {
    files[it.name] = it.path 
  }
  return new JsonBuilder(files).toString()
  //return [10, 20] as Object[]
}

 

ここで我らが Groovy。 ということで返値に JSON を使っています。 JSON で Map を構築して return して JavaScript 側で eval すれば良い感じでデータの受け渡しができます。

インターフェース的には、org.eclipse.swt.browser.WebKit:convertToJS() あたりをみると分かりますが、String、Boolean、Number、Object[] がやりとりできるようです。 Object[] はこれらの再起です。 上の最後のソースの再下位行が Object[] で返す場合の記述です。

以上、まぁアプリの作りとしては若干強引な気もしますが、既存 GUI コンポーネントを使わずに、特にリキットデザインにしたい UI の場合はこの方式は楽かもしれません。

てなわけで、、シルバーらい子ちゃんをだしておいて、なぜ Silverlight じゃないんだっ、WPF じゃないんだっ。 しかも Windows じゃないし! ゆ、ゆるさないんだからね! となど聞こえてきそうですが、ここはご勘弁ください。。

あぁ、らい子ちゃん・・・。(←はまりすぎ

あ、そうそう SWT + WebKit は Ubuntu 11.04 だと SWT 3.7 の現在 head を使わないと貼り付けられないのでご注意ください。 あとね、、なぜかその WebKit で jQuery Mobile を使うとアプリ終了時に WebKitGTK+ がセグるようです。 なんだろうなぁ。 ちょっと調べてみます。

—-

以下、スクラップソースなので全然アプリケーションの体裁になっていませんが、一応動くコードをおいておきます。

Groovy 側、

import groovy.json.JsonBuilder
import org.eclipse.swt.*
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.BrowserFunction
import org.eclipse.swt.browser.ProgressListener
import org.eclipse.swt.custom.CTabFolder
import org.eclipse.swt.custom.CTabItem
import org.eclipse.swt.events.SelectionEvent
import org.eclipse.swt.events.SelectionListener
import org.eclipse.swt.layout.GridData
import org.eclipse.swt.layout.GridLayout
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text
import org.eclipse.swt.widgets.ToolBar
import org.eclipse.swt.widgets.ToolItem
 
class BrowserApplication {
 
    Display display
    Shell shell
    CTabFolder tabFolder
    CTabItem tabScreen
    CTabItem tabSource
    Browser screen
    Text source
    Label statusBar
    ToolItem toolbarReload
    
    boolean ready
    def event
    def url
    
    def imagePath = "/home/hiromasa/ピクチャ/wallpaper/"
    
    def init() {
        display = new Display()
        
        // シェル
        shell = new Shell(display)
        shell.setSize(540, 640)
        shell.setLayout(new GridLayout())
        shell.setText("シルバーらい子ちゃん")
        
        // ツールバー作成
        ToolBar toolbar = new ToolBar(shell, SWT.NULL)
        toolbarReload = new ToolItem(toolbar, SWT.PUSH)
        toolbarReload.setText("リロード")
 
        // CTabFolder
        tabFolder = new CTabFolder(shell, SWT.BORDER)
        tabFolder.setTabPosition(SWT.BOTTOM)
        tabFolder.setSimple(false)
        tabFolder.setTabHeight(20)
        tabScreen = new CTabItem(tabFolder, SWT.NONE)
        tabScreen.setText("スクリーン")
        tabSource = new CTabItem(tabFolder, SWT.NONE)
        tabSource.setText("ソース")
        tabFolder.setSelection(0)
        // CTabFolder - Layout
        GridData tabLayout = new GridData()
        tabLayout.horizontalAlignment = GridData.FILL
        tabLayout.verticalAlignment = GridData.FILL
        tabLayout.grabExcessHorizontalSpace = true
        tabLayout.grabExcessVerticalSpace = true
        tabFolder.setLayoutData(tabLayout)
        
        // WebKit
        url = new File(".").getAbsolutePath()
        screen = new Browser(tabFolder, SWT.WEBKIT)
        screen.setUrl("file:///${url}/script/screen.html")
        tabScreen.setControl(screen)
 
        // ソース
        source = new Text(tabFolder, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL)
        tabSource.setControl(source)
 
        // ステータスバー
        statusBar = new Label(shell, SWT.LEFT)
        statusBar.setLayoutData(new GridData(GridData.FILL_HORIZONTAL))
    }
    
    def handle() {
        // screen.html 読み込み終了検知
        screen.addProgressListener([
            changed : { },
            completed : {
                ready = true
                statusBar.setText("Ready")
            }] as ProgressListener)
        // リロードボタン
        toolbarReload.addSelectionListener([
            widgetSelected : {
                screen.refresh()
            },
            widgetDefaultSelected : {}] as SelectionListener
        )
        // Groovy からブラウザの JavaScript を呼び出して HTML テキストをもらう。
        tabFolder.addSelectionListener([
            widgetSelected : { SelectionEvent e ->
                if(e.item == tabSource) {
                    source.setText(screen.evaluate('return getContentHtml()'))
                }
            },
            widgetDefaultSelected : {}] as SelectionListener)
        // JavaScript から Groovy を呼び出すための JS 関数を登録する。
        event = new BrowserFunction(screen, "getFileList") {
            public Object function(Object[] arguments) {
                return getFileList(arguments);
            }
        }
    }
    
    // ファイル一覧を取得して JSON 形式で返却
    def getFileList(args) {
        def files = [:]
        new File("${imagePath}").eachFileMatch(~/.*\.jpg/) {
            files[it.name] = it.path 
        }
        return new JsonBuilder(files).toString()
        //return [10, 20] as Object[]
    }
    
    def loop() {
        try {
            shell.open();
            while (!shell.isDisposed()) {
                if (!display.readAndDispatch()) display.sleep();
            }
        } catch (Exception e) {
            // TODO:
            e.printStackTrace()
        } finally {
            display.dispose()
        }
    }
 
    def BrowserApplication() {
        init()
        handle()
        loop()
    }
    
    def static main(def args) {
        new BrowserApplication();
    }
}

 

JavaScript + HTML。

<!DOCTYPE html>
<html>
<head>
<title>jQuery Moblie test</title>
<link rel="stylesheet" href="./css/jquery.mobile-1.0a4.1.css" />
<link rel="stylesheet" href="./css/style.css" />
<script src="./js/jquery-1.6.1.js"></script>
<script src="./js/jquery.mobile-1.0a4.1.js"></script>
<script language="JavaScript">
$(document).ready(function() {
    // 右クリックでコンテキストメニューを表示しない。
    $('body').live('contextmenu', function(e){
        e.preventDefault();
    });
    // ファイル一覧取得
    var fileList = eval("(" + getFileList() + ")");
    for (var name in fileList) {
        $("#filelist").prepend(
            "<li class='ui-li-has-thumb ui-li ui-li-static ui-body-b'>"
            + "<a href='#bar' class='ui-link-inherit'"
            + "onclick='imageChange(\"" + fileList[name] + "\")'>"
            + "<img src='" + fileList[name] + "' class='ui-li-thumb'/>"
            + "<h3 class='ui-li-heading'>" + name + "</h3></li>\n")
            + "</a>";
    }
    $('ul').listview('refresh');
});
 
// 詳細画面に遷移
function imageChange(path) {
    $("#image").attr({src: path});
    return true;
}
 
// タブ切替時にソース表示を行う。
function getContentHtml() {
    return $('html').html(); 
}
 
</script>
</head>
<body>
 
<!-- メインページ -->
<div data-role="page" data-theme="b" class="setting">
    <div data-role="header" data-position="fixed">
        <h1>らい子ちゃん</h1>
    </div>
    <ul data-role="listview" data-theme="b" id="filelist">
    </ul>
    <div data-role="footer" data-id="footer" id="footer" data-position="fixed">
        <h4>ばよえ〜ん</h4>
    </div>
</div>
 
<!-- サブページ -->
<div data-role="page" id="bar" data-theme="b" class="setting">
    <div data-role="header">
        <h1>らい子ちゃん</h1>
    </div>
    <div data-role="content">
        <img id="image" src="" />
    </div>
    <div data-role="footer" data-id="footer" id="footer" data-position="fixed">
        <h4>ぱんなこった〜</h4>
    </div>
</div>
 
</body>
</html>

WordPress Live Writer が欲しい

ぼくは家では基本的に Linux を使っているのですが、ブログを書くときは Windows 。 というのは Windows Live Writer を使っているためなのです。 このソフトはできが良いので Windows 起動するのはやぶさかではないのですが、他の OS でも動くの欲しいじゃないですか、ということで contenteditable のお話です。 🙂

Windows Live Writer のキモはブログの CSS をそのままもってきて、編集時の表示にそのまま使えること。 <p> の改行幅から引用表記からそのままで書けますので出来上がりの想像がつきやすいので気に入って使っています。 画像やソースコードの挿入が簡単なのも良いですね。

live03

このソフトがどうやって実装されているのかちょっと分かりませんが、IE の contenteditable か、、いや DOM で外からつっこんでるのかなぁ。 Trident いじったことないので一瞬の動きでわからないひろまさくん。

さて contenteditable というのはもともと IE 独自の HTML 属性で最近は HTML5 に入ったのかな、ちょっと詳しくないですけども。 ajax の基盤記述 XMLHttpRequest も実は IE 5 の独自実装が始まりですし、嫌われ者のくせに意外と世界を変えている IE くん。(笑)

これが何かって言うと、、デモ見てもらった方が早いですね。

HTML5 Demo: ContentEditable

Any elements with the contenteditable attribute set will have a grey outline as you hover over. Feel free to edit and change their contents. I’m using local storage to maintain your changes.

HTML の属性に contenteditable=true って記述すればその中があろうことかブラウザから編集可能になる技術です。

じゃあ、Linux でうごく Live Writer をつくるには WebKit をウインドウにはって サイトの CSS と HTML を読んで、コンテンツのところを contenteditable=true にしちゃえばいいんじゃないってことで、、

live02

って、まだなんも動きませんが一応ちゃんと CSS もってきて編集できていますぞ。 表示形式がこのサイトになっているところに注目です。

 

Groovy + SWT + WebKit でつくっているので JVM さえ入っていれば Windows / Linux / Max OS X で動くはずです。

ただ SWT は 3.7 M7 、、でも Ubuntu 11.04 ではそれでも動かなくて head を使っています。 Mac とか Windows は 3.7 M7 で WebKit ぺたっとできるはずです。(Windows は safari 入れるらしい)

Ubuntu 11.04 はこの修正で直っています。

Bug 343454 – XULRunner 2.0 kills Eclipse when opening anything Browser related

1. WebKitGTK 1.4 is not ABI-compatible with 1.2, so the initial attempt to use it is failing.  This is fixed with the patch in comment 19, so SWT.NONE-style Browsers should no longer fall back to using Mozilla when WebKit 1.4 is present.

まぁ動きを見ているといろいろ苦難はありそうですが、知恵と勇気でなんとかなるレベルの気がします。

実のところ、SWT と WebKit のインターフェースは JS のみで、Trident (IE) のように直接 DOM にアクセスできないので処理的に厳しい部分もあります。

でも最近この WebKit 張ってクライアントアプリつくるパターンって結構見ますね。 たとえば、Linux の gwibber とか Hotot とかそのタイプだと思います。

live01

Hotot はネイティブとのインターフェースに JS の alert だしてそのイベントハンドルからアラート文字列で Python と通信しあうという無茶をしてるようです(笑) ネイティブは設定ファイルの読み書きとか、ネイティブの通知を出すとかだけでほとんどの処理を JS に依存しています。

たとえば jQuery + jQuery Mobile を使うと iPhone 風のアプリとかすぐつくれそうですね。 クライアントアプリなんかかいたことないけど、JavaScript は得意だぜって方はこの方式は面白いかもしれません。 chrome extention とか chrome os とかの考え方に近そうです。

contenteditable 自体は別にブラウザでも動きますので、WordPress 自体にサイトを管理画面に入らずに直接編集できる機能がついたりしても楽しそうですぞ。 管理バーからぴっとするとそのままの画面で編集モードとかね。 😀

WordPress 3.2 から文書編集にも力が入っているようなので、こんな直接編集のも将来入ったらいいなと思っております。

SaCSS vol.24 コーディング勉強会と OSC 北海道 2011 WordPress セミナー告知

5/14 のハムさん主催、SaCSS 勉強会に参加させていただきました。 こちらの勉強会におじゃまさせていただきますのは 2回目となりますが、Web 製作の方とお話できるのでいつも楽しみにしています。 今回は東京から WordPress メンバもこられていましたのでさらに楽しかったです。 🙂

SaCSS vol.24 札幌コーディング勉強会 開催 | THE HAM MEDIA BLOG

今月のテーマ者WordPress。何度かテーマとしてとりあつかっていましたけど、今月は少し内容も時間も盛りだくさんになる予定です!

ぼくはライトニングトークということで「PHPをかくときに便利なツール・手法の紹介」をちょっとだけしゃべらせていただきました。

sacss2402

Eclipse for PHP Developer に WordPress をのせた紹介にしてみましたが、やっぱりカーソルが動いた方が面白いということでスクリーンキャスト形式で。(実は最近スクリーンキャストネタが多いのはちょっと練習も兼ねていました(笑)

当日使ったものを、youtube のほうに編集してアノテーションとかつけてアップロードしましたので良ければご覧ください。 LT なのですぐ見れる長さです。

 

そしていつも通りカメラもっているのに写真を撮っていないぼく。。 どなたかください(笑)

みなさんのプレゼンも、、分かる限りでリンクさせていただきます。

SaCSS vol. 24に参加してきました | diary | understandard.net

今回僕は、「WordPress を自分好みに」というタイトルで、functions.php にちょっと書き足すだけでできるカスタマイズ方法を紹介しました。

SaCSS Vol.24参加レポート(講演資料公開) | Simple Colors

私の方は、「WordPressでの企業WEBの作り方」と題して、基本的な構成方法、設定、プラグインと簡単な利用方法の紹介、設定などをお話させていただきました。

みなさん果敢にライブ WordPress 操作に挑戦されていたのが印象的でした。 さすがに普段から慣れているだけのことはあります。

たくさんの方が集まりましたので、初めましての方、実は初めましての方(笑)、ご無沙汰しておりますの方、どうもありがとうございました。 夜の方も楽しかったです。

さてもうすぐ 6月てなわけで、OSC 北海道が 6/11 に開催されます。 今年も WordBench 札幌よりブースと、あとぼくが WordPress のセミナーをやらせていただくことになりました。 Ktai Style ゆりこさんも去年に引き続きまして来てくださいます。

オープンソースカンファレンス2011 Hokkaido – イベント案内 | 2011-06-11 (土): 世界標準ウェブ制作ツール WordPress の魅力

講師:田中 広将

担当:WordPress 地域コミュニティー「WordBench 札幌」

対象者:WordPress ユーザー、CMS に興味のある人、ウェブ制作者など
レベル:初級~中級者向け

ブログツールから汎用のウェブ制作ツールへと用途が広がりつつある WordPress。その機能紹介およびカスタマイズの魅力を伝えます。

去年もセミナぼくでしたので、もうひろまさならいいや、、と言わず良ければ遊びに来てください(笑) セミナーは長くて、、という向きはブースのほうにどぞどぞ。

ぼくはいつも通りふらふらしていると思いますので、見つけたら声をかけてやってください。 最近は知っている方が増えてきたので心強いです。 🙂