Hatena::ブログ(Diary)

Fly me to the Juno! このページをアンテナに追加 RSSフィード

2008-04-11

アイコンをその場で合成しながらアニメーション

SWTを使ったアプリケーションでアニメーションをさせるためのコード片を公開しておきます。(ほとんど自分のためにですが。)肝はThread#sleep()でフレーム秒間を調整したらだめで、Display#timerExec(int,Runnable)を使って再帰的に画像合成と描画を行うようにしておくことです。この考え方はDraw2dを使ったアプリケーションでももちろん有効です。

  private static final class AnimationListener implements Listener {

    // WindowsとLinuxで画像の位置が変なので、調整するための定数を宣言
    private static final int PLATFORM_DELTA_X;
    private static final int PLATFORM_DELTA_Y;
    static {
      if("gtk".equals(SWT.getPlatform())){
        PLATFORM_DELTA_X = 1;
        PLATFORM_DELTA_Y = 3;
      }else{
        PLATFORM_DELTA_X = 0;
        PLATFORM_DELTA_Y = 0;      
      }
    }
    
    private int animationIndex = 0;
    private ImageData[] animationImages;
    protected int per10msec;
    private Control control;

    private AnimationListener(Control control) {
      this.control = control;
      // アニメーションの画像をロードする
      ImageLoader loader = new ImageLoader();
      InputStream stream = this.getClass().getResourceAsStream("loading.gif");
      animationImages = loader.load(stream);
    }

    public void handleEvent(final Event event) {
      if (event.item instanceof TreeItem) {
        TreeItem item = (TreeItem) event.item;
        Rectangle rect = item.getImageBounds(0);
        // elementはアニメーションの終了条件を判断する。
        // SWTでは各Controlはdata領域にモデルを格納できる。SWTプラットフォームからはdata領域は触らない。
        Object element = item.getData();
        if (element instanceof INotificationService) {
          INotificationService service = (INotificationService) element;
          Image image = item.getImage();
          doAnimation(control, item, rect, image, service);
          // 終了後の画像描画
          if(image != null && !event.gc.isDisposed()) {
            event.gc.drawImage(image, rect.x + PLATFORM_DELTA_X,rect.y + PLATFORM_DELTA_Y);
          }
        }
      }
    }

    private void doAnimation(final Control tree,
        final TreeItem item, final Rectangle rect, final Image image,final INotificationService service) {
      // アニメーション用のRunnableインスタンス
      Runnable animation = new Runnable() {
        public void run() {
          if(item.isDisposed()) return;
          GC imageGc = new GC(tree);
          final Image animationImage = getAnimationImage(image.getImageData());
          if(animationImage != null) {
            imageGc.drawImage(animationImage, rect.x + PLATFORM_DELTA_X,rect.y + PLATFORM_DELTA_Y);
            animationImage.dispose();
          }            
          if(service.isLoading()){
            // ここが肝。再帰的にRunnableを呼び出す 
            tree.getDisplay().timerExec(per10msec,this);
          }else{
            imageGc.drawImage(image,rect.x + PLATFORM_DELTA_X,rect.y + PLATFORM_DELTA_Y);
          }
          imageGc.dispose();
        }
      };
      if(service.isLoading()){
        tree.getDisplay().asyncExec(animation);
      }else{
        GC imageGc = new GC(tree);
        imageGc.drawImage(image,rect.x + PLATFORM_DELTA_X,rect.y + PLATFORM_DELTA_Y);
        imageGc.dispose();
      }
    }

    private Image getAnimationImage(final ImageData baseImage){
      //画像合成のためのクラス。めんどいので匿名に。
      CompositeImageDescriptor icon = new CompositeImageDescriptor(){
        @Override
        protected void drawCompositeImage(int width, int height) {
          // 合成するための元の画像を書き出す
          drawImage(baseImage,0,0);
          // 合成するアニメーションの画像を取得する
          ImageData imageData = animationImages[animationIndex];
          animationIndex = animationIndex == animationImages.length - 1 ? 0 : animationIndex + 1;
          drawImage(imageData, 0, 0);
          // アニメーションのフレーム秒間を計算する
          per10msec = imageData.delayTime * 10;
        }

        @Override
        protected Point getSize() {
          return new Point(16,16);
        }
      };
      return icon.createImage();
    }
  }  
  
  ViewLabelProvider(TreeViewer viewer){
    Control tree = viewer.getControl();
    // 描画時のListenerとして登録する
    tree.addListener(SWT.PaintItem, new AnimationListener(tree));
  }

2008-04-08

Percs 0.4.0 リリース

3ヶ月ほど開きましたが、Percs 0.4.0をリリースしました。

更新サイト

https://eclipse-study.svn.sourceforge.net/svnroot/eclipse-study/PercsProject/trunk/org.kompiro.readviewer.site

今回のリリースではRCP版も作成してみました。

RCP版配布元

https://sourceforge.net/project/showfiles.php?group_id=207441

主な改良点は

  • 更新を通知するポップアップを邪魔にならない程度にいい感じにしてみました。

f:id:kompiro:20080408234322p:image

  • JRE 1.5でも動作するようにしました。
  • LInux上のクライアントから各ノードのポップアップを見やすくしました。

(Windows版はフォントが大きすぎて見にくいかも)

  • 読み込んでいる最中はアニメーションをさせてみました。(プログレスバーが動かなくなる不具合あり。)

多分チケットなどで変更点を管理すべきでしょうが、欲しくて作っているものなので、1.0まではそのまま特にリリースノートを作らずにいきたいと思います。個人的には結構やりたいネタが溜まっている(例えばPOP3に対応したり、設定した時間で通知したり)ので、それらのサービスをどんどん追加していきます。ただ、ふと立ち止まって考えると世の中RSSAtomだけで結構情報がとれるとれる。ちょっとしたサイトだったらJSONでやりとりしているデータをこそっと持ってくるだけでまた色々やれちゃったり。最後のは微妙か…。でも面白いですね。

2007-12-19

Percs 0.3.0リリース

一番大きいのは複数のサービスに対応したことですね。後は細かなバグフィックス等。

更新サイト

https://eclipse-study.svn.sourceforge.net/svnroot/eclipse-study/PercsProject/trunk/org.kompiro.readviewer.site

f:id:kompiro:20071219234535p:image:w450インターフェースと拡張ポイントを実装するだけでいろんなサービスをView上に表示できます。手始めにGmailを実装してみた。URLとか入力しないでもGmailのIDとパスワードがあれば、最新のメールを通知してくれます。ただ、まだログイン画面をすっ飛ばす実装はできてません。

でもまだパスワードの暗号化をしていません。それを実現できると結構手軽に色々拡張できそうです。Viewの表示もまだまだ微妙だなぁ。やること盛りだくさん。

ところで画像はGnome ScreenShotで撮ってみたんですが、タイトルバーとかないのでさみしい感じですね。

2007-11-30

Gmailに認証後にメッセージの詳細を取得する

Gmailはメッセージの一覧をAtomで配信している。なので、メッセージのIDが埋め込まれたURLはそこから取得することができるが、そのままURLをブラウザに渡すとまだ認証されていないため、認証画面が出てくる。どうにかこうにかしてこの画面をすっ飛ばしたいなと思って書いたのが次のコード。例のごとくJUnit4です。で、CookieManagerを使いたかったので、Java6使ってます。もしあれだったらApache CommonsのHttpClientあたりを使うようにすればJava1.4くらいでも動きそうですな。それとHTMLのパースにJericho HTML Parser、ウィジェットにはSWTを使ってます。

public class LearningGmailConnection {
  private String email = "******";
  private String passwd = "*****";
  private String messageId = "******";
  
  private static String loginformId = "gaia_loginform";
  private static String gmailLoginURL = "https://www.google.com/accounts/ServiceLogin?service=mail&passive=true&rm=false&continue=http%3A%2F%2Fmail.google.com%2Fmail%2F%3Fui%3Dhtml%26zy%3Dl&ltmpl=default&ltmplcache=2";

  @Test
  public void learningGmailURL() throws Exception {
    CookieManager manager = new CookieManager();
    manager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
    CookieHandler.setDefault(manager);

    URL url = new URL(gmailLoginURL);
    Source source = new Source(url);
    List<?> forms = source.findAllElements(Tag.FORM);
    for (Iterator<?> itr = forms.iterator(); itr.hasNext();) {
      Element form = (Element) itr.next();
      if (loginformId.equals(form.getAttributeValue("id"))) {
        authenticationToGmail(form);
        
      }
    }
        String script = makeCookies(manager);
    url = new URL(messageId);
    showBrowser(url,script);
  }


  private void authenticationToGmail(Element form)
      throws MalformedURLException, IOException, ProtocolException {
    URL action = new URL(form.getAttributeValue("action"));
    HttpURLConnection con = (HttpURLConnection) action
        .openConnection();
    con.setRequestMethod("POST");

    List<?> tags = form.findAllElements(Tag.INPUT);
    
    con.setDoOutput(true);
    DataOutputStream outStream = new DataOutputStream(con
        .getOutputStream());
    String params = buildParameters(tags);
    byte[] bytes = params.getBytes();
    outStream.write(bytes, 0, bytes.length);
    con.connect();
    BufferedReader br = new BufferedReader(new InputStreamReader(
        con.getInputStream()));
    while (br.readLine() != null){}
    con.disconnect();
  }

  private String buildParameters(List<?> tags) {
    StringBuilder builder = new StringBuilder(
        "Email=" + email + "&Passwd=" + passwd);
    for (Iterator<?> iitr = tags.iterator(); iitr.hasNext();) {
      Element tag = (Element) iitr.next();
      if ("hidden".equals(tag.getAttributeValue("type"))) {
        String name = tag.getAttributeValue("name");
        String value = tag.getAttributeValue("value");
        builder.append("&");
        builder.append(name);
        builder.append("=");
        builder.append(value);
      }
    }
    return builder.toString();
  }

  private String makeCookies(CookieManager manager) {
    CookieStore store = manager.getCookieStore();
    List<HttpCookie> cookies = store.getCookies();
    StringBuilder scriptBuilder = new StringBuilder();
    for (int i = 0; i < cookies.size(); i++) {
        HttpCookie cookie = cookies.get(i);
        scriptBuilder.append(createCookieScript(cookie));
    }
    String script = scriptBuilder.toString();
    return script;
  }

  private String createCookieScript(HttpCookie cookie) {
    StringBuilder builder = new StringBuilder("document.cookie=\"");
    builder.append(cookie.getName());
    builder.append("=");
    builder.append(cookie.getValue());
    builder.append(";");
    String domain = cookie.getDomain();
    if(domain != null){
      builder.append("domain=");
      builder.append(domain);
      builder.append(";");
    }
    builder.append("\"\n");
    return builder.toString();
  }
  
  private void showBrowser(URL url, String script) {
    showBrowser(url, null, script);
  }


  private void showBrowser(URL url, String html,String script) {
    if(url == null && html == null){
      throw new NullPointerException("url or html, which one is needed.");
    }
    Display display = new Display();
    Shell shell = new Shell(display);
    shell.setLayout(new FillLayout());
    Browser browser = new Browser(shell, SWT.NONE);
    if(script != null){
      browser.execute(script);
    }
    if(url != null){
      browser.setUrl(url.toString());
    }
    if(html != null){
      browser.setText(html);
    }
    shell.open();

    while (!shell.isDisposed()) {
      if (!display.readAndDispatch())
        display.sleep();
    }

    display.dispose();
  }
}

前のエントリで書いたとおり、認証画面をどうにかこうにかしてPOSTすることでクッキーを取得し、それをブラウザに食わせるというやり方をとってみたらすんなりうまくいった。他のサイトでもFormで認証させる場合は同様のやり方でいける。

このコードを見ている限りコードの基盤はオブジェクト指向というよりも構造化なんだよなと思った。構造化されたコードをわかりやすく分類したものがクラスで、そのメッセージの飛ばし具合がオブジェクト指向っていうのがクラスベースオブジェクト指向。インスタンスベースオブジェクト指向はそもそもインスタンスがあるよねっていう世界だからJavaには合わないね。脱線。

2007-11-20

Percs v0.2.0リリース

といっても開発中なので、更新サイトは例によってリポジトリのtrunkの中にあったりしますが。

f:id:kompiro:20071120073925p:image

更新サイト

https://eclipse-study.svn.sourceforge.net/svnroot/eclipse-study/PercsProject/trunk/org.kompiro.readviewer.site

実はGmailも読めました。

f:id:kompiro:20071120074004p:image

Gmailのフィードは

https://mail.google.com/mail/feed/atom

とですが、ユーザー名とパスワードを指定してvalidationボタンを押してください。フィードを受信後、メッセージをダブルクリックするとブラウザが開きます。最初ログイン画面が表示されますが、ログインしてしまえば2度目以降Eclipseを再起動するまでログイン画面をすっ飛ばして表示してくれる模様です。今はEclipseが用意しているEditorをそのまま使ってますが、適当に使いやすいように改良を加えるつもりです。