Inkscape にナイフ機能を付けてみた 4/4 - その他編

前回までに大方の機能を実装したので、これからは細かい部分の改善に着手します。

ナイフの太さをゼロに固定する

ナイフの太さが有効になっていると、例えば縦に線を引いたならナイフの線の右縁と左縁で切断が起こり、図形が3つになってしまいます。
これを回避するためにナイフの線の太さが 0 から変更出来ないようにします。
元々はナイフの太さは整形オプションから変更可能になっているので、オプションから該当部分を削除します。

src/widgets/knife-toolbar.cpp

太さをゼロに固定

void sp_knife_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder)
{
  sp_add_freehand_mode_toggle(mainActions, holder, true);

  EgeAdjustmentAction* eact = 0;

  /* Tolerance */
  {
...
  }

  /* shape options */
  {
    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
    prefs->setInt("/tools/freehand/knife/shape", 0);
  }
  
  /* Reset */
  {
...
  }

  g_signal_connect( holder, "destroy", G_CALLBACK(purge_repr_listener), holder );

}

このほかに freehand_change_shape 関数、freehand_shape_dropdown_items_list 関数、freehand_add_advanced_shape_options 関数を削除するなどすると、太さが変更できないようになりました。

アンドゥ機能の改善

複数の命令を一度に行う複合機能としてナイフを実装したことで、アンドゥを行うと一回のナイフ操作に対して複数回のアンドゥが必要になってしまっているので、これを修正します。

パスの分割を行う sp_selected_path_cu t関数の近くに sp_selected_path_diff 関数、sp_selected_path_diff_skip_undo 関数を見つけました。
それらはどちらも sp_selected_path_boolop 関数を呼ぶだけなのですが、後者の第三引数が SP_VERB_NONE になっていたので、ナイフツールでも変更してみます。

src/splivarot.cpp
void
sp_selected_path_knife(Inkscape::Selection *selection, SPDesktop *desktop)
{
  sp_selected_path_boolop(selection, desktop, bool_op_cut, SP_VERB_NONE, _("Knife"), true);
}

すると分割処理中にはアンドゥが記録されなくなりました。
sp_selected_path_boolop関数を辿ると、

if (verb != SP_VERB_NONE) {
  DocumentUndo::done(doc, verb, description);
}

という部分があり、名前からもDocumentUndo::done関数が呼ばれるとアンドゥの記録がなされるようです。
pencilツール側との共用部分でもこれを探し、ナイフの場合には分割後に実行されるようにします。

src/ui/tools/freehand-base.cpp

ナイフの場合のアンドゥ記録位置の変更

static void spdc_flush_white(FreehandBase *dc, SPCurve *gc)
{
...
  if ( c && !c->is_empty() ) {
...
    if(!SP_IS_KNIFE_CONTEXT(dc))
      DocumentUndo::done(doc, SP_IS_PEN_CONTEXT(dc)? SP_VERB_CONTEXT_PEN : SP_VERB_CONTEXT_PENCIL,_("Draw path"));
...
    spdc_selection_modified(sp_desktop_selection(desktop), 0, dc);
    if(SP_IS_KNIFE_CONTEXT(dc)){
...
      s->clear();
      line->deleteObject();
      g_slist_free(objects);
      DocumentUndo::done(doc,SP_VERB_CONTEXT_KNIFE,_("Knife"));
    }
  }
...
}

ショートカットキー

pencil ツールでは F6 キーがショートカットキーになっていたので "F6" で grep すると、ショートカットキーは share/keys/default.xml で定義されていることが簡単に見つかりました。
他の記述に倣いナイフツールのショートカットを追加します。F5+Shift か K キーを押すとナイフツールが選択されるようになりました。

share/keys/default.xml

ショートカットキー追加

...
<bind key="F5" modifiers="Shift" action="ToolKnife" display="true" />
<bind key="k" action="ToolKnife" />
<bind key="K" action="ToolKnife" />
...

カーソル・アイコン変更

カーソルはフォルダを探っているとsrc/pixmaps/フォルダに置いてあることがわかりました。
pencilのカーソルのファイル名でgrepすると普通にsrc/ui/tools/pencil-tool.cppでインクルードして使っていたので、knife-tool.cppで対応する部分を変更し、カーソル画像を新たに作成すると、別のカーソルが表示されるようになりました。

src/ui/tools/knife-tool.cpp
...
#include "pixmaps/cursor-knife.xpm"
...
KnifeTool::KnifeTool()
  : FreehandBase(cursor_knife_xpm, 4, 4)
  , p()
  , npoints(0)
  , state(SP_KNIFE_CONTEXT_IDLE)
  , req_tangent(0, 0)
  , is_drawing(false)
  , sketch_n(0)
{
}
...

アイコンも同様にフォルダを探ると、share/icons/icons.svgにまとめて保存されていることがわかりました。
アイコンごとにグループ化されており、pencilのアイコンのグループ名"draw-freehand"でgrepするとverb.cppに記述がありました。
ナイフツールのアイコンを作成し、対応する部分を書き換えると、アイコンも別の画像が表示されました。

verb.cpp
Verb *Verb::_base_verbs[] = {
...
  new ContextVerb(SP_VERB_CONTEXT_KNIFE, "ToolKnife", NC_("ContextVerb", "Knife"),
                    N_("Cut shapes"), INKSCAPE_ICON("draw-knife")),
...
};

結び

50 万行以上になってるプロジェクトを積極的に触ろうと思うことは流石になかったので、そういった意味で良い演習でした。
C 時代の遺産かインタフェースがやや無茶苦茶っぽい部分があったのも、
結果的に気合やテクニックが養われたので題材として面白かったかなと思います。
特にググり力の次くらいに grep 力が大事だというのが一番大きな教訓でした。