LibreOfficeCalcに関数候補表示機能を付けるまで 第5回 関数候補の集合を返す

#前回のまとめ
なんかいい感じのフォルダを見つけた。

#inputhdl.cxxに辿り着くまで
##関数の引数の説明を出す関数
前回読んでいた`/sc/source/core/data/funcdesk.cxx`の中身を目で追っていると、こんな関数を発見。

```cpp:/sc/source/core/data/funcdesk.cxx
OUString ScFuncDesc::getSignature() const
{
    OUStringBuffer aSig;

    if(pFuncName)
    {
        aSig.append(*pFuncName);

        OUString aParamList = GetParamList();
        if( !aParamList.isEmpty() )
        {
            aSig.appendAscii( "( " );
            aSig.append(aParamList);
            // U+00A0 (NBSP) prevents automatic line break
            aSig.append( static_cast< sal_Unicode >(0xA0) );
            aSig.appendAscii( ")" );
        }
        else
            aSig.appendAscii( "()" );
    }
    return aSig.makeStringAndClear();
}
```
pFuncNameを足して、(を足して、aParamListを足して、)を足す…。これって、もしかして関数の引数の説明では？
というわけで、ちょっと内容を改変して実行してみる。

```cpp:/sc/source/core/data/funcdesk.cxx
    if(pFuncName)
    {
        aSig.append(*pFuncName);

        OUString aParamList = GetParamList();
        if( !aParamList.isEmpty() )
        {
            aSig.appendAscii( "( " );
// added 
            aSig.appendAscii( "!" );
            aSig.appendAscii( "t" );
            aSig.appendAscii( "!" );
// end added
            aSig.append(aParamList);
            // U+00A0 (NBSP) prevents automatic line break
            aSig.append( static_cast< sal_Unicode >(0xA0) );
            aSig.appendAscii( ")" );
        }
        else
            aSig.appendAscii( "()" );
    }
```
実行してみると、
![Screenshot from 2015-11-25 12:39:58.png](https://qiita-image-store.s3.amazonaws.com/0/100640/c31061c1-057c-2f73-cc1f-042704ae50d3.png)
うまくいった！やはり関数の引数の説明はここで作っていたようだ。

##バックトレースの旅
ここにブレークポイントを挟んで、この関数を呼び出している元を見てみる。

```cpp:/sc/source/ui/app/inputhdl.cxx
//  The other types are defined in ScDocument::GetFormulaEntries
void ScInputHandler::GetFormulaData()
{
    if ( pActiveViewSh )
    {
        ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocShell()->GetDocument();

        if ( pFormulaData )
            pFormulaData->clear();
        else
        {
            pFormulaData = new ScTypedCaseStrSet;
        }

        if( pFormulaDataPara )
            pFormulaDataPara->clear();
        else
            pFormulaDataPara = new ScTypedCaseStrSet;

        const OUString aParenthesesReplacement( cParenthesesReplacement);
        const ScFunctionList* pFuncList = ScGlobal::GetStarCalcFunctionList();
        sal_uLong nListCount = pFuncList->GetCount();
        for(sal_uLong i=0;i<nListCount;i++)
        {
            const ScFuncDesc* pDesc = pFuncList->GetFunction( i );
            if ( pDesc->pFuncName )
            {
                const sal_Unicode* pName = pDesc->pFuncName->getStr();
                const sal_Int32 nLen = pDesc->pFuncName->getLength();
                // fdo#75264 fill maFormulaChar with all characters used in formula names
                for ( sal_Int32 j = 0; j < nLen; j++ )
                {
                    sal_Unicode c = pName[ j ];
                    maFormulaChar.insert( c );
                }
                OUString aFuncName = *pDesc->pFuncName + aParenthesesReplacement;
                pFormulaData->insert(ScTypedStrData(aFuncName, 0.0, ScTypedStrData::Standard));
                pDesc->initArgumentInfo();
                OUString aEntry = pDesc->getSignature();
                pFormulaDataPara->insert(ScTypedStrData(aEntry, 0.0, ScTypedStrData::Standard));
            }
        }
        miAutoPosFormula = pFormulaData->end();
        rDoc.GetFormulaEntries( *pFormulaData );
        rDoc.GetFormulaEntries( *pFormulaDataPara );
    }
}
```
どうやら、これらの処理は起動して最初に=を押した時だけ発動するようだ。うーむ、それだと目的と違うなあ。

```
pFormulaDataPara->insert(ScTypedStrData(aEntry, 0.0, ScTypedStrData::Standard));
```
という一文に着目。ここにデータが入っているようなので、このデータを呼び出している関数を探す。

```cpp:/sc/source/ui/inc/inputhdl.hxx
//  ScInputHandler

class ScInputHandler : boost::noncopyable
{
private:
    ScInputWindow*          pInputWin;

    ScEditEngineDefaulter*  pEngine;                // edited data in the sheet
    EditView*               pTableView;                 // associated active EditView
    EditView*               pTopView;                   // EditView in dthe input row

    ScTypedCaseStrSet* pColumnData;
    ScTypedCaseStrSet* pFormulaData;
    ScTypedCaseStrSet* pFormulaDataPara;
    ScTypedCaseStrSet::const_iterator miAutoPosColumn;
    ScTypedCaseStrSet::const_iterator miAutoPosFormula;
// 以下、無関係なので略
```
ScTypedCaseStrSetクラスの定義はこれ。

```cpp:/sc/inc/typedstrdata.hxx
typedef std::set<ScTypedStrData, ScTypedStrData::LessCaseSensitive> ScTypedCaseStrSet;
```
setはC++のSTL（標準コンテナ、配列のすごいやつ）の一つで、有限集合を扱うクラス。なんだけど、普通型のとこ（<>の中）にはクラス名を1個だけ指定するよね？なんでコンマ区切りで2個あるんだ…？
[調べてみて解決。](http://vivi.dyndns.org/blog/archives/692)なんとなく想像ついてはいたけど、比較関数を指定できるのね。
で、肝心の「ScTypedStrData(おなまえ, 0.0, ScTypedStrData::Standard)」がわからないとどうしようもないので、ScTypedStrDataクラスの定義を探した。

```cpp:/sc/inc/typedstrdata.hxx
class ScTypedStrData
{
public:
    enum StringType {
        Value    = 0,
        Standard = 1,
        Name     = 2,
        DbName   = 3,
        Header   = 4
    };

    ScTypedStrData( const OUString& rStr, double nVal = 0.0,
                    StringType eType = Standard, bool bDate = false );

    ScTypedStrData( const ScTypedStrData& rCpy );

    bool IsStrData() const;
    bool IsDate() const { return mbIsDate;}
    const OUString& GetString() const { return maStrValue;}
    StringType GetStringType() const { return meStrType;}
    double GetValue() const { return mfValue; }

    struct LessCaseSensitive : std::binary_function<ScTypedStrData, ScTypedStrData, bool>
    {
        bool operator() (const ScTypedStrData& left, const ScTypedStrData& right) const;
    };

    struct LessCaseInsensitive : std::binary_function<ScTypedStrData, ScTypedStrData, bool>
    {
        bool operator() (const ScTypedStrData& left, const ScTypedStrData& right) const;
    };

    struct EqualCaseSensitive : std::binary_function<ScTypedStrData, ScTypedStrData, bool>
    {
        bool operator() (const ScTypedStrData& left, const ScTypedStrData& right) const;
    };

    struct EqualCaseInsensitive : std::binary_function<ScTypedStrData, ScTypedStrData, bool>
    {
        bool operator() (const ScTypedStrData& left, const ScTypedStrData& right) const;
    };

    bool operator== (const ScTypedStrData& r) const;
    bool operator< (const ScTypedStrData& r) const;

private:
    OUString maStrValue;
    double mfValue;
    StringType meStrType;
    bool   mbIsDate;
};
```
無難に比較演算子とかがオーバーロードされてるだけ。
コンストラクタを見てみる。

```cpp:/sc/source/core/tool/typedstrdata.cxx
ScTypedStrData::ScTypedStrData(
    const OUString& rStr, double nVal, StringType nType, bool bDate ) :
    maStrValue(rStr),
    mfValue(nVal),
    meStrType(nType),
    mbIsDate( bDate ) {}

ScTypedStrData::ScTypedStrData( const ScTypedStrData& rCpy ) :
    maStrValue(rCpy.maStrValue),
    mfValue(rCpy.mfValue),
    meStrType(rCpy.meStrType),
    mbIsDate( rCpy.mbIsDate ) {}
```
boolなんか指定してなかったような…？まあでも、とりあえず文字列はmaStrValueに保存されるらしい。あんまり収穫なさそうだな…。

##ついに発見、関数候補を見つける関数
というわけで、ScInputHandlerオブジェクトがどこで呼び出されているのか、を見に行くことに。もちろんpFormulaDataで検索。すると…

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

    // Formulas may only have 1 paragraph
    if ( pActiveView && pFormulaData && pEngine->GetParagraphCount() == 1 )
    {
        OUString aParagraph = pEngine->GetText( 0 );
        ESelection aSel = pActiveView->GetSelection();
        aSel.Adjust();

        // Due to differences between table and input cell (e.g clipboard with line breaks),
        // the selection may not be in line with the EditEngine anymore.
        // Just return without any indication as to why.
        if ( aSel.nEndPos > aParagraph.getLength() )
            return;

        //  Is the cursor at the end of a word?
        if ( aSel.nEndPos > 0 )
        {
            OUString aSelText( aParagraph.copy( 0, aSel.nEndPos ));

            OUString aText;
            if ( GetFuncName( aSelText, aText ) )
            {
                // function name is incomplete:
                // show first matching function name as tip above cell
                OUString aNew;
                miAutoPosFormula = pFormulaData->end();
                miAutoPosFormula = findText(*pFormulaData, miAutoPosFormula, aText, aNew, false);
                if (miAutoPosFormula != pFormulaData->end())
                {
                    if (aNew[aNew.getLength()-1] == cParenthesesReplacement)
                        aNew = aNew.copy( 0, aNew.getLength()-1) + "()";
                    ShowTip( aNew );
                    aAutoSearch = aText;
                }
                return;
            }

            // function name is complete:
            // show tip below the cell with function name and arguments of function
            ShowArgumentsTip( aParagraph, aSelText, aSel, false);
        }
    }
}
```

```
// function name is incomplete:
// show first matching function name as tip above cell
```
これ、まさに私が今回やりたかったことですよ！！！！！

このプログラムから色々なことがわかるけど、大きい収穫は

- GetFuncName()関数で関数名の一部から関数名を取得し、オートコンプリートまでした上で完全一致かどうかまで調べてくれる。
- ShowTip()関数で上にTipsが表示できる。ShowArgumentTip()関数で下にTipsが表示できる。

いや、これまでで一番大きい収穫ですね。間違いなく。
findText(arg1, arg2, arg3, arg4, arg5)関数は、文字列arg1からポインタarg2を使って文字列arg3を検索して、見つかった単語をarg4に格納する関数…かな？細かいことは定義をみてみないとわかんないけど。
まずはGetFuncName()関数を見てみよう。

```cpp:/sc/source/ui/app/inputhdl.cxx
bool ScInputHandler::GetFuncName( OUString& aStart, OUString& aResult )
{
    if ( aStart.isEmpty() )
        return false;

    aStart = ScGlobal::pCharClass->uppercase( aStart );
    sal_Int32 nPos = aStart.getLength() - 1;
    sal_Unicode c = aStart[ nPos ];
    // fdo#75264 use maFormulaChar to check if characters are used in function names
    ::std::set< sal_Unicode >::const_iterator p = maFormulaChar.find( c );
    if ( p == maFormulaChar.end() )
        return false; // last character is not part of any function name, quit

    ::std::vector<sal_Unicode> aTemp;
    while ( nPos >= 0 && p != maFormulaChar.end() )
    {
        aTemp.push_back( c );
        c = aStart[ --nPos ];
        p = maFormulaChar.find( c );
    }

    ::std::vector<sal_Unicode>::reverse_iterator rIt = aTemp.rbegin();
    aResult = OUString( *rIt++ );
    while ( rIt != aTemp.rend() )
        aResult += OUString( *rIt++ );

    return true;
}
```
setオブジェクトのfind(key)関数は、現在のセットから key に一致する要素を検索し、 その要素へのイテレータを返す関数。maFormulaCharは

```cpp:/sc/source/ui/inc/inputhdl.hxx
    ::std::set< sal_Unicode >    maFormulaChar;  //fdo 75264
```
と、Unicodeの集合。中身は以下の通り、ちょっと前に紹介したGetFormulaData()関数内で作られてる。

```cpp:/sc/source/ui/app/inputhdl.cxx
                const sal_Unicode* pName = pDesc->pFuncName->getStr();
                const sal_Int32 nLen = pDesc->pFuncName->getLength();
                // fdo#75264 fill maFormulaChar with all characters used in formula names
                for ( sal_Int32 j = 0; j < nLen; j++ )
                {
                    sal_Unicode c = pName[ j ];
                    maFormulaChar.insert( c );
                }
```
関数名を1文字ずつ登録しまくったもの（ただしsetなので重複は無視、かつ並べ替え済み）。
一応maFormulaCharがちゃんと思ったとおりに動作してるか確認。

```text:gdb
Breakpoint 22, ScInputHandler::GetFormulaData (this=0x167aa40)
    at /home/denjo/[pass]/team_babumi_project/libreoffice-4.4.5.2/sc/source/ui/app/inputhdl.cxx:750
(gdb) p /x (sal_Unicode [10])(*pName)
$38 = {0x49, 0x46, 0x0, 0x1000, 0x18, 0x0, 0x0, 0x0, 0x2, 0x0}
(gdb) p maFormulaChar
$36 = {(中略)
_M_parent = 0x1c1e490, _M_left = 0x1b55090, _M_right = 0x1c1e490}, _M_node_count = 2}}},
(中略)}
(gdb) c
Continuing.

Breakpoint 22, ScInputHandler::GetFormulaData (this=0x167aa40)
    at /home/denjo/Documents/[pass]/team_babumi_project/libreoffice-4.4.5.2/sc/source/ui/app/inputhdl.cxx:750
(gdb) p /x (sal_Unicode [10])(*pName)
$39 = {0x49, 0x46, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x0, 0x6e, 0x6a}
(gdb) p maFormulaChar
$36 = {(中略)
_M_node_count = 5}}},
(中略)}
```
0x49はI、0x46はF、0x45はE、0x52はR、0x4fはO。0x00はヌルポインタ。
ちゃんと、文字の種類数だけノードの数が増えていることがわかる。

長くなったが、結局GetFuncName(aStart, aResult)関数はaStartを大文字に変換して後ろから1文字ずつ読んでいき、関数の一部としてはあり得ないような文字（括弧とか、数字とか、記号とか？）にぶつかったらぶつかる手前までの文字列をaResultに代入する関数だった。この関数がtrueを返すということは、aStartの最後の1文字が関数っぽい文字だったということで、つまり括弧が開いたり閉じたりしてないということなので、// function name is incomplete:というわけ。
で、その際の処理を再掲。

```cpp:/sc/source/ui/app/inputhdl.cxx
            if ( GetFuncName( aSelText, aText ) )
            {
                // function name is incomplete:
                // show first matching function name as tip above cell
                OUString aNew;
                miAutoPosFormula = pFormulaData->end();
                miAutoPosFormula = findText(*pFormulaData, miAutoPosFormula, aText, aNew, false);
                if (miAutoPosFormula != pFormulaData->end())
                {
                    if (aNew[aNew.getLength()-1] == cParenthesesReplacement)
                        aNew = aNew.copy( 0, aNew.getLength()-1) + "()";
                    ShowTip( aNew );
                    aAutoSearch = aText;
                }
                return;
            }
```
aTextには、aSelText（セルに実際に入力されている文字列）のうち後半の関数部分だけが入ってる（例えば、aSelTextが”IF(SUM()=0, AVERAGE(SUMI”だった場合、aTextは”SUMI”）。で、findText。おそらくaNewにaTextから始まる関数（この例だと”SUMIF”）が代入されてるはず。それを表示してShowTip。

さて、じゃあfindText()関数を見てみよう。

```cpp:sc/source/ui/app/inputhdl.cxx
ScTypedCaseStrSet::const_iterator findText(
    const ScTypedCaseStrSet& rDataSet, ScTypedCaseStrSet::const_iterator itPos,
    const OUString& rStart, OUString& rResult, bool bBack)
{
    if (bBack) // Backwards
    {
// 今回使わないので省略
    }
    else // Forwards
    {
        ScTypedCaseStrSet::const_iterator it = rDataSet.begin(), itEnd = rDataSet.end();
        if (itPos != rDataSet.end())
        {
            it = itPos;
            ++it;
        }

        for (; it != itEnd; ++it)
        {
            const ScTypedStrData& rData = *it;
            if (rData.GetStringType() == ScTypedStrData::Value)
                // skip values
                continue;

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

            rResult = rData.GetString();
            return it;
        }
    }

    return rDataSet.end(); // no matching text found
}
```
思ってたよりだいぶ長い関数だったけど、ForwardsとBackWardsで分かれてたので、今回使わないBackwardsは思い切ってカット。
関数の中身は、第一引数ｒDataSetのなかで第二引数のイテレータitPosから前進しつつ第三引数rStartを探して、isMatch()関数にrDataSet[i]とrStartをぶちこんだ答えがtrueになるようなiがあったらそのrDataSet[i]を第四引数rResultに代入してそこのイテレータを返すって感じ。

#関数候補表示の実装
##とりあえず作ってみる
とりあえず、これまでわかったことだけでもやりたい機能はそれなりに？作れる、ような、気がする。
ので、作ってみた。

```cpp:/sc/source/ui/app/inputhdl.cxx
ScTypedCaseStrSet::const_iterator findText(
    const ScTypedCaseStrSet& rDataSet, ScTypedCaseStrSet::const_iterator itPos,
// exchange
//    const OUString& rStart, OUString& rResult, bool bBack)
// exchange to
    const OUString& rStart, OUString& rResult, bool bBack, int findnumber)
// end exchange
{
    if (bBack) // Backwards
    {
// 関係ないので略
    }
    else // Forwards
    {
        ScTypedCaseStrSet::const_iterator it = rDataSet.begin(), itEnd = rDataSet.end();
        if (itPos != rDataSet.end())
        {
            it = itPos;
            ++it;
        }

// added
        int nCount = 0;
// end added
        for (; it != itEnd; ++it)
        {
            const ScTypedStrData& rData = *it;
            if (rData.GetStringType() == ScTypedStrData::Value)
                // skip values
                continue;

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

// added
            if(nCount < findnumber) { // not yet
                nCount ++;
                continue;
            }
// end added    
            rResult = rData.GetString();
            return it;
        }
    }

    return rDataSet.end(); // no matching text found
}

// 
// 1000行くらい省略
//

void ScInputHandler::UseFormulaData()
{
    EditView* pActiveView = pTopView ? pTopView : pTableView;

    // Formulas may only have 1 paragraph
    if ( pActiveView && pFormulaData && pEngine->GetParagraphCount() == 1 )
    {
// 長いので省略
        if ( aSel.nEndPos > 0 )
        {
            OUString aSelText( aParagraph.copy( 0, aSel.nEndPos ));
            OUString aText;
            if ( GetFuncName( aSelText, aText ) )
            {
                // function name is incomplete:
                // show first matching function name as tip above cell
                OUString aNew;
                miAutoPosFormula = pFormulaData->end();
// exchange
//                miAutoPosFormula = findText(*pFormulaData, miAutoPosFormula, aText, aNew, false);
// exchange to
                miAutoPosFormula = findText(*pFormulaData, miAutoPosFormula, aText, aNew, false, 0);
// exchange end
                if (miAutoPosFormula != pFormulaData->end())
                {
                    if (aNew[aNew.getLength()-1] == cParenthesesReplacement)
                        aNew = aNew.copy( 0, aNew.getLength()-1) + "()";
// added
                    OUString aNew2;
                    ScTypedCaseStrSet::const_iterator it = pFormulaData->end();
                    it = findText(*pFormulaData, miAutoPosFormula, aText, aNew2, false, 1);
                    if (it != pFormulaData->end())
                        {
                            if (aNew2[aNew2.getLength()-1] == cParenthesesReplacement)
                                aNew2 = aNew2.copy( 0, aNew2.getLength()-1) + "()";
                            aNew = aNew.copy( 0, aNew.getLength()-1) + "), " + aNew2.copy( 0, aNew2.getLength()-1) + ")";
                        }

// end added

                    ShowTip( aNew );
                    aAutoSearch = aText;
                }
                return;
            }
// これまた長いので省略
        }
    }
}
```
後々たくさん表示することも考慮しつつ、findText()関数に「n番目に見つかったものを返却」する機能を追加。それに伴っていろんなところにあるfindText関数の呼び出し部分を全て修正し（ちょっと頭わるかったかも。反省）、UseFormulaData()関数には「1番目の次に2番目の関数も表示」する機能を追加（したつもり）
さあ、makeしてみるぞ…。

![Screenshot from 2015-11-05 19:42:25.png](https://qiita-image-store.s3.amazonaws.com/0/100640/cd0bc65f-9949-cd8b-8006-ac5b002e36a9.png)
![Screenshot from 2015-11-05 19:42:55.png](https://qiita-image-store.s3.amazonaws.com/0/100640/66f6a0a0-91fc-fbf6-f74d-95289768e470.png)
![Screenshot from 2015-11-05 19:43:08.png](https://qiita-image-store.s3.amazonaws.com/0/100640/bf83f2b2-e8fb-1331-4a32-96b264dabcce.png)
きました！！！！！成功です！！！！

##集合で返すように修正
findText()を、文字列の集合を返すように拡張したfindTextAll()関数を作りたいなあ、と思ったので、作ってみる。
集合の表現にはstd::vector<>を使う。OUStringの代わりにstd::vector<OUString>を渡すように修正。で、集合を返して、読む側ではイテレータを使ってぶん回す。
作ったものがこちら。

```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, int findnumber)
{
    rResultVec.clear(); // clear contents

    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);
            ++it;
        }

        ScTypedCaseStrSet::const_iterator retit = rDataSet.begin();
        int nCount = 0;
        for (; it != itEnd; ++it) {
            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;
            ++it;
        }

        ScTypedCaseStrSet::const_iterator retit = rDataSet.begin();
        int nCount = 0;
        for (; it != itEnd; ++it)
        {
            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;            
        }
    }

    return rDataSet.end(); // no matching text found
}
// end added
```

で、読み出す方はこう。

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

    // Formulas may only have 1 paragraph
    if ( pActiveView && pFormulaData && pEngine->GetParagraphCount() == 1 )
    {
        OUString aParagraph = pEngine->GetText( 0 );
        ESelection aSel = pActiveView->GetSelection();
        aSel.Adjust();

        // Due to differences between table and input cell (e.g clipboard with line breaks),
        // the selection may not be in line with the EditEngine anymore.
        // Just return without any indication as to why.
        if ( aSel.nEndPos > aParagraph.getLength() )
            return;

        //  Is the cursor at the end of a word?
        if ( aSel.nEndPos > 0 )
        {
            OUString aSelText( aParagraph.copy( 0, aSel.nEndPos ));

            OUString aText;
            if ( GetFuncName( aSelText, aText ) )
            {
// exchange
//                // function name is incomplete:
//                // show first matching function name as tip above cell
//                OUString aNew;
//                miAutoPosFormula = pFormulaData->end();
//                miAutoPosFormula = findText(*pFormulaData, miAutoPosFormula, aText, aNew, false);
//                if (miAutoPosFormula != pFormulaData->end())
//                {
//                    if (aNew[aNew.getLength()-1] == cParenthesesReplacement)
//                        aNew = aNew.copy( 0, aNew.getLength()-1) + "()";
//                    ShowTip( aNew );
//                    aAutoSearch = aText;
//                }
//                return;
// exchange for
                // function name is incomplete:
                // show matching functions name as tip above cell
                int maxFindNumber = 4;
                ::std::vector<OUString> aNewVec;
                miAutoPosFormula = pFormulaData->end();
                miAutoPosFormula = findTextAll(*pFormulaData, miAutoPosFormula, aText, aNewVec, false, maxFindNumber);
                if (miAutoPosFormula != pFormulaData->end())
                {
                    OUString tipStr;
                    ::std::vector<OUString>::iterator itStr = aNewVec.begin();
                    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());
                        }
                    }
                    ShowTip( tipStr );
                    aAutoSearch = aText;
                }
                return;
// end exchange
            }

            // function name is complete:
            // show tip below the cell with function name and arguments of function
            ShowArgumentsTip( aParagraph, aSelText, aSel, false);
        }
    }
}
```
maxFindNumberで、検索する最大数を指定できる（はず）。
で、実行してみたらこうなった。
![Screenshot from 2015-11-06 20:16:10.png](https://qiita-image-store.s3.amazonaws.com/0/100640/4791e850-7bc7-6d41-9ab5-16ef2bf195fc.png)
![Screenshot from 2015-11-06 20:16:35.png](https://qiita-image-store.s3.amazonaws.com/0/100640/729af24b-6329-fa4a-4130-d24c474420e0.png)

う、うーん…これはあれかな、maxFindNumber未満しか見つからなかった時の処理がしょぼいのかな。…で、読みなおしてみたら処理が下手くそだった。やり直し…

```cpp:/sc/source/ui/app/inputhdl.cxx
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
    {
// 中略
    }
    else // Forwards
    {
// 中略
    }

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

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

    // Formulas may only have 1 paragraph
    if ( pActiveView && pFormulaData && pEngine->GetParagraphCount() == 1 )
    {
// 中略
                if (miAutoPosFormula != pFormulaData->end())
                {
                    OUString tipStr;
                    ::std::vector<OUString>::iterator itStr = aNewVec.begin();
                    int maxFindNumber = 4;
                    for( ; itStr != aNewVec.end(); ++itStr ) {
// 中略
                        if(--maxFindNumber <= 0)
                            break;
                    }
                    ShowTip( tipStr );
                    aAutoSearch = aText;
                }
                return;
// 中略
    }
}
```
ついでに、findTextAll()関数が該当する関数を全て返すように仕様を変更。
これで、無事うまくいった。
![Screenshot from 2015-11-06 21:14:24.png](https://qiita-image-store.s3.amazonaws.com/0/100640/d468b8bd-3634-31d0-ab95-f171d01ab654.png)
![Screenshot from 2015-11-06 21:14:41.png](https://qiita-image-store.s3.amazonaws.com/0/100640/ed3a491d-ef56-23fb-230d-3688bcc067e7.png)
![Screenshot from 2015-11-06 21:15:01.png](https://qiita-image-store.s3.amazonaws.com/0/100640/643100fd-a0a8-3f12-d292-dc51a00edb8a.png)
![Screenshot from 2015-11-06 21:15:13.png](https://qiita-image-store.s3.amazonaws.com/0/100640/f71ba39e-0942-c006-e3df-302451a050a9.png)

#今回のまとめ
ついに目的を半分くらい達成した。やったね。
#次回予告
次回、いよいよ大詰め！やりたいことをどんどん実装していきます。


#リンク
##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)