1
0
Fork 0
libreoffice/sw/source/uibase/misc/swruler.cxx
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

371 lines
12 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// Design proposal: https://wiki.documentfoundation.org/Design/Whiteboards/Comments_Ruler_Control
#include <swruler.hxx>
#include <viewsh.hxx>
#include <viewopt.hxx>
#include <edtwin.hxx>
#include <PostItMgr.hxx>
#include <view.hxx>
#include <wrtsh.hxx>
#include <cmdid.h>
#include <sfx2/request.hxx>
#include <vcl/commandevent.hxx>
#include <vcl/event.hxx>
#include <vcl/ptrstyle.hxx>
#include <vcl/window.hxx>
#include <vcl/settings.hxx>
#include <strings.hrc>
#include <comphelper/lok.hxx>
#define CONTROL_BORDER_WIDTH 1
namespace
{
/**
* Draw a little arrow / triangle with different directions
*
* \param nX left coordinate of arrow square
* \param nY top coordinate of arrow square
* \param nSize size of the long triangle side / arrow square
* \param Color arrow color
* \param bCollapsed if the arrow should display the collapsed state
*/
void ImplDrawArrow(vcl::RenderContext& rRenderContext, tools::Long nX, tools::Long nY,
tools::Long nSize, const Color& rColor, bool bCollapsed)
{
tools::Polygon aTrianglePolygon(4);
if (bCollapsed)
{
if (AllSettings::GetLayoutRTL()) // <
{
aTrianglePolygon.SetPoint({ nX + nSize / 2, nY }, 0);
aTrianglePolygon.SetPoint({ nX + nSize / 2, nY + nSize }, 1);
aTrianglePolygon.SetPoint({ nX, nY + nSize / 2 }, 2);
aTrianglePolygon.SetPoint({ nX + nSize / 2, nY }, 3);
}
else // >
{
aTrianglePolygon.SetPoint({ nX, nY }, 0);
aTrianglePolygon.SetPoint({ nX + nSize / 2, nY + nSize / 2 }, 1);
aTrianglePolygon.SetPoint({ nX, nY + nSize }, 2);
aTrianglePolygon.SetPoint({ nX, nY }, 3);
}
}
else // v
{
aTrianglePolygon.SetPoint({ nX, nY + nSize / 2 }, 0);
aTrianglePolygon.SetPoint({ nX + nSize, nY + nSize / 2 }, 1);
aTrianglePolygon.SetPoint({ nX + nSize / 2, nY + nSize }, 2);
aTrianglePolygon.SetPoint({ nX, nY + nSize / 2 }, 3);
}
rRenderContext.SetLineColor();
rRenderContext.SetFillColor(rColor);
rRenderContext.DrawPolygon(aTrianglePolygon);
}
}
// Constructor
SwCommentRuler::SwCommentRuler(SwViewShell* pViewSh, vcl::Window* pParent, SwEditWin* pWin,
SvxRulerSupportFlags nRulerFlags, SfxBindings& rBindings,
WinBits nWinStyle)
: SvxRuler(pParent, pWin, nRulerFlags, rBindings, nWinStyle | WB_HSCROLL)
, mpViewShell(pViewSh)
, mpSwWin(pWin)
, mbIsDrag(false)
, mbIsHighlighted(false)
, maFadeTimer("sw::SwCommentRuler maFadeTimer")
, mnFadeRate(0)
, maVirDev(VclPtr<VirtualDevice>::Create(*GetOutDev()))
{
// Set fading timeout: 5 x 40ms = 200ms
maFadeTimer.SetTimeout(40);
maFadeTimer.SetInvokeHandler(LINK(this, SwCommentRuler, FadeHandler));
// we have a little bit more space, as we don't draw ruler ticks
vcl::Font aFont(maVirDev->GetFont());
aFont.SetFontHeight(aFont.GetFontHeight() + 1);
maVirDev->SetFont(aFont);
}
SwCommentRuler::~SwCommentRuler() { disposeOnce(); }
void SwCommentRuler::dispose()
{
mpSwWin.clear();
SvxRuler::dispose();
}
sw::sidebarwindows::SidebarPosition SwCommentRuler::GetSidebarPosition()
{
if (SwPostItMgr* pPostItMgr = mpViewShell->GetPostItMgr())
return pPostItMgr->GetSidebarPos(mpSwWin->GetView().GetWrtShell().GetCursorDocPos());
return sw::sidebarwindows::SidebarPosition::NONE;
}
void SwCommentRuler::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
{
if (comphelper::LibreOfficeKit::isActive())
return; // no need to waste time on startup
SvxRuler::Paint(rRenderContext, rRect);
// Don't draw if there is not any note
if (mpViewShell->GetPostItMgr() && mpViewShell->GetPostItMgr()->HasNotes())
DrawCommentControl(rRenderContext);
}
void SwCommentRuler::DrawCommentControl(vcl::RenderContext& rRenderContext)
{
const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
const bool bIsCollapsed = !mpViewShell->GetPostItMgr()->ShowNotes();
const tools::Rectangle aControlRect = GetCommentControlRegion();
maVirDev->SetOutputSizePixel(aControlRect.GetSize());
// set colors
if (!bIsCollapsed)
{
if (mbIsHighlighted)
maVirDev->SetFillColor(
GetFadedColor(rStyleSettings.GetHighlightColor(), rStyleSettings.GetDialogColor()));
else
maVirDev->SetFillColor(rStyleSettings.GetDialogColor());
maVirDev->SetLineColor(rStyleSettings.GetShadowColor());
}
else
{
if (mbIsHighlighted)
maVirDev->SetFillColor(GetFadedColor(rStyleSettings.GetHighlightColor(),
rStyleSettings.GetWorkspaceColor()));
else
maVirDev->SetFillColor(rStyleSettings.GetWorkspaceColor());
maVirDev->SetLineColor();
}
Color aTextColor = GetFadedColor(rStyleSettings.GetHighlightTextColor(),
rStyleSettings.GetButtonTextColor());
maVirDev->SetTextColor(aTextColor);
// calculate label and arrow positions
const OUString aLabel = SwResId(STR_COMMENTS_LABEL);
const tools::Long nTriangleSize = maVirDev->GetTextHeight() / 2 + 1;
const tools::Long nTrianglePad = maVirDev->GetTextHeight() / 2;
Point aLabelPos(0, (aControlRect.GetHeight() - maVirDev->GetTextHeight()) / 2);
Point aArrowPos(0, (aControlRect.GetHeight() - nTriangleSize) / 2);
if (!AllSettings::GetLayoutRTL()) // | > Comments |
{
aArrowPos.setX(nTrianglePad);
aLabelPos.setX(aArrowPos.X() + nTriangleSize + nTrianglePad / 2);
}
else // RTL => | Comments < |
{
const tools::Long nLabelWidth = maVirDev->GetTextWidth(aLabel);
if (!bIsCollapsed)
{
aArrowPos.setX(aControlRect.GetWidth() - 1 - nTrianglePad - CONTROL_BORDER_WIDTH
- nTriangleSize);
aLabelPos.setX(aArrowPos.X() - nTrianglePad - nLabelWidth);
}
else
{
// if comments are collapsed, left align the text, because otherwise it's very likely to be invisible
aArrowPos.setX(nLabelWidth + nTrianglePad + nTriangleSize);
aLabelPos.setX(aArrowPos.X() - nTrianglePad - nLabelWidth);
}
}
// draw control
maVirDev->DrawRect(tools::Rectangle(Point(), aControlRect.GetSize()));
maVirDev->DrawText(aLabelPos, aLabel);
ImplDrawArrow(*maVirDev, aArrowPos.X(), aArrowPos.Y(), nTriangleSize, aTextColor, bIsCollapsed);
rRenderContext.DrawOutDev(aControlRect.TopLeft(), aControlRect.GetSize(), Point(),
aControlRect.GetSize(), *maVirDev);
}
// Just accept double-click outside comment control
void SwCommentRuler::Command(const CommandEvent& rCEvt)
{
Point aMousePos = rCEvt.GetMousePosPixel();
// Ignore command request if it is inside Comment Control
if (!mpViewShell->GetPostItMgr() || !mpViewShell->GetPostItMgr()->HasNotes()
|| !GetCommentControlRegion().Contains(aMousePos))
SvxRuler::Command(rCEvt);
}
void SwCommentRuler::MouseMove(const MouseEvent& rMEvt)
{
if (mbIsDrag)
{
mpSwWin->DrawCommentGuideLine(rMEvt.GetPosPixel());
return;
}
SvxRuler::MouseMove(rMEvt);
if (!mpViewShell->GetPostItMgr() || !mpViewShell->GetPostItMgr()->HasNotes())
return;
UpdateCommentHelpText();
Point aMousePos = rMEvt.GetPosPixel();
if (GetDragArea().Contains(aMousePos))
SetPointer(PointerStyle::HSizeBar);
bool bWasHighlighted = mbIsHighlighted;
mbIsHighlighted = GetCommentControlRegion().Contains(aMousePos);
if (mbIsHighlighted != bWasHighlighted)
// Do start fading
maFadeTimer.Start();
}
void SwCommentRuler::MouseButtonDown(const MouseEvent& rMEvt)
{
// If right-click while dragging to resize the comment width, stop resizing
if (mbIsDrag && rMEvt.GetButtons() == MOUSE_RIGHT)
{
ReleaseMouse();
mpSwWin->ReleaseCommentGuideLine();
mbIsDrag = false;
return;
}
Point aMousePos = rMEvt.GetPosPixel();
if (!rMEvt.IsLeft() || IsTracking()
|| (!GetCommentControlRegion().Contains(aMousePos) && !GetDragArea().Contains(aMousePos)))
{
SvxRuler::MouseButtonDown(rMEvt);
return;
}
if (GetDragArea().Contains(aMousePos))
{
mbIsDrag = true;
CaptureMouse();
}
else
{
// Toggle notes visibility
SwView& rView = mpSwWin->GetView();
SfxRequest aRequest(rView.GetViewFrame(), SID_TOGGLE_NOTES);
rView.ExecViewOptions(aRequest);
// It is inside comment control, so update help text
UpdateCommentHelpText();
}
Invalidate();
}
void SwCommentRuler::MouseButtonUp(const MouseEvent& rMEvt)
{
if (!mbIsDrag)
{
SvxRuler::MouseButtonUp(rMEvt);
return;
}
mpSwWin->SetSidebarWidth(rMEvt.GetPosPixel());
ReleaseMouse();
mpSwWin->ReleaseCommentGuideLine();
mbIsDrag = false;
Invalidate();
}
void SwCommentRuler::Update()
{
tools::Rectangle aPreviousControlRect = GetCommentControlRegion();
SvxRuler::Update();
if (aPreviousControlRect != GetCommentControlRegion())
Invalidate();
}
void SwCommentRuler::UpdateCommentHelpText()
{
TranslateId pTooltipResId;
if (mpViewShell->GetPostItMgr()->ShowNotes())
pTooltipResId = STR_HIDE_COMMENTS;
else
pTooltipResId = STR_SHOW_COMMENTS;
SetQuickHelpText(SwResId(pTooltipResId));
}
tools::Rectangle SwCommentRuler::GetDragArea()
{
tools::Rectangle base(GetCommentControlRegion());
if (GetSidebarPosition() == sw::sidebarwindows::SidebarPosition::LEFT)
base.Move(-5, 0);
else
base.Move(Size(base.GetWidth() - 5, 0));
base.SetWidth(10);
return base;
}
// TODO Make Ruler return its central rectangle instead of margins.
tools::Rectangle SwCommentRuler::GetCommentControlRegion()
{
SwPostItMgr* pPostItMgr = mpViewShell->GetPostItMgr();
//rhbz#1006850 When the SwPostItMgr ctor is called from SwView::SwView it
//triggers an update of the uiview, but the result of the ctor hasn't been
//set into the mpViewShell yet, so GetPostItMgr is temporarily still NULL
if (!pPostItMgr)
return tools::Rectangle();
const tools::ULong nSidebarWidth = pPostItMgr->GetSidebarWidth(true);
//FIXME When the page width is larger then screen, the ruler is misplaced by one pixel
tools::Long nLeft = GetPageOffset();
if (GetSidebarPosition() == sw::sidebarwindows::SidebarPosition::LEFT)
nLeft += GetBorderOffset() - nSidebarWidth;
else
nLeft += GetWinOffset() + mpSwWin->LogicToPixel(Size(GetPageWidth(), 0)).Width();
// Ruler::ImplDraw uses RULER_OFF (value: 3px) as offset, and Ruler::ImplFormat adds one extra pixel
tools::Long nTop = 4;
// Somehow pPostItMgr->GetSidebarBorderWidth() returns border width already doubled
tools::Long nRight = nLeft + nSidebarWidth + pPostItMgr->GetSidebarBorderWidth(true);
tools::Long nBottom = nTop + GetRulerVirHeight() - 3;
tools::Rectangle aRect(nLeft, nTop, nRight, nBottom);
return aRect;
}
Color SwCommentRuler::GetFadedColor(const Color& rHighColor, const Color& rLowColor)
{
if (!maFadeTimer.IsActive())
return mbIsHighlighted ? rHighColor : rLowColor;
Color aColor = rHighColor;
aColor.Merge(rLowColor, mnFadeRate * 255 / 100.0f);
return aColor;
}
IMPL_LINK_NOARG(SwCommentRuler, FadeHandler, Timer*, void)
{
const int nStep = 25;
if (mbIsHighlighted && mnFadeRate < 100)
mnFadeRate += nStep;
else if (!mbIsHighlighted && mnFadeRate > 0)
mnFadeRate -= nStep;
else
return;
Invalidate();
if (mnFadeRate != 0 && mnFadeRate != 100)
maFadeTimer.Start();
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */