sw/UIConfig_swriter.mk | 1 sw/inc/strings.hrc | 2 sw/source/uibase/sidebar/QuickFindPanel.cxx | 433 ++++++++++------ sw/source/uibase/sidebar/QuickFindPanel.hxx | 54 + sw/source/uibase/sidebar/SwPanelFactory.cxx | 3 sw/source/uibase/uiview/viewsrch.cxx | 2 sw/uiconfig/swriter/ui/sidebarquickfind.ui | 116 +++- sw/uiconfig/swriter/ui/sidebarquickfindoptionsdialog.ui | 175 ++++++ 8 files changed, 612 insertions(+), 174 deletions(-)
New commits: commit 601cf05456073c082ac6bbe43b501eb81256711b Author: Jim Raykowski <[email protected]> AuthorDate: Wed Jun 12 18:16:06 2024 -0800 Commit: Jim Raykowski <[email protected]> CommitDate: Mon Jun 24 22:48:51 2024 +0200 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 <[email protected]> Tested-by: Jenkins diff --git a/sw/UIConfig_swriter.mk b/sw/UIConfig_swriter.mk index 27b7248d74e5..262123969dae 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 298b861eecf6..b9e46f13d0c6 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 da6e016ac394..0f620fd09985 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)) +{ + m_xSimilarityCheckButton->connect_toggled( + LINK(this, SearchOptionsDialog, SimilarityCheckButtonToggledHandler)); + m_xSimilaritySettingsDialogButton->connect_clicked( + LINK(this, SearchOptionsDialog, SimilaritySettingsDialogButtonClickedHandler)); +} + +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 css::lang::IllegalArgumentException( - u"no parent Window given to QuickFindPanel::Create"_ustr, nullptr, 0); - return std::make_unique<QuickFindPanel>(pParent); + 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) +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,14 +178,11 @@ QuickFindPanel::~QuickFindPanel() m_xSearchFindsList.reset(); } -IMPL_LINK(QuickFindPanel, MousePressHandler, const MouseEvent&, rMEvt, bool) +IMPL_LINK_NOARG(QuickFindPanel, SearchFindEntryChangedHandler, weld::Entry&, void) { - if (std::unique_ptr<weld::TreeIter> xEntry(m_xSearchFindsList->make_iterator()); - m_xSearchFindsList->get_dest_row_at_pos(rMEvt.GetPosPixel(), xEntry.get(), false, false)) - { - return m_xSearchFindsList->get_id(*xEntry)[0] == '-'; - } - return false; + m_xSearchFindEntry->set_message_type(weld::EntryMessageType::Normal); + m_xSearchFindsList->clear(); + m_xSearchFindFoundTimesLabel->set_label(OUString()); } IMPL_LINK_NOARG(QuickFindPanel, SearchFindEntryActivateHandler, weld::Entry&, bool) @@ -104,6 +191,16 @@ IMPL_LINK_NOARG(QuickFindPanel, SearchFindEntryActivateHandler, weld::Entry&, bo 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)) + { + return m_xSearchFindsList->get_id(*xEntry)[0] == '-'; + } + return false; +} + 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)) }, - })); - - comphelper::dispatchCommand(u".uno:ExecuteSearch"_ustr, aPropertyValues); + m_xSearchFindFoundTimesLabel->set_label(OUString()); - if (!m_pWrtShell->HasMark()) + const OUString& rsFindEntry = m_xSearchFindEntry->get_text(); + if (rsFindEntry.isEmpty()) return; - 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)); - } + SwWait aWait(*m_pWrtShell->GetDoc()->GetDocShell(), true); - // 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; - }); + m_pWrtShell->AssureStdMode(); - // fill list - for (sal_uInt16 nPage = 0, i = 0; std::unique_ptr<SwPaM> & xPaM : m_vPaMs) + 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 = 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) + 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()) { - nStartIndex = 0; + 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)); } - else + + // 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) { - // tdf#160539 format search finds results also to word boundaries - sal_Unicode ch; - do + 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) { - ch = sNodeText[nStartIndex]; - } while (++nStartIndex < nMarkIndex && ch != ' ' && ch != ' '); - if (nStartIndex < nMarkIndex) + nStartIndex = 0; + } + else { - // move past neighboring space and tab characters - ch = sNodeText[nStartIndex]; - while (nStartIndex < nMarkIndex && (ch == ' ' || ch == ' ')) - ch = sNodeText[++nStartIndex]; + // tdf#160539 format search finds results also to word boundaries + sal_Unicode ch; + do + { + ch = sNodeText[nStartIndex]; + } while (++nStartIndex < nMarkIndex && ch != ' ' && ch != ' '); + if (nStartIndex < nMarkIndex) + { + // move past neighboring space and tab characters + ch = sNodeText[nStartIndex]; + while (nStartIndex < nMarkIndex && (ch == ' ' || ch == ' ')) + ch = sNodeText[++nStartIndex]; + } + if (nStartIndex == nMarkIndex) // no white space found + nStartIndex = nMarkIndex - CharactersBeforeAndAfter; } - 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 + // determine the text node text subview end index for the list entry text + auto nEndIndex = nPointIndex + CharactersBeforeAndAfter; + if (nEndIndex >= sNodeText.getLength()) { - ch = sNodeText[nEndIndex]; - } while (--nEndIndex > nPointIndex && ch != ' ' && ch != ' '); - if (nEndIndex > nPointIndex) + nEndIndex = sNodeText.getLength() - 1; + } + else { - // move past neighboring space and tab characters - ch = sNodeText[nEndIndex]; - while (nEndIndex > nPointIndex && (ch == ' ' || ch == ' ')) - ch = sNodeText[--nEndIndex]; + // tdf#160539 format search finds results also to word boundaries + sal_Unicode ch; + do + { + ch = sNodeText[nEndIndex]; + } while (--nEndIndex > nPointIndex && ch != ' ' && ch != ' '); + if (nEndIndex > nPointIndex) + { + // move past neighboring space and tab characters + ch = sNodeText[nEndIndex]; + while (nEndIndex > nPointIndex && (ch == ' ' || ch == ' ')) + ch = sNodeText[--nEndIndex]; + } + if (nEndIndex == nPointIndex) // no white space found + { + nEndIndex = nPointIndex + CharactersBeforeAndAfter; + if (nEndIndex >= sNodeText.getLength()) + nEndIndex = sNodeText.getLength() - 1; + } } - if (nEndIndex == nPointIndex) // no white space found + + // tdf#161291 indicate page of search finds + if (xPaM->GetPageNum() != nPage) { - nEndIndex = nPointIndex + CharactersBeforeAndAfter; - if (nEndIndex >= sNodeText.getLength()) - nEndIndex = sNodeText.getLength() - 1; + nPage = xPaM->GetPageNum(); + OUString sPageEntry(u"-"_ustr + SwResId(ST_PGE) + u" "_ustr + + OUString::number(nPage)); + m_xSearchFindsList->append(sPageEntry, sPageEntry); } - } - // 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); } + } - 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; + // Any finds? + auto nSearchFindFoundTimes = m_vPaMs.size(); - OUString sId = OUString::number(i++); - m_xSearchFindsList->append(sId, sStr); - } + // 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)); + + // 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 2f9cf4bad143..10d3664832cb 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 { + class SearchOptionsDialog final : public weld::GenericDialogController + { + friend class QuickFindPanel; + + 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); + static std::unique_ptr<PanelLayout> Create(weld::Widget* pParent, + const uno::Reference<frame::XFrame>& rxFrame); - QuickFindPanel(weld::Widget* pParent); + QuickFindPanel(weld::Widget* pParent, const uno::Reference<frame::XFrame>& rxFrame); virtual ~QuickFindPanel() override; private: + 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::vector<std::unique_ptr<SwPaM>> m_vPaMs; + 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 ee378d8e6461..4e5cd83d4116 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 d005899f8229..9f7c9cabe130 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 e2dfdb085fe2..cc9c1226e9c0 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 000000000000..d80bafa2629c --- /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>
