3 LibreOfficeのツールバーの機能を変えてみた

この記事は2人の大学生がLibreOfficeWriterの一部機能を変えようと格闘した記録の3ページ目(全4ページ)です。 大規模ソフトウェアを手探るという授業の一環で行い、そのレポートを兼ねています。
目次

1. 変更したこと

Writerの画面上部はツールボックスと呼ばれ、アイコンをカーソルで選択することでUndoやファイル操作などの動作を行うことができます。一部のボタンではアイコンの右に小さな三角▼のアイコンがあり、そこを押すことによってその動作のメニューを開くことができます。

変更前
そのままでも使うことはできるのですが、私たちはこのカーソルを合わせる→小さいボタンを押す→メニューを選択するという3ステップの動作が必要なことや、小さいボタンを押す際の押し間違いが発生することを減らすためにこの機能を変更しました。下が変更した後の動きになります。
変更後
ここでは、マウスのカーソルをアイコンの上に持っていくだけで自動でメニューが開くようになっています。(画像ではクリックしているようにも見えますが、かざすだけでメニューが展開されています。)本来アイコン本体をクリックすることで行われていた動作は、変更前と同様にアイコンをクリックすることで利用できます。このように変更することによってメニューを出すためのステップが一つ減り、作業をスムーズに行えるようになりました。

2. 変更方法

ツールボックス上でのマウス操作を管理するメソッドとして、core/vcl/source/window/toolbox.cxxToolBox::MouseMoveToolBox::ButtonDownがあります。前者はマウスのカーソルがツールボックス中のアイコン上に来た時の動作を記述したメソッドで、後者はアイコンがクリックされたときの動作を記述しています。今回の変更では、カーソルをアイコン上に持ってくるだけで▼ボタンをクリックしたときと同等の動きをさせるために、前者の中で▼ボタンがあるアイコンの場合は後者のようなメソッドを実行することにします。今回はToolBox::ButtonDownの一部を改良したメソッドToolBox::ButtonHoverというメソッドを新たに作成し、ToolBox::MouseMoveの途中で実行できるようにしました。

メソッドの変更イメージ

3.ソースコード

具体的に変更箇所を見ていきます。
まず、ToolBox::MouseMoveでは、カーソルがどのアイコンに乗っているのかを判別するために、以下のfor文を回して探していました。 探し方としては、図のコメントで書いているように、マウスの座標がどのアイコンの中にあるかというのを、nTempPosという変数を1つずつ増やして1つ1つ見て探しています。 今回私たちがやりたかったことは、カーソルをアイコン上に持ってくるだけで▼ボタンをクリックしたときと同等の動きをさせることなので、このfor文中の指しているアイコンが変わったときに、▼ボタンをクリックしたときのメソッドを呼び出せばいいことになります。そのため以下の図で左側が緑になっている部分を付け足しました。 3111行目で選択されたアイコンにドロップダウン機能がついているかを判定し、真のときMouseButtonHoverというメソッドを実行させています。このMouseButtonHoverは私たちが新しく作ったメソッドで、ここで▼ボタンをクリックしたときと同等の動きをさせます。
MouseButtonHoverメソッドは、MouseButtonDownメソッドから、▼ボタンをクリックしたときに行う動作の部分だけを取り出して作成したものです。
中身は以下のようになっています。

void ToolBox::MouseButtonHover( const MouseEvent& rMEvt, const int nTempPos )  
{   

    Point aMousePos = rMEvt.GetPosPixel();

    // call activate already here, as items could
    // be exchanged
    Activate();

    // update ToolBox here, such that user knows it
    if ( mbFormat ) // False
    {
        ImplFormat();
        PaintImmediately();
    }

    if ( !mpData->m_aItems[nTempPos].mbEnabled ) // True
    {
        Deactivate();
        return;
    }
    
    // update actual data
    StartTrackingFlags nTrackFlags = StartTrackingFlags::NONE;
    mnCurPos         = nTempPos;                                  
    mnCurItemId      = mpData->m_aItems[nTempPos].mnId;      
    mnDownItemId     = mnCurItemId;                           
    mnMouseModifier  = rMEvt.GetModifier();
    if ( mpData->m_aItems[nTempPos].mnBits & ToolBoxItemBits::REPEAT )
        nTrackFlags |= StartTrackingFlags::ButtonRepeat;

    // update bDrag here, as it is evaluated in the EndSelection
    mbDrag = true;

    // on double-click: only call the handler, but do so before the button
    // is hit, as in the handler dragging
    // can be terminated
    if ( rMEvt.GetClicks() == 2 )
        DoubleClick();

    if ( mbDrag )              
    {
        InvalidateItem(mnCurPos);
        Highlight();
    }

    if( ( (mpData->m_aItems[nTempPos].mnBits & ToolBoxItemBits::DROPDOWNONLY) == ToolBoxItemBits::DROPDOWNONLY)
        || mpData->m_aItems[nTempPos].GetDropDownRect( mbHorz ).Contains( aMousePos ))
    {
        // dropdownonly always triggers the dropdown handler, over the whole button area

        // the drop down arrow should not trigger the item action
        mpData->mbDropDownByKeyboard = false;
        mpData->maDropdownClickHdl.Call( this );

        // do not reset data if the dropdown handler opened a floating window
        // see ImplFloatControl()
        if( !mpFloatWin )
        {
            // no floater was opened
            Deactivate();
            InvalidateItem(mnCurPos);

            mnCurPos         = ITEM_NOTFOUND;
            mnCurItemId      = ToolBoxItemId(0);
            mnDownItemId     = ToolBoxItemId(0);
            mnMouseModifier  = 0;
            mnHighItemId     = ToolBoxItemId(0);
        }
        return;
    }
    else // activate long click timer
        mpData->maDropdownTimer.Start();
    // call Click handler
    if ( rMEvt.GetClicks() != 2 )
        Click();

    // also call Select handler at repeat
    if ( nTrackFlags & StartTrackingFlags::ButtonRepeat )
        Select();

    // if the actions was not aborted in Click handler
    if ( mbDrag )
        StartTracking( nTrackFlags );
    
    // if mouse was clicked over an item we
    // can abort here
    return;
}

最後に、新しいメソッドを作成したため、これをヘッダファイルで宣言しなければなりません。したがって、include/vcl/toolbox.hxx内に、

    virtual void        MouseButtonDown( const MouseEvent& rMEvt ) override;
    void        MouseButtonHover( const MouseEvent& rMEvt, const int nTempPos);   // 付け加えた。
    virtual void        MouseButtonUp( const MouseEvent& rMEvt ) override;
    virtual void        MouseMove( const MouseEvent& rMEvt ) override;

このようにMouseButtonDownMouseMoveメソッドが定義されているところに定義を付け加えました。
以上で、マウスのカーソルをアイコンの上に持っていくだけで自動でメニューが開くように変更できました。

4. 変更箇所にたどり着くまで

ここではどのようにして上述のcore/vcl/source/window/toolbox.cxxToolBox::MouseMoveToolBox::MouseButtonDownにたどり着いたかを解説します。
まず、もともと私たちはUndoRedo機能自体に変更を加えようとしていたため、Undo関連の箇所についてはある程度構造や重要なメソッドを把握していましたが、ツールボックス関連のメソッドについては一切知りませんでした。そこで、まずツールボックスをクリックしたときや、ツールボックスにマウスをかざしたときの動作を記述しているメソッドを見つけよう!と考えました。その方針としては、Undo操作にたどり着く前にどこかで操作の判別(Undoをするのか、クリックをするのかなど)をしているところがあると踏んで、Undo操作を行うときに必ず呼び出されるSwBaseShell::ExecUndoメソッドからStep Outを繰り返すことでその箇所を探します。またそこにはおそらくマウスの動作関連の分岐もあるだろうと考え、さらにたどっていき、ツールボックスにカーソルをかざしたときの動作を記述しているメソッドを見つけようと考えました。
したがって以下のように既に見つけていたSwBaseShell::ExceUndoというUndo操作の比較的上の階層(Undo操作を呼び出してそうなところ)にあるメソッドにブレークポイントを打って、ひたすらにStep Outをしていきます。 すると、しばらく遡ると、 ここにたどり着きました。どうやらなにかイベントの種類でswtich文で分岐させている箇所のような感じがします。怪しいのでこのメソッド全体を見ると、 ありました!まさしくnEventというイベントの種類で、switch文で分岐させています。そして見事すご〜く怪しそうなMouseMoveMouseButtonDownというイベントが見つかりました。 これらの中身を見ていきましょう。とりあえずSwBaseShell::ExceUndoはもう必要ないのでブレークポイントを外して、MouseMoveの箇所にブレークポイントを打って、デバッグを続けます。すると、Writerを開くだけでデバッグの画面に戻ってしまうようになりますが、デバッグを進める上では問題ありません。ImplHandleSalMouseMoveメソッドの中身をStep inで見ていき、さらに深く見ていくと、
さらに怪しい場所にいきつきました。中を具体的に見ていきます。 すると、いかにも怪しいMouseMoveというメソッドが!!!そしてこの中身をみると、 たどり着きましたね。ここで、メソッド名に注目すると、ToolBox::MouseMoveとなっていて、ToolBoxクラスのメソッドであることがわかりました。よって、これはツールボックス内でのマウスの動作を扱っているメソッドなのではないかと考えて、一度すべてのブレークポイントを解除して、このメソッドのものだけにしてみたところ、見事ツールボックスにマウスをかざしたときのみ反応しました。

このように、方針として、どこかで今行った操作はクリックやらUndoやらと判定して分岐させているところがあるはず!という考えのもと遡り、そこからマウスの動きと判定している部分を見つけ、そこを丁寧に深ぼっていった結果、求めていたメソッドが見つかりました。また、同じファイル内にツールボックスをクリックしたときの動作などを書いたメソッドもあるはずだ!と見ていった結果、すぐ下に ToolBox::MouseButtonDownにたどり着きました。
前ページはこちら
続きはこちら