LoginSignup
1
1

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-11-25

前回のまとめ

なんかいい感じのフォルダを見つけた。

inputhdl.cxxに辿り着くまで

関数の引数の説明を出す関数

前回読んでいた/sc/source/core/data/funcdesk.cxxの中身を目で追っていると、こんな関数を発見。

/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を足して、)を足す…。これって、もしかして関数の引数の説明では?
というわけで、ちょっと内容を改変して実行してみる。

/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
うまくいった!やはり関数の引数の説明はここで作っていたようだ。

バックトレースの旅

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

/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));

という一文に着目。ここにデータが入っているようなので、このデータを呼び出している関数を探す。

/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クラスの定義はこれ。

/sc/inc/typedstrdata.hxx
typedef std::set<ScTypedStrData, ScTypedStrData::LessCaseSensitive> ScTypedCaseStrSet;

setはC++のSTL(標準コンテナ、配列のすごいやつ)の一つで、有限集合を扱うクラス。なんだけど、普通型のとこ(<>の中)にはクラス名を1個だけ指定するよね?なんでコンマ区切りで2個あるんだ…?
調べてみて解決。なんとなく想像ついてはいたけど、比較関数を指定できるのね。
で、肝心の「ScTypedStrData(おなまえ, 0.0, ScTypedStrData::Standard)」がわからないとどうしようもないので、ScTypedStrDataクラスの定義を探した。

/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;
};

無難に比較演算子とかがオーバーロードされてるだけ。
コンストラクタを見てみる。

/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()関数を見てみよう。

/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は

/sc/source/ui/inc/inputhdl.hxx
    ::std::set< sal_Unicode >    maFormulaChar;  //fdo 75264

と、Unicodeの集合。中身は以下の通り、ちょっと前に紹介したGetFormulaData()関数内で作られてる。

/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がちゃんと思ったとおりに動作してるか確認。

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:というわけ。
で、その際の処理を再掲。

/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()関数を見てみよう。

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は思い切ってカット。
関数の中身は、第一引数rDataSetのなかで第二引数のイテレータitPosから前進しつつ第三引数rStartを探して、isMatch()関数にrDataSet[i]とrStartをぶちこんだ答えがtrueになるようなiがあったらそのrDataSet[i]を第四引数rResultに代入してそこのイテレータを返すって感じ。

関数候補表示の実装

とりあえず作ってみる

とりあえず、これまでわかったことだけでもやりたい機能はそれなりに?作れる、ような、気がする。
ので、作ってみた。

/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
Screenshot from 2015-11-05 19:42:55.png
Screenshot from 2015-11-05 19:43:08.png
きました!!!!!成功です!!!!

集合で返すように修正

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

/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

で、読み出す方はこう。

/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
Screenshot from 2015-11-06 20:16:35.png

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

/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
/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
Screenshot from 2015-11-06 21:14:41.png
Screenshot from 2015-11-06 21:15:01.png
Screenshot from 2015-11-06 21:15:13.png

今回のまとめ

ついに目的を半分くらい達成した。やったね。

次回予告

次回、いよいよ大詰め!やりたいことをどんどん実装していきます。

リンク

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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1