LibreOfficeCalcに関数候補表示機能を付けるまで 第6回 関数候補・説明表示機能を実装する

#前回のまとめ
でっかい進捗を産んだ。

#キー入力について
##偶然見つけたコマンド
ソースコードをぼーっと眺めていると、こんな関数を発見した。

```cpp:/sc/source/ui/app/inputhdl.cxx
bool ScInputHandler::KeyInput( const KeyEvent& rKEvt, bool bStartEdit /* = false */ )
{
    if (!bOptLoaded)
    {
        bAutoComplete = SC_MOD()->GetAppOptions().GetAutoComplete();
        bOptLoaded = true;
    }

    vcl::KeyCode aCode = rKEvt.GetKeyCode();
    sal_uInt16 nModi  = aCode.GetModifier();
    bool bShift   = aCode.IsShift();
    bool bControl = aCode.IsMod1();
    bool bAlt     = aCode.IsMod2();
    sal_uInt16 nCode  = aCode.GetCode();
    sal_Unicode nChar = rKEvt.GetCharCode();

    if (bAlt && !bControl && nCode != KEY_RETURN)
        // Alt-Return and Alt-Ctrl-* are accepted. Everything else with ALT are not.
        return false;

    if (!bControl && nCode == KEY_TAB)
    {
        // Normal TAB moves the cursor right.
        EnterHandler();

        if (pActiveViewSh)
            pActiveViewSh->FindNextUnprot( bShift );
        return true;
    }

    bool bInputLine = ( eMode==SC_INPUT_TOP );

    bool bUsed = false;
    bool bSkip = false;
    bool bDoEnter = false;

    switch ( nCode )
    {
        case KEY_RETURN:
// (中略)
        case KEY_TAB:
            if (bControl && !bAlt)
            {
                if (pFormulaData && nTipVisible && miAutoPosFormula != pFormulaData->end())
                {
                    // Iterate
                    NextFormulaEntry( bShift );
                    bUsed = true;
                }
                else if (pColumnData && bUseTab && miAutoPosColumn != pColumnData->end())
                {
                    // Iterate through AutoInput entries
                    NextAutoEntry( bShift );
                    bUsed = true;
                }
            }
            break;
        case KEY_ESCAPE:
// (後略)
```
こ、これじゃん！！！まさにこれじゃん！！！
欲しかった「関数の候補を別の候補に切り替える機能」は、Ctrl+TABでできるらしい。いやいやそのコマンドわかりづらすぎるでしょ。

##Ctrl+Tabを使えるようにする
これを使って求めている関数候補を一覧で表示するやつをつくる。とその前に、三度目のfindText()修正。いまのままだと関数はイテレータを無視して全候補を返しているので、「候補を次に進める」という操作ができない。そこで、「与えられたイテレータの次要素から始めて全検索し、最後まで行ったら最初に戻り、与えられたイテレータまで来たら終了する」ようにfindTextAll()関数を修正する。

```cpp:/sc/source/ui/app/inputhdl.cxx
// added
ScTypedCaseStrSet::const_iterator findTextAll(
    const ScTypedCaseStrSet& rDataSet, ScTypedCaseStrSet::const_iterator itPos,
    const OUString& rStart, ::std::vector< OUString > &rResultVec, bool bBack)
{
    rResultVec.clear(); // clear contents

    int nCount = 0;
    ScTypedCaseStrSet::const_iterator retit = rDataSet.begin();
    if (bBack) // Backwards
    {
        ScTypedCaseStrSet::const_reverse_iterator it = rDataSet.rbegin(), itEnd = rDataSet.rend();
        if (itPos != rDataSet.end())
        {
            size_t nPos = std::distance(rDataSet.begin(), itPos);
            size_t nRPos = rDataSet.size() - 1 - nPos;
            std::advance(it, nRPos);
            itEnd = it;
            ++it;
        }

        for (; ; ++it) {
            if(it == itEnd)
                break;
            if(it == rDataSet.rend()) // go to the first if reach the end
                it = rDataSet.rbegin();

            const ScTypedStrData& rData = *it;
            if (rData.GetStringType() == ScTypedStrData::Value)
                // skip values
                continue;
            
            if (!ScGlobal::GetpTransliteration()->isMatch(rStart, rData.GetString()))
                // not a match
                continue;

            rResultVec.push_back(rData.GetString()); // set the match data
            if(nCount == 0)
                retit = it.base(); // convert the reverse iterator back to iterator.
            nCount ++;
            if(nCount >= findnumber)
                return retit;
        }
    }
    else // Forwards
    {
        ScTypedCaseStrSet::const_iterator it = rDataSet.begin(), itEnd = rDataSet.end();
        if (itPos != rDataSet.end())
        {
            it = itPos;
            itEnd = it;
            ++it;
        }

        for (; ; ++it)
        {
            if(it == itEnd)
                break;
            if(it == rDataSet.end()) // go to the first if reach the end
                it = rDataSet.begin();
            
            const ScTypedStrData& rData = *it;
            if (rData.GetStringType() == ScTypedStrData::Value)
                // skip values
                continue;

            if (!ScGlobal::GetpTransliteration()->isMatch(rStart, rData.GetString()))
                // not a match
                continue;

            rResultVec.push_back(rData.GetString()); // set the match data
            if(nCount == 0)
                retit = it; // remember first match iterator
            nCount ++;
            if(nCount >= findnumber)
                return retit;            
        }
    }

    if(nCount > 0) // at least one function has matched
        return retit;
    return rDataSet.end(); // no matching text found
}
// end added
```

さーどうかなー…？

![Screenshot from 2015-11-09 14:38:31.png](https://qiita-image-store.s3.amazonaws.com/0/100640/4f37ef50-71f2-8320-b5e9-408d58d189bb.png)
この状態で、Ctrl+Tabを押すと、
![Screenshot from 2015-11-09 14:38:47.png](https://qiita-image-store.s3.amazonaws.com/0/100640/903f2915-87d5-1ae4-013c-34acceeb5bde.png)
この状態になる。
「IFERROR(),IFNA(), IMABS(), \[次の関数]()」ってなってほしいんだけど…あれ？なんか違うかも！

どうやら、`NextFormulaEntry()`関数（Ctrl+Tabをおしたときに呼ばれる関数）内でも似たような処理をしており、その部分も書き換えなきゃいけないらしい。統一してよ〜。

```cpp:/sc/source/ui/app/inputhdl.cxx
void ScInputHandler::NextFormulaEntry( bool bBack )
{
    EditView* pActiveView = pTopView ? pTopView : pTableView;
    if ( pActiveView && pFormulaData )
    {
// exchange
//        OUString aNew;
//        ScTypedCaseStrSet::const_iterator itNew = findText(*pFormulaData, miAutoPosFormula, aAutoSearch, aNew, bBack);
//        if (itNew != pFormulaData->end())
//        {
//            miAutoPosFormula = itNew;
//            if (aNew[aNew.getLength()-1] == cParenthesesReplacement)
//                aNew = aNew.copy( 0, aNew.getLength()-1) + "()";
//            ShowTip(aNew); // Display a quick help
//        }
// exchange for
        ::std::vector<OUString> aNewVec;
        ScTypedCaseStrSet::const_iterator itNew = findTextAll(*pFormulaData, miAutoPosFormula, aAutoSearch, aNewVec, bBack);
        if (itNew != pFormulaData->end())
        {
            miAutoPosFormula = itNew;
            OUString tipStr;
            ::std::vector<OUString>::iterator itStr = aNewVec.begin();
            int maxFindNumber = 4;
            for( ; itStr != aNewVec.end(); ++itStr ) {
                if(itStr != aNewVec.begin())
                    tipStr = tipStr.copy(0, tipStr.getLength()) + ", ";
                tipStr = tipStr.copy(0, tipStr.getLength()) + (*itStr).copy(0, (*itStr).getLength()-1); // tipStr += *itStr without last character
                if ((*itStr)[(*itStr).getLength()-1] == cParenthesesReplacement) {
                    tipStr = tipStr.copy(0, tipStr.getLength()) + "()";
                } else {
                    tipStr = tipStr.copy(0, (*itStr).getLength()) + (*itStr).copy((*itStr).getLength()-1, (*itStr).getLength());
                }
                if(--maxFindNumber <= 0)
                    break;
            }
            ShowTip( tipStr );
        }
// end exchange
    }

    // For Tab we always call HideCursor first
    if (pActiveView)
        pActiveView->ShowCursor();
}
```

で、やってみたけど、どうもいまいち。たまに3つしか表示されなくなるし（4つ候補があるはずなのに3つしか出ない時がある）、Ctrl+Shift+TABの逆戻りが効かない。gdbで原因を探すぞー。
よくわからないけど、一番後ろと一番最初をつなげる部分で不具合があるっぽい。
イテレータのbegin()関数は最初の要素を返すのだが、end()関数は最後の要素の次を返す（while(itr != end())とか書けるようにするため）。なので、一周回るときはend()とbegin()を同一視することになる。しかも、while(itr != end())と書くためには、一周回るときの最初と最後を一致させておかなければならない（わかりづらい！）。
がんばってるけど微妙にうまくいかない…。

```cpp:/sc/source/ui/app/inputhdl.cxx
// added
ScTypedCaseStrSet::const_iterator findTextAll(
    const ScTypedCaseStrSet& rDataSet, ScTypedCaseStrSet::const_iterator itPos,
    const OUString& rStart, ::std::vector< OUString > &rResultVec, bool bBack)
{
    rResultVec.clear(); // clear contents

    int nCount = 0;
    ScTypedCaseStrSet::const_iterator retit = rDataSet.begin();
    if (bBack) // Backwards
    {
        ScTypedCaseStrSet::const_reverse_iterator it, itEnd;
        it = rDataSet.rbegin();
        size_t nPos = std::distance(rDataSet.begin(), itPos);
        size_t nRPos = rDataSet.size() - 1 - nPos;
        std::advance(it, nRPos);
        if(it == rDataSet.rend())
            it = rDataSet.rbegin();
        itEnd = it;
        bool isFirstTime = true;

        while(it != itEnd || isFirstTime) {
            ++it;
            if(it == rDataSet.rend()) // go to the first if reach the end
                it = rDataSet.rbegin();

            if(isFirstTime)
                isFirstTime = false;
            const ScTypedStrData& rData = *it;
            if (rData.GetStringType() == ScTypedStrData::Value)
                // skip values
                continue;
            
            if (!ScGlobal::GetpTransliteration()->isMatch(rStart, rData.GetString()))
                // not a match
                continue;

            rResultVec.push_back(rData.GetString()); // set the match data
            if(nCount == 0)
                retit = it.base(); // convert the reverse iterator back to iterator.
            ++nCount;
        }
    }
    else // Forwards
    {
        ScTypedCaseStrSet::const_iterator it, itEnd;
        it = itPos;
        if(it == rDataSet.end())
            it = rDataSet.begin();
        itEnd = it;
        bool isFirstTime = true;

        while(it != itEnd || isFirstTime) {
            ++it;
            if(it == rDataSet.end()) // go to the first if reach the end
                it = rDataSet.begin();

            if(isFirstTime)
                isFirstTime = false;
            const ScTypedStrData& rData = *it;
            if (rData.GetStringType() == ScTypedStrData::Value)
                // skip values
                continue;

            if (!ScGlobal::GetpTransliteration()->isMatch(rStart, rData.GetString()))
                // not a match
                continue;

            rResultVec.push_back(rData.GetString()); // set the match data
            if(nCount == 0)
                retit = it; // remember first match iterator
            ++nCount;
        }
    }
    
    if(nCount > 0) // at least one function has matched
        return retit;
    return rDataSet.end(); // no matching text found
}
// end added
```

イテレータの初期値はitPos。最終値もitPos。ループ内でインクリメントするたびにend()になってないか確かめる。うまくいきそうだけど、やっぱりCtrl+Shift+Tabが効かない…。

```/sc/source/ui/app/inputhdl.cxx
       it = rDataSet.rbegin();
       size_t nPos = std::distance(rDataSet.begin(), itPos);
       size_t nRPos = rDataSet.size() - 1 - nPos;
       std::advance(it, nRPos);
```
症状だけ見ると、この部分がめっちゃ怪しいんだよなー。
たとえばrDataSetが{sum, sumif, sumifs}だったとして、itPosがsumifを指してたとする。rbegin()はsumifsを指す。distance()は[The number of increments needed to go from first to lastを返す](http://en.cppreference.com/w/cpp/iterator/distance)ので、この場合は1を返すはず。だからnPosは1、nRPosは1。で、itはadvance(it, 1)でsumifを指すようになる。
合ってるよね…？うーん。
じゃあ、itPosがrDataSet.end()を指してたとしたら。
さっきと同じく、itはsumifsを指す。nPosは3になる。なので、nRPos=-1。
-1！？原因これでは！？

advance()関数は、第二引数が負の時どうなるのか。
[http://en.cppreference.com/w/cpp/iterator/advance](http://en.cppreference.com/w/cpp/iterator/advance)

>std::advance
>Increments given iterator it by n elements.
>If n is negative, the iterator is decremented. In this case, InputIt must meet the requirements ofBidirectionalIterator, otherwise the behavior is undefined.

デクリメント(--)さえ定義されていれば、普通に前に戻ってくれるらしい。でも今回の場合、itはrbegin()にあるから、前に戻ることはできない。ここでおかしなことになってたんだな。
というわけで、advance()に渡る値が負だったらitをrend()から動かすように修正（何度も書くが、ループしてるのでbegin()=end()）。

```cpp:/sc/source/ui/app/inputhdl.cxx
        ScTypedCaseStrSet::const_reverse_iterator it, itEnd;
        it = rDataSet.rbegin();
        size_t nPos = std::distance(rDataSet.begin(), itPos);
        size_t nRPos = rDataSet.size() - 1 - nPos;
        if(nRpos < 0)
            it = rDataSet.rend();
        std::advance(it, nRPos);
        if(it == rDataSet.rend())
            it = rDataSet.rbegin();
        itEnd = it;
        bool isFirstTime = true;
```

makeしてたら、こんなwarningが出た。

```sh:shell
/sc/source/ui/app/inputhdl.cxx:187:20: warning: comparison of unsigned expression < 0 is always false [-Wtype-limits]
         if(nRPos < 0)
                    ^
```
あ、unsigned！！！やっちまった！！！size_tはunsignedだった（0以上しかサポートしないクラスだった）のか！
ってことは、これまで何度もunsigned型に負の数を入れてオーバーフローを起こしてたのか…これはやばいな。
再び修正。

```cpp:/sc/source/ui/app/inputhdl.cxx
        ScTypedCaseStrSet::const_reverse_iterator it, itEnd;
        if(itPos == rDataSet.end()) {
            it = rDataSet.rend();
            --it;
            itEnd = it;
        } else {
            it = rDataSet.rbegin();
            size_t nPos = std::distance(rDataSet.begin(), itPos);
            size_t nRPos = rDataSet.size() - 1 - nPos; // if itPos == rDataSet.end(), then nRPos = -1
            std::advance(it, nRPos);
            if(it == rDataSet.rend())
                it = rDataSet.rbegin();
            itEnd = it;
        }
        bool isFirstTime = true;
```
きちんと例外処理を行うようにした。これで大丈夫かな？
が、ダメ…！　なんでや！

gdbで見てみた。まずは、 上記のit,itEndの初期化が終わったところ。

```text:gdb
(gdb) p *itPos
$13 = (const ScTypedStrData &) @0x1c79e70: {maStrValue = {
    pData = 0x7fffa61d5e90}, mfValue = 0, 
  meStrType = ScTypedStrData::Standard, mbIsDate = false}
(gdb) p *it
$14 = (const ScTypedStrData &) @0x1c79e70: {maStrValue = {
    pData = 0x7fffa61d5e90}, mfValue = 0, 
  meStrType = ScTypedStrData::Standard, mbIsDate = false}
```
ちゃんと*it=*itPosになってる。
で、retit=itを実行した直後。

```text:gdb
(gdb) p *it
$15 = (const ScTypedStrData &) @0x1c79ca0: {maStrValue = {
    pData = 0x7fffa61e7190}, mfValue = 0, 
  meStrType = ScTypedStrData::Standard, mbIsDate = false}
```
*itPosとは異なる値。で、ループを抜けた後。

```text:gdb
(gdb) p *retit
$16 = (const ScTypedStrData &) @0x1c79e70: {maStrValue = {
    pData = 0x7fffa61d5e90}, mfValue = 0, 
  meStrType = ScTypedStrData::Standard, mbIsDate = false}
```

*retit=*itPos。どうやら、イテレータのコピーに失敗してるらしい…。深いコピーはしてくれないのか。
とか思いつつ該当箇所を見てみたら、こうなってた。

```cpp:/sc/source/ui/app/inputhdl.cxx
            rResultVec.push_back(rData.GetString()); // set the match data
            if(nCount == 0)
                retit = it.base(); // convert the reverse iterator back to iterator.
            ++nCount;
```
it.base()、結局なにする関数かよくわかってなかったやつだ…。
こう書き換えてみる。

```cpp:/sc/source/ui/app/inputhdl.cxx
            rResultVec.push_back(rData.GetString()); // set the match data
            if(nCount == 0) { // convert the reverse iterator back to iterator.
                // actually we want to do "retit = it;".
                retit = rDataSet.begin();
                size_t nRPos = std::distance(rDataSet.rbegin(), it);
                size_t nPos = rDataSet.size() - 1 - nRPos;
                std::advance(retit, nPos);
            }
            ++nCount;
```
これで実行してみると…。で、できたー！！！

#関数の説明を出す
関数候補だけじゃなくて、せっかくならその関数の説明も出したい。

```cpp:/sc/source/core/data/funcdesc.cxx
OUString ScFuncDesc::getDescription() const
{
    OUString sRet;
    if ( pFuncDesc )
        sRet = *pFuncDesc;
    return sRet;
}
```
これを呼べば、なんかうまくいきそう。

`inputhdl.cxx`に関数`ShowDescriptionTip()`を追加してみた。とりあえず、テスト的に、関数を表示しただけ。

```cpp:/sc/source/ui/app/inputhdl.cxx
// added
void ScInputHandler::ShowDescriptionTip( const OUString& rFuncName )
{
    ShowTipBelow ( rFuncName );
}
// end added
```
で、これをUseFormulaData()で読み込む。

```
ShowDescriptionTip ( aNewVec[0] );
```

あとちゃんとヘッダファイルも書き換えておく。

![Screenshot from 2015-11-25 14:26:55.png](https://qiita-image-store.s3.amazonaws.com/0/100640/541e9de6-be50-f2f4-9c34-437d1d0ebd85.png)
よし、問題なし。

```cpp:/sc/source/ui/app/inputhdl.cxx
// added
void ScInputHandler::ShowDescriptionTip( const OUString& rFuncName )
{
    FormulaHelper aHelper(ScGlobal::GetStarCalcFunctionMgr());
    sal_Int32 nLeftParentPos = lcl_MatchParenthesis( rFuncName, rFuncName.getLength()-1 );
    sal_Int32 nNextFStart = aHelper.GetFunctionStart( rFuncName, nLeftParentPos, true);
    const IFunctionDescription* ppFDesc;
    ::std::vector< OUString> aArgs;
    if( aHelper.GetNextFunc( rFuncName, false, nNextFStart, NULL, &ppFDesc, &aArgs ) )
    {
        if( !ppFDesc->getFunctionName().isEmpty() )
        {
            sal_Int32 nArgPos = aHelper.GetArgStart( rFuncName, nNextFStart, 0 );
            sal_uInt16 nArgs = static_cast<sal_uInt16>(ppFDesc->getParameterCount());
            OUString aFuncName( ppFDesc->getDescription() );
            ShowTipBelow ( aFuncName );
        }
    }                  
}
// end added
```
これで動くかな？と思ったが、結局うまくいかず。うーん。

`GetNextFunc()`の中身がこちら。

```cpp:/formula/source/ui/dlg/FormulaHelper.cxx
bool FormulaHelper::GetNextFunc( const OUString&  rFormula,
                                     bool             bBack,
                                     sal_Int32&       rFStart,   // Input and output
                                     sal_Int32*       pFEnd,     // = NULL
                                     const IFunctionDescription**   ppFDesc,   // = NULL
                                     ::std::vector< OUString>*   pArgs )  const // = NULL
{
    sal_Int32  nOldStart = rFStart;
    OUString   aFname;

    rFStart = GetFunctionStart( rFormula, rFStart, bBack, ppFDesc ? &aFname : NULL );
    bool bFound  = ( rFStart != FUNC_NOTFOUND );

    if ( bFound )
    {
        if ( pFEnd )
            *pFEnd = GetFunctionEnd( rFormula, rFStart );

        if ( ppFDesc )
        {
            *ppFDesc = NULL;
            const OUString sTemp( aFname );
            const sal_uInt32 nCategoryCount = m_pFunctionManager->getCount();
            for(sal_uInt32 j= 0; j < nCategoryCount && !*ppFDesc; ++j)
            {
                const IFunctionCategory* pCategory = m_pFunctionManager->getCategory(j);
                const sal_uInt32 nCount = pCategory->getCount();
                for(sal_uInt32 i = 0 ; i < nCount; ++i)
                {
                    const IFunctionDescription* pCurrent = pCategory->getFunction(i);
                    if ( pCurrent->getFunctionName().equalsIgnoreAsciiCase(sTemp) )
                    {
                        *ppFDesc = pCurrent;
                        break;
                    }
                }// for(sal_uInt32 i = 0 ; i < nCount; ++i)
            }
            if ( *ppFDesc && pArgs )
            {
                GetArgStrings( *pArgs,rFormula, rFStart, static_cast<sal_uInt16>((*ppFDesc)->getParameterCount() ));
            }
            else
            {
                static OEmptyFunctionDescription s_aFunctionDescription;
                *ppFDesc = &s_aFunctionDescription;
            }
        }
    }
    else
        rFStart = nOldStart;

    return bFound;
}
```

`GetFunctionStart()`、`GetFunctionEnd()`は、文字列から関数っぽい文字を探しだしてその場所を返す関数。こいつが曲者で、一文字目から関数っぽい文字だった場合0ではなく1を返してしまう上に、最後の文字が関数っぽい文字だった場合65535などという頭のおかしい値を返してくる。そこで、最初と最後に意味のない文字をつける（最初に=、最後に()を付けることでセル内の文字列っぽく仕立てあげる）。

```cpp:/sc/source/ui/app/inputhdl.cxx
// added
void ScInputHandler::ShowDescTip( OUString funcName )
{
    FormulaHelper aHelper(ScGlobal::GetStarCalcFunctionMgr());
    sal_Int32 nNextFStart = 0;
    const IFunctionDescription* ppFDesc;
    ::std::vector< OUString> aArgs;
    OUString eqPlusFuncName = "=" + funcName.copy(0, funcName.getLength());
    if( aHelper.GetNextFunc( eqPlusFuncName, false, nNextFStart, NULL, &ppFDesc, &aArgs ) ) 
    {
        if( !ppFDesc->getFunctionName().isEmpty() )
        {
            OUString aFuncDesc( ppFDesc->getDescription());
            ShowTipBelow( aFuncDesc );
        }
    }
}
// end added
```

```cpp:/sc/source/ui/app/inputhdl.cxx
void ScInputHandler::UseFormulaData()
{
// (中略)
                    OUString tipStr;
                    OUString funcNameStr;
                    OUString discFuncNameStr;
                    ::std::vector<OUString>::iterator itStr = aNewVec.begin();
                    int maxFindNumber = 4;
                    for( ; itStr != aNewVec.end(); ++itStr ) {
                        funcNameStr = (*itStr).copy(0, (*itStr).getLength()-1);
                        if ((*itStr)[(*itStr).getLength()-1] == cParenthesesReplacement) {
                            funcNameStr = funcNameStr.copy(0, funcNameStr.getLength()) + "()";
                        } else {
                            funcNameStr = funcNameStr.copy(0, funcNameStr.getLength()) + (*itStr).copy((*itStr).getLength()-1, (*itStr).getLength());
                        }
                        if(itStr != aNewVec.begin()) {
                            tipStr = tipStr.copy(0, tipStr.getLength()) + ", ";
                        } else {
                            discFuncNameStr = funcNameStr;
                        }
                        tipStr = tipStr.copy(0, tipStr.getLength()) + funcNameStr.copy(0, funcNameStr.getLength());
                        if(--maxFindNumber <= 0)
                            break;
                    }
                    ShowTip( tipStr );
                    ShowDescTip( discFuncNameStr );
                    aAutoSearch = aText;
// (中略)
```

さあ、実行してみると…
![Screenshot from 2015-11-10 20:15:48.png](https://qiita-image-store.s3.amazonaws.com/0/100640/48c31aeb-9f62-f746-83b2-c4fbe5ac3d9c.png)
おっ！？
ここで、Ctrl+Tabを押して…
![Screenshot from 2015-11-10 20:16:08.png](https://qiita-image-store.s3.amazonaws.com/0/100640/a32d1d5d-e255-347e-2e4c-abc01b759b75.png)

うまくできた!

#バックスペースを押しても候補が消えないようにする
バックスペースを押すと選択肢が消えるのは非常に使いにくいので、修正する。

```/sc/source/ui/app/inputhdl.cxx
bool ScInputHandler::KeyInput( const KeyEvent& rKEvt, bool bStartEdit /* = false */ )
{
// (中略)
}
```

この関数の一行目にブレークポイントを張り、バックスペースを押した時にどういう挙動をするか確かめてみる。

```/sc/source/ui/app/inputhdl.cxx
    // Only execute cursor keys if already in EditMode
    // E.g. due to Shift-Ctrl-PageDn (not defined as an accelerator)
    bool bCursorKey = EditEngine::DoesKeyMoveCursor(rKEvt);
    bool bInsKey = ( nCode == KEY_INSERT && !nModi ); // Treat Insert like Cursorkeys
    if ( !bUsed && !bSkip && ( bDoEnter || EditEngine::DoesKeyChangeText(rKEvt) ||
                    ( eMode != SC_INPUT_NONE && ( bCursorKey || bInsKey ) ) ) )
    {
        HideTip();
        HideTipBelow();

        if (bSelIsRef)
        {
            RemoveSelection();
            bSelIsRef = false;
        }
```

このif文に入った。`bUsed`、`bSkip`、`bDoEnter`、`bCursorKey`、`bInsKey`はいずれもfalse。つまりbackspace時は`EditEngine::DoesKeyChangeText(rKEvt)`のみがtrueらしい。
つまりはこのif文内でバックスペースかどうかをなんとかして判定して、バックスペースのときだけ

```
HideTip();
HideTipBelow();
```
が起動しないようにすれば万事解決。

```cpp:/editeng/source/editeng/editeng.cxx
bool EditEngine::DoesKeyChangeText( const KeyEvent& rKeyEvent )
{
    bool bDoesChange = false;

    KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction();
    if ( eFunc != KeyFuncType::DONTKNOW )
    {
        switch ( eFunc )
        {
            case KeyFuncType::UNDO:
            case KeyFuncType::REDO:
            case KeyFuncType::CUT:
            case KeyFuncType::PASTE: bDoesChange = true;
            break;
            default:    // is then possibly edited below.
                        eFunc = KeyFuncType::DONTKNOW;
        }
    }
    if ( eFunc == KeyFuncType::DONTKNOW )
    {
        switch ( rKeyEvent.GetKeyCode().GetCode() )
        {
            case KEY_DELETE:
            case KEY_BACKSPACE: bDoesChange = true;
            break;
            case KEY_RETURN:
            case KEY_TAB:
            {
                if ( !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() )
                    bDoesChange = true;
            }
            break;
            default:
            {
                bDoesChange = IsSimpleCharInput( rKeyEvent );
            }
        }
    }
    return bDoesChange;
}
```
この部分が大事。

```
switch ( rKeyEvent.GetKeyCode().GetCode() )
        {
            case KEY_DELETE:
            case KEY_BACKSPACE: bDoesChange = true;
```
これを利用して、バックスペースかどうかを判定する。

```cpp:/editeng/source/editeng/editeng.cxx
    // Only execute cursor keys if already in EditMode
    // E.g. due to Shift-Ctrl-PageDn (not defined as an accelerator)
    bool bCursorKey = EditEngine::DoesKeyMoveCursor(rKEvt);
    bool bInsKey = ( nCode == KEY_INSERT && !nModi ); // Treat Insert like Cursorkeys
    if ( !bUsed && !bSkip && ( bDoEnter || EditEngine::DoesKeyChangeText(rKEvt) ||
                    ( eMode != SC_INPUT_NONE && ( bCursorKey || bInsKey ) ) ) )
    {
// exchange
//        HideTip();
//        HideTipBelow();
// exchange to
        if( rKeyEvent.GetKeyCode().GetCode() != KEY_BACKSPACE ) {
            HideTip();
            HideTipBelow();
        }            
// end exchange
        if (bSelIsRef)
        {
            RemoveSelection();
            bSelIsRef = false;
        }
```

これで、バックスペースを押しても候補や説明は消えないはず。
→と、思ったけどダメだった。

```cpp:/sc/source/ui/app/inputhdl.cxx
                // When the selection is changed manually or an opening parenthesis
                // is typed, stop overwriting parentheses
                if ( bUsed && nChar == '(' )
                    ResetAutoPar();

                if ( KEY_INSERT == nCode )
                {
                    SfxViewFrame* pViewFrm = SfxViewFrame::Current();
                    if (pViewFrm)
                        pViewFrm->GetBindings().Invalidate( SID_ATTR_INSERT );
                }
                if( bUsed && bFormulaMode && ( bCursorKey || bInsKey || nCode == KEY_DELETE || nCode == KEY_BACKSPACE ) )
                {
                    ShowTipCursor();
                }
```

`KeyInput()`関数の奥底で`ShowTipCursor()`関数が呼び出されていて、その冒頭で

```cpp:/sc/source/ui/app/inputhdl.cxx
void ScInputHandler::ShowTipCursor()
{
    HideTip();
    HideTipBelow();
    EditView* pActiveView = pTopView ? pTopView : pTableView;
```

このように、HideTip()が呼ばれている。
うーむ、なんでShowTipCursorを呼ぶ必要があるんだろう？試しにこれを消してみる。

```cpp:/sc/source/ui/app/inputhdl.cxx
                // When the selection is changed manually or an opening parenthesis
                // is typed, stop overwriting parentheses
                if ( bUsed && nChar == '(' )
                    ResetAutoPar();

                if ( KEY_INSERT == nCode )
                {
                    SfxViewFrame* pViewFrm = SfxViewFrame::Current();
                    if (pViewFrm)
                        pViewFrm->GetBindings().Invalidate( SID_ATTR_INSERT );
                }
// exchange
//                if( bUsed && bFormulaMode && ( bCursorKey || bInsKey || nCode == KEY_DELETE || nCode == KEY_BACKSPACE ) )
//                {
//                    ShowTipCursor();
//                }
// exchange to
                if( bUsed && bFormulaMode && ( bCursorKey || bInsKey || nCode == KEY_DELETE ) )
                {
                    ShowTipCursor();
                }
// end exchange
```
実行すると…
![Screenshot from 2015-11-12 13:59:00.png](https://qiita-image-store.s3.amazonaws.com/0/100640/00b6fd17-1c0a-5afd-1aec-f399a28f05e5.png)
![Screenshot from 2015-11-12 13:59:58.png](https://qiita-image-store.s3.amazonaws.com/0/100640/8b6114b6-6405-0189-48e7-b5d0a1a229f2.png)
お、消えない！！
![Screenshot from 2015-11-12 14:00:08.png](https://qiita-image-store.s3.amazonaws.com/0/100640/a7384ab7-4b38-7777-ed4c-55440a6871fa.png)
あ、あれ？
もうifは予測できないはずなのに？？
![Screenshot from 2015-11-12 14:00:16.png](https://qiita-image-store.s3.amazonaws.com/0/100640/2d3b4000-fede-185b-bc28-3675f12ed66c.png)
ああー…。
残念、中身を更新してくれないっぽい。

というわけで、UseFormulaData()関数を呼んで無理やり更新させてみる。

```cpp:/sc/source/ui/app/inputhdl.cxx
                // When the selection is changed manually or an opening parenthesis
                // is typed, stop overwriting parentheses
                if ( bUsed && nChar == '(' )
                    ResetAutoPar();

                if ( KEY_INSERT == nCode )
                {
                    SfxViewFrame* pViewFrm = SfxViewFrame::Current();
                    if (pViewFrm)
                        pViewFrm->GetBindings().Invalidate( SID_ATTR_INSERT );
                }
// exchange
//                if( bUsed && bFormulaMode && ( bCursorKey || bInsKey || nCode == KEY_DELETE || nCode == KEY_BACKSPACE ) )
//                {
//                    ShowTipCursor();
//                }
// exchange to
                if( bUsed && bFormulaMode && ( bCursorKey || bInsKey || nCode == KEY_DELETE ) )
                {
                    ShowTipCursor();
                }
                if( bUsed && bFormulaMode && nCode == KEY_BACKSPACE )
                {
                     UseFormulaData();
                }
// end exchange
```
しかし、うまくいかない。
今度は、最初のHideTip()のコメントアウトを外してからUseFormulaData()を呼び出してみる。
![Screenshot from 2015-11-12 15:33:56.png](https://qiita-image-store.s3.amazonaws.com/0/100640/cb1e4083-9472-09c5-4dfe-9266a0864c54.png)
↓Del
![Screenshot from 2015-11-12 15:35:10.png](https://qiita-image-store.s3.amazonaws.com/0/100640/0c7038bc-17a0-0037-a88f-2d8ab69801b3.png)
↓Del
![Screenshot from 2015-11-12 15:35:17.png](https://qiita-image-store.s3.amazonaws.com/0/100640/b7926c7a-5236-2122-a1b4-c33039f538bd.png)
↓Del
![Screenshot from 2015-11-12 15:35:26.png](https://qiita-image-store.s3.amazonaws.com/0/100640/2fea8a45-d02a-d98a-92d6-85cb2fef34de.png)
できたー！！

#引数の説明を表示
GetArgmentDescription()などという関数があるので、せっかくだから使ってみる。

```cpp:/sc/inc/funcdesc.hxx
    /**
      Returns description of parameter at given position

      @param _nPos
      Position of the parameter

      @return   OUString description of the parameter
    */
    virtual OUString getParameterDescription(sal_uInt32 _nPos) const SAL_OVERRIDE ;
```
これがその関数。検索を掛けてみたが、あまり使われてないみたい。


```cpp:/sc/inc/funcdesc.hxx
void ScInputHandler::ShowArgumentsTip( const OUString& rParagraph, OUString& rSelText, const ESelection& rSel,
        bool bTryFirstSel )
{
(中略)
                if( !ppFDesc->getFunctionName().isEmpty() )
                {
                    sal_Int32 nArgPos = aHelper.GetArgStart( rSelText, nNextFStart, 0 );
                    sal_uInt16 nArgs = static_cast<sal_uInt16>(ppFDesc->getParameterCount());
                    OUString aFuncName( ppFDesc->getFunctionName() + "(");
                    OUString aNew;
                    ScTypedCaseStrSet::const_iterator it =
                        findText(*pFormulaDataPara, pFormulaDataPara->end(), aFuncName, aNew, false);
                    if (it != pFormulaDataPara->end())
                    {
                        bool bFlag = false;
                        sal_uInt16 nActive = 0;
                        for( sal_uInt16 i=0; i < nArgs; i++ )
                        {
                            sal_Int32 nLength = aArgs[i].getLength();
                            if( nArgPos <= rSelText.getLength()-1 )
                            {
                                nActive = i+1;
                                bFlag = true;
                            }
                            nArgPos+=nLength+1;
                        }
                        if( bFlag )
                        {
// (中略)
                            if (nStartPosition > 0)
                            {
                                OUStringBuffer aBuf;
                                aBuf.append(aNew.copy(0, nStartPosition));
                                aBuf.append(static_cast<sal_Unicode>(0x25BA));
                                aBuf.append(aNew.copy(nStartPosition));
                                aNew = aBuf.makeStringAndClear();
// exchange
//                                ShowTipBelow( aNew );
// exchange to
                                ShowTip( aNew );
                                ShowTipBelow( ppFDesc->getParameterDescription(nActive-1) );
// end exchange
                                bFound = true;
                            }
                        }
                        else
                        {
// exchange
//                            ShowTipBelow( aNew );
// exchange to
                            ShowTip( aNew );
                            ShowTipBelow( ppFDesc->getParameterDescription(nActive-1) );
// end exchange
                            bFound = true;
                        }
```
これでうまくいった。
![Screenshot from 2015-11-25 14:46:03.png](https://qiita-image-store.s3.amazonaws.com/0/100640/41e2d72b-0407-b3a2-3895-7b7bffcfcc12.png)


#今回のまとめ
inputhdr.cxxに自分のやりたかったことが全て入っていたので、簡単に実装することができた。
結局、

- 関数候補をたくさん表示
- 関数の説明を表示
- 関数の引数の説明を表示
- 上記のツールチップがバックスペースを押しても消えないように改良

を実装した。

#次回予告
せっかく作ったので、LibreOfficeにコミットしてreviewを受けてみる。


#リンク
##LibreOfficeCalcに関数候補表示機能を付けるまで

- [第1回：目的の設定と準備](http://qiita.com/KRiver1/items/2188d71d316a28ad23ac)
- [第2回：デバッガでメインループを探す](http://qiita.com/KRiver1/items/1f93efcf47d2146c78b8)
- [第3回：メインループと全探索](http://qiita.com/KRiver1/items/d7ab9614d5bbd409c541)
- [第4回：フォルダ名でアタリを付ける](http://qiita.com/KRiver1/items/3531679de3c262332de7)
- [第5回：関数候補の集合を返す](http://qiita.com/KRiver1/items/08ef4586866833048805)
- [第6回：関数候補・説明表示機能を実装する](http://qiita.com/KRiver1/items/c33d62f4bcc73e76b359)
- [第7回：LibreOfficeにコミットしてみる](http://qiita.com/KRiver1/items/7aba2a32cc42672c105d)