Find and replace For Base – difficulty interesting EasyHack

LibreOffice Base is part of LibreOffice productivity suite that makes it possible to work with databases. It is an alternative to MS Access. One of the proposed enhancement for Base is to add a “Find and replace” dialog. Right now, a “Find” dialog is available, but it is not possible to do the replacement with the LibreOffice Base dialogs. This issue is filed as tdf#32506.

The importance

This was requested for a long time ago, but until now no developer has put time to make it a reality. This feature request has is a difficutlyIntersting EasyHack, which means it is among the EasyHacks that need more work compared to the difficutlyBeginner and difficutlyMedium ones.

I will describe the details of the task, and if you find it interesting, you can start working on it. Solving difficutlyIntersting EasyHacks is among the criterias for selecting GSoC candidates, so it worth trying if you want to be among next year GSoC candidates.

It is worth mentioning that MS Office provide a comparable functionality in “Find and replace” dialog for MS Access. Thus, it would be helpful for the people migrating from Access to Base.

Proposed UI Design for Find and Replace

Enrique, which proposed this enhancement, also provided a design for the “Search and replace” dialog.

Proposed design for LibreOffice Base Find and Replace dialog

Proposed design for LibreOffice Base Find and Replace dialog

Code Pointers For Implementing Find and Replace

As described, this enhancement will be extending the search functionality of Base with the ability to do replacement, which is not currently available from dialogs. It is however possible to use SQL queries to do the replacement. Then, the task would be extending the search dialog, and then adding the required methods that use SQL to do search and replacement.

Lionel, a LibreOffice Base developer, has suggested this path, which I have updated:

The discussed dialog is instantiated in this C++ file
dbaccess/source/ui/browser/brwctrlr.cxx:1798:

pDialog = pFact->CreateFmSearchDialog(getFrameWeld(), sInitialText, aContextNames, 0, LINK(this, SbaXDataBrowserController, OnSearchContextRequest));
pDialog->SetActiveField( sActiveField );
pDialog->SetFoundHandler( LINK( this, SbaXDataBrowserController, OnFoundData ) );
pDialog->SetCanceledNotFoundHdl( LINK( this, SbaXDataBrowserController, OnCanceledNotFound ) );
pDialog->Execute();
pDialog.disposeAndClear();

As the SetFoundHandler() uses OnFoundData, we search the same file for "OnFoundData", and find it in the line 2347:

IMPL_LINK(SbaXDataBrowserController, OnFoundData, FmFoundRecordInformation&, rInfo, void)
{
...
}

This function is called, when a match is found.

The comment above the function SetFoundHandler() describes the idea of “found handler”s:

/** The found-handler gets in the 'found'-case a pointer on a FmFoundRecordInformation-structure
(which is only valid in the handler; so if one needs to memorize the data, don't copy the pointer but
the structure).
This handler MUST be set.
Furthermore, it should be considered, that during the handler the search-dialog is still modal.
*/
void SetFoundHandler(const Link<FmFoundRecordInformation&, void>& lnk)
{
...
}

In the above mentioned file, brwctlr.cxx, this is the start of handler function:

Reference< css::sdbcx::XRowLocate > xCursor(getRowSet(), UNO_QUERY);

This "xCursor" is the form object. The brwctlr.cxx is only for grid (table) controls. For other controls, one should look into svx/source/form/fmshimp.cxx:1544:

SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create();
ScopedVclPtr<AbstractFmSearchDialog> pDialog(
pFact->CreateFmSearchDialog(
m_pShell->GetViewShell()->GetViewFrame().GetFrameWeld(),
strInitialText, aContextNames, nInitialContext,
LINK(this, FmXFormShell, OnSearchContextRequest_Lock) ));
pDialog->SetActiveField( strActiveField );
pDialog->SetFoundHandler(LINK(this, FmXFormShell, OnFoundData_Lock));
pDialog->SetCanceledNotFoundHdl(LINK(this, FmXFormShell, OnCanceledNotFound_Lock));
pDialog->Execute();
pDialog.disposeAndClear();

The corresponding OnFoundData is line 2150:

IMPL_LINK(FmXFormShell, OnFoundData_Lock, FmFoundRecordInformation&, rfriWhere, void)
{
    if (impl_checkDisposed_Lock())
        return;

    DBG_ASSERT((rfriWhere.nContext >= 0) && (o3tl::make_unsigned(rfriWhere.nContext) < m_aSearchForms.size()),
        "FmXFormShell::OnFoundData : invalid context!");
    Reference< XForm> xForm( m_aSearchForms.at(rfriWhere.nContext));
    DBG_ASSERT(xForm.is(), "FmXFormShell::OnFoundData : invalid form!");
...
}

And then we can use the form object to implement the required change to fulfill the request.

Possible Pitfalls

It is important not to cause troubles with the keys, both foreign keys and primary keys. The idea is to allow find and replace in primary and foreign keys, but then it would be the role of the underlying database engine to see if the replacement is actually possible, or not, and then raise an error message.

Also, it would be the responsibility of the users to make sure that the search and replace they issue is a meaningful one. But, anyway the developer should handle the errors from the underlying database engine.

Final Notes

To implement this feature, first you have to build LibreOffice from the sources. If you have not done that yet, please refer to this guide first:

Getting Started (Video Tutorial)