From 6a3cbb79c697b1d6a67f6deb7844c07284e07dfe Mon Sep 17 00:00:00 2001 From: Jim Raykowski Date: Thu, 18 Jun 2026 23:44:35 -0800 Subject: [PATCH] tdf#172401 Add Hyperlink navigation to Navigate By Change-Id: I0304ec359b3a03af4c1393ac6346105d0c65b184 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207016 Tested-by: Jenkins Reviewed-by: Jim Raykowski --- sw/inc/bitmaps.hlst | 1 + sw/inc/crsrsh.hxx | 1 + sw/inc/strings.hrc | 2 + sw/source/core/crsr/crstrvl.cxx | 108 +++++++++++++++++++++++++++ sw/source/uibase/inc/workctrl.hxx | 3 +- sw/source/uibase/ribbar/workctrl.cxx | 13 +++- sw/source/uibase/uiview/viewmdi.cxx | 6 ++ 7 files changed, 129 insertions(+), 5 deletions(-) diff --git a/sw/inc/bitmaps.hlst b/sw/inc/bitmaps.hlst index 99ae5b0fae55..cf2868072a98 100644 --- a/sw/inc/bitmaps.hlst +++ b/sw/inc/bitmaps.hlst @@ -78,6 +78,7 @@ inline constexpr OUString RID_BMP_RIBBAR_ERROR = u"sw/res/sr20019.png"_ustr; inline constexpr OUString RID_BMP_RIBBAR_RECENCY = u"sw/res/sr20020.png"_ustr; inline constexpr OUString RID_BMP_RIBBAR_FIELD = u"sw/res/sr20021.png"_ustr; inline constexpr OUString RID_BMP_RIBBAR_FIELD_BYTYPE = u"sw/res/sr20022.png"_ustr; +inline constexpr OUString RID_BMP_RIBBAR_HYPERLINK = u"sw/res/nc20007.png"_ustr; inline constexpr OUString RID_BMP_NAVI_OUTLINE = u"sw/res/nc20000.png"_ustr; inline constexpr OUString RID_BMP_NAVI_TABLE = u"sw/res/nc20001.png"_ustr; diff --git a/sw/inc/crsrsh.hxx b/sw/inc/crsrsh.hxx index f905e0e15a1a..d21dac474ced 100644 --- a/sw/inc/crsrsh.hxx +++ b/sw/inc/crsrsh.hxx @@ -737,6 +737,7 @@ public: // optionally only to broken formulas bool GotoNxtPrvTableFormula( bool bNext = true, bool bOnlyErrors = false ); + bool GotoNxtPrvHyperlink(bool bNext); // jump to the next / previous hyperlink - inside text and also // on graphics bool SelectNxtPrvHyperlink( bool bNext ); diff --git a/sw/inc/strings.hrc b/sw/inc/strings.hrc index 4bd7e6f0be85..85014c4a1a82 100644 --- a/sw/inc/strings.hrc +++ b/sw/inc/strings.hrc @@ -1286,6 +1286,8 @@ #define STR_IMGBTN_FIELD_DOWN NC_("STR_IMGBTN_FIELD_DOWN", "Next field") #define STR_IMGBTN_FIELD_BYTYPE_UP NC_("STR_IMGBTN_FIELD_BYTYPE_UP", "Previous '%FIELDTYPE' field") #define STR_IMGBTN_FIELD_BYTYPE_DOWN NC_("STR_IMGBTN_FIELD_BYTYPE_DOWN", "Next '%FIELDTYPE' field") +#define STR_IMGBTN_HYPERLINK_UP NC_("STR_IMGBTN_HYPERLINK_UP", "Previous hyperlink") +#define STR_IMGBTN_HYPERLINK_DOWN NC_("STR_IMGBTN_HYPLERLINK_DOWN", "Next hyperlink") #define STR_GOTO_PAGE_DLG_TITLE NC_("STR_GOTO_PAGE_DLG_TITLE", "Go to Page") #define STR_REDLINE_INSERT NC_("STR_REDLINE_INSERT", "Inserted") diff --git a/sw/source/core/crsr/crstrvl.cxx b/sw/source/core/crsr/crstrvl.cxx index 9d42a2f64b15..60680fb9181c 100644 --- a/sw/source/core/crsr/crstrvl.cxx +++ b/sw/source/core/crsr/crstrvl.cxx @@ -76,6 +76,8 @@ #include #include +#include + using namespace ::com::sun::star; void SwCursorShell::MoveCursorToNum() @@ -2961,4 +2963,110 @@ bool SwCursorShell::SelectNxtPrvHyperlink( bool bNext ) return bRet; } +// 1. Create a sorted list of document model positions containing the current cursor position and +// hyperlink positions excluding TOX links. +// 2. Search the sorted list for the current cursor position entry. +// 3. Assign the current cursor to the next or previous list entry document model position. +bool SwCursorShell::GotoNxtPrvHyperlink(bool bNext) +{ + std::vector> aArr; + + // add current cursor position + SwPosition* pCursorPos = m_pCurrentCursor->GetPoint(); + aArr.push_back( + std::make_unique(*pCursorPos->GetContentNode(), pCursorPos->GetContentIndex())); + + const SwCharFormats* pFormats = GetDoc()->GetCharFormats(); + for (size_t i = pFormats->size(); i > 1;) + { + SwIterator aIter(*(*pFormats)[--i]); + for (SwTextINetFormat* pFnd = aIter.First(); pFnd; pFnd = aIter.Next()) + { + const SwTextNode* pTextNode = pFnd->GetpTextNode(); + if (pTextNode && pTextNode->GetNodes().IsDocNodes()) + { + // don't include table of contents hyperlinks + if (const SwSectionNode* pSectNd = pTextNode->FindSectionNode(); + pSectNd && pSectNd->GetSection().GetType() == SectionType::ToxContent) + continue; + + aArr.push_back(std::make_unique(*pFnd->GetTextNode().GetContentNode(), + pFnd->GetStart())); + } + } + } + + // return if there is only the cursor entry in the list + if (aArr.size() == 1) + return false; + + const SwNodeOffset nEndOfExtrasIndex = GetNodes().GetEndOfExtras().GetIndex(); + + std::stable_sort( + aArr.begin(), aArr.end(), + [nEndOfExtrasIndex](const std::unique_ptr& a, + const std::unique_ptr& b) + { + const SwNode& aTextNode = a->GetNode(); + const SwNode& bTextNode = b->GetNode(); + SwPosition aPos(*aTextNode.GetContentNode(), a->GetContentIndex()); + SwPosition bPos(*bTextNode.GetContentNode(), b->GetContentIndex()); + // use anchor position for entries that are located in flys + if (nEndOfExtrasIndex >= aTextNode.GetIndex()) + if (auto pFlyFormat = aTextNode.GetFlyFormat()) + if (const SwPosition* pPos = pFlyFormat->GetAnchor().GetContentAnchor()) + aPos = *pPos; + if (nEndOfExtrasIndex >= bTextNode.GetIndex()) + if (auto pFlyFormat = bTextNode.GetFlyFormat()) + if (const SwPosition* pPos = pFlyFormat->GetAnchor().GetContentAnchor()) + bPos = *pPos; + return aPos < bPos; + }); + + // Search the array for the entry that has the cursor position then move the cursor to the + // position stored in the next or previous entry. + SwPosition aAssignPos(*aArr[0]); + for (size_t i = 0; i < aArr.size(); ++i) + { + if (*pCursorPos != *aArr[i]) + continue; + if (bNext) + { + if (i == aArr.size() - 1) + i = 0; + else + { + while (*pCursorPos == *aArr[i]) + { + if (++i == aArr.size()) + { + i = 0; + break; + } + } + } + } + else + { + if (i == 0) + i = aArr.size() - 1; + else + --i; + } + aAssignPos.Assign(*aArr[i]->GetNode().GetContentNode(), aArr[i]->GetContentIndex()); + break; + } + + CurrShell aCurr(this); + SwCallLink aLk(*this); // watch Cursor-Moves + SwCursorSaveState aSaveState(*m_pCurrentCursor); + + m_pCurrentCursor->GetPoint()->Assign(aAssignPos.GetNode(), aAssignPos.GetContentIndex()); + + if (!m_pCurrentCursor->IsSelOvr()) + UpdateCursor(SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE | SwCursorShell::READONLY); + + return true; +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/inc/workctrl.hxx b/sw/source/uibase/inc/workctrl.hxx index 083884618ab0..88532c5cf368 100644 --- a/sw/source/uibase/inc/workctrl.hxx +++ b/sw/source/uibase/inc/workctrl.hxx @@ -53,7 +53,8 @@ using namespace ::com::sun::star; #define NID_RECENCY 20018 #define NID_FIELD 20019 #define NID_FIELD_BYTYPE 20020 -#define NID_COUNT 21 +#define NID_HYPERLINK 20021 +#define NID_COUNT 22 class SwTbxAutoTextCtrl final : public SfxToolBoxControl { diff --git a/sw/source/uibase/ribbar/workctrl.cxx b/sw/source/uibase/ribbar/workctrl.cxx index f3f5fccedf88..c62bf8e5f767 100644 --- a/sw/source/uibase/ribbar/workctrl.cxx +++ b/sw/source/uibase/ribbar/workctrl.cxx @@ -58,7 +58,7 @@ #include // Size check -#define NAVI_ENTRIES 21 +#define NAVI_ENTRIES 22 using namespace ::com::sun::star; using namespace ::com::sun::star::uno; @@ -189,7 +189,8 @@ const sal_uInt16 aNavigationInsertIds[ NAVI_ENTRIES ] = NID_TABLE_FORMULA_ERROR, NID_RECENCY, NID_FIELD, - NID_FIELD_BYTYPE + NID_FIELD_BYTYPE, + NID_HYPERLINK }; OUString constexpr aNavigationImgIds[ NAVI_ENTRIES ] = @@ -214,7 +215,8 @@ OUString constexpr aNavigationImgIds[ NAVI_ENTRIES ] = RID_BMP_RIBBAR_ERROR, RID_BMP_RIBBAR_RECENCY, RID_BMP_RIBBAR_FIELD, - RID_BMP_RIBBAR_FIELD_BYTYPE + RID_BMP_RIBBAR_FIELD_BYTYPE, + RID_BMP_RIBBAR_HYPERLINK }; const TranslateId aNavigationStrIds[ NAVI_ENTRIES ] = @@ -239,7 +241,8 @@ const TranslateId aNavigationStrIds[ NAVI_ENTRIES ] = ST_TABLE_FORMULA_ERROR, ST_RECENCY, STR_CONTENT_TYPE_TEXTFIELD, - ST_FIELD_BYTYPE + ST_FIELD_BYTYPE, + STR_CONTENT_TYPE_URLFIELD }; // these are global strings @@ -266,6 +269,7 @@ const TranslateId STR_IMGBTN_ARY[] = STR_IMGBTN_RECENCY_DOWN, STR_IMGBTN_FIELD_DOWN, STR_IMGBTN_FIELD_BYTYPE_DOWN, + STR_IMGBTN_HYPERLINK_DOWN, STR_IMGBTN_TBL_UP, STR_IMGBTN_FRM_UP, STR_IMGBTN_PGE_UP, @@ -287,6 +291,7 @@ const TranslateId STR_IMGBTN_ARY[] = STR_IMGBTN_RECENCY_UP, STR_IMGBTN_FIELD_UP, STR_IMGBTN_FIELD_BYTYPE_UP, + STR_IMGBTN_HYPERLINK_UP }; static OUString lcl_GetScrollToolTip(bool bNext) diff --git a/sw/source/uibase/uiview/viewmdi.cxx b/sw/source/uibase/uiview/viewmdi.cxx index 73108feee767..7f8ae989c802 100644 --- a/sw/source/uibase/uiview/viewmdi.cxx +++ b/sw/source/uibase/uiview/viewmdi.cxx @@ -641,6 +641,12 @@ void SwView::MoveNavigation(bool bNext) rSh.UpdateCursor(SwCursorShell::SCROLLWIN); } break; + case NID_HYPERLINK: + { + rSh.AssureStdMode(); + rSh.GotoNxtPrvHyperlink(bNext); + break; + } } if (NID_POSTIT != s_nMoveType) m_pEditWin->GrabFocus();