LibreOfficeCalcに関数候補表示機能を付けるまで 第3回 メインループと全探索

#前回のまとめ
btとgdbで、一番のメインっぽい関数を見つけた。

#Main関数を見る
##Main関数のなかみ
```cpp:/desktop/source/app/app.cxx
int Desktop::Main()
{
    pExecGlobals = new ExecuteGlobals();

    SAL_INFO( "desktop.app", "desktop (cd100003) ::Desktop::Main" );

    // Remember current context object
    com::sun::star::uno::ContextLayer layer(
        com::sun::star::uno::getCurrentContext() );

    if ( m_aBootstrapError != BE_OK )
    {
        HandleBootstrapErrors( m_aBootstrapError, m_aBootstrapErrorMessage );
        return EXIT_FAILURE;
    }

    BootstrapStatus eStatus = GetBootstrapStatus();
    if (eStatus == BS_TERMINATE) {
        return EXIT_SUCCESS;
    }

    // Detect desktop environment - need to do this as early as possible
    com::sun::star::uno::setCurrentContext(
        new DesktopContext( com::sun::star::uno::getCurrentContext() ) );

    CommandLineArgs& rCmdLineArgs = GetCommandLineArgs();

#if HAVE_FEATURE_DESKTOP
    OUString aUnknown( rCmdLineArgs.GetUnknown() );
    if ( !aUnknown.isEmpty() )
    {
        displayCmdlineHelp( aUnknown );
        return EXIT_FAILURE;
    }
    if ( rCmdLineArgs.IsHelp() )
    {
        displayCmdlineHelp( OUString() );
        return EXIT_SUCCESS;
    }
    if ( rCmdLineArgs.IsVersion() )
    {
        displayVersion();
        return EXIT_SUCCESS;
    }
#endif
    // setup configuration error handling
    ConfigurationErrorHandler aConfigErrHandler;
    if (!ShouldSuppressUI(rCmdLineArgs))
        aConfigErrHandler.activate();

    ResMgr::SetReadStringHook( ReplaceStringHookProc );

    // Startup screen
    SAL_INFO( "desktop.app", "desktop (lo119109) Desktop::Main { OpenSplashScreen" );
    OpenSplashScreen();
    SAL_INFO( "desktop.app", "desktop (lo119109) Desktop::Main } OpenSplashScreen" );

    SetSplashScreenProgress(100/*10*/);

    userinstall::Status inst_fin = userinstall::finalize();
    if (inst_fin != userinstall::EXISTED && inst_fin != userinstall::CREATED)
    {
        SAL_WARN( "desktop.app", "userinstall failed");
        if ( inst_fin == userinstall::ERROR_NO_SPACE )
            HandleBootstrapErrors(
                BE_USERINSTALL_NOTENOUGHDISKSPACE, OUString() );
        else if ( inst_fin == userinstall::ERROR_CANT_WRITE )
            HandleBootstrapErrors( BE_USERINSTALL_NOWRITEACCESS, OUString() );
        else
            HandleBootstrapErrors( BE_USERINSTALL_FAILED, OUString() );
        return EXIT_FAILURE;
    }
    // refresh path information
    utl::Bootstrap::reloadData();
    SetSplashScreenProgress(90/*20*/);

    Reference< XComponentContext > xContext = ::comphelper::getProcessComponentContext();

    Reference< XRestartManager > xRestartManager( OfficeRestartManager::get(xContext) );

    Reference< XDesktop2 > xDesktop;
    try
    {
        RegisterServices(xContext);

        SetSplashScreenProgress(75/*25*/);

#if HAVE_FEATURE_DESKTOP
        // check user installation directory for lockfile so we can be sure
        // there is no other instance using our data files from a remote host
        SAL_INFO( "desktop.app", "desktop (lo119109) Desktop::Main -> Lockfile" );
        m_xLockfile.reset(new Lockfile);

        if ( !rCmdLineArgs.IsHeadless() && !rCmdLineArgs.IsInvisible() &&
             !rCmdLineArgs.IsNoLockcheck() && !m_xLockfile->check( Lockfile_execWarning ))
        {
            // Lockfile exists, and user clicked 'no'
            return EXIT_FAILURE;
        }
        SAL_INFO( "desktop.app", "desktop (lo119109) Desktop::Main <- Lockfile" );

        // check if accessibility is enabled but not working and allow to quit
        SAL_INFO( "desktop.app", "{ GetEnableATToolSupport" );
        if( Application::GetSettings().GetMiscSettings().GetEnableATToolSupport() )
        {
            if( !InitAccessBridge() )
                return EXIT_FAILURE;
        }
        SAL_INFO( "desktop.app", "} GetEnableATToolSupport" );
#endif

        // terminate if requested...
        if( rCmdLineArgs.IsTerminateAfterInit() )
            return EXIT_SUCCESS;

        //  Read the common configuration items for optimization purpose
        if ( !InitializeConfiguration() )
            return EXIT_FAILURE;

        SetSplashScreenProgress(70/*30*/);

        // set static variable to disable crash reporting
        osl_setErrorReporting( false );

        // create title string
        LanguageTag aLocale( LANGUAGE_SYSTEM);
        ResMgr* pLabelResMgr = ResMgr::SearchCreateResMgr( "ofa", aLocale );
        OUString aTitle = pLabelResMgr ? ResId(RID_APPTITLE, *pLabelResMgr).toString() : OUString();
        delete pLabelResMgr;

#ifdef DBG_UTIL
        //include buildid in non product builds
        OUString aDefault("development");
        aTitle += " [";
        aTitle += utl::Bootstrap::getBuildIdData(aDefault);
        aTitle += "]";
#endif

        SetDisplayName( aTitle );
        SetSplashScreenProgress(65/*35*/);
        SAL_INFO( "desktop.app", "{ create SvtPathOptions and SvtLanguageOptions" );
        pExecGlobals->pPathOptions.reset( new SvtPathOptions);
        SetSplashScreenProgress(60/*40*/);
        SAL_INFO( "desktop.app", "} create SvtPathOptions and SvtLanguageOptions" );

        xDesktop = css::frame::Desktop::create( xContext );

        // create service for loadin SFX (still needed in startup)
        pExecGlobals->xGlobalBroadcaster = Reference < css::document::XDocumentEventListener >
            ( css::frame::theGlobalEventBroadcaster::get(xContext), UNO_SET_THROW );

        /* ensure existence of a default window that messages can be dispatched to
           This is for the benefit of testtool which uses PostUserEvent extensively
           and else can deadlock while creating this window from another tread while
           the main thread is not yet in the event loop.
        */
        Application::GetDefaultDevice();

#if HAVE_FEATURE_EXTENSIONS
        // Check if bundled or shared extensions were added /removed
        // and process those extensions (has to be done before checking
        // the extension dependencies!
        SynchronizeExtensionRepositories();
        bool bAbort = CheckExtensionDependencies();
        if ( bAbort )
            return EXIT_FAILURE;

        if (inst_fin == userinstall::CREATED)
        {
            Migration::migrateSettingsIfNecessary();
        }
#endif

        // keep a language options instance...
        pExecGlobals->pLanguageOptions.reset( new SvtLanguageOptions(true));

        css::document::DocumentEvent aEvent;
        aEvent.EventName = "OnStartApp";
        pExecGlobals->xGlobalBroadcaster->documentEventOccured(aEvent);

        SetSplashScreenProgress(50/*50*/);

        // Backing Component
        bool bCrashed            = false;
        bool bExistsRecoveryData = false;
        bool bExistsSessionData  = false;

        SAL_INFO( "desktop.app", "{ impl_checkRecoveryState" );
        impl_checkRecoveryState(bCrashed, bExistsRecoveryData, bExistsSessionData);
        SAL_INFO( "desktop.app", "} impl_checkRecoveryState" );

        OUString pidfileName = rCmdLineArgs.GetPidfileName();
        if ( !pidfileName.isEmpty() )
        {
            OUString pidfileURL;

            if ( osl_getFileURLFromSystemPath(pidfileName.pData, &pidfileURL.pData) == osl_File_E_None )
            {
                osl::File pidfile( pidfileURL );
                osl::FileBase::RC rc;

                osl::File::remove( pidfileURL );
                if ( (rc = pidfile.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ) ) == osl::File::E_None )
                {
                    OString pid( OString::number( GETPID() ) );
                    sal_uInt64 written = 0;
                    if ( pidfile.write(pid.getStr(), pid.getLength(), written) != osl::File::E_None )
                    {
                        SAL_WARN("desktop.app", "cannot write pidfile " << pidfile.getURL());
                    }
                    pidfile.close();
                }
                else
                {
                    SAL_WARN("desktop.app", "cannot open pidfile " << pidfile.getURL() << osl::FileBase::RC(rc));
                }
            }
            else
            {
                SAL_WARN("desktop.app", "cannot get pidfile URL from path" << pidfileName);
            }
        }

        if ( rCmdLineArgs.IsHeadless() )
        {
            // Ensure that we use not the system file dialogs as
            // headless mode relies on Application::EnableHeadlessMode()
            // which does only work for VCL dialogs!!
            SvtMiscOptions aMiscOptions;
            pExecGlobals->bUseSystemFileDialog = aMiscOptions.UseSystemFileDialog();
            aMiscOptions.SetUseSystemFileDialog( false );
        }

        pExecGlobals->bRestartRequested = xRestartManager->isRestartRequested(
            true);
        if ( !pExecGlobals->bRestartRequested )
        {
            if ((!rCmdLineArgs.WantsToLoadDocument() && !rCmdLineArgs.IsInvisible() && !rCmdLineArgs.IsHeadless() && !rCmdLineArgs.IsQuickstart()) &&
                (SvtModuleOptions().IsModuleInstalled(SvtModuleOptions::E_SSTARTMODULE)) &&
                (!bExistsRecoveryData                                                  ) &&
                (!bExistsSessionData                                                   ) &&
                (!Application::AnyInput( VCL_INPUT_APPEVENT )                          ))
            {
                 SAL_INFO( "desktop.app", "{ create BackingComponent" );
                 ShowBackingComponent(this);
                 SAL_INFO( "desktop.app", "} create BackingComponent" );
            }
        }
    }
    catch ( const com::sun::star::lang::WrappedTargetException& wte )
    {
        com::sun::star::uno::Exception te;
        wte.TargetException >>= te;
        FatalError( MakeStartupConfigAccessErrorMessage(wte.Message + te.Message) );
    }
    catch ( const com::sun::star::uno::Exception& e )
    {
        FatalError( MakeStartupErrorMessage(e.Message) );
    }
    SetSplashScreenProgress(45/*55*/);

    SvtFontSubstConfig().Apply();

    SvtTabAppearanceCfg aAppearanceCfg;
    SvtTabAppearanceCfg::SetInitialized();
    aAppearanceCfg.SetApplicationDefaults( this );
    SvtAccessibilityOptions aOptions;
    aOptions.SetVCLSettings();
    SetSplashScreenProgress(40/*60*/);

#if ENABLE_TELEPATHY
    bool bListen = rCmdLineArgs.IsInvisible();
    TeleManager::init( bListen );
#endif

    if ( !pExecGlobals->bRestartRequested )
    {
        Application::SetFilterHdl( LINK( this, Desktop, ImplInitFilterHdl ) );
        bool bTerminateRequested = false;

        // Preload function depends on an initialized sfx application!
        SetSplashScreenProgress(25/*75*/);

        // use system window dialogs
        Application::SetSystemWindowMode( SYSTEMWINDOW_MODE_DIALOG );

        SetSplashScreenProgress(20/*80*/);

        if ( !bTerminateRequested && !rCmdLineArgs.IsInvisible() &&
             !rCmdLineArgs.IsNoQuickstart() )
            InitializeQuickstartMode( xContext );

        SAL_INFO( "desktop.app", "desktop (cd100003) createInstance com.sun.star.frame.Desktop" );
        try
        {
            if ( xDesktop.is() )
                xDesktop->addTerminateListener( new OfficeIPCThreadController );
            SetSplashScreenProgress(5/*100*/);
        }
        catch ( const com::sun::star::uno::Exception& e )
        {
            FatalError( MakeStartupErrorMessage(e.Message) );
        }

        // Release solar mutex just before we wait for our client to connect
        int nAcquireCount = Application::ReleaseSolarMutex();

        // Post user event to startup first application component window
        // We have to send this OpenClients message short before execute() to
        // minimize the risk that this message overtakes type detection construction!!
        Application::PostUserEvent( LINK( this, Desktop, OpenClients_Impl ) );

        // Post event to enable acceptors
        Application::PostUserEvent( LINK( this, Desktop, EnableAcceptors_Impl) );

        // The configuration error handler currently is only for startup
        aConfigErrHandler.deactivate();

       // Acquire solar mutex just before we enter our message loop
        if ( nAcquireCount )
            Application::AcquireSolarMutex( nAcquireCount );

        // call Application::Execute to process messages in vcl message loop
        SAL_INFO( "desktop.app", "PERFORMANCE - enter Application::Execute()" );

        try
        {
#if HAVE_FEATURE_JAVA
            // The JavaContext contains an interaction handler which is used when
            // the creation of a Java Virtual Machine fails
            com::sun::star::uno::ContextLayer layer2(
                new svt::JavaContext( com::sun::star::uno::getCurrentContext() ) );
#endif
            // check whether the shutdown is caused by restart just before entering the Execute
            pExecGlobals->bRestartRequested = pExecGlobals->bRestartRequested ||
                xRestartManager->isRestartRequested(true);

            if ( !pExecGlobals->bRestartRequested )
            {
                // if this run of the office is triggered by restart, some additional actions should be done
                DoRestartActionsIfNecessary( !rCmdLineArgs.IsInvisible() && !rCmdLineArgs.IsNoQuickstart() );

                Execute();
            }
        }
        catch(const com::sun::star::document::CorruptedFilterConfigurationException& exFilterCfg)
        {
            OfficeIPCThread::SetDowning();
            FatalError( MakeStartupErrorMessage(exFilterCfg.Message) );
        }
        catch(const com::sun::star::configuration::CorruptedConfigurationException& exAnyCfg)
        {
            OfficeIPCThread::SetDowning();
            FatalError( MakeStartupErrorMessage(exAnyCfg.Message) );
        }
        catch( const ::com::sun::star::uno::Exception& exUNO)
        {
            OfficeIPCThread::SetDowning();
            FatalError( exUNO.Message);
        }
        catch( const std::exception& exSTD)
        {
            OfficeIPCThread::SetDowning();
            FatalError( OUString::createFromAscii( exSTD.what()));
        }
        catch( ...)
        {
            OfficeIPCThread::SetDowning();
            FatalError( OUString( "Caught Unknown Exception: Aborting!"));
        }
    }
    else
    {
        if (xDesktop.is())
            xDesktop->terminate();
    }
    // CAUTION: you do not necessarily get here e.g. on the Mac.
    // please put all deinitialization code into doShutdown
    return doShutdown();
}

```
##プログレスバー
`CommandLineArgs& rCmdLineArgs = GetCommandLineArgs();`
ここを`next`で通過すると、

```text:gdb
(gdb) p rCmdLineArgs
$6 = (desktop::CommandLineArgs &) @0x7ffff7b331a0: 
(中略)
  m_help = false, m_writer = false, m_calc = true, m_draw = false, 
(後略)
```
`--calc`のコマンドが読み取られていることがわかる。
`OpenSplashScreen();`を実行すると、中身が空のウィンドウが宙に浮いたような感じででてくる。いまからここにロゴを書いていくのだろう。
`SetSplashScreenProgress(10);`、`SetSplashScreenProgress(20);`などを実行するたびに、画面上のプログレスバーが増えていく。

`Execute();`を実行するとCalcが起動するので、この関数を見ていく。

```cpp:/vcl/source/app/svapp.cxx
void Application::Execute()
{
    ImplSVData* pSVData = ImplGetSVData();
    pSVData->maAppData.mbInAppExecute = true;

    while ( !pSVData->maAppData.mbAppQuit )
        Application::Yield();

    pSVData->maAppData.mbInAppExecute = false;
}
```

この後、`Application::Yield();`をどんどん探っていったが、いろいろな関数をたらい回しにされた挙句、最終的にGTKとかのウィンドウ制御のメインループにたどり着いてしまった。これ以降をgdbで探るのは無理そう…。

#全探索する
ここまでの知識を投げ捨てて、全検索に走るしかなくなってしまった。ので、全検索しまくる。
##全検索に便利なサイト
[Opengrok](http://opengrok.libreoffice.org/)というサイトがあり、ここでLibreOfficeのコードを簡単に検索することができる。
[ソースコードのDocumentation](http://docs.libreoffice.org/)によると、LibreOffice Calcは「sc」という名前で管理されているようなので、scフォルダ以下を探るのが良さそう。

##検索する言葉
いまやりたいのは「=ifなどと打った時に出てくるツールチップをもっと良くしたい」ということなので、とりあえず「AVERAGEIF」で検索してみる（関数一覧がどこかにあるはずなので、それを見つければ話は早いはず）。
こんなのを見つけた。

```cpp:/sc/source/filter/oox/formulabase.cxx
static const FunctionData saFuncTableBiff2[] =
{
    { "COUNT",                  "COUNT",                0,      0,      0,  MX, V, { RX }, 0 },
    { "IF",                     "IF",                   1,      1,      2,  3,  R, { VO, RO }, 0 },
    { "ISNA",                   "ISNA",                 2,      2,      1,  1,  V, { VR }, 0 },
 // 中略
};
```
これはかなり求めてるものに近いぞ、ということで、この変数を呼んでいる場所を探す。
…が、`formulabase.cxx`内でしか見つからない。

```cpp:/sc/source/filter/oox/formulabase.cxx
void FunctionProviderImpl::initFuncs( const FunctionData* pBeg, const FunctionData* pEnd, sal_uInt8 nMaxParam,
        bool bImportFilter, FilterType eFilter )
{
    for( const FunctionData* pIt = pBeg; pIt != pEnd; ++pIt )
        if( pIt->isSupported( bImportFilter, eFilter ) )
            initFunc( *pIt, nMaxParam );
}
```

```cpp:/sc/source/filter/oox/formulabase.cxx
void FunctionProviderImpl::initFunc( const FunctionData& rFuncData, sal_uInt8 nMaxParam )
{
    // create a function info object
    FunctionInfoRef xFuncInfo( new FunctionInfo );
    if( rFuncData.mpcOdfFuncName )
        xFuncInfo->maOdfFuncName = OUString::createFromAscii( rFuncData.mpcOdfFuncName );
    if( rFuncData.mpcOoxFuncName )
        xFuncInfo->maOoxFuncName = OUString::createFromAscii( rFuncData.mpcOoxFuncName );

    if( getFlag( rFuncData.mnFlags, FUNCFLAG_MACROCALL ) )
    {
        OSL_ENSURE( !xFuncInfo->maOoxFuncName.isEmpty(), "FunctionProviderImpl::initFunc - missing OOXML function name" );
        OSL_ENSURE( !getFlag( rFuncData.mnFlags, FUNCFLAG_MACROCALLODF ), "FunctionProviderImpl::initFunc - unexpected flag FUNCFLAG_MACROCALLODF" );
        xFuncInfo->maBiffMacroName = "_xlfn." + xFuncInfo->maOoxFuncName;
        if( getFlag( rFuncData.mnFlags, FUNCFLAG_MACROCALL_FN ) )
        {
            xFuncInfo->maOoxFuncName = "_xlfn." + xFuncInfo->maOoxFuncName;
            //! From here on maOoxFuncName contains the _xlfn. prefix!
        }
    }
    else if( getFlag( rFuncData.mnFlags, FUNCFLAG_MACROCALLODF ) )
    {
        OSL_ENSURE( !xFuncInfo->maOdfFuncName.isEmpty(), "FunctionProviderImpl::initFunc - missing ODF function name" );
        xFuncInfo->maBiffMacroName = "_xlfnodf." + xFuncInfo->maOdfFuncName;
    }

    xFuncInfo->meFuncLibType = FUNCFLAGS_TO_FUNCLIB( rFuncData.mnFlags );
    xFuncInfo->mnApiOpCode = -1;
    xFuncInfo->mnBiff12FuncId = rFuncData.mnBiff12FuncId;
    xFuncInfo->mnBiffFuncId = rFuncData.mnBiffFuncId;
    xFuncInfo->mnMinParamCount = rFuncData.mnMinParamCount;
    xFuncInfo->mnMaxParamCount = (rFuncData.mnMaxParamCount == MX) ? nMaxParam : rFuncData.mnMaxParamCount;
    xFuncInfo->mnRetClass = rFuncData.mnRetClass;
    xFuncInfo->mpParamInfos = rFuncData.mpParamInfos;
    xFuncInfo->mbParamPairs = getFlag( rFuncData.mnFlags, FUNCFLAG_PARAMPAIRS );
    xFuncInfo->mbVolatile = getFlag( rFuncData.mnFlags, FUNCFLAG_VOLATILE );
    xFuncInfo->mbExternal = getFlag( rFuncData.mnFlags, FUNCFLAG_EXTERNAL );
    xFuncInfo->mbInternal = !xFuncInfo->mbExternal || getFlag( rFuncData.mnFlags, FUNCFLAG_INTERNAL );
    bool bMacroCmd = getFlag( rFuncData.mnFlags, FUNCFLAG_MACROCMD );
    xFuncInfo->mbMacroFunc = bMacroCmd || getFlag( rFuncData.mnFlags, FUNCFLAG_MACROFUNC );
    xFuncInfo->mbVarParam = bMacroCmd || (rFuncData.mnMinParamCount != rFuncData.mnMaxParamCount) || getFlag( rFuncData.mnFlags, FUNCFLAG_ALWAYSVAR );

    setFlag( xFuncInfo->mnBiff12FuncId, BIFF_TOK_FUNCVAR_CMD, bMacroCmd );
    setFlag( xFuncInfo->mnBiffFuncId, BIFF_TOK_FUNCVAR_CMD, bMacroCmd );

    // insert the function info into the member maps
    maFuncs.push_back( xFuncInfo );
    if( !xFuncInfo->maOoxFuncName.isEmpty() )
        maOoxFuncs[ xFuncInfo->maOoxFuncName ] = xFuncInfo;
    if( xFuncInfo->mnBiff12FuncId != NOID )
        maBiff12Funcs[ xFuncInfo->mnBiff12FuncId ] = xFuncInfo;
    if( xFuncInfo->mnBiffFuncId != NOID )
        maBiffFuncs[ xFuncInfo->mnBiffFuncId ] = xFuncInfo;
    if( !xFuncInfo->maBiffMacroName.isEmpty() )
        maMacroFuncs[ xFuncInfo->maBiffMacroName ] = xFuncInfo;
}
```
`// create a function info object`の文字にテンションが上がる。引数に付いている&マークは参照渡しの意味。
この後、この関数についていろいろ調べてみたが、どうもいまいち空振りっぽい感触。ブレークポイントを仕込んでも止まらず、printfをはさんでも出力されない。この関数はなんなんだ…？

##ooxの意味
ここで、なんとなく[ソースコードのDocumentation](http://docs.libreoffice.org/)を読んでいて、あることに気付いた。ソースコードのパスにある「oox」の文字が見出しにある。意味は、「Support for Office Open XML, the office XML-format designed by Microsoft.」
ん、もしかして、このファイルって「*.xlsx」を読み込むときにしか使われないプログラムなんですかね…？ま、まさかね…？
恐る恐るxlsxファイルを与えて起動してみると、事前に設定しておいたブレークポイントで止まった！！

##結局
今回調べたところは、関係のない部分だった。

#次回予告
今回の教訓を活かして、次回はフォルダ名から的を絞って検索を行ってみる。


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