3 LibreOfficeのツールバーの機能を変えてみた
この記事は2人の大学生がLibreOfficeWriterの一部機能を変えようと格闘した記録の3ページ目(全4ページ)です。
大規模ソフトウェアを手探るという授業の一環で行い、そのレポートを兼ねています。
目次
1. 変更したこと
Writerの画面上部はツールボックスと呼ばれ、アイコンをカーソルで選択することでUndoやファイル操作などの動作を行うことができます。一部のボタンではアイコンの右に小さな三角▼のアイコンがあり、そこを押すことによってその動作のメニューを開くことができます。 そのままでも使うことはできるのですが、私たちはこのカーソルを合わせる→小さいボタンを押す→メニューを選択するという3ステップの動作が必要なことや、小さいボタンを押す際の押し間違いが発生することを減らすためにこの機能を変更しました。下が変更した後の動きになります。 ここでは、マウスのカーソルをアイコンの上に持っていくだけで自動でメニューが開くようになっています。(画像ではクリックしているようにも見えますが、かざすだけでメニューが展開されています。)本来アイコン本体をクリックすることで行われていた動作は、変更前と同様にアイコンをクリックすることで利用できます。このように変更することによってメニューを出すためのステップが一つ減り、作業をスムーズに行えるようになりました。
2. 変更方法
ツールボックス上でのマウス操作を管理するメソッドとして、core/vcl/source/window/toolbox.cxx
にToolBox::MouseMove
とToolBox::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;
このようにMouseButtonDown
やMouseMove
メソッドが定義されているところに定義を付け加えました。
以上で、マウスのカーソルをアイコンの上に持っていくだけで自動でメニューが開くように変更できました。
4. 変更箇所にたどり着くまで
ここではどのようにして上述のcore/vcl/source/window/toolbox.cxx
のToolBox::MouseMove
とToolBox::MouseButtonDown
にたどり着いたかを解説します。
まず、もともと私たちはUndoRedo機能自体に変更を加えようとしていたため、Undo関連の箇所についてはある程度構造や重要なメソッドを把握していましたが、ツールボックス関連のメソッドについては一切知りませんでした。そこで、まずツールボックスをクリックしたときや、ツールボックスにマウスをかざしたときの動作を記述しているメソッドを見つけよう!と考えました。その方針としては、Undo操作にたどり着く前にどこかで操作の判別(Undoをするのか、クリックをするのかなど)をしているところがあると踏んで、Undo操作を行うときに必ず呼び出されるSwBaseShell::ExecUndo
メソッドからStep Outを繰り返すことでその箇所を探します。またそこにはおそらくマウスの動作関連の分岐もあるだろうと考え、さらにたどっていき、ツールボックスにカーソルをかざしたときの動作を記述しているメソッドを見つけようと考えました。
したがって以下のように既に見つけていたSwBaseShell::ExceUndo
というUndo操作の比較的上の階層(Undo操作を呼び出してそうなところ)にあるメソッドにブレークポイントを打って、ひたすらにStep Outをしていきます。
すると、しばらく遡ると、
ここにたどり着きました。どうやらなにかイベントの種類でswtich文で分岐させている箇所のような感じがします。怪しいのでこのメソッド全体を見ると、
ありました!まさしくnEvent
というイベントの種類で、switch文で分岐させています。そして見事すご〜く怪しそうなMouseMove
とMouseButtonDown
というイベントが見つかりました。
これらの中身を見ていきましょう。とりあえずSwBaseShell::ExceUndo
はもう必要ないのでブレークポイントを外して、MouseMove
の箇所にブレークポイントを打って、デバッグを続けます。すると、Writerを開くだけでデバッグの画面に戻ってしまうようになりますが、デバッグを進める上では問題ありません。ImplHandleSalMouseMove
メソッドの中身をStep inで見ていき、さらに深く見ていくと、
さらに怪しい場所にいきつきました。中を具体的に見ていきます。
すると、いかにも怪しいMouseMove
というメソッドが!!!そしてこの中身をみると、
たどり着きましたね。ここで、メソッド名に注目すると、ToolBox::MouseMove
となっていて、ToolBox
クラスのメソッドであることがわかりました。よって、これはツールボックス内でのマウスの動作を扱っているメソッドなのではないかと考えて、一度すべてのブレークポイントを解除して、このメソッドのものだけにしてみたところ、見事ツールボックスにマウスをかざしたときのみ反応しました。
このように、方針として、どこかで今行った操作はクリックやらUndoやらと判定して分岐させているところがあるはず!という考えのもと遡り、そこからマウスの動きと判定している部分を見つけ、そこを丁寧に深ぼっていった結果、求めていたメソッドが見つかりました。また、同じファイル内にツールボックスをクリックしたときの動作などを書いたメソッドもあるはずだ!と見ていった結果、すぐ下に
とToolBox::MouseButtonDown
にたどり着きました。
前ページはこちら
続きはこちら