tdf#152029 Visually draw attention to in-view bookmark

when mouse pointer is over bookmark entry in the Navigator content tree

This patch brings attention to in-view bookmarks when the mouse pointer
is positioned over bookmark content entries in the Navigator content
tree. All in-view bookmarks are brought to attention by placing the
mouse pointer over the bookmark content type (category) entry.

The patch adds a parameter to the weld::get_dest_row_at_pos function to
give the option not to auto scroll. It is needed to prevent auto
scrolling when used in the the mouse move handler. Additional use can
be made in the content tree CommandHdl to make the tree not jump when
the context popup menu is activated for entries near the top and bottom
of the the visible tree.

Change-Id: I04e306286ca58ab6c8ae7e5c02b25a0592c4a9d3
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143628
Tested-by: Jenkins
Reviewed-by: Jim Raykowski <raykowj@gmail.com>
This commit is contained in:
Jim Raykowski 2022-12-03 20:11:07 -09:00
parent 968242fe32
commit 3cb654972b
7 changed files with 224 additions and 36 deletions

View file

@ -1352,7 +1352,9 @@ public:
* after the row
* b) dnd highlight the dest row
*/
virtual bool get_dest_row_at_pos(const Point& rPos, weld::TreeIter* pResult, bool bDnDMode) = 0;
virtual bool get_dest_row_at_pos(const Point& rPos, weld::TreeIter* pResult, bool bDnDMode,
bool bAutoScroll = true)
= 0;
virtual void unset_drag_dest_row() = 0;
virtual tools::Rectangle get_row_area(const weld::TreeIter& rIter) const = 0;
// for dragging and dropping between TreeViews, return the active source

View file

@ -32,6 +32,8 @@
#include <o3tl/enumarray.hxx>
#include <o3tl/typed_flags_set.hxx>
#include <svx/sdr/overlay/overlayobject.hxx>
class SwWrtShell;
class SwContentType;
class SwNavigationPI;
@ -91,6 +93,7 @@ class SwContentTree final : public SfxListener
SwNavigationPI* m_pDialog;
OUString m_sSpace;
AutoTimer m_aUpdTimer;
AutoTimer m_aOverlayObjectDelayTimer;
o3tl::enumarray<ContentTypeId,std::unique_ptr<SwContentType>> m_aActiveContentArr;
o3tl::enumarray<ContentTypeId,std::unique_ptr<SwContentType>> m_aHiddenContentArr;
@ -129,6 +132,11 @@ class SwContentTree final : public SfxListener
bool m_bDocHasChanged = true;
bool m_bIgnoreDocChange = false; // used to prevent tracking update
std::unique_ptr<weld::TreeIter> m_xOverlayCompareEntry;
std::unique_ptr<sdr::overlay::OverlayObject> m_xOverlayObject;
void BringBookmarksToAttention(const std::vector<OUString>& rNames);
/**
* Before any data will be deleted, the last active entry has to be found.
* After this the UserData will be deleted
@ -183,6 +191,8 @@ class SwContentTree final : public SfxListener
DECL_LINK(QueryTooltipHdl, const weld::TreeIter&, OUString);
DECL_LINK(DragBeginHdl, bool&, bool);
DECL_LINK(TimerUpdate, Timer *, void);
DECL_LINK(m_aOverlayObjectDelayTimerHdl, Timer *, void);
DECL_LINK(MouseMoveHdl, const MouseEvent&, bool);
public:
SwContentTree(std::unique_ptr<weld::TreeView> xTreeView, SwNavigationPI* pDialog);

View file

@ -102,6 +102,12 @@
#include <txtftn.hxx>
#include <fmtftn.hxx>
#include <txtfrm.hxx>
#include <svx/sdr/overlay/overlayselection.hxx>
#include <svx/sdr/overlay/overlayobject.hxx>
#include <svx/sdr/overlay/overlaymanager.hxx>
#include <svx/sdrpaintwindow.hxx>
#define CTYPE_CNT 0
#define CTYPE_CTT 1
@ -1066,6 +1072,7 @@ SwContentTree::SwContentTree(std::unique_ptr<weld::TreeView> xTreeView, SwNaviga
, m_pDialog(pDialog)
, m_sSpace(OUString(" "))
, m_aUpdTimer("SwContentTree m_aUpdTimer")
, m_aOverlayObjectDelayTimer("SwContentTree m_aOverlayObjectDelayTimer")
, m_sInvisible(SwResId(STR_INVISIBLE))
, m_pHiddenShell(nullptr)
, m_pActiveShell(nullptr)
@ -1082,6 +1089,7 @@ SwContentTree::SwContentTree(std::unique_ptr<weld::TreeView> xTreeView, SwNaviga
, m_bIsLastReadOnly(false)
, m_bIsOutlineMoveable(true)
, m_bViewHasChanged(false)
, m_xOverlayCompareEntry(m_xTreeView->make_iterator())
{
m_xTreeView->set_size_request(m_xTreeView->get_approximate_digit_width() * 30,
m_xTreeView->get_text_height() * 14);
@ -1097,6 +1105,7 @@ SwContentTree::SwContentTree(std::unique_ptr<weld::TreeView> xTreeView, SwNaviga
m_xTreeView->connect_popup_menu(LINK(this, SwContentTree, CommandHdl));
m_xTreeView->connect_query_tooltip(LINK(this, SwContentTree, QueryTooltipHdl));
m_xTreeView->connect_drag_begin(LINK(this, SwContentTree, DragBeginHdl));
m_xTreeView->connect_mouse_move(LINK(this, SwContentTree, MouseMoveHdl));
for (ContentTypeId i : o3tl::enumrange<ContentTypeId>())
{
@ -1121,6 +1130,8 @@ SwContentTree::SwContentTree(std::unique_ptr<weld::TreeView> xTreeView, SwNaviga
m_aUpdTimer.SetInvokeHandler(LINK(this, SwContentTree, TimerUpdate));
m_aUpdTimer.SetTimeout(1000);
m_aOverlayObjectDelayTimer.SetInvokeHandler(LINK(this, SwContentTree, m_aOverlayObjectDelayTimerHdl));
m_aOverlayObjectDelayTimer.SetTimeout(500);
}
SwContentTree::~SwContentTree()
@ -1135,6 +1146,74 @@ SwContentTree::~SwContentTree()
SetActiveShell(nullptr);
}
IMPL_LINK(SwContentTree, MouseMoveHdl, const MouseEvent&, rMEvt, bool)
{
if (rMEvt.IsEnterWindow())
{
m_xTreeView->get_iter_first(*m_xOverlayCompareEntry);
return false;
}
bool bRemoveOverlayObject = false;
if (rMEvt.IsLeaveWindow())
{
bRemoveOverlayObject = true;
}
else if (std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator());
m_xTreeView->get_dest_row_at_pos(rMEvt.GetPosPixel(), xEntry.get(), false, false))
{
if (lcl_IsContent(*xEntry, *m_xTreeView)) // content entry
{
SwContent* pCnt = weld::fromId<SwContent*>(m_xTreeView->get_id(*xEntry));
const ContentTypeId nType = pCnt->GetParent()->GetType();
bRemoveOverlayObject = nType != ContentTypeId::BOOKMARK;
if (!bRemoveOverlayObject &&
m_xTreeView->iter_compare(*xEntry, *m_xOverlayCompareEntry) != 0)
{
m_xTreeView->copy_iterator(*xEntry, *m_xOverlayCompareEntry);
if (nType == ContentTypeId::BOOKMARK)
{
BringBookmarksToAttention(std::vector<OUString> {pCnt->GetName()});
}
}
}
else // content type entry
{
const ContentTypeId nType =
weld::fromId<SwContentType*>(m_xTreeView->get_id(*xEntry))->GetType();
bRemoveOverlayObject = nType != ContentTypeId::BOOKMARK;
if (!bRemoveOverlayObject &&
m_xTreeView->iter_compare(*xEntry, *m_xOverlayCompareEntry) != 0)
{
m_xTreeView->copy_iterator(*xEntry, *m_xOverlayCompareEntry);
if (nType == ContentTypeId::BOOKMARK)
{
Reference<frame::XModel> xModel =
m_pActiveShell->GetView().GetDocShell()->GetBaseModel();
Reference<text::XBookmarksSupplier> xBkms(xModel, uno::UNO_QUERY);
Reference<container::XNameAccess> xNames = xBkms->getBookmarks();
if (xNames.is())
{
auto aNames(comphelper::sequenceToContainer<std::vector<OUString>>(
xNames->getElementNames()));
BringBookmarksToAttention(aNames);
}
}
}
}
}
if (bRemoveOverlayObject)
{
m_aOverlayObjectDelayTimer.Stop();
if (m_xOverlayObject && m_xOverlayObject->getOverlayManager())
{
m_xOverlayObject->getOverlayManager()->remove(*m_xOverlayObject);
m_xOverlayObject.reset();
}
m_xTreeView->get_iter_first(*m_xOverlayCompareEntry);
}
return false;
}
// Drag&Drop methods
IMPL_LINK(SwContentTree, DragBeginHdl, bool&, rUnsetDragIcon, bool)
{
@ -4836,6 +4915,23 @@ void SwContentTree::ShowActualView()
GetParentWindow()->UpdateListBox();
}
IMPL_LINK_NOARG(SwContentTree, m_aOverlayObjectDelayTimerHdl, Timer *, void)
{
m_aOverlayObjectDelayTimer.Stop();
if (m_xOverlayObject)
{
if (SdrView* pView = m_pActiveShell->GetDrawView())
{
if (SdrPaintWindow* pPaintWindow = pView->GetPaintWindow(0))
{
const rtl::Reference<sdr::overlay::OverlayManager>& xOverlayManager =
pPaintWindow->GetOverlayManager();
xOverlayManager->add(*m_xOverlayObject);
}
}
}
}
IMPL_LINK_NOARG(SwContentTree, SelectHdl, weld::TreeView&, void)
{
if (m_pConfig->IsNavigateOnSelect())
@ -5407,4 +5503,78 @@ void SwContentTree::SelectContentType(std::u16string_view rContentTypeName)
} while (m_xTreeView->iter_next_sibling(*xIter));
}
void SwContentTree::BringBookmarksToAttention(const std::vector<OUString>& rNames)
{
std::vector<basegfx::B2DRange> aRanges;
IDocumentMarkAccess* const pMarkAccess = m_pActiveShell->getIDocumentMarkAccess();
for (const auto& rName : rNames)
{
IDocumentMarkAccess::const_iterator_t ppBkmk = pMarkAccess->findBookmark(rName);
if (ppBkmk != pMarkAccess->getBookmarksEnd())
{
SwPosition aMarkStart = (*ppBkmk)->GetMarkStart();
if (const SwTextNode* pMarkStartTextNode = aMarkStart.GetNode().GetTextNode())
{
if (const SwTextFrame* pMarkStartFrame = static_cast<const SwTextFrame*>(
pMarkStartTextNode->getLayoutFrame(m_pActiveShell->GetLayout())))
{
SwPosition aMarkEnd = (*ppBkmk)->GetMarkEnd();
if (const SwTextNode* pMarkEndTextNode = aMarkEnd.GetNode().GetTextNode())
{
if (const SwTextFrame* pMarkEndFrame = static_cast<const SwTextFrame*>(
pMarkEndTextNode->getLayoutFrame(
m_pActiveShell->GetLayout())))
{
// adjust span when mark start equals mark end
if (aMarkStart == aMarkEnd)
{
if (aMarkEnd.GetContentIndex() < pMarkEndTextNode->Len() - 1)
aMarkEnd.AdjustContent(+1);
else if (aMarkStart.GetContentIndex() > 0)
aMarkStart.AdjustContent(-1);
}
SwRect aStartCharRect;
pMarkStartFrame->GetCharRect(aStartCharRect, aMarkStart);
SwRect aEndCharRect;
pMarkEndFrame->GetCharRect(aEndCharRect, aMarkEnd);
if (aStartCharRect.Top() == aEndCharRect.Top())
{
// single line range
aRanges.emplace_back(aStartCharRect.Left(),
aStartCharRect.Top(),
aEndCharRect.Right() + 1,
aEndCharRect.Bottom() + 1);
}
else
{
// multi line range
SwRect aMarkStartFrameRect = pMarkStartFrame->getFrameArea();
aRanges.emplace_back(aStartCharRect.Left(),
aStartCharRect.Top(),
aMarkStartFrameRect.Right(),
aStartCharRect.Bottom() + 1);
if (aStartCharRect.Bottom() + 1 != aEndCharRect.Top())
aRanges.emplace_back(aMarkStartFrameRect.Left(),
aStartCharRect.Bottom() + 1,
aMarkStartFrameRect.Right(),
aEndCharRect.Top() + 1);
aRanges.emplace_back(aMarkStartFrameRect.Left(),
aEndCharRect.Top() + 1,
aEndCharRect.Right() + 1,
aEndCharRect.Bottom() + 1);
}
}
}
}
}
}
}
if (m_xOverlayObject && m_xOverlayObject->getOverlayManager())
m_xOverlayObject->getOverlayManager()->remove(*m_xOverlayObject);
m_xOverlayObject.reset(new sdr::overlay::OverlaySelection(sdr::overlay::OverlayType::Invert,
Color(), std::move(aRanges),
true /*unused for Invert type*/));
m_aOverlayObjectDelayTimer.Start();
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View file

@ -1716,8 +1716,8 @@ public:
SvTabListBox& getTreeView();
virtual bool get_dest_row_at_pos(const Point& rPos, weld::TreeIter* pResult,
bool bDnDMode) override;
virtual bool get_dest_row_at_pos(const Point& rPos, weld::TreeIter* pResult, bool bDnDMode,
bool bAutoScroll = true) override;
virtual void unset_drag_dest_row() override;

View file

@ -121,26 +121,29 @@ public:
m_aModelChangedHdl.Call(this);
}
SvTreeListEntry* GetTargetAtPoint(const Point& rPos, bool bHighLightTarget)
SvTreeListEntry* GetTargetAtPoint(const Point& rPos, bool bHighLightTarget, bool bScroll = true)
{
SvTreeListEntry* pOldTargetEntry = pTargetEntry;
pTargetEntry = PosOverBody(rPos) ? pImpl->GetEntry(rPos) : nullptr;
if (pOldTargetEntry != pTargetEntry)
ImplShowTargetEmphasis(pOldTargetEntry, false);
// scroll
if (rPos.Y() < 12)
if (bScroll)
{
ImplShowTargetEmphasis(pTargetEntry, false);
ScrollOutputArea(+1);
}
else
{
Size aSize(pImpl->GetOutputSize());
if (rPos.Y() > aSize.Height() - 12)
// scroll
if (rPos.Y() < 12)
{
ImplShowTargetEmphasis(pTargetEntry, false);
ScrollOutputArea(-1);
ScrollOutputArea(+1);
}
else
{
Size aSize(pImpl->GetOutputSize());
if (rPos.Y() > aSize.Height() - 12)
{
ImplShowTargetEmphasis(pTargetEntry, false);
ScrollOutputArea(-1);
}
}
}

View file

@ -5154,12 +5154,12 @@ void SalInstanceTreeView::set_sort_column(int nColumn)
SvTabListBox& SalInstanceTreeView::getTreeView() { return *m_xTreeView; }
bool SalInstanceTreeView::get_dest_row_at_pos(const Point& rPos, weld::TreeIter* pResult,
bool bDnDMode)
bool bDnDMode, bool bAutoScroll)
{
LclTabListBox* pTreeView
= !bDnDMode ? dynamic_cast<LclTabListBox*>(m_xTreeView.get()) : nullptr;
SvTreeListEntry* pTarget
= pTreeView ? pTreeView->GetTargetAtPoint(rPos, false) : m_xTreeView->GetDropTarget(rPos);
SvTreeListEntry* pTarget = pTreeView ? pTreeView->GetTargetAtPoint(rPos, false, bAutoScroll)
: m_xTreeView->GetDropTarget(rPos);
if (pTarget && pResult)
{

View file

@ -16240,7 +16240,7 @@ public:
weld::TreeView::connect_popup_menu(rLink);
}
virtual bool get_dest_row_at_pos(const Point &rPos, weld::TreeIter* pResult, bool bDnDMode) override
virtual bool get_dest_row_at_pos(const Point &rPos, weld::TreeIter* pResult, bool bDnDMode, bool bAutoScroll) override
{
if (rPos.X() < 0 || rPos.Y() < 0)
{
@ -16311,28 +16311,31 @@ public:
gtk_tree_path_free(path);
gtk_tree_path_free(lastpath);
// auto scroll if we're close to the edges
GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
double fStep = gtk_adjustment_get_step_increment(pVAdjustment);
if (rPos.Y() < fStep)
if (bAutoScroll)
{
double fValue = gtk_adjustment_get_value(pVAdjustment) - fStep;
if (fValue < 0)
fValue = 0.0;
gtk_adjustment_set_value(pVAdjustment, fValue);
}
else
{
GdkRectangle aRect;
gtk_tree_view_get_visible_rect(m_pTreeView, &aRect);
if (rPos.Y() > aRect.height - fStep)
// auto scroll if we're close to the edges
GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
double fStep = gtk_adjustment_get_step_increment(pVAdjustment);
if (rPos.Y() < fStep)
{
double fValue = gtk_adjustment_get_value(pVAdjustment) + fStep;
double fMax = gtk_adjustment_get_upper(pVAdjustment);
if (fValue > fMax)
fValue = fMax;
double fValue = gtk_adjustment_get_value(pVAdjustment) - fStep;
if (fValue < 0)
fValue = 0.0;
gtk_adjustment_set_value(pVAdjustment, fValue);
}
else
{
GdkRectangle aRect;
gtk_tree_view_get_visible_rect(m_pTreeView, &aRect);
if (rPos.Y() > aRect.height - fStep)
{
double fValue = gtk_adjustment_get_value(pVAdjustment) + fStep;
double fMax = gtk_adjustment_get_upper(pVAdjustment);
if (fValue > fMax)
fValue = fMax;
gtk_adjustment_set_value(pVAdjustment, fValue);
}
}
}
return ret;