Inkscape にナイフ機能を付けてみた 1/4 - UI をいじる編

先日、大学の学生実験にて大規模なOSSプロジェクトを解読して変更を加えるという演習を行いました。 
大規模ソフトウェアを手探る

その中で我々の班は C++クロスプラットフォームなドローツールである Inkscape (https://inkscape.org/ja/) を題材として、それに新たにナイフ機能を加えました。

最終的な実装に至るまでの紆余曲折を含め、その過程を以下に記録していきたいと思います。

環境など

Ubuntu 14.04 64bit
Inkscape 0.91

目標

引いた線に沿って図形を切断できるツールをナイフツールとして作成し、既存のツール群に加えます。
同様の機能は鉛筆ツールで線を引いた線と図形を選択してパスの切断を行えば実現できますが、2ステップの操作が必要であることや一度に一つの図形しか切断できないことを不満点として感じていたので、専用のツールを作ることでこれを解消します。

手始めにビルドしてみる

ソースコードは以下から入手できます。
https://launchpad.net/inkscape/+download
ソースコードファイル数 1,914、 ステップ数計 633,509 行とかなり厳つい様子が確認できますが、手を加える際に実際に読まなければいけない部分や理解しなければいけない部分はほんの一部分なので見た目に気圧されてはいけません。弾幕シューティングのようなものです。

ビルド時には依存パッケージが多くエラーも大量に出ましたが、

$ sudo apt-get build-dep inkscape

で依存パッケージを一気にインストールしたらあっさりと通りました。

ナイフボタンを追加する

ビルドも出来たところで、改めて機能追加に臨みます。
まずは、ナイフボタンを GUI 上に追加することから始めます。

もちろん頭からコードを読んでいくなどはせず関連する場所を探し当てていく方法を取る訳ですが、InkscapeGUI なので CUI のように入力待ちで一旦プログラムを停止してステップ実行しながら該当箇所を探すといった直接的な戦略を取るのは困難です。
仕方がないので今回はツール名である "Pencil"/"pencil" あたりで grep してそれらしいことを行っているファイルを特定します。
するとあまり余計なものも引っかからず、以下のような事がわかりました。

  • src/tools-switch.h でツール ID を定義している。
  • src/verbs.h で動作 ID (ツール選択、アンドゥやファイル作成など UI からのあらゆる操作に対応する ID) を定義している。
  • src/widgets/hoge-toolbar.h で hoge ツール固有の UI の動作設定を行っている。
  • src/widgets/toolbox.h でツールボックスのボタン定義を行っている。
  • src/ui/tools/hoge-tool.h で hoge ツールの動作の実体を定義している。
  • src/ui/dialog/inkscape-preferences.h で各種ツールの設定ダイアログを定義している。
  • ツールの設定などは全て Inkscape::Preferences というシングルトンに対しツール固有の文字列をキーにして突っ込まれている。

これだけわかればなんだか行けそうな気がしてきました。

判明した情報を元に、以下のような変更を加えます。

verbs.h

動作 ID にナイフボタンを押した時の ID を追加

enum {
    /* Header */
    SP_VERB_INVALID,               /**< A dummy verb to represent doing something wrong. */
    SP_VERB_NONE,                  /**< A dummy verb to represent not having a verb. */
    /* File */
    SP_VERB_FILE_NEW,              /**< A new file in a new window. */
    SP_VERB_FILE_OPEN,             /**< Open a file. */
...
    SP_VERB_CONTEXT_KNIFE,
...
};
verbs.cpp

対応する Verb の実体を定義。ここで指定した動作名を後で UI に関連付ける。

Verb *Verb::_base_verbs[] = {
    // Header
    new Verb(SP_VERB_INVALID, NULL, NULL, NULL, NULL, NULL),
    new Verb(SP_VERB_NONE, "None", NC_("Verb", "None"), N_("Does nothing"), NULL, NULL),

    // File
    new FileVerb(SP_VERB_FILE_NEW, "FileNew", N_("_New"), N_("Create new document from the default template"),
                 INKSCAPE_ICON("document-new")),
    new FileVerb(SP_VERB_FILE_OPEN, "FileOpen", N_("_Open..."),
...
    new ContextVerb(SP_VERB_CONTEXT_KNIFE, "ToolKnife", NC_("ContextVerb", "Knife"),
                    N_("Draw freehand lines"), INKSCAPE_ICON("draw-freehand")),
...
};

ツール選択の Verb に対応するツールの中身を設定。とりあえず既存ツールの鉛筆にしておく。

    switch (verb) {
        case SP_VERB_CONTEXT_SELECT:
            tools_switch(dt, TOOLS_SELECT);
            break;
        case SP_VERB_CONTEXT_NODE:
            tools_switch(dt, TOOLS_NODES);
            break;
    ...
        case SP_VERB_CONTEXT_KNIFE:
            tools_switch(dt, TOOLS_FREEHAND_PENCIL);
            break;
    ...
    }
toolbox.cpp

ツール設定用のキー文字列や動作 ID を設定。基本的に鉛筆ツールのコピペだが一応定義した動作 ID だけは設定しておく。設定画面は作っていないので設定画面を開いた際の動作 ID はそのまま。

static struct {
    gchar const *type_name;
    gchar const *data_name;
    sp_verb_t verb;
    sp_verb_t doubleclick_verb;
} const tools[] = {
	{ "/tools/select",   "select_tool",    SP_VERB_CONTEXT_SELECT,  SP_VERB_CONTEXT_SELECT_PREFS},
	{ "/tools/nodes",     "node_tool",      SP_VERB_CONTEXT_NODE, SP_VERB_CONTEXT_NODE_PREFS },
...
	{ "/tools/freehand/pencil",   "pencil_tool",    SP_VERB_CONTEXT_KNIFE, SP_VERB_CONTEXT_PENCIL_PREFS },
...
};

ツールボックス用のボタン設定を登録。同じく鉛筆のコピペ。

static struct {
    gchar const *type_name;
    gchar const *data_name;
    GtkWidget *(*create_func)(SPDesktop *desktop);
    void (*prep_func)(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder);
    gchar const *ui_name;
    gint swatch_verb_id;
    gchar const *swatch_tool;
    gchar const *swatch_tip;
} const aux_toolboxes[] = {
    { "/tools/select", "select_toolbox", 0, sp_select_toolbox_prep,            "SelectToolbar",
      SP_VERB_INVALID, 0, 0},
    { "/tools/nodes",   "node_toolbox",   0, sp_node_toolbox_prep,              "NodeToolbar",
      SP_VERB_INVALID, 0, 0},
...
    { "/tools/freehand/pencil", "pencil_toolbox", 0, sp_pencil_toolbox_prep,            "PencilToolbar",
      SP_VERB_CONTEXT_PENCIL_PREFS, "/tools/freehand/pencil", N_("Style of new paths created by Pencil")},
...
};

また、void setup_tool_toolbox(GtkWidget *toolbox, SPDesktop *desktop) 内の gchar const * descr でツールボックスのボタン配置設定が、どちらも XML で記述されているので、 Verb で設定した動作名を使ってナイフについて適当に追記してやります。

ビルドすると、確かにボタンが増えています。元から存在している鉛筆ボタンと大半の設定がシンクロしているせいか選ぶと 2 つのボタンが選択されている状態になってしまいますが。
f:id:esudo:20151031210414p:plain

まだまだ続きます。