#前回のまとめ
でっかい進捗を産んだ。
#キー入力について
##偶然見つけたコマンド
ソースコードをぼーっと眺めていると、こんな関数を発見した。
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()関数を修正する。
// 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
さーどうかなー…?
この状態で、Ctrl+Tabを押すと、
この状態になる。
「IFERROR(),IFNA(), IMABS(), [次の関数]()」ってなってほしいんだけど…あれ?なんか違うかも!
どうやら、NextFormulaEntry()
関数(Ctrl+Tabをおしたときに呼ばれる関数)内でも似たような処理をしており、その部分も書き換えなきゃいけないらしい。統一してよ〜。
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())と書くためには、一周回るときの最初と最後を一致させておかなければならない(わかりづらい!)。
がんばってるけど微妙にうまくいかない…。
// 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が効かない…。
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を返すので、この場合は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
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())。
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が出た。
/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型に負の数を入れてオーバーフローを起こしてたのか…これはやばいな。
再び修正。
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の初期化が終わったところ。
(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を実行した直後。
(gdb) p *it
$15 = (const ScTypedStrData &) @0x1c79ca0: {maStrValue = {
pData = 0x7fffa61e7190}, mfValue = 0,
meStrType = ScTypedStrData::Standard, mbIsDate = false}
*itPosとは異なる値。で、ループを抜けた後。
(gdb) p *retit
$16 = (const ScTypedStrData &) @0x1c79e70: {maStrValue = {
pData = 0x7fffa61d5e90}, mfValue = 0,
meStrType = ScTypedStrData::Standard, mbIsDate = false}
*retit=*itPos。どうやら、イテレータのコピーに失敗してるらしい…。深いコピーはしてくれないのか。
とか思いつつ該当箇所を見てみたら、こうなってた。
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()、結局なにする関数かよくわかってなかったやつだ…。
こう書き換えてみる。
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;
これで実行してみると…。で、できたー!!!
#関数の説明を出す
関数候補だけじゃなくて、せっかくならその関数の説明も出したい。
OUString ScFuncDesc::getDescription() const
{
OUString sRet;
if ( pFuncDesc )
sRet = *pFuncDesc;
return sRet;
}
これを呼べば、なんかうまくいきそう。
inputhdl.cxx
に関数ShowDescriptionTip()
を追加してみた。とりあえず、テスト的に、関数を表示しただけ。
// added
void ScInputHandler::ShowDescriptionTip( const OUString& rFuncName )
{
ShowTipBelow ( rFuncName );
}
// end added
で、これをUseFormulaData()で読み込む。
ShowDescriptionTip ( aNewVec[0] );
あとちゃんとヘッダファイルも書き換えておく。
// 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()
の中身がこちら。
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などという頭のおかしい値を返してくる。そこで、最初と最後に意味のない文字をつける(最初に=、最後に()を付けることでセル内の文字列っぽく仕立てあげる)。
// 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
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;
// (中略)
さあ、実行してみると…
おっ!?
ここで、Ctrl+Tabを押して…
うまくできた!
#バックスペースを押しても候補が消えないようにする
バックスペースを押すと選択肢が消えるのは非常に使いにくいので、修正する。
bool ScInputHandler::KeyInput( const KeyEvent& rKEvt, bool bStartEdit /* = false */ )
{
// (中略)
}
この関数の一行目にブレークポイントを張り、バックスペースを押した時にどういう挙動をするか確かめてみる。
// 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();
が起動しないようにすれば万事解決。
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;
これを利用して、バックスペースかどうかを判定する。
// 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;
}
これで、バックスペースを押しても候補や説明は消えないはず。
→と、思ったけどダメだった。
// 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()
関数が呼び出されていて、その冒頭で
void ScInputHandler::ShowTipCursor()
{
HideTip();
HideTipBelow();
EditView* pActiveView = pTopView ? pTopView : pTableView;
このように、HideTip()が呼ばれている。
うーむ、なんでShowTipCursorを呼ぶ必要があるんだろう?試しにこれを消してみる。
// 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
実行すると…
お、消えない!!
あ、あれ?
もうifは予測できないはずなのに??
ああー…。
残念、中身を更新してくれないっぽい。
というわけで、UseFormulaData()関数を呼んで無理やり更新させてみる。
// 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()を呼び出してみる。
↓Del
↓Del
↓Del
できたー!!
#引数の説明を表示
GetArgmentDescription()などという関数があるので、せっかくだから使ってみる。
/**
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 ;
これがその関数。検索を掛けてみたが、あまり使われてないみたい。
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;
}
#今回のまとめ
inputhdr.cxxに自分のやりたかったことが全て入っていたので、簡単に実装することができた。
結局、
- 関数候補をたくさん表示
- 関数の説明を表示
- 関数の引数の説明を表示
- 上記のツールチップがバックスペースを押しても消えないように改良
を実装した。
#次回予告
せっかく作ったので、LibreOfficeにコミットしてreviewを受けてみる。
#リンク
##LibreOfficeCalcに関数候補表示機能を付けるまで