tdf#160543 Quickfind sidebar enhancements
* Makes the search independent of find and replace options.
* Adds a button to launch a dialog for match case, whole words only,
and similarity search options.
* Adds a button to launch the find and replace dialog.
* Makes the find entry control have visual feedback when there are no
matches.
* Adds a label at the bottom of panel that shows the number of matches.
Change-Id: I2cffcf86978773471bb86c5e5cf8b967c24efa7b
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/168834
Reviewed-by: Jim Raykowski <raykowj@gmail.com>
Tested-by: Jenkins
diff --git a/sw/UIConfig_swriter.mk b/sw/UIConfig_swriter.mk
index 27b7248..2621239 100644
--- a/sw/UIConfig_swriter.mk
+++ b/sw/UIConfig_swriter.mk
@@ -292,6 +292,7 @@ $(eval $(call gb_UIConfig_add_uifiles,modules/swriter,\
sw/uiconfig/swriter/ui/sidebartableedit \
sw/uiconfig/swriter/ui/sidebartheme \
sw/uiconfig/swriter/ui/sidebarquickfind \
sw/uiconfig/swriter/ui/sidebarquickfindoptionsdialog \
sw/uiconfig/swriter/ui/sortdialog \
sw/uiconfig/swriter/ui/spellmenu \
sw/uiconfig/swriter/ui/splittable \
diff --git a/sw/inc/strings.hrc b/sw/inc/strings.hrc
index 298b861..b9e46f1 100644
--- a/sw/inc/strings.hrc
+++ b/sw/inc/strings.hrc
@@ -1396,7 +1396,7 @@
#define STR_NUM_OUTLINE NC_("STR_NUM_OUTLINE", "Outline ")
#define STR_EDIT_FOOTNOTE NC_("STR_EDIT_FOOTNOTE", "Edit Footnote/Endnote")
#define STR_NB_REPLACED NC_("STR_NB_REPLACED", "Search key replaced XX times.")
#define STR_SEARCH_KEY_FOUND_TIMES NC_("STR_SEARCH_KEY_FOUND_TIMES", "Search key found %1 times.")
#define STR_SEARCH_KEY_FOUND_TIMES NNC_("STR_SEARCH_KEY_FOUND_TIMES", "One match found.", "%1 matches found.")
#define STR_SRCVIEW_ROW NC_("STR_SRCVIEW_ROW", "Row ")
#define STR_SRCVIEW_COL NC_("STR_SRCVIEW_COL", "Column ")
#define STR_SAVEAS_SRC NC_("STR_SAVEAS_SRC", "~Export source...")
diff --git a/sw/source/uibase/sidebar/QuickFindPanel.cxx b/sw/source/uibase/sidebar/QuickFindPanel.cxx
index da6e016..0f620fd 100644
--- a/sw/source/uibase/sidebar/QuickFindPanel.cxx
+++ b/sw/source/uibase/sidebar/QuickFindPanel.cxx
@@ -12,8 +12,6 @@
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <svl/srchitem.hxx>
#include <view.hxx>
#include <comphelper/dispatchcommand.hxx>
#include <comphelper/propertysequence.hxx>
#include <swmodule.hxx>
#include <pam.hxx>
#include <node.hxx>
@@ -25,9 +23,8 @@
#include <vcl/event.hxx>
#include <vcl/svapp.hxx>
#include <vcl/sysdata.hxx>
#include <swwait.hxx>
const int MinimumContainerWidth = 250;
const int Rounding = 6;
const int CharactersBeforeAndAfter = 40;
namespace
@@ -52,25 +49,85 @@ void getAnchorPos(SwPosition& rPos)
namespace sw::sidebar
{
std::unique_ptr<PanelLayout> QuickFindPanel::Create(weld::Widget* pParent)
QuickFindPanel::SearchOptionsDialog::SearchOptionsDialog(weld::Window* pParent)
: GenericDialogController(pParent, u"modules/swriter/ui/sidebarquickfindoptionsdialog.ui"_ustr,
u"SearchOptionsDialog"_ustr)
, m_xMatchCaseCheckButton(m_xBuilder->weld_check_button(u"matchcase"_ustr))
, m_xWholeWordsOnlyCheckButton(m_xBuilder->weld_check_button(u"wholewordsonly"_ustr))
, m_xSimilarityCheckButton(m_xBuilder->weld_check_button(u"similarity"_ustr))
, m_xSimilaritySettingsDialogButton(m_xBuilder->weld_button(u"similaritysettingsdialog"_ustr))
{
if (pParent == nullptr)
throw css::lang::IllegalArgumentException(
u"no parent Window given to QuickFindPanel::Create"_ustr, nullptr, 0);
return std::make_unique<QuickFindPanel>(pParent);
m_xSimilarityCheckButton->connect_toggled(
LINK(this, SearchOptionsDialog, SimilarityCheckButtonToggledHandler));
m_xSimilaritySettingsDialogButton->connect_clicked(
LINK(this, SearchOptionsDialog, SimilaritySettingsDialogButtonClickedHandler));
}
QuickFindPanel::QuickFindPanel(weld::Widget* pParent)
short QuickFindPanel::SearchOptionsDialog::executeSubDialog(VclAbstractDialog* dialog)
{
assert(!m_executingSubDialog);
comphelper::ScopeGuard g([this] { m_executingSubDialog = false; });
m_executingSubDialog = true;
return dialog->Execute();
}
IMPL_LINK_NOARG(QuickFindPanel::SearchOptionsDialog, SimilarityCheckButtonToggledHandler,
weld::Toggleable&, void)
{
m_xSimilaritySettingsDialogButton->set_sensitive(m_xSimilarityCheckButton->get_active());
}
IMPL_LINK_NOARG(QuickFindPanel::SearchOptionsDialog, SimilaritySettingsDialogButtonClickedHandler,
weld::Button&, void)
{
SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create();
ScopedVclPtr<AbstractSvxSearchSimilarityDialog> pDlg(pFact->CreateSvxSearchSimilarityDialog(
m_xDialog.get(), m_bIsLEVRelaxed, m_nLEVOther, m_nLEVShorter, m_nLEVLonger));
if (executeSubDialog(pDlg.get()) == RET_OK)
{
m_bIsLEVRelaxed = pDlg->IsRelaxed();
m_nLEVOther = pDlg->GetOther();
m_nLEVShorter = pDlg->GetShorter();
m_nLEVLonger = pDlg->GetLonger();
}
}
std::unique_ptr<PanelLayout>
QuickFindPanel::Create(weld::Widget* pParent,
const css::uno::Reference<css::frame::XFrame>& rxFrame)
{
if (pParent == nullptr)
throw lang::IllegalArgumentException("no parent Window given to QuickFindPanel::Create",
nullptr, 0);
if (!rxFrame.is())
throw lang::IllegalArgumentException("no XFrame given to QuickFindPanel::Create", nullptr,
0);
return std::make_unique<QuickFindPanel>(pParent, rxFrame);
}
QuickFindPanel::QuickFindPanel(weld::Widget* pParent, const uno::Reference<frame::XFrame>& rxFrame)
: PanelLayout(pParent, u"QuickFindPanel"_ustr, u"modules/swriter/ui/sidebarquickfind.ui"_ustr)
, m_xSearchFindEntry(m_xBuilder->weld_entry(u"Find"_ustr))
, m_xSearchOptionsToolbar(m_xBuilder->weld_toolbar(u"searchoptionstoolbar"_ustr))
, m_xFindAndReplaceToolbar(m_xBuilder->weld_toolbar(u"findandreplacetoolbar"_ustr))
, m_xFindAndReplaceToolbarDispatch(
new ToolbarUnoDispatcher(*m_xFindAndReplaceToolbar, *m_xBuilder, rxFrame))
, m_xSearchFindsList(m_xBuilder->weld_tree_view(u"searchfinds"_ustr))
, m_xSearchFindFoundTimesLabel(m_xBuilder->weld_label("numberofsearchfinds"))
, m_pWrtShell(::GetActiveWrtShell())
{
m_xContainer->set_size_request(MinimumContainerWidth, 1);
m_nMinimumPanelWidth
= m_xBuilder->weld_widget(u"box"_ustr)->get_preferred_size().getWidth() + (6 * 2) + 6;
m_xContainer->set_size_request(m_nMinimumPanelWidth, 1);
m_xSearchFindEntry->connect_activate(
LINK(this, QuickFindPanel, SearchFindEntryActivateHandler));
m_xSearchFindEntry->connect_changed(LINK(this, QuickFindPanel, SearchFindEntryChangedHandler));
m_xSearchOptionsToolbar->connect_clicked(
LINK(this, QuickFindPanel, SearchOptionsToolbarClickedHandler));
m_xSearchFindsList->connect_custom_get_size(
LINK(this, QuickFindPanel, SearchFindsListCustomGetSizeHandler));
m_xSearchFindsList->connect_custom_render(LINK(this, QuickFindPanel, SearchFindsListRender));
@@ -79,7 +136,40 @@ QuickFindPanel::QuickFindPanel(weld::Widget* pParent)
LINK(this, QuickFindPanel, SearchFindsListSelectionChangedHandler));
m_xSearchFindsList->connect_row_activated(
LINK(this, QuickFindPanel, SearchFindsListRowActivatedHandler));
m_xSearchFindsList->connect_mouse_press(LINK(this, QuickFindPanel, MousePressHandler));
m_xSearchFindsList->connect_mouse_press(
LINK(this, QuickFindPanel, SearchFindsListMousePressHandler));
}
IMPL_LINK_NOARG(QuickFindPanel, SearchOptionsToolbarClickedHandler, const OUString&, void)
{
SearchOptionsDialog aDlg(GetFrameWeld());
aDlg.m_xMatchCaseCheckButton->set_active(m_bMatchCase);
aDlg.m_xWholeWordsOnlyCheckButton->set_active(m_bWholeWordsOnly);
aDlg.m_xSimilarityCheckButton->set_active(m_bSimilarity);
aDlg.m_xSimilaritySettingsDialogButton->set_sensitive(m_bSimilarity);
if (m_bSimilarity)
{
aDlg.m_bIsLEVRelaxed = m_bIsLEVRelaxed;
aDlg.m_nLEVOther = m_nLEVOther;
aDlg.m_nLEVShorter = m_nLEVShorter;
aDlg.m_nLEVLonger = m_nLEVLonger;
}
if (aDlg.run() == RET_OK)
{
m_bMatchCase = aDlg.m_xMatchCaseCheckButton->get_active();
m_bWholeWordsOnly = aDlg.m_xWholeWordsOnlyCheckButton->get_active();
m_bSimilarity = aDlg.m_xSimilarityCheckButton->get_active();
if (m_bSimilarity)
{
m_bIsLEVRelaxed = aDlg.m_bIsLEVRelaxed;
m_nLEVOther = aDlg.m_nLEVOther;
m_nLEVShorter = aDlg.m_nLEVShorter;
m_nLEVLonger = aDlg.m_nLEVLonger;
}
FillSearchFindsList();
}
}
QuickFindPanel::~QuickFindPanel()
@@ -88,7 +178,20 @@ QuickFindPanel::~QuickFindPanel()
m_xSearchFindsList.reset();
}
IMPL_LINK(QuickFindPanel, MousePressHandler, const MouseEvent&, rMEvt, bool)
IMPL_LINK_NOARG(QuickFindPanel, SearchFindEntryChangedHandler, weld::Entry&, void)
{
m_xSearchFindEntry->set_message_type(weld::EntryMessageType::Normal);
m_xSearchFindsList->clear();
m_xSearchFindFoundTimesLabel->set_label(OUString());
}
IMPL_LINK_NOARG(QuickFindPanel, SearchFindEntryActivateHandler, weld::Entry&, bool)
{
FillSearchFindsList();
return true;
}
IMPL_LINK(QuickFindPanel, SearchFindsListMousePressHandler, const MouseEvent&, rMEvt, bool)
{
if (std::unique_ptr<weld::TreeIter> xEntry(m_xSearchFindsList->make_iterator());
m_xSearchFindsList->get_dest_row_at_pos(rMEvt.GetPosPixel(), xEntry.get(), false, false))
@@ -98,12 +201,6 @@ IMPL_LINK(QuickFindPanel, MousePressHandler, const MouseEvent&, rMEvt, bool)
return false;
}
IMPL_LINK_NOARG(QuickFindPanel, SearchFindEntryActivateHandler, weld::Entry&, bool)
{
FillSearchFindsList();
return true;
}
IMPL_LINK(QuickFindPanel, SearchFindsListCustomGetSizeHandler, weld::TreeView::get_size_args,
aPayload, Size)
{
@@ -129,7 +226,7 @@ IMPL_LINK(QuickFindPanel, SearchFindsListCustomGetSizeHandler, weld::TreeView::g
tools::Long nScrollBarThickness
= Application::GetSettings().GetStyleSettings().GetScrollBarSize();
tools::Rectangle aInRect(Point(), Size(MinimumContainerWidth - (x * 2) - leftTextMargin
tools::Rectangle aInRect(Point(), Size(m_nMinimumPanelWidth - (x * 2) - leftTextMargin
- nScrollBarThickness - rightTextMargin,
1));
@@ -198,7 +295,7 @@ IMPL_LINK(QuickFindPanel, SearchFindsListRender, weld::TreeView::render_args, aP
if (!bPageEntry)
{
aRect.AdjustRight(-3);
rRenderContext.DrawRect(aRect, Rounding, Rounding);
rRenderContext.DrawRect(aRect, 6, 6);
aRect.AdjustLeft(+6);
rRenderContext.DrawText(aRect, aEntry,
@@ -282,161 +379,201 @@ IMPL_LINK_NOARG(QuickFindPanel, SearchFindsListRowActivatedHandler, weld::TreeVi
return true;
}
IMPL_LINK_NOARG(QuickFindPanel, SearchFindEntryChangedHandler, weld::Entry&, void)
{
m_xSearchFindsList->clear();
}
void QuickFindPanel::FillSearchFindsList()
{
m_vPaMs.clear();
m_xSearchFindsList->clear();
const OUString& sText = m_xSearchFindEntry->get_text();
css::uno::Sequence<css::beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence({
{ "SearchItem.SearchString", css::uno::Any(sText) },
{ "SearchItem.Backward", css::uno::Any(false) },
{ "SearchItem.Command", css::uno::Any(sal_uInt16(SvxSearchCmd::FIND_ALL)) },
}));
m_xSearchFindFoundTimesLabel->set_label(OUString());
comphelper::dispatchCommand(u".uno:ExecuteSearch"_ustr, aPropertyValues);
if (!m_pWrtShell->HasMark())
const OUString& rsFindEntry = m_xSearchFindEntry->get_text();
if (rsFindEntry.isEmpty())
return;
for (SwPaM& rPaM : m_pWrtShell->GetCursor()->GetRingContainer())
SwWait aWait(*m_pWrtShell->GetDoc()->GetDocShell(), true);
m_pWrtShell->AssureStdMode();
i18nutil::SearchOptions2 aSearchOptions;
aSearchOptions.Locale = GetAppLanguageTag().getLocale();
aSearchOptions.searchString = rsFindEntry;
aSearchOptions.replaceString.clear();
if (m_bWholeWordsOnly)
aSearchOptions.searchFlag |= css::util::SearchFlags::NORM_WORD_ONLY;
if (m_bSimilarity)
{
SwPosition* pMarkPosition = rPaM.GetMark();
SwPosition* pPointPosition = rPaM.GetPoint();
std::unique_ptr<SwPaM> xPaM(std::make_unique<SwPaM>(*pMarkPosition, *pPointPosition));
m_vPaMs.push_back(std::move(xPaM));
aSearchOptions.AlgorithmType2 = css::util::SearchAlgorithms2::APPROXIMATE;
if (m_bIsLEVRelaxed)
aSearchOptions.searchFlag |= css::util::SearchFlags::LEV_RELAXED;
aSearchOptions.changedChars = m_nLEVOther;
aSearchOptions.insertedChars = m_nLEVShorter;
aSearchOptions.deletedChars = m_nLEVLonger;
}
else
aSearchOptions.AlgorithmType2 = css::util::SearchAlgorithms2::ABSOLUTE;
TransliterationFlags nTransliterationFlags = TransliterationFlags::IGNORE_WIDTH;
if (!m_bMatchCase)
nTransliterationFlags |= TransliterationFlags::IGNORE_CASE;
aSearchOptions.transliterateFlags = nTransliterationFlags;
m_pWrtShell->SttSelect();
/*sal_Int32 nFound =*/m_pWrtShell->SearchPattern(
aSearchOptions, false, SwDocPositions::Start, SwDocPositions::End,
FindRanges::InBody | FindRanges::InSelAll, false);
m_pWrtShell->EndSelect();
if (m_pWrtShell->HasMark())
{
for (SwPaM& rPaM : m_pWrtShell->GetCursor()->GetRingContainer())
{
SwPosition* pMarkPosition = rPaM.GetMark();
SwPosition* pPointPosition = rPaM.GetPoint();
std::unique_ptr<SwPaM> xPaM(std::make_unique<SwPaM>(*pMarkPosition, *pPointPosition));
m_vPaMs.push_back(std::move(xPaM));
}
// tdf#160538 sort finds in frames and footnotes in the order they occur in the document
const SwNodeOffset nEndOfInsertsIndex
= m_pWrtShell->GetNodes().GetEndOfInserts().GetIndex();
const SwNodeOffset nEndOfExtrasIndex = m_pWrtShell->GetNodes().GetEndOfExtras().GetIndex();
std::stable_sort(m_vPaMs.begin(), m_vPaMs.end(),
[&nEndOfInsertsIndex, &nEndOfExtrasIndex,
this](const std::unique_ptr<SwPaM>& a, const std::unique_ptr<SwPaM>& b) {
SwPosition aPos(*a->Start());
SwPosition bPos(*b->Start());
// use page number for footnotes and endnotes
if (aPos.GetNodeIndex() >= nEndOfInsertsIndex
&& bPos.GetNodeIndex() < nEndOfInsertsIndex)
return b->GetPageNum() >= a->GetPageNum();
// use anchor position for finds that are located in flys
if (nEndOfExtrasIndex >= aPos.GetNodeIndex())
getAnchorPos(aPos);
if (nEndOfExtrasIndex >= bPos.GetNodeIndex())
getAnchorPos(bPos);
if (aPos == bPos)
{
// probably in same or nested fly frame
// sort using layout position
SwRect aCharRect, bCharRect;
if (SwContentFrame* pFrame
= a->GetMarkContentNode()->GetTextNode()->getLayoutFrame(
m_pWrtShell->GetLayout()))
{
pFrame->GetCharRect(aCharRect, *a->GetMark());
}
if (SwContentFrame* pFrame
= b->GetMarkContentNode()->GetTextNode()->getLayoutFrame(
m_pWrtShell->GetLayout()))
{
pFrame->GetCharRect(bCharRect, *b->GetMark());
}
return aCharRect.Top() < bCharRect.Top();
}
return aPos < bPos;
});
// fill list
for (sal_uInt16 nPage = 0, i = 0; std::unique_ptr<SwPaM> & xPaM : m_vPaMs)
{
SwPosition* pMarkPosition = xPaM->GetMark();
SwPosition* pPointPosition = xPaM->GetPoint();
const SwContentNode* pContentNode = pMarkPosition->GetContentNode();
const SwTextNode* pTextNode = pContentNode->GetTextNode();
const OUString& sNodeText = pTextNode->GetText();
auto nMarkIndex = pMarkPosition->GetContentIndex();
auto nPointIndex = pPointPosition->GetContentIndex();
// determine the text node text subview start index for the list entry text
auto nStartIndex = nMarkIndex - CharactersBeforeAndAfter;
if (nStartIndex < 0)
{
nStartIndex = 0;
}
else
{
// tdf#160539 format search finds results also to word boundaries
sal_Unicode ch;
do
{
ch = sNodeText[nStartIndex];
} while (++nStartIndex < nMarkIndex && ch != ' ' && ch != '\t');
if (nStartIndex < nMarkIndex)
{
// move past neighboring space and tab characters
ch = sNodeText[nStartIndex];
while (nStartIndex < nMarkIndex && (ch == ' ' || ch == '\t'))
ch = sNodeText[++nStartIndex];
}
if (nStartIndex == nMarkIndex) // no white space found
nStartIndex = nMarkIndex - CharactersBeforeAndAfter;
}
// determine the text node text subview end index for the list entry text
auto nEndIndex = nPointIndex + CharactersBeforeAndAfter;
if (nEndIndex >= sNodeText.getLength())
{
nEndIndex = sNodeText.getLength() - 1;
}
else
{
// tdf#160539 format search finds results also to word boundaries
sal_Unicode ch;
do
{
ch = sNodeText[nEndIndex];
} while (--nEndIndex > nPointIndex && ch != ' ' && ch != '\t');
if (nEndIndex > nPointIndex)
{
// move past neighboring space and tab characters
ch = sNodeText[nEndIndex];
while (nEndIndex > nPointIndex && (ch == ' ' || ch == '\t'))
ch = sNodeText[--nEndIndex];
}
if (nEndIndex == nPointIndex) // no white space found
{
nEndIndex = nPointIndex + CharactersBeforeAndAfter;
if (nEndIndex >= sNodeText.getLength())
nEndIndex = sNodeText.getLength() - 1;
}
}
// tdf#161291 indicate page of search finds
if (xPaM->GetPageNum() != nPage)
{
nPage = xPaM->GetPageNum();
OUString sPageEntry(u"-"_ustr + SwResId(ST_PGE) + u" "_ustr
+ OUString::number(nPage));
m_xSearchFindsList->append(sPageEntry, sPageEntry);
}
auto nCount = nMarkIndex - nStartIndex;
OUString sTextBeforeFind = OUString::Concat(sNodeText.subView(nStartIndex, nCount));
auto nCount1 = nPointIndex - nMarkIndex;
OUString sFind = OUString::Concat(sNodeText.subView(nMarkIndex, nCount1));
auto nCount2 = nEndIndex - nPointIndex + 1;
OUString sTextAfterFind = OUString::Concat(sNodeText.subView(nPointIndex, nCount2));
OUString sStr = sTextBeforeFind + "[" + sFind + "]" + sTextAfterFind;
OUString sId = OUString::number(i++);
m_xSearchFindsList->append(sId, sStr);
}
}
// tdf#160538 sort finds in frames and footnotes in the order they occur in the document
const SwNodeOffset nEndOfInsertsIndex = m_pWrtShell->GetNodes().GetEndOfInserts().GetIndex();
const SwNodeOffset nEndOfExtrasIndex = m_pWrtShell->GetNodes().GetEndOfExtras().GetIndex();
std::stable_sort(
m_vPaMs.begin(), m_vPaMs.end(),
[&nEndOfInsertsIndex, &nEndOfExtrasIndex, this](const std::unique_ptr<SwPaM>& a,
const std::unique_ptr<SwPaM>& b) {
SwPosition aPos(*a->Start());
SwPosition bPos(*b->Start());
// use page number for footnotes and endnotes
if (aPos.GetNodeIndex() >= nEndOfInsertsIndex
&& bPos.GetNodeIndex() < nEndOfInsertsIndex)
return b->GetPageNum() >= a->GetPageNum();
// use anchor position for finds that are located in flys
if (nEndOfExtrasIndex >= aPos.GetNodeIndex())
getAnchorPos(aPos);
if (nEndOfExtrasIndex >= bPos.GetNodeIndex())
getAnchorPos(bPos);
if (aPos == bPos)
{
// probably in same or nested fly frame
// sort using layout position
SwRect aCharRect, bCharRect;
if (SwContentFrame* pFrame = a->GetMarkContentNode()->GetTextNode()->getLayoutFrame(
m_pWrtShell->GetLayout()))
{
pFrame->GetCharRect(aCharRect, *a->GetMark());
}
if (SwContentFrame* pFrame = b->GetMarkContentNode()->GetTextNode()->getLayoutFrame(
m_pWrtShell->GetLayout()))
{
pFrame->GetCharRect(bCharRect, *b->GetMark());
}
return aCharRect.Top() < bCharRect.Top();
}
return aPos < bPos;
});
// Any finds?
auto nSearchFindFoundTimes = m_vPaMs.size();
// fill list
for (sal_uInt16 nPage = 0, i = 0; std::unique_ptr<SwPaM> & xPaM : m_vPaMs)
{
SwPosition* pMarkPosition = xPaM->GetMark();
SwPosition* pPointPosition = xPaM->GetPoint();
// set the search term entry background
m_xSearchFindEntry->set_message_type(nSearchFindFoundTimes ? weld::EntryMessageType::Normal
: weld::EntryMessageType::Error);
// make the search finds list focusable or not
m_xSearchFindsList->set_sensitive(bool(nSearchFindFoundTimes));
const SwContentNode* pContentNode = pMarkPosition->GetContentNode();
const SwTextNode* pTextNode = pContentNode->GetTextNode();
const OUString& sNodeText = pTextNode->GetText();
auto nMarkIndex = pMarkPosition->GetContentIndex();
auto nPointIndex = pPointPosition->GetContentIndex();
// determine the text node text subview start index for the list entry text
auto nStartIndex = nMarkIndex - CharactersBeforeAndAfter;
if (nStartIndex < 0)
{
nStartIndex = 0;
}
else
{
// tdf#160539 format search finds results also to word boundaries
sal_Unicode ch;
do
{
ch = sNodeText[nStartIndex];
} while (++nStartIndex < nMarkIndex && ch != ' ' && ch != '\t');
if (nStartIndex < nMarkIndex)
{
// move past neighboring space and tab characters
ch = sNodeText[nStartIndex];
while (nStartIndex < nMarkIndex && (ch == ' ' || ch == '\t'))
ch = sNodeText[++nStartIndex];
}
if (nStartIndex == nMarkIndex) // no white space found
nStartIndex = nMarkIndex - CharactersBeforeAndAfter;
}
// determine the text node text subview end index for the list entry text
auto nEndIndex = nPointIndex + CharactersBeforeAndAfter;
if (nEndIndex >= sNodeText.getLength())
{
nEndIndex = sNodeText.getLength() - 1;
}
else
{
// tdf#160539 format search finds results also to word boundaries
sal_Unicode ch;
do
{
ch = sNodeText[nEndIndex];
} while (--nEndIndex > nPointIndex && ch != ' ' && ch != '\t');
if (nEndIndex > nPointIndex)
{
// move past neighboring space and tab characters
ch = sNodeText[nEndIndex];
while (nEndIndex > nPointIndex && (ch == ' ' || ch == '\t'))
ch = sNodeText[--nEndIndex];
}
if (nEndIndex == nPointIndex) // no white space found
{
nEndIndex = nPointIndex + CharactersBeforeAndAfter;
if (nEndIndex >= sNodeText.getLength())
nEndIndex = sNodeText.getLength() - 1;
}
}
// tdf#161291 indicate page of search finds
if (xPaM->GetPageNum() != nPage)
{
nPage = xPaM->GetPageNum();
OUString sPageEntry(u"-"_ustr + SwResId(ST_PGE) + u" "_ustr + OUString::number(nPage));
m_xSearchFindsList->append(sPageEntry, sPageEntry);
}
auto nCount = nMarkIndex - nStartIndex;
OUString sTextBeforeFind = OUString::Concat(sNodeText.subView(nStartIndex, nCount));
auto nCount1 = nPointIndex - nMarkIndex;
OUString sFind = OUString::Concat(sNodeText.subView(nMarkIndex, nCount1));
auto nCount2 = nEndIndex - nPointIndex + 1;
OUString sTextAfterFind = OUString::Concat(sNodeText.subView(nPointIndex, nCount2));
OUString sStr = sTextBeforeFind + "[" + sFind + "]" + sTextAfterFind;
OUString sId = OUString::number(i++);
m_xSearchFindsList->append(sId, sStr);
}
// set the search term found label number of times found
OUString sText(SwResId(STR_SEARCH_KEY_FOUND_TIMES, nSearchFindFoundTimes));
sText = sText.replaceFirst("%1", OUString::number(nSearchFindFoundTimes));
m_xSearchFindFoundTimesLabel->set_label(sText);
}
}
// end of namespace ::sw::sidebar
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/uibase/sidebar/QuickFindPanel.hxx b/sw/source/uibase/sidebar/QuickFindPanel.hxx
index 2f9cf4b..10d36648 100644
--- a/sw/source/uibase/sidebar/QuickFindPanel.hxx
+++ b/sw/source/uibase/sidebar/QuickFindPanel.hxx
@@ -10,32 +10,76 @@
#pragma once
#include <sfx2/sidebar/PanelLayout.hxx>
#include <svx/svxdlg.hxx>
#include <wrtsh.hxx>
#include <sfx2/weldutils.hxx>
namespace sw::sidebar
{
class QuickFindPanel : public PanelLayout
{
public:
static std::unique_ptr<PanelLayout> Create(weld::Widget* pParent);
class SearchOptionsDialog final : public weld::GenericDialogController
{
friend class QuickFindPanel;
QuickFindPanel(weld::Widget* pParent);
std::unique_ptr<weld::CheckButton> m_xMatchCaseCheckButton;
std::unique_ptr<weld::CheckButton> m_xWholeWordsOnlyCheckButton;
std::unique_ptr<weld::CheckButton> m_xSimilarityCheckButton;
std::unique_ptr<weld::Button> m_xSimilaritySettingsDialogButton;
DECL_LINK(SimilarityCheckButtonToggledHandler, weld::Toggleable&, void);
DECL_LINK(SimilaritySettingsDialogButtonClickedHandler, weld::Button&, void);
short executeSubDialog(VclAbstractDialog* pVclAbstractDialog);
bool m_executingSubDialog = false;
bool m_bIsLEVRelaxed = true;
sal_uInt16 m_nLEVOther = 2;
sal_uInt16 m_nLEVShorter = 2;
sal_uInt16 m_nLEVLonger = 2;
public:
SearchOptionsDialog(weld::Window* pParent);
};
public:
static std::unique_ptr<PanelLayout> Create(weld::Widget* pParent,
const uno::Reference<frame::XFrame>& rxFrame);
QuickFindPanel(weld::Widget* pParent, const uno::Reference<frame::XFrame>& rxFrame);
virtual ~QuickFindPanel() override;
private:
std::unique_ptr<weld::Entry> m_xSearchFindEntry;
std::unique_ptr<weld::TreeView> m_xSearchFindsList;
std::vector<std::unique_ptr<SwPaM>> m_vPaMs;
std::unique_ptr<weld::Entry> m_xSearchFindEntry;
std::unique_ptr<weld::Toolbar> m_xSearchOptionsToolbar;
std::unique_ptr<weld::Toolbar> m_xFindAndReplaceToolbar;
std::unique_ptr<ToolbarUnoDispatcher> m_xFindAndReplaceToolbarDispatch;
std::unique_ptr<weld::TreeView> m_xSearchFindsList;
std::unique_ptr<weld::Label> m_xSearchFindFoundTimesLabel;
SwWrtShell* m_pWrtShell;
int m_nMinimumPanelWidth;
bool m_bMatchCase = false;
bool m_bWholeWordsOnly = false;
bool m_bSimilarity = false;
bool m_bIsLEVRelaxed = true;
sal_uInt16 m_nLEVOther = 2;
sal_uInt16 m_nLEVShorter = 2;
sal_uInt16 m_nLEVLonger = 2;
DECL_LINK(SearchFindEntryActivateHandler, weld::Entry&, bool);
DECL_LINK(SearchFindEntryChangedHandler, weld::Entry&, void);
DECL_LINK(SearchFindsListCustomGetSizeHandler, weld::TreeView::get_size_args, Size);
DECL_LINK(SearchFindsListRender, weld::TreeView::render_args, void);
DECL_LINK(SearchFindsListSelectionChangedHandler, weld::TreeView&, void);
DECL_LINK(SearchFindEntryChangedHandler, weld::Entry&, void);
DECL_LINK(SearchFindsListRowActivatedHandler, weld::TreeView&, bool);
DECL_LINK(MousePressHandler, const MouseEvent&, bool);
DECL_LINK(SearchFindsListMousePressHandler, const MouseEvent&, bool);
DECL_LINK(SearchOptionsToolbarClickedHandler, const OUString&, void);
void FillSearchFindsList();
};
diff --git a/sw/source/uibase/sidebar/SwPanelFactory.cxx b/sw/source/uibase/sidebar/SwPanelFactory.cxx
index ee378d8..4e5cd83 100644
--- a/sw/source/uibase/sidebar/SwPanelFactory.cxx
+++ b/sw/source/uibase/sidebar/SwPanelFactory.cxx
@@ -206,7 +206,8 @@ Reference<ui::XUIElement> SAL_CALL SwPanelFactory::createUIElement (
}
else if (rsResourceURL.endsWith("/QuickFindPanel"))
{
std::unique_ptr<PanelLayout> xPanel = sw::sidebar::QuickFindPanel::Create(pParent);
std::unique_ptr<PanelLayout> xPanel
= sw::sidebar::QuickFindPanel::Create(pParent, xFrame);
xElement = sfx2::sidebar::SidebarPanelBase::Create(rsResourceURL, xFrame, std::move(xPanel),
ui::LayoutSize(-1, -1, -1));
}
diff --git a/sw/source/uibase/uiview/viewsrch.cxx b/sw/source/uibase/uiview/viewsrch.cxx
index d005899..9f7c9ca 100644
--- a/sw/source/uibase/uiview/viewsrch.cxx
+++ b/sw/source/uibase/uiview/viewsrch.cxx
@@ -278,7 +278,7 @@ void SwView::ExecSearch(SfxRequest& rReq)
lcl_emitSearchResultCallbacks(s_pSrchItem, m_pWrtShell.get(), /* bHighlightAll = */ true);
if (!bQuiet)
{
OUString sText(SwResId(STR_SEARCH_KEY_FOUND_TIMES));
OUString sText(SwResId(STR_SEARCH_KEY_FOUND_TIMES, nFound));
sText = sText.replaceFirst("%1", OUString::number(nFound));
SvxSearchDialogWrapper::SetSearchLabel(sText);
}
diff --git a/sw/uiconfig/swriter/ui/sidebarquickfind.ui b/sw/uiconfig/swriter/ui/sidebarquickfind.ui
index e2dfdb0..cc9c122 100644
--- a/sw/uiconfig/swriter/ui/sidebarquickfind.ui
+++ b/sw/uiconfig/swriter/ui/sidebarquickfind.ui
@@ -2,11 +2,9 @@
<!-- Generated with glade 3.40.0 -->
<interface domain="sw">
<requires lib="gtk+" version="3.20"/>
<object class="GtkTreeStore" id="liststore1">
<object class="GtkListStore" id="liststore">
<columns>
<!-- column-name text1 -->
<column type="gchararray"/>
<!-- column-name text2 -->
<!-- column-name text -->
<column type="gchararray"/>
<!-- column-name id -->
<column type="gchararray"/>
@@ -18,22 +16,89 @@
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="row-spacing">6</property>
<property name="column-spacing">6</property>
<property name="row-homogeneous">True</property>
<property name="column-homogeneous">True</property>
<child>
<object class="GtkBox">
<object class="GtkBox" id="box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="border-width">6</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<property name="spacing">6</property>
<child>
<object class="GtkEntry" id="Find">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="placeholder-text" translatable="yes" context="sidebarquickfind|find">Find</property>
<property name="can-focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkEntry" id="Find">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="truncate-multiline">True</property>
<property name="placeholder-text" translatable="yes" context="sidebarquickfind|find">Find</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkToolbar" id="searchoptionstoolbar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="toolbar-style">icons</property>
<property name="show-arrow">False</property>
<child>
<object class="GtkToolButton" id="searchoptions">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes" context="quickfindpanel|moresearchoptions|tooltip_text">Search Options</property>
<property name="icon-name">sw/res/sr20006.png</property>
<child internal-child="accessible">
<object class="AtkObject" id="searchoptions-atkobject">
<property name="AtkObject::accessible-name" translatable="yes" context="quickfindpanel|moresearchoptions|accessible_name">More Search Options</property>
<property name="AtkObject::accessible-description" translatable="yes" context="quickfindpanel|extended_tip|moresearchoptions">Click here to open a dialog to set more search options.</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">False</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkToolbar" id="findandreplacetoolbar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="toolbar-style">icons</property>
<property name="show-arrow">False</property>
<child>
<object class="GtkToolButton" id=".uno:SearchDialog">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">False</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -47,19 +112,18 @@
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="vscrollbar-policy">always</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="searchfinds">
<property name="width-request">-1</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="border-width">0</property>
<property name="model">liststore1</property>
<property name="model">liststore</property>
<property name="headers-visible">False</property>
<property name="enable-search">False</property>
<property name="search-column">1</property>
<property name="headers-clickable">False</property>
<property name="search-column">0</property>
<property name="show-expanders">False</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
@@ -70,22 +134,38 @@
<child>
<object class="GtkTreeViewColumn" id="treeviewcolumn1">
<child>
<object class="GtkCellRendererText" id="cellrenderertext"/>
<object class="GtkCellRendererText" id="cellrenderertext1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child internal-child="accessible">
<object class="AtkObject" id="searchfinds-atkobject">
<property name="AtkObject::accessible-description" translatable="yes" context="sidebarquickfind|extended_tip|searchfinds">Lists the positions in the document that the searched term is found.</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="numberofsearchfinds">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">0</property>
diff --git a/sw/uiconfig/swriter/ui/sidebarquickfindoptionsdialog.ui b/sw/uiconfig/swriter/ui/sidebarquickfindoptionsdialog.ui
new file mode 100644
index 0000000..d80bafa
--- /dev/null
+++ b/sw/uiconfig/swriter/ui/sidebarquickfindoptionsdialog.ui
@@ -0,0 +1,175 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.40.0 -->
<interface domain="sw">
<requires lib="gtk+" version="3.20"/>
<object class="GtkDialog" id="SearchOptionsDialog">
<property name="can-focus">False</property>
<property name="border-width">6</property>
<property name="title" translatable="yes" context="sidebarquickfindoptionsdialog|SearchOptionsDialog">Search Options</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="default-width">0</property>
<property name="default-height">0</property>
<property name="type-hint">dialog</property>
<child internal-child="vbox">
<object class="GtkBox" id="dialog-vbox1">
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="dialog-action_area1">
<property name="can-focus">False</property>
<property name="layout-style">end</property>
<child>
<object class="GtkButton" id="ok">
<property name="label" translatable="yes" context="stock">_OK</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="can-default">True</property>
<property name="has-default">True</property>
<property name="receives-default">True</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="cancel">
<property name="label" translatable="yes" context="stock">_Cancel</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<!-- n-columns=1 n-rows=3 -->
<object class="GtkGrid" id="grid4">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="row-spacing">6</property>
<child>
<object class="GtkBox" id="box3">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkCheckButton" id="similarity">
<property name="label" translatable="yes" context="sidebarquickfindoptionsdialog|similarity">Similarity</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="no-show-all">True</property>
<property name="use-underline">True</property>
<property name="draw-indicator">True</property>
<child internal-child="accessible">
<object class="AtkObject" id="similarity-atkobject">
<property name="AtkObject::accessible-description" translatable="yes" context="sidebarquickfindoptionsdialog|extended_tip|similarity">Find terms that are similar to the Find text. Select this checkbox, and then click the Similarities button to define the similarity options.</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="similaritysettingsdialog">
<property name="label" translatable="yes" context="sidebarquickfindoptionsdialog|similaritybtn">Similarities...</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="no-show-all">True</property>
<property name="halign">end</property>
<property name="hexpand">True</property>
<property name="use-underline">True</property>
<child internal-child="accessible">
<object class="AtkObject" id="similaritysettingsdialog-atkobject">
<property name="AtkObject::accessible-description" translatable="yes" context="sidebarquickfindoptionsdialog|extended_tip|similaritybtn">Set the options for the similarity search.</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="matchcase">
<property name="label" translatable="yes" context="sidebarquickfindoptionsdialog|matchcase">Match case</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="no-show-all">True</property>
<property name="use-underline">True</property>
<property name="draw-indicator">True</property>
<child internal-child="accessible">
<object class="AtkObject" id="matchcase-atkobject">
<property name="AtkObject::accessible-description" translatable="yes" context="sidebarquickfindoptionsdialog|extended_tip|matchcase">Finds only exact case matches.</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="wholewordsonly">
<property name="label" translatable="yes" context="sidebarquickfindoptionsdialog|wholewordsonly">Whole words only</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="no-show-all">True</property>
<property name="use-underline">True</property>
<property name="draw-indicator">True</property>
<child internal-child="accessible">
<object class="AtkObject" id="wholewordsonly-atkobject">
<property name="AtkObject::accessible-description" translatable="yes" context="sidebarquickfindoptionsdialog|extended_tip|wholewordsonly">Finds only whole words.</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-5">ok</action-widget>
<action-widget response="-6">cancel</action-widget>
</action-widgets>
</object>
</interface>