include/vcl/weld.hxx                        |   11 ++++
 sw/source/uibase/inc/QuickFindPanel.hxx     |    6 ++
 sw/source/uibase/sidebar/QuickFindPanel.cxx |   75 ++++++++++++++++++++++++++++
 sw/uiconfig/swriter/ui/sidebarquickfind.ui  |   71 +++++++++++++++++++++++++-
 4 files changed, 161 insertions(+), 2 deletions(-)

New commits:
commit 8fa5f638dbc65c30020b851b3d59bec748922c00
Author:     NickWingate <[email protected]>
AuthorDate: Wed Sep 10 12:59:28 2025 +0100
Commit:     Szymon Kłos <[email protected]>
CommitDate: Tue Sep 16 10:23:28 2025 +0200

    QuickFindPanel: Add navigation (up, down) buttons
    
    We add `iter_nth_from_start` to `weld::TreeView`
    to access the last entry in the treeview.
    
    Signed-off-by: NickWingate <[email protected]>
    Change-Id: Ib2f71719a84ab6f1a73fb1c844e68c3907d69c0c
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/190754
    Tested-by: Jenkins CollaboraOffice <[email protected]>
    Reviewed-by: Szymon Kłos <[email protected]>

diff --git a/include/vcl/weld.hxx b/include/vcl/weld.hxx
index fdda5b5168bb..277686c5061f 100644
--- a/include/vcl/weld.hxx
+++ b/include/vcl/weld.hxx
@@ -1175,6 +1175,17 @@ public:
             return false;
         return iter_nth_sibling(rIter, nChild);
     }
+    bool iter_nth_from_start(TreeIter& rIter, int nIndex) const
+    {
+        if (!get_iter_first(rIter))
+            return false;
+        for (int i = 0; i < nIndex; ++i)
+        {
+            if (!iter_next(rIter))
+                return false;
+        }
+        return true;
+    }
     virtual bool iter_parent(TreeIter& rIter) const = 0;
     virtual int get_iter_depth(const TreeIter& rIter) const = 0;
     virtual int get_iter_index_in_parent(const TreeIter& rIter) const = 0;
diff --git a/sw/source/uibase/inc/QuickFindPanel.hxx 
b/sw/source/uibase/inc/QuickFindPanel.hxx
index 39b2ad7afa6e..4cc129ead7f5 100644
--- a/sw/source/uibase/inc/QuickFindPanel.hxx
+++ b/sw/source/uibase/inc/QuickFindPanel.hxx
@@ -60,8 +60,11 @@ private:
     std::unique_ptr<weld::Toolbar> m_xFindAndReplaceToolbar;
     std::unique_ptr<ToolbarUnoDispatcher> m_xFindAndReplaceToolbarDispatch;
     std::unique_ptr<weld::Box> m_xTopbar;
+    std::unique_ptr<weld::Box> m_xQuickFindControls;
     std::unique_ptr<weld::TreeView> m_xSearchFindsList;
     std::unique_ptr<weld::Label> m_xSearchFindFoundTimesLabel;
+    std::unique_ptr<weld::Button> m_xFindNextButton;
+    std::unique_ptr<weld::Button> m_xFindPreviousButton;
 
     SwWrtShell* m_pWrtShell;
 
@@ -84,7 +87,10 @@ private:
     DECL_LINK(SearchFindsListMousePressHandler, const MouseEvent&, bool);
     DECL_LINK(SearchOptionsToolbarClickedHandler, const OUString&, void);
     DECL_LINK(FindAndReplaceToolbarClickedHandler, const OUString&, void);
+    DECL_LINK(FindNextClickedHandler, weld::Button&, void);
+    DECL_LINK(FindPreviousClickedHandler, weld::Button&, void);
 
+    void NavigateSearchFinds(bool bNext);
     void FillSearchFindsList();
     static OUString CreatePageEntry(sal_Int32 nPageNum);
     bool IsPageEntry(const weld::TreeIter& rEntry);
diff --git a/sw/source/uibase/sidebar/QuickFindPanel.cxx 
b/sw/source/uibase/sidebar/QuickFindPanel.cxx
index 7d3ca86d2b3e..50cead0d1673 100644
--- a/sw/source/uibase/sidebar/QuickFindPanel.cxx
+++ b/sw/source/uibase/sidebar/QuickFindPanel.cxx
@@ -145,8 +145,11 @@ QuickFindPanel::QuickFindPanel(weld::Widget* pParent, 
const uno::Reference<frame
     , m_xFindAndReplaceToolbarDispatch(
           new ToolbarUnoDispatcher(*m_xFindAndReplaceToolbar, *m_xBuilder, 
rxFrame))
     , m_xTopbar(m_xBuilder->weld_box(u"topbar"_ustr))
+    , m_xQuickFindControls(m_xBuilder->weld_box(u"quickfindcontrols"_ustr))
     , m_xSearchFindsList(m_xBuilder->weld_tree_view(u"searchfinds"_ustr))
     , 
m_xSearchFindFoundTimesLabel(m_xBuilder->weld_label("numberofsearchfinds"))
+    , m_xFindNextButton(m_xBuilder->weld_button(u"findnext"_ustr))
+    , m_xFindPreviousButton(m_xBuilder->weld_button(u"findprevious"_ustr))
     , m_pWrtShell(::GetActiveWrtShell())
 {
     if (comphelper::LibreOfficeKit::isActive())
@@ -186,6 +189,11 @@ QuickFindPanel::QuickFindPanel(weld::Widget* pParent, 
const uno::Reference<frame
         LINK(this, QuickFindPanel, SearchFindsListRowActivatedHandler));
     m_xSearchFindsList->connect_mouse_press(
         LINK(this, QuickFindPanel, SearchFindsListMousePressHandler));
+
+    m_xQuickFindControls->set_visible(false);
+
+    m_xFindNextButton->connect_clicked(LINK(this, QuickFindPanel, 
FindNextClickedHandler));
+    m_xFindPreviousButton->connect_clicked(LINK(this, QuickFindPanel, 
FindPreviousClickedHandler));
 }
 
 IMPL_LINK_NOARG(QuickFindPanel, SearchOptionsToolbarClickedHandler, const 
OUString&, void)
@@ -249,6 +257,7 @@ IMPL_LINK_NOARG(QuickFindPanel, 
SearchFindEntryChangedHandler, weld::Entry&, voi
     m_xSearchFindEntry->set_message_type(weld::EntryMessageType::Normal);
     m_xSearchFindsList->clear();
     m_xSearchFindFoundTimesLabel->set_label(OUString());
+    m_xQuickFindControls->set_visible(false);
 }
 
 IMPL_LINK_NOARG(QuickFindPanel, SearchFindEntryActivateHandler, weld::Entry&, 
bool)
@@ -429,6 +438,8 @@ IMPL_LINK_NOARG(QuickFindPanel, 
SearchFindsListSelectionChangedHandler, weld::Tr
     sText = sText.replaceFirst("%1", OUString::number(sId.toUInt32() + 1));
     sText = sText.replaceFirst("%2", OUString::number(nSearchFindFoundTimes));
     m_xSearchFindFoundTimesLabel->set_label(sText);
+    if (nSearchFindFoundTimes > 1)
+        m_xQuickFindControls->set_visible(true);
 
     SwShellCursor* pShellCursor = m_pWrtShell->GetCursor_();
     std::vector<basegfx::B2DRange> vRanges;
@@ -454,11 +465,73 @@ IMPL_LINK_NOARG(QuickFindPanel, 
SearchFindsListRowActivatedHandler, weld::TreeVi
     return true;
 }
 
+IMPL_LINK_NOARG(QuickFindPanel, FindNextClickedHandler, weld::Button&, void)
+{
+    NavigateSearchFinds(true); // true = next/down
+}
+
+IMPL_LINK_NOARG(QuickFindPanel, FindPreviousClickedHandler, weld::Button&, 
void)
+{
+    NavigateSearchFinds(false); // false = previous/up
+}
+
+void QuickFindPanel::NavigateSearchFinds(bool bNext)
+{
+    if (!m_xSearchFindsList || m_xSearchFindsList->n_children() == 0)
+        return;
+
+    std::unique_ptr<weld::TreeIter> 
xEntry(m_xSearchFindsList->make_iterator());
+
+    bool bMoved = false;
+
+    // no current selection, select first/last entry
+    if (!m_xSearchFindsList->get_cursor(xEntry.get()))
+    {
+        bMoved = m_xSearchFindsList->iter_nth_from_start(
+            *xEntry, bNext ? 0 : m_xSearchFindsList->n_children() - 1);
+    }
+    else
+    {
+        if (bNext)
+            bMoved = m_xSearchFindsList->iter_next(*xEntry);
+        else
+            bMoved = m_xSearchFindsList->iter_previous(*xEntry);
+    }
+
+    if (!bMoved)
+    {
+        bMoved = m_xSearchFindsList->iter_nth_from_start(
+            *xEntry, bNext ? 0 : m_xSearchFindsList->n_children() - 1);
+    }
+
+    // Keep going until we find a non-page entry
+    while (IsPageEntry(*xEntry))
+    {
+        // skip page entry
+        if (bNext)
+            bMoved = m_xSearchFindsList->iter_next(*xEntry);
+        else
+            bMoved = m_xSearchFindsList->iter_previous(*xEntry);
+
+        if (!bMoved)
+        {
+            bMoved = m_xSearchFindsList->iter_nth_from_start(
+                *xEntry, bNext ? 0 : m_xSearchFindsList->n_children() - 1);
+        }
+    }
+    m_xSearchFindsList->set_cursor(*xEntry);
+
+    // todo: ideally we dont need to manually call this handler, but select 
disables event propagation see SalInstanceTreeView::select(const 
weld::TreeIter& rIter)
+    m_xSearchFindsList->select(*xEntry);
+    SearchFindsListSelectionChangedHandler(*m_xSearchFindsList);
+}
+
 void QuickFindPanel::FillSearchFindsList()
 {
     m_vPaMs.clear();
     m_xSearchFindsList->clear();
     m_xSearchFindFoundTimesLabel->set_label(OUString());
+    m_xQuickFindControls->set_visible(false);
 
     const OUString sFindEntry = m_xSearchFindEntry->get_text();
     if (sFindEntry.isEmpty())
@@ -647,6 +720,8 @@ void QuickFindPanel::FillSearchFindsList()
     OUString sText(SwResId(STR_SEARCH_KEY_FOUND_TIMES, nSearchFindFoundTimes));
     sText = sText.replaceFirst("%1", OUString::number(nSearchFindFoundTimes));
     m_xSearchFindFoundTimesLabel->set_label(sText);
+    if (nSearchFindFoundTimes > 1)
+        m_xQuickFindControls->set_visible(true);
 }
 
 OUString QuickFindPanel::CreatePageEntry(sal_Int32 nPageNum)
diff --git a/sw/uiconfig/swriter/ui/sidebarquickfind.ui 
b/sw/uiconfig/swriter/ui/sidebarquickfind.ui
index 7701a42005cf..9345733622dd 100644
--- a/sw/uiconfig/swriter/ui/sidebarquickfind.ui
+++ b/sw/uiconfig/swriter/ui/sidebarquickfind.ui
@@ -10,6 +10,18 @@
       <column type="gchararray"/>
     </columns>
   </object>
+  <object class="GtkImage" id="prevImage">
+    <property name="visible">True</property>
+    <property name="can-focus">False</property>
+    <property name="icon-name">pan-up-symbolic</property>
+    <property name="icon_size">2</property>
+  </object>
+  <object class="GtkImage" id="nextImage">
+    <property name="visible">True</property>
+    <property name="can-focus">False</property>
+    <property name="icon-name">pan-down-symbolic</property>
+    <property name="icon_size">2</property>
+  </object>
   <!-- n-columns=1 n-rows=1 -->
   <object class="GtkGrid" id="QuickFindPanel">
     <property name="visible">True</property>
@@ -156,16 +168,71 @@
           </packing>
         </child>
         <child>
-          <object class="GtkLabel" id="numberofsearchfinds">
+          <object class="GtkBox" id="searchresultscontainer">
             <property name="visible">True</property>
             <property name="can-focus">False</property>
+            <property name="spacing">6</property>
+            <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">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="quickfindcontrols">
+                <property name="visible">True</property>
+                <property name="can-focus">False</property>
+                <property name="spacing">3</property>
+                <child>
+                  <object class="GtkButton" id="findprevious">
+                    <property name="visible">True</property>
+                    <property name="can-focus">True</property>
+                    <property name="receives-default">True</property>
+                    <property name="tooltip-text">Previous Result</property> 
<!-- TODO: add translatable -->
+                    <property name="image">prevImage</property>
+                    <property name="always-show-image">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="findnext">
+                    <property name="visible">True</property>
+                    <property name="can-focus">True</property>
+                    <property name="receives-default">True</property>
+                    <property name="tooltip-text">Next Result</property> <!-- 
TODO: add translatable -->
+                    <property name="image">nextImage</property>
+                    <property name="always-show-image">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">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
           </object>
           <packing>
             <property name="expand">False</property>
             <property name="fill">True</property>
-            <property name="position">3</property>
+            <property name="position">2</property>
           </packing>
         </child>
+
       </object>
       <packing>
         <property name="left-attach">0</property>

Reply via email to