fubのプラグインの作り方(最初の一歩+ツールバー編)

必要なもの

Visual Studio .NET 2005もしくは同等の機能を有するもの (言語は問わないと思いますが例はC#です)
持っていない人は無料のExpress Editionというお手軽なものがあります。こちらでも可能です。(但し容量は最大1.7GB必要だそうです(システム要件))
また、その言語を使える。もしくは習得する気合が必要となります。

オリジナルのコードを書く前の下準備

  • Visual Studioを起動します。
  • メニューの ファイル/新規作成/プロジェクト を選択してください。
  • ダイアログが出ますので、プロジェクトの種類を指定します。左のツリーから Visual C#/Windows を選択し、テンプレートから Windows コントロール ライブラリを選択。下のプロジェクト名や場所等を決定してください。プロジェクト名や.csファイル名は作成するプラグインの名前が妥当です。(今回はNavigationBarとします)
    • Express Editionの場合(Express Editionでは Windows コントロール ライブラリ というテンプレートが無いそうです)
      • 代わりに空のプロジェクトを選択して作成します。
      • プロジェクトメニューの一番下のプロパティを開いてください。
      • 出力の種類という欄が「コンソール アプリケーション」となっていますので、それを「クラス ライブラリ」に変更してください。
      • メニューのプロジェクト/ユーザーコントロールの追加を行ってください。
  • メニューの プロジェクト/参照の追加参照タブからfubのインストール済みフォルダにある、fubPlugin.dllを設定してください。
  • 初期状態ではただのグレーで四角いヘンなコントロールが作成されます。F7を押すか、もしくはソリューションエクスプローラーと書いてあるツリーから.csファイルの右クリックメニューから コードの表示 でコードが表示されます。初期状態のコードは以下の通り。*1
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace NavigationBar
{
    public partial class NavigationBar : UserControl
    {
        public NavigationBar()
        {
            InitializeComponent();
        }
    }
}
  • 追加と書き換え(コメントは分かりやすくしただけのことで、汚いので直接書き換えて結構です)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
// using追加(こちらはコードを書きやすくするためのもので必須ではありません)
using fubPlugin;

// namespace変更
//namespace NavigationBar
namespace fubPlugin
{
    // インタフェースの変更(PluginBaseは必須、IToolbarはツールバータイプの場合)
    //public partial class NavigationBar : UserControl
    public partial class NavigationBar : PluginBase, IToolbar
    {
        public NavigationBar()
        {
            InitializeComponent();

            // プラグイン名設定
            this.PluginName = "NavigationBar";
         }
    }
}

オリジナルのコードを書く(例:簡易リンクナビゲーション)

いわゆるlinkタグで指定されたリンク先から「次の文書」「前の文書」へ遷移します。例なのでこれだけ。

リソースの作成
  • 前の文書、次の文書に該当するボタン画像を作成するなり探してくるなりしてください。
  • Express Editionでもあるかどうかは分かりませんが、以下のファイルにかなり揃っています。Program Files\Microsoft Visual Studio 8\Common7\VS2005ImageLibrary\VS2005ImageLibrary.zip
  • プロジェクトメニューの一番下のプロパティを開いてください。
  • 左側のタブから リソース を選択します。空のプロジェクトから作った場合などはクリックしろ、とかリンクが出てくるのでクリックしてください。
  • 上部ツールバーが「文字列」になっていると思いますので、「イメージ」に変更してください。(アイコンの場合はアイコンで)
  • リソースの追加」の右のメニューを押し、既存のファイルの追加から最初に用意した画像ファイルを選択してください。
コーディング

fubPlugin.dllに含まれているCommandBarというclassがツールバーとしてはお勧めです。私の作成したプラグインは大抵このツールバーを使っています。選択肢としては他にMS謹製のSystem.Windows.Forms.ToolBarとSystem.Windows.Forms.ToolStripがあります。
但し自分用なのでデザイナに対応させていません。ポトペタ感覚ではありませんが、対して難しくも無いかと。
HTMLを触るプラグインを書く場合はfubhtml.dllも参照に加えてください。fubhtml.dllには含まれないタグを使用する場合はMicrosoft.mshtml.dllを使って構わないのですが、今後配布対象からは外します。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using fubPlugin;
using fubhtml;

namespace fubPlugin
{
    // インタフェースの種類
    // PluginBase       :必須
    // IToolbar         :ツールバー型
    // IExplorerbar     :サイドバー型
    // IModule          :ショートカットキーやマウスジェスチャーから機能を呼び出す場合は必要
    //                   Execute(string command)をイベント代わりに実装
    // ISyncActiveTab   :アクティブタブと同期を取る必要がある場合に必要
    //                   OnSelectedTabChange(), OnActiveTabNavigated()をイベント代わりに実装
    public partial class NavigationBar : PluginBase, IToolbar, IModule
    {
        private CommandBar toolBar;         // ツールバー

        public NavigationBar()
        {
            InitializeComponent();

            // プラグイン名設定
            this.PluginName = "ナビゲーションバー";

            // ツールバー作成
            toolBar = new CommandBar();

            // ボタン追加
            // 引数1:追加したイメージリソース
            // 引数2:イベントハンドラ(今回は小さいので匿名メソッドで書いてあります)
            // イメージの後にstringでテキストも表示できます。今回は割愛。
            // 仕様はインテリセンスで確認してください。
            CommandBarItem prevButton = new CommandBarItem(
                global::NavigationBar.Properties.Resources.prev,
                delegate(object sender, EventArgs e) { navigation(false); });
            CommandBarItem nextButton = new CommandBarItem(
                global::NavigationBar.Properties.Resources.next,
                delegate(object sender, EventArgs e) { navigation(true); });
            toolBar.Items.AddRange(new CommandBarItem[] { prevButton, nextButton });

            toolBar.Resize += new EventHandler(toolBar_Resize);
            this.Controls.Add(toolBar);
        }

        // テーマの適用(ツールバーの場合はこれと同じで結構です)
        protected override void OnPaintBackground(PaintEventArgs pevent)
        {
            base.OnPaintBackground(pevent);
            VisualStyleAPI.DrawRebarBackground(this.Handle, pevent.Graphics,
                pevent.ClipRectangle, VisualStyleAPI.ThemeRebarParts.RP_BAND);
        }

        // ショートカットキーやマウスジェスチャーから呼び出すにはIModuleインタフェースを追加し
        // 下記Executeメソッドを実装して定義ファイルに手動で定義する必要があります
        // 例:Tag="NavigationBar.Prev"
        public void Execute(string command)
        {
            switch (command.ToLower())
            {
                case "prev": // NavigationBar.Prev
                    navigation(false);
                    break;
                case "next": // NavigationBar.Next
                    navigation(true);
                    break;
            }
        }

        // 決めうちでもいいんですけど、ボタン数可変の場合等考えなくていいので
        void toolBar_Resize(object sender, EventArgs e)
        {
            this.Size = toolBar.Size;
        }

        // ナビゲーション処理
        // ※COMの参照カウンタ上げっぱなしの作法の悪い例です
        private void navigation(bool next)
        {
            IHTMLDocument2 doc = base.GetActiveFrameDocument() as IHTMLDocument2;

            // LINKタグから調査
            IHTMLElementCollection links = doc.all.tags("LINK") as IHTMLElementCollection;
            for (int i = 0; i < links.length; i++)
            {
                IHTMLLinkElement link = links.item(i, i) as IHTMLLinkElement;
                if (link == null || link.rel == null)
                    continue;

                string rel = link.rel.ToLower();
                if (!next && (rel == "prev" || rel == "previous"))
                {
                    doc.url = combineUrl(doc.url, link.href);
                    return;
                }
                if (next && (rel == "next" || rel == "child"))
                {
                    doc.url = combineUrl(doc.url, link.href);
                    return;
                }
            }

            // アンカーから調査(適当なリンクがあれば使う)
            Regex reg;
            if (next)
                reg = new Regex("^次[の|へ]", RegexOptions.Compiled);
            else
                reg = new Regex("^前[の|へ]", RegexOptions.Compiled);

            links = doc.all.tags("A") as IHTMLElementCollection;
            for (int i = 0; i < links.length; i++)
            {
                IHTMLElement el = links.item(i, i) as IHTMLElement;
                if (el == null || el.innerText == null)
                    continue;

                string caption = el.innerText.Trim();
                if (reg.IsMatch(caption))
                {
                    IHTMLAnchorElement a = el as IHTMLAnchorElement;
                    // a.hrefは取得時に例外が発生する経験があるので握り潰す
                    try
                    {
                        doc.url = combineUrl(doc.url, a.href);
                        return;
                    }
                    catch { }
                }
            }
        }

        // 簡易相対URL変換
        private string combineUrl(string baseurl, string url)
        {
            if (url.StartsWith("http://") ||
                url.StartsWith("https://") ||
                url.StartsWith("ftp://") ||
                url.StartsWith("file://"))
                return url;

            try
            {
                Uri uri = new Uri(new Uri(baseurl), url);
                return uri.AbsoluteUri;
            }
            catch
            {
                return url;
            }
        }
    }
}
ショートカットキーやマウスジェスチャーへの登録

ユーザーフォルダ内にmenu.xmlが存在しない人はmenu.defaultをユーザーフォルダにコピーし、menu.xmlにリネームしてください。
エディタで開き、定義を追加します。

<Menu Text="前の文書" Shortcut="Ctrl+Shift+Left" Tag="NavigationBar.Prev"/>
<Menu Text="次の文書" Shortcut="Ctrl+Shift+Right" Tag="NavigationBar.Next"/>

どこでもいいのですが、色々作る気があるのでしたら思い切ってルートから定義してもいいです。オプションとヘルプの間辺りにプラグイン拡張というメニューを作りプラグイン毎に階層を作るとA型は整理整頓好きだなあ、とか言われます。

<Menu Text="プラグイン拡張(&P)" Shortcut="Alt+P">
    <Menu Text="ナビゲーションバー(&N)">
        <Menu Text="前の文書" Shortcut="Ctrl+Shift+Left" Tag="NavigationBar.Prev"/>
        <Menu Text="次の文書" Shortcut="Ctrl+Shift+Right" Tag="NavigationBar.Next"/>
    </Menu>
</Menu>

メニュー定義ファイルに定義することでマウスジェスチャーのコマンド一覧にも表示されます。

終わり

以上です。Releaseビルドして出来上がったdllをPluginsフォルダに放り込んでfubを起動するとツールバーが表示されます。
個人で開発して個人で使う、ソースごと配布されているものを良く読んでコンパイルして使う、信頼できる人の配布しているものだけを使う等、自衛は各自でお願いします。ユーザーが少ないので野良プラグインが蔓延するとは思えませんが。
あと、初めて配布するような人は、バージョン情報に個人情報がついてないかよく確認したほうがいいです。
ちなみに、例を書いておいてなんですが、今回例で作った機能はスクリプトで充分ですね。

*1:はてなシンタックス・ハイライトにC#が見当たらないよ?つーか色合いも気に入らないなあ