/* -*- 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/. */ // FIX fdo#38246 https://bugs.libreoffice.org/show_bug.cgi?id=38246 // Design proposal: https://wiki.documentfoundation.org/Design/Whiteboards/Comments_Ruler_Control // TODO Alpha blend border when it doesn't fit in window #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define CONTROL_BORDER_WIDTH 1 #define CONTROL_LEFT_OFFSET 6 #define CONTROL_RIGHT_OFFSET 3 #define CONTROL_TOP_OFFSET 4 #define CONTROL_TRIANGLE_WIDTH 4 #define CONTROL_TRIANGLE_PAD 3 namespace { /** * Draw a little horizontal arrow tip on VirtualDevice. * \param nX left coordinate of arrow * \param nY top coordinate of arrow * \param Color arrow color * \param bPointRight if arrow should point to right. Otherwise, it will point left. */ void ImplDrawArrow(vcl::RenderContext& rRenderContext, long nX, long nY, const Color& rColor, bool bPointRight) { rRenderContext.SetLineColor(); rRenderContext.SetFillColor(rColor); if (bPointRight) { rRenderContext.DrawRect(tools::Rectangle(nX + 0, nY + 0, nX + 0, nY + 6) ); rRenderContext.DrawRect(tools::Rectangle(nX + 1, nY + 1, nX + 1, nY + 5) ); rRenderContext.DrawRect(tools::Rectangle(nX + 2, nY + 2, nX + 2, nY + 4) ); rRenderContext.DrawRect(tools::Rectangle(nX + 3, nY + 3, nX + 3, nY + 3) ); } else { rRenderContext.DrawRect(tools::Rectangle(nX + 0, nY + 3, nX + 0, nY + 3)); rRenderContext.DrawRect(tools::Rectangle(nX + 1, nY + 2, nX + 1, nY + 4)); rRenderContext.DrawRect(tools::Rectangle(nX + 2, nY + 1, nX + 2, nY + 5)); rRenderContext.DrawRect(tools::Rectangle(nX + 3, nY + 0, nX + 3, nY + 6)); } } } // 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) , mbIsHighlighted(false) , mnFadeRate(0) , maVirDev( VclPtr::Create(*this) ) { // Set fading timeout: 5 x 40ms = 200ms maFadeTimer.SetTimeout(40); maFadeTimer.SetInvokeHandler( LINK( this, SwCommentRuler, FadeHandler ) ); maFadeTimer.SetDebugName( "sw::SwCommentRuler maFadeTimer" ); } // Destructor SwCommentRuler::~SwCommentRuler() { disposeOnce(); } void SwCommentRuler::dispose() { mpSwWin.clear(); SvxRuler::dispose(); } 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(); bool bIsCollapsed = ! mpViewShell->GetPostItMgr()->ShowNotes(); tools::Rectangle aControlRect = GetCommentControlRegion(); maVirDev->SetOutputSizePixel(aControlRect.GetSize()); // Paint comment control background // TODO Check if these are best colors to be used Color aBgColor = GetFadedColor( rStyleSettings.GetDarkShadowColor(), rStyleSettings.GetWorkspaceColor() ); maVirDev->SetFillColor( aBgColor ); if ( mbIsHighlighted || !bIsCollapsed ) { // Draw borders maVirDev->SetLineColor( rStyleSettings.GetShadowColor() ); } else { // No borders maVirDev->SetLineColor(); } maVirDev->DrawRect( tools::Rectangle( Point(), aControlRect.GetSize() ) ); // Label and arrow tip OUString aLabel( SwResId ( STR_COMMENTS_LABEL ) ); // Get label and arrow coordinates Point aLabelPos; Point aArrowPos; bool bArrowToRight; // TODO Discover why it should be 0 instead of CONTROL_BORDER_WIDTH + CONTROL_TOP_OFFSET aLabelPos.Y() = 0; aArrowPos.Y() = CONTROL_BORDER_WIDTH + CONTROL_TOP_OFFSET; if ( !AllSettings::GetLayoutRTL() ) { // LTR if ( bIsCollapsed ) { // It should draw something like | > Comments | aLabelPos.X() = CONTROL_LEFT_OFFSET + CONTROL_TRIANGLE_WIDTH + CONTROL_TRIANGLE_PAD; aArrowPos.X() = CONTROL_LEFT_OFFSET; } else { // It should draw something like | Comments < | aLabelPos.X() = CONTROL_LEFT_OFFSET; aArrowPos.X() = aControlRect.GetSize().Width() - 1 - CONTROL_RIGHT_OFFSET - CONTROL_BORDER_WIDTH - CONTROL_TRIANGLE_WIDTH; } bArrowToRight = bIsCollapsed; } else { // RTL long nLabelWidth = GetTextWidth( aLabel ); if ( bIsCollapsed ) { // It should draw something like | Comments < | aArrowPos.X() = aControlRect.GetSize().Width() - 1 - CONTROL_RIGHT_OFFSET - CONTROL_BORDER_WIDTH - CONTROL_TRIANGLE_WIDTH; aLabelPos.X() = aArrowPos.X() - CONTROL_TRIANGLE_PAD - nLabelWidth; } else { // It should draw something like | > Comments | aLabelPos.X() = aControlRect.GetSize().Width() - 1 - CONTROL_RIGHT_OFFSET - CONTROL_BORDER_WIDTH - nLabelWidth; aArrowPos.X() = CONTROL_LEFT_OFFSET; } bArrowToRight = !bIsCollapsed; } // Draw label Color aTextColor = GetFadedColor( rStyleSettings.GetButtonTextColor(), rStyleSettings.GetDarkShadowColor() ); maVirDev->SetTextColor( aTextColor ); // FIXME Expected font size? maVirDev->DrawText( aLabelPos, aLabel ); // Draw arrow // FIXME consistence of button colors. http://opengrok.libreoffice.org/xref/core/vcl/source/control/button.cxx#785 Color aArrowColor = GetFadedColor(Color(COL_BLACK), rStyleSettings.GetShadowColor()); ImplDrawArrow(*maVirDev.get(), aArrowPos.X(), aArrowPos.Y(), aArrowColor, bArrowToRight); // Blit comment control rRenderContext.DrawOutDev(aControlRect.TopLeft(), aControlRect.GetSize(), Point(), aControlRect.GetSize(), *maVirDev.get()); } // 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().IsInside( aMousePos ) ) SvxRuler::Command( rCEvt ); } void SwCommentRuler::MouseMove(const MouseEvent& rMEvt) { SvxRuler::MouseMove(rMEvt); if ( ! mpViewShell->GetPostItMgr() || ! mpViewShell->GetPostItMgr()->HasNotes() ) return; Point aMousePos = rMEvt.GetPosPixel(); bool bWasHighlighted = mbIsHighlighted; mbIsHighlighted = GetCommentControlRegion().IsInside( aMousePos ); if ( mbIsHighlighted != bWasHighlighted ) { // Set proper help text if ( mbIsHighlighted ) { // Mouse over comment control UpdateCommentHelpText(); } else { // Mouse out of comment control // FIXME Should remember previous tooltip text? SetQuickHelpText( OUString() ); } // Do start fading maFadeTimer.Start(); } } void SwCommentRuler::MouseButtonDown( const MouseEvent& rMEvt ) { Point aMousePos = rMEvt.GetPosPixel(); if ( !rMEvt.IsLeft() || IsTracking() || !GetCommentControlRegion().IsInside( aMousePos ) ) { SvxRuler::MouseButtonDown(rMEvt); return; } // 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(); } const std::string SwCommentRuler::CreateJsonNotification() { boost::property_tree::ptree jsonNotif; // Note that GetMargin1(), GetMargin2(), GetNullOffset(), and GetPageOffset() return values in // pixels. Not twips. So "converting" the returned values with convertTwipToMm100() is quite // wrong. (Also, even if the return values actually were in twips, it is questionable why we // would want to pass them in mm100, as all other length values in the LOKit protocol apparently // are in twips.) // Anyway, as the consuming code in Online mostly seems to work anyway, it is likely that it // would work as well even if the values in pixels were passed without a bogus "conversion" to // mm100. But let's keep this as is for now. // Also note that in desktop LibreOffice, these pixel values for the rules of course change as // one changes the zoom level. (Can be seen if one temporarily modifies the NotifyKit() function // below to call this CreateJsonNotification() function and print its result in all cases even // without LibreOfficeKit::isActive().) But in both web-based Online and in the iOS app, the // zoom level from the point of view of this code here apparently does not change even if one // zooms from the Online code's point of view. jsonNotif.put("margin1", convertTwipToMm100(GetMargin1())); jsonNotif.put("margin2", convertTwipToMm100(GetMargin2())); jsonNotif.put("leftOffset", convertTwipToMm100(GetNullOffset())); jsonNotif.put("pageOffset", convertTwipToMm100(GetPageOffset())); // GetPageWidth() on the other hand does return a value in twips. jsonNotif.put("pageWidth", convertTwipToMm100(GetPageWidth())); boost::property_tree::ptree tabs; for (auto const& tab : GetTabs()) { boost::property_tree::ptree element; element.put("position", tab.nPos); element.put("style", tab.nStyle); tabs.push_back(std::make_pair("", element)); } jsonNotif.add_child("tabs", tabs); RulerUnitData aUnitData = GetCurrentRulerUnit(); jsonNotif.put("unit", aUnitData.aUnitStr); std::stringstream aStream; boost::property_tree::write_json(aStream, jsonNotif); std::string aPayload = aStream.str(); return aPayload; } void SwCommentRuler::NotifyKit() { if (!comphelper::LibreOfficeKit::isActive()) return; const std::string test = CreateJsonNotification(); mpViewShell->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_RULER_UPDATE, test.c_str()); } void SwCommentRuler::Update() { tools::Rectangle aPreviousControlRect = GetCommentControlRegion(); SvxRuler::Update(); if (aPreviousControlRect != GetCommentControlRegion()) Invalidate(); NotifyKit(); } void SwCommentRuler::UpdateCommentHelpText() { const char* pTooltipResId; if ( mpViewShell->GetPostItMgr()->ShowNotes() ) pTooltipResId = STR_HIDE_COMMENTS; else pTooltipResId = STR_SHOW_COMMENTS; SetQuickHelpText(SwResId(pTooltipResId)); } // TODO Make Ruler return its central rectangle instead of margins. tools::Rectangle SwCommentRuler::GetCommentControlRegion() { long nLeft = 0; 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(); unsigned long nSidebarWidth = pPostItMgr->GetSidebarWidth(true); //FIXME When the page width is larger then screen, the ruler is misplaced by one pixel if (GetTextRTL()) nLeft = GetPageOffset() - nSidebarWidth + GetBorderOffset(); else nLeft = GetWinOffset() + GetPageOffset() + mpSwWin->LogicToPixel(Size(GetPageWidth(), 0)).Width(); long nTop = 0 + 4; // Ruler::ImplDraw uses RULER_OFF (value: 3px) as offset, and Ruler::ImplFormat adds one extra pixel // Somehow pPostItMgr->GetSidebarBorderWidth() returns border width already doubled long nRight = nLeft + nSidebarWidth + pPostItMgr->GetSidebarBorderWidth(true); 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: */