/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * 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/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #define TIP_HEIGHT 15 #define TIP_WIDTH 7 #define TIP_RIGHT_OFFSET 18 #define BUBBLE_BORDER 10 #define TEXT_MAX_WIDTH 300 #define TEXT_MAX_HEIGHT 200 BubbleWindow::BubbleWindow( vcl::Window* pParent, OUString aTitle, OUString aText, Image aImage ) : FloatingWindow( pParent, WB_SYSTEMWINDOW | WB_OWNERDRAWDECORATION | WB_NOBORDER ) , maBubbleTitle(std::move( aTitle )) , maBubbleText(std::move( aText )) , maBubbleImage(std::move( aImage )) , maMaxTextSize( TEXT_MAX_WIDTH, TEXT_MAX_HEIGHT ) , mnTipOffset( 0 ) { SetBackground( Wallpaper( GetSettings().GetStyleSettings().GetHelpColor() ) ); } void BubbleWindow::Resize() { FloatingWindow::Resize(); Size aSize = GetSizePixel(); if ( ( aSize.Height() < 20 ) || ( aSize.Width() < 60 ) ) return; tools::Rectangle aRect( 0, TIP_HEIGHT, aSize.Width(), aSize.Height() - TIP_HEIGHT ); maRectPoly = tools::Polygon( aRect, 6, 6 ); vcl::Region aRegion( maRectPoly ); tools::Long nTipOffset = aSize.Width() - TIP_RIGHT_OFFSET + mnTipOffset; Point aPointArr[4]; aPointArr[0] = Point( nTipOffset, TIP_HEIGHT ); aPointArr[1] = Point( nTipOffset, 0 ); aPointArr[2] = Point( nTipOffset + TIP_WIDTH , TIP_HEIGHT ); aPointArr[3] = Point( nTipOffset, TIP_HEIGHT ); maTriPoly = tools::Polygon( 4, aPointArr ); vcl::Region aTriRegion( maTriPoly ); aRegion.Union( aTriRegion); maBounds = std::move(aRegion); SetWindowRegionPixel( maBounds ); } void BubbleWindow::SetTitleAndText( const OUString& rTitle, const OUString& rText, const Image& rImage ) { maBubbleTitle = rTitle; maBubbleText = rText; maBubbleImage = rImage; Resize(); } void BubbleWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/) { LineInfo aThickLine( LineStyle::Solid, 2 ); rRenderContext.DrawPolyLine( maRectPoly, aThickLine ); rRenderContext.DrawPolyLine( maTriPoly ); Color aOldLine = rRenderContext.GetLineColor(); Size aSize = GetSizePixel(); tools::Long nTipOffset = aSize.Width() - TIP_RIGHT_OFFSET + mnTipOffset; rRenderContext.SetLineColor( GetSettings().GetStyleSettings().GetHelpColor() ); rRenderContext.DrawLine( Point( nTipOffset+2, TIP_HEIGHT ), Point( nTipOffset + TIP_WIDTH -1 , TIP_HEIGHT ), aThickLine ); rRenderContext.SetLineColor( aOldLine ); Size aImgSize = maBubbleImage.GetSizePixel(); rRenderContext.DrawImage( Point( BUBBLE_BORDER, BUBBLE_BORDER + TIP_HEIGHT ), maBubbleImage ); vcl::Font aOldFont = GetFont(); vcl::Font aBoldFont = aOldFont; aBoldFont.SetWeight( WEIGHT_BOLD ); SetFont( aBoldFont ); tools::Rectangle aTitleRect = maTitleRect; aTitleRect.Move( aImgSize.Width(), 0 ); rRenderContext.DrawText( aTitleRect, maBubbleTitle, DrawTextFlags::MultiLine | DrawTextFlags::WordBreak ); SetFont( aOldFont ); tools::Rectangle aTextRect = maTextRect; aTextRect.Move( aImgSize.Width(), 0 ); rRenderContext.DrawText( aTextRect, maBubbleText, DrawTextFlags::MultiLine | DrawTextFlags::WordBreak ); } void BubbleWindow::MouseButtonDown( const MouseEvent& ) { Show( false ); } void BubbleWindow::Show( bool bVisible ) { if ( !bVisible ) { FloatingWindow::Show( bVisible ); return; } // don't show bubbles without a text if ( ( maBubbleTitle.isEmpty() ) && ( maBubbleText.isEmpty() ) ) return; Size aWindowSize = GetSizePixel(); Size aImgSize = maBubbleImage.GetSizePixel(); RecalcTextRects(); aWindowSize.setHeight( maTitleRect.GetHeight() * 7 / 4+ maTextRect.GetHeight() + 3 * BUBBLE_BORDER + TIP_HEIGHT ); if ( maTitleRect.GetWidth() > maTextRect.GetWidth() ) aWindowSize.setWidth( maTitleRect.GetWidth() ); else aWindowSize.setWidth( maTextRect.GetWidth() ); aWindowSize.setWidth( aWindowSize.Width() + 3 * BUBBLE_BORDER + aImgSize.Width() ); if ( aWindowSize.Height() < aImgSize.Height() + TIP_HEIGHT + 2 * BUBBLE_BORDER ) aWindowSize.setHeight( aImgSize.Height() + TIP_HEIGHT + 2 * BUBBLE_BORDER ); Point aPos; aPos.setX( maTipPos.X() - aWindowSize.Width() + TIP_RIGHT_OFFSET ); aPos.setY( maTipPos.Y() ); AbsoluteScreenPixelPoint aScreenPos = GetParent()->OutputToAbsoluteScreenPixel( aPos ); if ( aScreenPos.X() < 0 ) { mnTipOffset = aScreenPos.X(); aPos.AdjustX( -mnTipOffset ); } SetPosSizePixel( aPos, aWindowSize ); FloatingWindow::Show( bVisible, ShowFlags::NoActivate ); } void BubbleWindow::RecalcTextRects() { Size aTotalSize; bool bFinished = false; vcl::Font aOldFont = GetFont(); vcl::Font aBoldFont = aOldFont; aBoldFont.SetWeight( WEIGHT_BOLD ); while ( !bFinished ) { SetFont( aBoldFont ); maTitleRect = GetTextRect( tools::Rectangle( Point( 0, 0 ), maMaxTextSize ), maBubbleTitle, DrawTextFlags::MultiLine | DrawTextFlags::WordBreak ); SetFont( aOldFont ); maTextRect = GetTextRect( tools::Rectangle( Point( 0, 0 ), maMaxTextSize ), maBubbleText, DrawTextFlags::MultiLine | DrawTextFlags::WordBreak ); if ( maTextRect.GetHeight() < 10 ) maTextRect.setHeight( 10 ); aTotalSize.setHeight( maTitleRect.GetHeight() + aBoldFont.GetFontHeight() * 3 / 4 + maTextRect.GetHeight() + 3 * BUBBLE_BORDER + TIP_HEIGHT ); if ( aTotalSize.Height() > maMaxTextSize.Height() ) { maMaxTextSize.setWidth( maMaxTextSize.Width() * 3 / 2 ); maMaxTextSize.setHeight( maMaxTextSize.Height() * 3 / 2 ); } else bFinished = true; } maTitleRect.Move( 2*BUBBLE_BORDER, BUBBLE_BORDER + TIP_HEIGHT ); maTextRect.Move( 2*BUBBLE_BORDER, BUBBLE_BORDER + TIP_HEIGHT + maTitleRect.GetHeight() + aBoldFont.GetFontHeight() * 3 / 4 ); } MenuBarUpdateIconManager::MenuBarUpdateIconManager() : maTimeoutTimer("MenuBarUpdateIconManager") , maWaitIdle("vcl MenuBarUpdateIconManager maWaitIdle") , mbShowMenuIcon(false) , mbShowBubble(false) , mbBubbleChanged( false ) { maTimeoutTimer.SetTimeout( 10000 ); maTimeoutTimer.SetInvokeHandler(LINK(this, MenuBarUpdateIconManager, TimeOutHdl)); maWaitIdle.SetPriority( TaskPriority::LOWEST ); maWaitIdle.SetInvokeHandler(LINK(this, MenuBarUpdateIconManager, WaitTimeOutHdl)); maApplicationEventHdl = LINK(this, MenuBarUpdateIconManager, ApplicationEventHdl); Application::AddEventListener( maApplicationEventHdl ); maWindowEventHdl = LINK(this, MenuBarUpdateIconManager, WindowEventHdl); } sal_uInt16 MenuBarUpdateIconManager::GetIconID(MenuBar* pMenuBar) const { auto aI = std::find(maIconMBars.begin(), maIconMBars.end(), pMenuBar); if (aI == maIconMBars.end()) return 0; return maIconIDs[std::distance(maIconMBars.begin(), aI)]; } VclPtr MenuBarUpdateIconManager::GetBubbleWindow() { if (!mpActiveSysWin) return nullptr; tools::Rectangle aIconRect = mpActiveMBar->GetMenuBarButtonRectPixel(GetIconID(mpActiveMBar)); if( aIconRect.IsEmpty() ) return nullptr; auto pBubbleWin = mpBubbleWin; if ( !pBubbleWin ) { pBubbleWin = VclPtr::Create( mpActiveSysWin, maBubbleTitle, maBubbleText, maBubbleImage ); mbBubbleChanged = false; } else if ( mbBubbleChanged ) { pBubbleWin->SetTitleAndText( maBubbleTitle, maBubbleText, maBubbleImage ); mbBubbleChanged = false; } Point aWinPos = aIconRect.BottomCenter(); pBubbleWin->SetTipPosPixel( aWinPos ); return pBubbleWin; } IMPL_LINK_NOARG(MenuBarUpdateIconManager, TimeOutHdl, Timer *, void) { RemoveBubbleWindow(); } IMPL_LINK(MenuBarUpdateIconManager, WindowEventHdl, VclWindowEvent&, rEvent, void) { VclEventId nEventID = rEvent.GetId(); if ( VclEventId::ObjectDying == nEventID ) { if (mpActiveSysWin == rEvent.GetWindow()) { RemoveBubbleWindow(); mpActiveSysWin = nullptr; mpActiveMBar = nullptr; } } else if ( VclEventId::WindowMenubarAdded == nEventID ) { vcl::Window *pWindow = rEvent.GetWindow(); if ( pWindow ) { SystemWindow *pSysWin = pWindow->GetSystemWindow(); if (pSysWin) AddMenuBarIcon(*pSysWin, false); } } else if ( VclEventId::WindowMenubarRemoved == nEventID ) { MenuBar *pMBar = static_cast(rEvent.GetData()); if (pMBar) { if (pMBar == mpActiveMBar) { RemoveBubbleWindow(); mpActiveMBar = nullptr; } RemoveMenuBarIcon(pMBar); } } else if ( ( nEventID == VclEventId::WindowMove ) || ( nEventID == VclEventId::WindowResize ) ) { if ( mpActiveSysWin == rEvent.GetWindow() && mpBubbleWin && mpActiveMBar ) { tools::Rectangle aIconRect = mpActiveMBar->GetMenuBarButtonRectPixel(GetIconID(mpActiveMBar)); Point aWinPos = aIconRect.BottomCenter(); mpBubbleWin->SetTipPosPixel( aWinPos ); if ( mpBubbleWin->IsVisible() ) mpBubbleWin->Show(); // This will recalc the screen position of the bubble } } } IMPL_LINK(MenuBarUpdateIconManager, ApplicationEventHdl, VclSimpleEvent&, rEvent, void) { switch (rEvent.GetId()) { case VclEventId::WindowShow: case VclEventId::WindowActivate: case VclEventId::WindowGetFocus: { vcl::Window *pWindow = static_cast< VclWindowEvent * >(&rEvent)->GetWindow(); if ( pWindow && pWindow->IsTopWindow() ) { SystemWindow *pSysWin = pWindow->GetSystemWindow(); MenuBar *pMBar = pSysWin ? pSysWin->GetMenuBar() : nullptr; if (pMBar) AddMenuBarIcon(*pSysWin, true); } break; } default: break; } } IMPL_LINK_NOARG(MenuBarUpdateIconManager, UserEventHdl, void*, void) { vcl::Window *pTopWin = Application::GetFirstTopLevelWindow(); vcl::Window *pActiveWin = Application::GetActiveTopWindow(); SystemWindow *pActiveSysWin = nullptr; vcl::Window *pBubbleWin = nullptr; if ( mpBubbleWin ) pBubbleWin = mpBubbleWin; if ( pActiveWin && ( pActiveWin != pBubbleWin ) && pActiveWin->IsTopWindow() ) pActiveSysWin = pActiveWin->GetSystemWindow(); if ( pActiveWin == pBubbleWin ) pActiveSysWin = nullptr; while ( !pActiveSysWin && pTopWin ) { if ( ( pTopWin != pBubbleWin ) && pTopWin->IsTopWindow() ) pActiveSysWin = pTopWin->GetSystemWindow(); if ( !pActiveSysWin ) pTopWin = Application::GetNextTopLevelWindow( pTopWin ); } if ( pActiveSysWin ) AddMenuBarIcon(*pActiveSysWin, true); } IMPL_LINK_NOARG(MenuBarUpdateIconManager, ClickHdl, MenuBarButtonCallbackArg&, bool) { maWaitIdle.Stop(); if ( mpBubbleWin ) mpBubbleWin->Show( false ); maClickHdl.Call(nullptr); return false; } IMPL_LINK(MenuBarUpdateIconManager, HighlightHdl, MenuBarButtonCallbackArg&, rData, bool) { if ( rData.bHighlight ) maWaitIdle.Start(); else RemoveBubbleWindow(); return false; } IMPL_LINK_NOARG(MenuBarUpdateIconManager, WaitTimeOutHdl, Timer *, void) { mpBubbleWin = GetBubbleWindow(); if ( mpBubbleWin ) { mpBubbleWin->Show(); } } MenuBarUpdateIconManager::~MenuBarUpdateIconManager() { Application::RemoveEventListener( maApplicationEventHdl ); RemoveBubbleWindow(); RemoveMenuBarIcons(); } void MenuBarUpdateIconManager::RemoveMenuBarIcons() { while (!maIconMBars.empty()) RemoveMenuBarIcon(maIconMBars[0]); } void MenuBarUpdateIconManager::SetShowMenuIcon(bool bShowMenuIcon) { if ( bShowMenuIcon != mbShowMenuIcon ) { mbShowMenuIcon = bShowMenuIcon; if ( bShowMenuIcon ) Application::PostUserEvent(LINK(this, MenuBarUpdateIconManager, UserEventHdl)); else { RemoveBubbleWindow(); RemoveMenuBarIcons(); } } } void MenuBarUpdateIconManager::SetShowBubble(bool bShowBubble) { mbShowBubble = bShowBubble; if ( mbShowBubble ) Application::PostUserEvent(LINK(this, MenuBarUpdateIconManager, UserEventHdl)); else if ( mpBubbleWin ) mpBubbleWin->Show( false ); } void MenuBarUpdateIconManager::SetBubbleChanged() { mbBubbleChanged = true; if (mbBubbleChanged && mpBubbleWin) mpBubbleWin->Show( false ); } void MenuBarUpdateIconManager::SetBubbleImage(const Image& rImage) { maBubbleImage = rImage; SetBubbleChanged(); } void MenuBarUpdateIconManager::SetBubbleTitle(const OUString& rTitle) { if (rTitle != maBubbleTitle) { maBubbleTitle = rTitle; SetBubbleChanged(); } } void MenuBarUpdateIconManager::SetBubbleText(const OUString& rText) { if (rText != maBubbleText) { maBubbleText = rText; SetBubbleChanged(); } } namespace { Image GetMenuBarIcon( MenuBar const * pMBar ) { OUString sResID; vcl::Window *pMBarWin = pMBar->GetWindow(); sal_uInt32 nMBarHeight = 20; if ( pMBarWin ) nMBarHeight = pMBarWin->GetOutputSizePixel().getHeight(); if (nMBarHeight >= 35) sResID = RID_UPDATE_AVAILABLE_26; else sResID = RID_UPDATE_AVAILABLE_16; return Image(StockImage::Yes, sResID); } } void MenuBarUpdateIconManager::AddMenuBarIcon(SystemWindow& rSysWin, bool bAddEventHdl) { if (!mbShowMenuIcon) return; MenuBar *pActiveMBar = rSysWin.GetMenuBar(); if (&rSysWin != mpActiveSysWin || pActiveMBar != mpActiveMBar) RemoveBubbleWindow(); auto aI = std::find(maIconMBars.begin(), maIconMBars.end(), pActiveMBar); if (aI == maIconMBars.end()) { if (pActiveMBar) { OUStringBuffer aBuf; if( !maBubbleTitle.isEmpty() ) aBuf.append( maBubbleTitle ); if( !maBubbleText.isEmpty() ) { if( !maBubbleTitle.isEmpty() ) aBuf.append( "\n\n" ); aBuf.append( maBubbleText ); } Image aImage = GetMenuBarIcon( pActiveMBar ); sal_uInt16 nIconID = pActiveMBar->AddMenuBarButton( aImage, LINK( this, MenuBarUpdateIconManager, ClickHdl ), aBuf.makeStringAndClear() ); maIconMBars.push_back(pActiveMBar); maIconIDs.push_back(nIconID); } if (bAddEventHdl) rSysWin.AddEventListener( maWindowEventHdl ); } if (mpActiveMBar != pActiveMBar) { if (mpActiveMBar) { mpActiveMBar->SetMenuBarButtonHighlightHdl(GetIconID(mpActiveMBar), Link()); } mpActiveMBar = pActiveMBar; if (mpActiveMBar) { mpActiveMBar->SetMenuBarButtonHighlightHdl(GetIconID(mpActiveMBar), LINK(this, MenuBarUpdateIconManager, HighlightHdl)); } } mpActiveSysWin = &rSysWin; if (mbShowBubble && pActiveMBar) { mpBubbleWin = GetBubbleWindow(); if ( mpBubbleWin ) { mpBubbleWin->Show(); maTimeoutTimer.Start(); } mbShowBubble = false; } } void MenuBarUpdateIconManager::RemoveMenuBarIcon(MenuBar* pMenuBar) { auto aI = std::find(maIconMBars.begin(), maIconMBars.end(), pMenuBar); if (aI == maIconMBars.end()) return; auto aIconI = maIconIDs.begin() + std::distance(maIconMBars.begin(), aI); try { pMenuBar->RemoveMenuBarButton(*aIconI); } catch (...) { } maIconMBars.erase(aI); maIconIDs.erase(aIconI); } void MenuBarUpdateIconManager::RemoveBubbleWindow() { maWaitIdle.Stop(); maTimeoutTimer.Stop(); mpBubbleWin.disposeAndClear(); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */